effective js重要笔记
变量作用域:
- 尽量减少全局对象(命名冲突,不利于模块化);
- 始终声明局部变量(JS lint工具);
- 避免使用with语句(使用简短的变量名代替重复访问的对象;显示地绑定局部变量到对象属性上,不使用with隐式绑定它)
- 闭包:
- 许使用在当前函数以外定义的变量;
- 即使外部函数已返回,当前函数仍然可以引用在外部函数所定义的变量;(闭包比创建它们的函数有更长的生命周期)
- 闭包可以更新外部变量的值;(闭包在内部存储其外部变量的引用,并能读写这些变量)
- 变量声明提升:
1. 代码块中的变量声明会被隐式地提升到封闭函数的顶部; 2. 重声明变量被视为单个变量; 3. 考虑手动提升局部变量声明,以避免混淆;
- 使用立即调用的函数表达式创建局部作用域;闭包存储的是外部变量的引用,而非值;只在进入一个作用域时,会为作用域中的变量在内存中分配一个’槽’(slot);
- 当心命名函数表达式的作用域:
1. 命名函数和匿名函数表达式的区别:前者会绑定与其函数名相同的变量上,该变量将作为该函数内的一个局部变量。(可以用来写递归表达式)但是,这个变量的作用域只在其自身函数中。(不像函数声明,命名函数表达式不能通过其内部的函数名在外部被引用)。 2. 命名函数表达式的真正用法是用来调试,用在栈跟踪中;(在开发阶段使用命名函数表达式做调试,发布前通过预处理将所有的函数表达式转为匿名)
- 避免eval创建局部变量:
1. 执行到eval()时,才对eval(“var y=x;")中的变量进行声明; 2. 如果eval函数代码可能创建全局变量,将此调用封装到嵌套的函数中以防止作用域污染;
- 间接调用eval优于直接调用eval。将eval()函数名存放在一变量x中,通过x()变量名调用能够函数失去对所有局部作用域的访问能力;
使用函数:
- 方法调用、函数调用、构造函数调用之间的区别;
- 使用call()自定义接受者来调用函数;可以用来调用在给定的对象中不存在的方法;可以定义告捷函数允许使用者给回调函数指定接收者;
forEach(fun1,obj1){fun1.call(obj1,argument1,argument2,…);}
使用apply()指定一个可计算的参数数组来调用可变参数数的函数;
使用apply()的第一个参数给可变参数的方法提供一个接收者,不需要时可设为null;使用隐式的arguments对象创建可变参数的函数;
对可变参数的函数提供一个额外的固定元数的版本,从而使使用者无需借助apply();不要修改arguments对象(所有命名参数都是arguments对象中对应索引的别名);
使用[].slice.call(arguments)将arguments对象复制到一个真正的数组中再进行修改;使用argument时当心函数嵌套层级;(每个函数体有自己的arguments变量);
绑定一个明确作用域的引用到arguments变量,从而可以在嵌套的函数中引用它;提取一个方法不会讲方法的接收者绑定到该方法的对象上;(有的高阶函数可以指定一个作为调用方法接收者的参数);
当给高阶函数传递对象方法时,使用匿名函数在适当的接收者上调用该方法;
使用bind()创建绑定到适当接收者的函数;函数柯里化(使用bind()实现,即创建一个固定需求参数子集的委托函数);
传入null或undefined作为接收者的参数来实现函数柯里化,从而忽略其接收者;eval()会将出现在字符串中的所有变量引用作为全局变量来解释;
所以,当将字符串传递给eval()执行它们的API时,不要在字符串中包含局部变量,因为eval(字符串)中定义的全局变量与那些通明的局部变量混淆;
优化:接受函数调用的API更优于eval()执行字符串的API;- 通常避免使用呢函数对象的toString();
不同的引擎执行结果不同;执行结果不会暴露存储在必报中的局部变量值 - 避免使用非标准的arguments.callee和arguments.caller属性;
对象和原型:
C.prototype属性是new C()创建的对象的原型;
Object.getPrototypeOf(obj)是ES5中检索对象原型的标准函数;
Obj.proto是检索对象原型的非标准方法;
类是由一个构造函数和一个关联的原型组成的一种设计模式;始终不要修改对象的proto属性:(并不是多有平台支持改变对象原型的特性,造成可移植性的问题;性能问题)
使用Object.create()给新对象设置自定义的原型;- 将方法存储在实例对象中将创建该函数的多个副本,因为每个实例对象都有一份副本;
使用闭包存储私有数据:
- for…in,ES5的Object.keys()和Object.getOwnPropertyNames()能获取对象的所有属性名;
- 使用命名规范给私有属性名前置/后置下划线_;用户不应该检查或修改该属性,以便对象仍能自由地改变其实现;
- 对象和闭包具有相反的策略:对象的属性会被自动地暴露出去,然而闭包中的变量会被自动地隐藏起来;
- for…in,ES5的Object.keys()和Object.getOwnPropertyNames()能获取对象的所有属性名;
- 一般情况下,任何不可变的数据可以被存储在原型中而被安全地共享;将可辨的实例状态(有状态的数据)存储在实例对象中;(在原型对象中最常见的数据是方法,而每个实例的状态都存储在实例对象中;)
- this变量隐式绑定的问题:this变量的作用与总是由其最近的封闭函数所决定。
避免继承标准类:
- 继承标准类往往会由于一些特殊的内部属性(如[class])而被破坏;
- 使用属性委托由于继承标准类;
- 继承标准类往往会由于一些特殊的内部属性(如[class])而被破坏;
对象是接口,原型是实现;
避免检查你无法控制的对象的原型结构;
避免检查实现在你无法控制的对象内部的属性;- 避免轻率地使用猴子补丁(修改对象的原型的属性)
数组和字典:
- 使用对象字面量构建轻量级字典;
使用null原型以防止原型污染;
- 设置一个构造函数的原型属性为null/undefined,但实例化该构造函数后的得到的是Object的实例;
- ES5中的Object.create(null)能够使用一个用户指定的原型链和一个属性描述符动态地构造对象;
- 比较老的环境中,{proto:null}对象字面量的语法也支持初始化一个原型链为null的新对象;但是proto极不标准,也不完全可移植;不要用proto作为字典中的key,因为一些环境将其作为特殊的属性对待;
- 设置一个构造函数的原型属性为null/undefined,但实例化该构造函数后的得到的是Object的实例;
使用hasOwnProperty()避免原型污染;
使用词法作用域和call()避免覆盖hasOwnProperty();使用数组而非字典来存储有序集合;
使用for…in…枚举对象属性应当与顺序无关;(将字符串连接,而非加运算)- 不在Object.prototype中增加可枚举属性;如果必须要增加属性,考虑用ES5中Object.defineProperty()将其定义为不可枚举属性;
避免在枚举期间修改对象;
如果迭代一个对象的时候,可能会修改对象,使用while()/for()代替for…in;
为了在不断变化的数据结构中能够预测枚举,考虑使用一个有序的数据结构,如数组,而不是使用字典对象;数组迭代优先使用for()而不是for…in;
考虑在循环之前讲数组的长度存储在一个局部变量中以避免重新计算数组长度;使用迭代方法(Array.prototype.forEach/Array.prototype.map)优于循环;
在需要终止循环的情况下,仍然推荐使用传统的循环。另,some/every()也可用于提前退出;对于类数组对象,通过提取方法对象并使用其call()来复用通用的Array方法;
任意一个具有索引属性和恰当length属性的对象都可以使用通用的Array方法;数组字面量和数组构造函数创建数组时是有区别的;
使用数组字面量定义数组优于使用数组构造函数;