effective javsscript(211)

js的值类型和引用类型

值类型:数值、布尔、null、undefined
引用类型:对象、数组、函数

js的值类型和引用类型

值类型:数值、布尔、null、undefined
引用类型:对象、数组、函数

类型判断

typeof 返回字符串
instanceof 后接类型,返回bool

隐式转换

  1. (1+2)+”3” //“33”
  2. 当多个运算符被重载时,js会盲目的选择valueOf; 所以要么明确重载一个,要么保证重载的俩个值相同。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    var obj = {
    toString: function() {
    return "hh";
    },
    valueOf: function() {
    return 12;
    }
    };
    //或者 obj.prototype.toString = function () ...
    "object:" + obj;//"object:17"
  3. 真值运算;if、|| 、&&需要布尔的运算
    js有7个假值:false、0、-0、””、NaN、null、undefined;

== 的强制转换

Param1 Param2 Force Format
null undefined 不转换,总返回true
null or undefined 非null、非undefined 不转换,总返回false
原始类型 Date对象、非Date对象 原始类型转换为数字,Date对象(优先toString)、非Date对象(优先valueOf)转换为原始类型
原始类型 原始类型 将原始类型转换为数字

Unicode字符

  1. js字符串由16位代码单元组成,而不是由unicode代码点组成。
  2. js使用俩个代码单元表示16位以上的unicode代码点,称为代码对。
  3. 代码对甩开了字符串元素计数,length、charAt、charCodeAt方法以及正则表达式模式。

字符串Trick

它字符串的构造
1
2
let tt= 'as';
let at = `aa${tt}`//注意这不是单引号

闭包

1
2
3
4
5
6
7
8
function makeSandwich(maigc) {
function make(filling) {
return maigc + " and " + filling;
}
return make;
}
var f = makeSandwich("peanut butter");
f("jelly");//"peanut butter and jelly"

立即调用函数表达式

1
2
3
4
5
6
7
8
9
function wara(a) {
var result = [];
for (var i = 0, n = a.length; i < n; i++) {
(function(j) {
result[j] = function() {return a[j];};
})(i);
}
return result;
}

变量声明提升

js对变量的作用域是函数级的,不是块级的。

1
2
3
4
5
6
7
function trime(header) {
for (var i = 0; n = header; i < n; i++)
...
for (var i = 0; n = header; i < n; i++)
...
}
//上面的i,n其实只申明了一次,剩下的都是同一个而已。js的变量提升可以视为会把声明自动放到函数开头。

命名函数

1
2
3
4
function f() ...
function test() {
function f() ... //在此函数里访问f,就只会是这个f
}

所以建议少用命名函数,多用匿名函数。

eval函数

  1. 书上的建议是不要用它创建变量,同名变量会导致访问相同地址的变量
  2. 间接调用eval函数;
    (0,eval)(src) 0值无意义以达到强制使用间接调用的目的。src是传入给eval的参数。

call和apply

obj.temprorary = f;
var result = obj.temprorary(arg1, arg2);
delete obj.temprorary;
当想使用其他对象的某个属性时,这种方法不仅别扭,而且相当危险。
所以有了call和apply

apply只是让数组变成多个参数。

1
2
3
var df = [1, 2, 3]
var vf = function(a1, a2, a3) { return (a1+a2+a3)/3;}
vf.apply(vf, df)

js函数有个arguments的默认参数,也是数组。所以默认支持变长参数函数。

bind

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var buffer = {
entries: [],
add: function(s) {
this.entries.push(s);
}
}
var sourc = ["874", "-", "53443"];
sourc.forEach(buffer.add.bind(buffer)) //保证buffer.add函数的接受者就是buffer对象
function simple(pro, dom, path) {
return pro + "://" + dom + "/" + path;
}
var ul = apath.map(simple.bind(null, "http", "ff"));//["http://ff/we", "http://ff/er", "http://ff/rt"]
//bind第一个参数提供接受者的值,其余参数提供给给新函数的所有参数。

使构造函数与new无关

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//一般我们写构造函数时都喜欢,传入参数
function User(name, password) {
this.name = name;
this.password = password;
}
var x = User("test", "demo");//这样做虽然可以成功,但是也会产生多余的全局变量name和password
//避免有俩种方法,要么 var x = new User("test", "demo")
//所以要这么写函数,加不加new都可以
function User(name, password) {
if (!(this instanceof User))
return new User(name, password);
this.name = name;
this.password = password;
}

将状态存储到示例对象而不是原型对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function Tree(x) {
this.value = x;
}
Tree.prototype = {
children: [],
addChild: function(x) {
this.children.push(x);
}
}
var left = new Tree(2);
left.addChild(1);
var right = new Tree(6);
right.addChild(4);
var top = new Tree(7);
top.addChild(left);
top.addChild(right);
top.children; //[1,4,left,right] 这样会多出俩个对象。所以把children放入成员变量是最好

this隐式绑定问题

1
2
3
4
5
6
7
8
9
10
function CSVReader(sep) {
this.regexp = ...;
}
CSVReader.prototype.read = function(str) {
var lines = str.trim().split(/\n/);
return lines.map(function(line) {
return line.split(this.regexp);//此处本来是想访问CSVReader的regexp,但是实际访问的是lines的
});
}
//改法加bind,或者 var self = this; .... self.regexp;

在子类的构造函数中调用父类的构造函数

继承可以用Object.Create

1
2
3
4
5
6
7
8
9
10
11
function Rectangle() {
Shape.call(this); //call super constructor.
}
Rectangle.prototype = Object.create(Shape.prototype);
Rectangle.prototype.constructor = Rectangle;
//这样能创造新的链
//如果不想使用新链
var sp = new Shape()
var b = Object.create(sp)//b的链指向sp

注意不要让子类属性和父类属性重名,这样会导致冲突访问同一属性。
避免继承标准类

1
2
3
4
5
6
7
8
9
10
11
function cls1(name) {
this.name = name;
this.show = function() {
console.log(this.name);
};
}
function cls2(name) {
cls1.call(this, name);//感觉使用call来传递继承要比直接用prototype方便。 cls2.prototype = Object.create(cls1.prototype);
}
var tt = new cls2("ss");
tt.show();//ss

猴子补丁

由于平台的不同有些对属性的修改可能会导致覆盖,应该如此

1
2
3
4
5
if (typeof Array.prototype.map !== "function") {
Array.prototype.map = function(f, thisArg) {
...;
};
}

使用object实例直接构造轻量级字典

书上建议使用实例来增加属性,而不是原型的prototype.
这样保证for in的正确执行
不建议用for in来循环数组,(特别是数字类型的数组;即使用[],但是遍历的变量是字符型)

1
2
3
4
5
6
7
var dict = {};
dict.alice = 23;
dict.bob = 4;
dict.ttt = 6;
for (var name in dict) {
...//循环遍历dict的属性,字符类型
}

使用null原型防止原型污染

1
2
3
4
5
6
7
8
9
function c() {}
c.prototype = null;
var o = new c();
Object.getPrototypeOf(o) === null//false
Object.getPrototypeOf(0) === Object.prototype;//true
//这个并不会成为真正的null,要如下设置
var o = Object.create(null);
Object.getPrototypeOf(0) === null;//true

使用hasOwnProperty避免原型污染

即使是个空的对象,也继承了大量属性。

1
2
3
var dict = {};
"alice" in dict; //false
"toString" in dict;//true

为了避免空对象的判断,可以使用hasOwnProperty

1
dict.hasOwnProperty("toString");//false

使用数组而不要用字典来存储有序集合

1
2
3
4
5
6
var ht = [{name:"q",number:1},{name:"w",number:2},{name:"e",number:3}];
for (var tn in ht) {
console.log(tn);
console.log(ht[tn]);
}
//当前平台的输出是:0 {name:"q",number:1} 1 {name:"w",number:2} 2 {name:"e",number:3}

书上说,for…in 在此类数组中它由环境的不同可能会选择不同的顺序来枚举对象。
所以即使以上写法可以,也建议使用普通的循环。

js的属性

属性点访问和方括号访问

属性允许字符和变量

点访问只能访问字符属性
方括号访问访问字符时obj[‘pro’],访问变量时obj[ele]

一般属性的字符定义(括号定义必须是字符):obj.pro = …; obj[‘pro’]=…
变量定义 obj[ele]= …

枚举性

对于对象来说,属性有可枚举和不可枚举
默认从prototype来的是可枚举的
不可枚举一般用

1
2
3
4
Object.defineProperty(a, 'sex', {
value: 'man',
enumerable: false
})//给a对象加个sex属性,不能枚举

当属性可枚举时:for…in能遍历出来。Object.keys()能遍历出来(数组非数字属性也能遍历出来)
当属性不可枚举: 只有Object.getOwnPropertyNames()能遍历出来, 和 …in语句

创建位置

属性明确创建:
构造函数this.a=...
创建的对象obj定义属性obj.a=...

原型创建:
创建的对象obj定义属性obj.prototype.a=...
class体内定义的属性

由于依附在原型上,hasOwnProperty,for...in, getOwnPropertyNames都会无法返回对应属性,只有...in语句能返回

不要在prototype中增加可枚举的属性

1
2
3
4
5
6
7
Object.prototype.allkey = function() {
var result = [];
for (var key in this) {
result.push(key);
}
return result;
}//这样会导致多个属性allkey ["allkey", "a", "b", "c"]

书上解决方法是封装成函数,如果实在要封装成属性,要如下写。

1
2
3
4
5
6
7
8
9
10
11
12
Object.defineProperty(Object.prototype, "allKeys", {
value: function() {
var result = [];
for (var key in this) {
result.push(key);
}
return result;
},
writable: true,
enumerable: false,
configurable: true
});

我感觉这样写又会污染原型,还是写成函数好点。

避免枚举期间修改对象

直接引用书上:

  1. 当使用for…in循环枚举一个对象的属性时,确保不要修改对象。
  2. 当迭代一个对象是,如果该对象可能会在循环期间被改变,应该使用while或for来代替for…in

迭代方法优于循环

书上建议多用 forEach/map/every 这些本身的迭代方法来代替循环。

函数默认值

es5上没有默认值得设定

1
2
3
4
5
6
7
8
9
10
function Element(w, h) {
this.w = w || 1;
this.h = h || 2;
}//如上确实在Element(),会返回默认值。但是在Element(0, 0)时就不会。
//所以该如下写法
function Element(w, h) {
this.w = w === undefined ? 1 : w;
this.h = h === undefined ? 2 : h;
}//如上在Element(0, 0)页可以正常运行

编写方法支持方法链

1
2
3
4
5
6
function escapeBasic(str) {
return str.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;");
}

不要阻塞I/O事件队列

如下代码会一直等待下载文件完成才继续执行下去,会导致卡死情况。

1
2
var text = downloadSync("http://example.com/file.txt");
console.log(text);

如下代码就不会阻塞了。

1
2
3
var text = downloadSync("http://example.com/file.txt", function(text) {
console.log(text);
});

对异步循环使用递归

1
2
3
4
5
function download(urls) {
for (...) {
...//下载文件,异步代码
}
}

循环的代码会等待下载执行完成。用递归函数在时间循环的单独轮次中执行迭代。

js递归函数同步调用太多次会导致失败(承受力各不同,几百次以上)

私有公有静态属性的访问

1
2
3
4
5
6
7
8
9
10
11
12
13
function user(name) {
var name = name;//私有属性
this.name = name; //公有属性
function getName() { return name; }//私有方法
function getHName() { return name; }//私有方法
}
user.prototype.getName = function() { return this.name;}//公有方法
user.name = 'ss'; //静态属性
user.sname = 'ss'; //静态属性
user.getName = function() { return this.name}//静态方法
var ws = new user('cats');
  • 调用公有方法、公有属性,必须先实例化对象。公有方法不能调用私有方法和静态方法。
    (即:ws.sname 不可以 user.sanme可以; )

  • 静态方法和静态属性无需示例化可访问 (user.name可以)

  • 私有方法和属性外部无法访问 (user.getHName() 不可以 user().getHName() 不可以)

变量提升

1
2
3
4
var getName = function() { console.log('cat');}
getName();//cat
function getName() { console.log('dog'); }
getName();//dog
  • js解释器会把声明提升到作用域(函数作用域)的最前面(即使写代码时放在最后也会被提升)

new的优先级

用上面现成的代码做例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
new user.getName();
//由于点运算优先级大于new无参数列表 这个就可视为 new (user.getName)();
//这下就成了将getName作为了构造函数来创建对象
new user().getName();
//new有参数列表和点运算同级,所以可视为 (new user()).getName();
function foo() {
}
foo.getName = function() { console.log(3);}
foo.prototype.getName = function() {console.log(4);}
new foo.getName();//3
new foo().getName();//4

import的括号

未加括号是默认引用:import A from './A' import Mya from './A';这样引用的都是A文件里的export default

加了括号是指定引用:
import {Som} from './A',A文件里必须有个export Som ...导出

import A, { Mya, Som} from './A'

// //