Effective JavaScript

习惯JavaScript(7)

  1. 了解使用的JavaScript版本
  2. 理解JavaScript浮点数
  • JavaScript的数字都是双精度浮点数
  • JavaSCript的整型是浮点数的一个子集,不是单独的类型
  • 位运算符将数字视为32位有符号整数
  • 浮点数中的精度陷阱
  1. 当心隐式的强制转换
  • 类型错误可能被隐式的强制转换所隐藏
  • 重载的运算符+是进行加法运算还是字符串连接取决于参数类型
  • 对象通过valueOf方法强制转换成数字,通过toString方法强制转换为字符串
  • 具有valueOf方法的对象应该实现toString方法,返回一个valueOf方法产生的数字字符串表示
  • 测试一个值是否为未定义的值,应该使用typeof或者与undefined进行比较而不是用真值运算
  1. 原型类型优于封装对象
  • 当做相等比较时,原始类型的封装对象与其原始值行为不一样
  • 或者和设置原始类型值的属性会隐式地创建封装对象
  1. 避免对混合类型使用== 运算符
  • 当类型不同时,==运算符应用了一套难以理解的隐式强制转换规则
  • 使用===
  1. 了解分号插入的局限
  2. 视字符串为16位的代码单元序列
  • JavaScript字符串由16位的代码单元组成,而不是由Unicode组成
  • JavaScript使用两个代码单元表示2^16及其以上的Unicode的代码点。这两个代码单元被称为代理对。
  • 代理对甩开了字符串元素计数,length、charAt、charCodeAt方法以及正则表达式模式(例如.)受到了影响
  • 使用第三方库识别代码点的字符串操作

变量作用域(10)

  1. 尽量减少全局对象
  2. 始终声明局部变量
  3. 避免使用width
  4. 熟练掌握闭包
  • 函数可以引用定义在其外部作用域的变量
  • 闭包比创建它们的函数有更长的生命周期
  • 闭包在内部存储其外部变量的引用,并能读写这些变量
  1. 理解变量声明提升
  • 在代码块中的变量声明会被隐式地提升到闭包函数的顶部
  • 重声明变量可视为单个变量
  • 考虑手动提升局部变量的声明,从而避免混淆
  1. 使用立即调用的函数表达式创建局部作用域
  • 理解绑定和赋值的区别
  • 闭包通过引用而不是值捕获它们的外部变量
  • 使用立即调用的函数表达式来创建局部作用域
  • 当心在立即调用的函数表达式中包裹代码块可能会改变其行为的情形
  1. 当心命名函数表达式笨拙的作用域
  2. 当心局部块函数声明笨拙的作用域
  3. 避免使用Eval创建局部变量
  4. 间接调用Eval函数优于直接调用

使用函数(12)

  1. 理解函数调用、方法调用以及构造函数调用之间的不同
  • 方法调用将被查找方法属性的对象作为调用接收者
  • 函数调用将全局对象作为接收者。
  • 构造函数需要通过new运算符调用,并产生一个新的对象作为其接收者
  1. 熟练掌握高阶函数
  • 高阶函数是那些将函数作为参数或者返回值的函数
  1. 使用call方法自定义接收者来调用方法
  • 使用call方法可以调用在给定的对象中不存在的方法
  • 使用call方法定义高阶函数允许使用者给回调函数指定接收者
  1. 使用apply方法通过不同数量的参数调用函数
  • 使用apply方法指定一个可计算的参数数组来调用可变参数的函数
  • 使用apply方法的第一个参数给可变参数的方法提供一个接收者
  1. 使用arguments创建可变参数的函数
  • 使用隐式的arguments对象实现可变参数的函数
  • 考虑对可变参数提供一个额外的固定元数的版本,从而使使用者无需借助apply方法
  1. 永远不要修改arguments对象
  • 使用[].slice.call(arguments)arguments对象复制到一个真正的数组中再进行修改
  1. 使用变量保持arguments的引用
  • 当引用arguments时当心函数嵌套层级
  • 绑定一个明确作用域的引用到arguments变量,从而可以在嵌套的函数中引用它
  1. 使用bind方法提取具有确定接收者的方法
  • 要注意,提取一个方法不会将方法的接收者绑定到方法的对象上
  • 当给高阶函数传递对象方法时,使用匿名函数在适当的接收者上调用该方法
  • 使用bind方法创建绑定到适当接收者的函数
  1. 使用bind方法实现函数柯里化(将函数与其参数的一个子集绑定)
  • 使用bind方法实现函数柯里化,即创建一个固定需求参数子集的委托函数
  • 传入nullundefined作为接收者的参数来实现函数柯里化
  1. 使用闭包而不是字符串来封装代码
  • 当将字符串传递给eval函数以执行它们的API时,绝不要在字符串中包含局部变量的引用
  • 接受函数调用的API由于使用eval函数执行字符串的API
  1. 不要信赖函数对象的toString方法
  2. 避免使用非标准的栈检查属性

对象和原型(13)

  1. 理解prototype、getPrototypeOf和__proto__之间的不同
  • C.prototype属性是new C()创建的对象原型
  • Object.getPrototypeOf(obj)ES5中检索对象原型的标准函数
  • obj.__proto__是检索对象原型的非标准方法
  • 类是由一个构造函数和一个关联的原型组成的一种设计模式
  1. 使用Object.getPrototypeOf函数而不要使用__proto__属性
  2. 始终不要修改__proto__属性
  • 使用Object.create函数给新对象设置自定义的原型
  1. 使构造函数与new操作符无关
  2. 在原型中存储方法
  • 将方法存储在实例对象中将创建该函数的多个副本,因为每个实例对象都有一份副本
  • 将方法存储于原型中优于存储在实例对象中
  1. 使用闭包存储私有数据
  • 闭包变量是私有的,只能通过局部的引用获取
  • 将局部变量作为私有数据从而通过方法实现信息隐藏
  1. 只将实例状态存储在实例对象中
  • 共享可变数据可能会出问题,因为原型是被其所有的实例共享的
  • 将可变的实例状态存储在实例对象中
  1. 认识到this变量的隐式绑定问题
  • this变量的作用域总是由其最近的封闭函数所绑定
  • 使用一个局部变量(通常命名为selfmethat)使得this绑定对于内部函数是可用的
  1. 在子类的构造函数中调用父类的构造函数
  • 使用Object.create函数来构造子类的原型对象以避免调用父类的构造函数
  1. 不要重用父类的属性名
  2. 避免继承标准类
  • 使用属性委托优于继承标准类
  1. 将原型视为实现细节
  • 对象是接口,原型是实现
  1. 避免使用轻率的猴子补丁

数组和字典(10)

  1. 使用Object的直接实例构造轻量级的字典
  2. 使用null原型以防止原型污染
  3. 使用hasOwnProperty方法以避免原型污染
  • 使用词法作用域和call方法避免覆盖hasOwnProperty方法
  1. 使用数组而不要使用字典来存储有序集合
  2. 绝不要在Object.prototype中增加可枚举的属性
  • 避免在Object.prototype中增加属性
  • 考虑编写一个函数代替Object.prototype方法
  1. 避免在枚举期间修改对象
  • 当使用for..in循环枚举一个对象的属性时确保不要修改该对象
  • 当迭代一个对象时,如果该对象的内容可能会在循环期间被改变,应该使用while循环或经典的for循环来替代for..in循环
  • 为了在不断变化的数据结构中能够预测枚举,考虑使用一个有序的数据结构
  1. 数组迭代要优先使用for循环而不是for..in循环
  2. 迭代方法优先于循环
  3. 在类数组对象上复用通用的数组方法
  4. 数组字面量优于数组构造函数

库和API设计(8)

  1. 保持一致的约定
  2. undefined看做“没有值”
  • 在允许0、NaN或空字符串为有效参数的地方,绝不要通过真值测试来实现参数默认值
  1. 接收关键字参数的选项对象
  • 使用extend函数抽象出从选项对象中提取值的逻辑
  1. 避免不必要的状态
  • 尽可能地使用无状态的API
  • 如果API是有状态的,标示出每个操作与哪些状态有关联
  1. 使用结构类型设计灵活的接口
  • 使用结构类型来设计灵活的对象接口
  • 结构接口更灵活、更轻便,所以应该避免使用继承
  1. 区分数组对象和类数组对象
  2. 避免过度的强制转换
  3. 支持方法链
  • 使用方法链来连接无状态的操作
  • 通过在无状态的方法中返回新对象来支持方法链
  • 通过在有状态的方法中返回this来支持方法链

并发(8)

  1. 不要阻塞I/O事件队列
  • 异步API使用回调函数来延缓代价高昂的操作以避免阻塞主应用程序
  • JavaScript并发地接收事件,但会使用一个事件队列按序地处理事件处理程序
  • 在应用程序事件队列中绝不要使用阻塞的I/O
  1. 在异步序列中使用嵌套或命名的回调函数
  • 使用嵌套或命名的回调函数按顺序地执行多个异步操作
  1. 当心丢弃错误
  2. 对异步循环使用递归
  • 循环不能是异步
  • 使用递归函数在事件循环的单独伦次中执行迭代
  • 在事件循环的单独轮次中执行递归,并不会导致调用栈溢出
  1. 不要在计算时阻塞事件队列
  2. 使用计数器来执行并行操作
  3. 绝不要同步地调用异步的回调函数
  4. 使用promise模式清洁异步逻辑
  • promise代表最终值,即并行操作完成时最终产生的结果
  • 使用promise组合不同的并行操作
  • 使用promise模式的API避免数据竞争
  • 在要求有意的竞争条件时使用select