首页 > 代码库 > 《编写高质量JavaScript代码的68个有效方法》

《编写高质量JavaScript代码的68个有效方法》

第1章 让自己习惯JavaScript

 

第1条:了解你使用的JavaScript版本

决定你的应用程序支持JavaScript的哪些版本。

确保你使用的JavaScript的特性对于应用程序将要运行的所有环境都是支持的。

第2条:理解JavaScript的浮点数

JavaScript中的数字都是作为双精度的64位浮点数来储存的;而JavaScript的整数都仅仅是双精度浮点数的一个子集,不是单独的数据类型。而小数和小数相加有时候是不精确的,如果你用作货币计算,最好换算成最小的货币来计算。

第3条:当心隐式的强制转换

在JavaScript中,“3+true”的结果你猜是什么?没错,是4!

加号既可以用于加法,也可以用于字符串连接,是什么运算取决于其参数类型。所以在进行计算时常常会伴随着隐式转换,尤其需要注意。

判断是不是数字,用isNaN?这也不见得靠谱。幸运的是,有一个简单而有效的办法,用一个值是否等与自身来检测该值是不是NaN。例如“a !== a”。

测试一个值是否是未定义的值,用typeof方法或者undefined进行比较而不是用真值运算。

第4条:原始类型优先于封装对象

当做比较时,原始类型的封装对象与其原始值行为不一样。大概的意思就是说直接的"Hello"字符串和var a = new String("Hello")并不相等,前者为原始类型,而后者是一个对象。

第5条:避免对混合类型使用==运算符

当参数类型不同时,==运算符会应用一套难以理解的隐式强制转换规则。所以始终贯穿使用===是有好处的,它不会产生任何的隐式转换。

第6条:了解分号插入的局限

总而言之就是尽量不要图代码优雅美观或忘记,记得在结束语句的末尾添加分号,不要依赖于JavaScript的自动插入分号技术。

第7条:视字符串为16位的代码单元序列

JavaScript字符串由16位的代码单元组成,而不是由Unicode代码点组成。了解一下就好,暂时不用深究。

 

第2 章 变量作用域

 

第8条:尽量少用全局对象

尽量避免使用全局变量,要用局部变量。同时避免对全局对象添加属性。

第9条:始终声明局部变量

比全局对象更麻烦的事,就是意外的全局变量。所以始终要使用var声明新的局部变量。

第10条:避免使用with

第11条:熟练掌握闭包

闭包是JavaScript最优雅、最富有表现力的特性之一,也是许多惯用用法的核心,所以理解闭包尤为重要。

闭包可以引用定义其外部作用域的变量。

闭包比创建它们的函数有更长的生命周期。

闭包在内部存储其外部变量的引用,并能读写这些变量。

第12条:理解变量声明提升

代码块中的声明变量会隐式地提升到封闭函数的顶部。

重声明被视为单个变量。

可以考虑手动提升局部变量声明,从而避免混淆。

第13条:使用立即调用函数表达式(IIFE)创建局部变量

理解绑定与赋值的区别。

闭包通过引用而不是值捕获它们的外部变量。

第14条:当心命名函数表达笨拙的作用域

第15条:当心局部块函数声明笨拙的作用域

始终将函数声明置于程序或被包含的函数的最外层以避免不可移植的行为。

使用var声明和有条件的赋值语句代替有条件的函数声明。

第16条:避免使用eval创建局部变量

第17条:间接调用eval函数优于直接调用

 

第3章 使用函数

 

第18条:理解函数调用、方法调用及构造函数调用之间的不同

函数调用将全局对象作为其接受者。一般很少用该调用方法。

方法调用将被查找方法属性的对象作为调用接受者。

构造函数调用通过new运算符调用,并产生一个新的对象作为接收者。

第19条:熟练掌握高阶函数

高阶函数是那些将函数作为参数或返回值的函数。

第20条:使用call方法自定义接受者来调用方法

使用call方法可以调用在给定的对象中不存在的方法。

第21条:使用apply方法通过不同数量的参数调用函数

第22条:使用arguments创建可变参数的函数

第23条:永远不要修改arguments对象

我们可以使用类似“var arg = Array.slice.call(arguments)”的技巧来调用数组的方法,原理是将对象复制到一个真正的数组中在进行修改。

第24条:使用变量保存arguments的引用

绑定一个明确的作用域的引用到arguments变量,从而可以在嵌套的函数中引用它。

第25条:使用bind方法提取具有确定接收者的方法

一定要注意,提取一个方法不会将方法的接收者绑定到该方法的对象上。

使用bind方法创建绑定到适当接收者的函数。

第26条:使用bind方法实现函数柯里化

传入null或undefined作为接收者的参数来实现函数柯里化,从而忽略其接受者。

第27条:使用闭包而不是字符串来封装代码

第28条:不要信赖函数对象toString方法

通常情况下,应该避免使用函数对象的toString方法。

第29条:避免使用非标准的栈检查属性

简言之,就是避免使用arguments.callee和arguments.caller方法,这两种非标准方法不具有良好的可移植性。

 

第4章 对象和原型

 

第30条:理解prototype、getPrototypeOf和__proto__的区别

C.prototype属性是new C()创建的对象的原型。

Object.getPrototypeOf(obj)是ES5中检索对象原型的标准函数。

obj.__proto__是检索对象原型的非标准方法。

类是一个构造函数和一个关联的原型组成的一种设计模式。

第31条:使用Object.getPrototypeOf函数而不要使用__proto__

第32条:始终不要修改__proto__属性

第33条:使构造函数与new操作符无关

通过使用new操作符或Object.create()方法在构造函数定义中调用自身使得该构造函数与调用语法无关。

当一个函数期望使用new操作符调用时,清晰地文档化该函数。

第34条:在原型中储存方法

将方法存储在实例对象中将创建该函数的多个副本,因为每个实例对象都有一份副本。

将方法存储于原型中优于存储在实例对象中。

第35条:使用闭包存储私有数据

闭包变量是私有的,只能通过局部的引用获取。

将局部变量作为私有数据从而通过方法实现信息隐藏。

第36条:只将实例状态存储在实例对象中

共享可变数据可能会出问题,因为原型是被其所有的实例共享的。

将可变的实例状态存储在实例对象中。

第37条:认识到this变量的隐式绑定问题

this变量的作用域总是由其最近的封闭函数所确定的。

使用一个局部变量(通常命名为self、me或that)使得this绑定对于内部函数是可用的。

第38条:在子类的构造函数中调用父类的构造函数

在子类构造函数中显式的传入this作为显式的接受者调用父类构造函数。

使用Object.create函数来构造子类的原型对象以避免调用父类的构造函数。

第39条:不要重用父类的属性名

第40条:避免继承标准类

第41条:将原型视为实现细节

对象是接口,原型是实现。

第42条:避免使用轻率的猴子补丁

猴子补丁就是指由于对象共享原型,因此每一个对象都可以增加、删除和修改原型的属性。

 

第5章 数组和字典

 

第43条:使用Object的直接实例构造轻量级字典

轻量级字典应该是Object.prototype的直接子类,以使for..。in循环免受原型污染。

第44条:使用null原型以防止原型污染

第45条:使用hasOwnProperty方法以避免原型污染

第46条:使用数组而不是使用字典来存储有序集合

使用for...in循环来枚举对象属性应当与顺序无关。

第47条:绝不要在Object.prototype中增加可枚举的属性

如果你确定需要在Object.prototype中增加属性,请使用ES5中的Object.defineProperty方法将它们定义为不可枚举的属性。

第48条:避免在枚举期间修改对象

当使用for...in循环枚举一个对象的属性时,确保不要修改该对象。

当迭代一个对象时,如果该对象的内容可能会在循环期间被改变,应该使用while或者for循环来代替for...in循环。

第49条:数组迭代要优先使用for循环而不是for...in循环

考虑在循环之前将数组的长度存储在一个局部变量中以避免重新计算数组长度。

第50条:迭代方法优于循环

使用迭代方法(如Array.prototype.forEach或Array.prototype.map)替换for循环使得代码更加可读,并且避免了重复循环控制逻辑。

在需要提前终止循环的情况下,还是推荐使用传统的循环。

第51条:在类数组对象上复用通用的数组方法

任意一个具有索引属性和恰当length属性的对象都可以使用通用的Array方法。

第52条:数组字面量优于数组构造函数

 

第6章 库和API设计

 

第53条:保持一致的约定

在变量命名和函数签名中使用一致的约定

第54条:将undefined视为“没有值”

避免使用undefined表示任何非特定值。

使用描述性的字符串或命名布尔属性的对象,而不要使用undefined或null来代表特定应用标志。

提供参数默认值应该采用测试undefined而不是检查arguments.length。

在允许0、NaN或空字符串为有效参数的地方,绝不要通过真值测试来实现参数默认值。

第55条:接受关键字参数的选项对象

使用选项对象使得API更具有可读性、更容易记住。

第56条:避免不必要的状态

尽可能地使用无状态的API。(比如"foo".toUpperCase总为"FOO",那么这就是无状态的;而Date()则对应着可变)

如果API是有状态的,辨识出每隔操作与哪些状态有关联。

第57条:使用结构类型(鸭子类型)设计灵活的接口

第58条:区分数组对象和类数组对象

第59条:避免过度的强制转换

第60条:支持方法链

无状态的API的部分功能是将复杂的操作分解成更小的操作的灵活性。

使用方法链来连接无状态的操作。

通过在无状态的方法中返回新对象来支持方法链。

通过在有状态的方法中返回this来支持方法链。

 

第7章 并发

 

第61条:不要阻塞I/O时间队列

异步API使用回调函数来延缓处理代价高昂的操作以避免阻塞主应用程序

JavaScript并发地接收时间,但会使用一个事件队列按序地处理事件处理程序。

在应用程序事件队列中绝不要使用阻塞的I/O。

第62条:在异步序列中使用嵌套或命名的回调函数

使用嵌套或命名的回调函数按顺序地执行多个异步操作。

尝试在过多的嵌套的回调函数和尴尬的命名的非嵌套回调函数之间取得平衡。

避免将可被并行执行的操作顺序化。

第63条:当心丢弃错误

通过编写共享的错误处理函数来避免复制和粘贴错误处理代码。

确保明确地处理所有的错误条件以避免丢弃错误。

第64条:对异步循环使用递归

循环是不能异步的。

使用递归函数在事件循环的单独伦次中执行迭代。

在事件循环的单独轮次中执行递归,并不会导致调用栈溢出。

第65条:不要在计算时阻塞事件队列

避免在主事件队列中执行代价高昂的算法。

第66条:使用计数器来执行并行操作

JavaScript应用程序中的事件发生是不确定的,即顺序是不可预测的。

使用计数器避免并行操作中的数据竞争。

第67条:绝不要同步地代用异步的回调函数

即使可以立即得到数据,也绝不要同步地调用异步回调函数。

同步地调用异步的回调函数扰乱了语气的操作序列,并可能导致意想不到的交错代码。

同步地调用异步的回调函数可能导致栈溢出或错误地处理异常。

使用异步的API,比如setTimeout函数来调度异步回调函数,使其运行于另一个回合。

第68条:使用promise模式清洁异步逻辑

promise代表最终值,即并行操作完成时最终产生的效果。

使用Promise组合不同的并行操作。

使用promise模式的API避免数据竞争。

在要求有意的竞争条件时使用select(也称为choose)。

 

《编写高质量JavaScript代码的68个有效方法》