第一部分
1 typeof 有一个特殊的安全防范机制
要为某个缺失的功能写 polyfill,如何避免重复声明变量?
- 使用 typeof 判断
1 | if (typeof atob === "undefined") { |
如果要为某个缺失的功能写polyfill(即衬垫代码或者补充代码,用来补充当前运行环境中缺失的功能),一般不会用 var atob 来声明变量 atob。如果在 if 语句中使用 var atob,声明会被提升(hoisted,参见《你不知道的
JavaScript(上卷)》1中的“作用域和闭包”部分)到作用域(即当前脚本或函数的作用域)的最顶层,即使 if 条件不成立也是如此(因为 atob 全局变量已经存在)。在有些浏览器中,对于一些特殊的内建全局变量(通常称为“宿主对象”,host object),这样的重复声明会报错。去掉 var 则可以防止声明被提升
- 检查所有全局变量是否是全局对象的属性,浏览器中的全局对象是 window。
1 | if (window.DEBUG) { |
- 还有一些人喜欢使用“依赖注入”(dependency injection)设计模式,就是将依赖通过参数显式地传递到函数中,
1 | function doSomethingCool(FeatureXYZ) { |
2 值
2.1 怎样来判断 0.1 + 0.2 和 0.3 是否相等?
最常见的方法是设置一个误差范围值,通常称为“机器精度”(machine epsilon),对 JavaScript 的数字来说,这个值通常是 2^-52 (2.220446049250313e-16)。
1 | function numbersCloseEnoughToEqual(n1,n2) { |
Number.EPSILON 为ES6 新增
2.2 整数检测
可以使用 ES6 中的 Number.isInteger(..)
方法
2.3 按惯例我们用 void 0 来获得 undefined
虽然undefined在现在主流的绝大部分浏览器的全局作用域上面都是不能更改了,但是在函数作用域或者块级作用域下面还是能被重写的,当然绝大部分人应该都不会去干这种傻事,但是还是用viod 0吧,这样可以以防万一,同时也更像一个老司机的代码啊
2.4 很多 JavaScript 程序都可能存在 NaN 方面的问题,所以我们应该尽量使用 Number.isNaN(..)这样可靠的方法,无论是系统内置还是 polyfill。
2.5 特殊等式
ES6 中新加入了一个工具方法 Object.is(..)来判断两个值是否绝对相等,可以用来处理上述所有的特殊情况:
1 | var a = 2 / "foo"; |
2.6 值和引用
简单值(即标量基本类型值,scalar primitive)总是通过值复制的方式来赋值 / 传递,包括
null、undefined、字符串、数字、布尔和 ES6 中的 symbol。
复合值(compound value)——对象(包括数组和封装对象)和函数,则总是通过引用复制的方式来赋值 / 传递。
3 原生函数
3.1
1 | var a = new String( "abc" ); |
通过构造函数(如 new String(“abc”))创建出来的是封装了基本类型值(如 “abc”)的封装对象。
封 装 对 象(object wrapper) 扮 演 着 十 分 重 要 的 角 色。 由 于 基 本 类 型 值 没 有.length和 .toString()这样的属性和方法,需要通过封装对象才能访问,此时 JavaScript 会自动为基本类型值包装(box 或者 wrap)一个封装对象:
1 | var a = "abc"; |
一般情况下,我们不需要直接使用封装对象。最好的办法是让 JavaScript引擎自己决定什么时候应该使用封装对象。换句话说,就是应该优先考虑使用 “abc” 和 42 这样的基本类型值,而非 new String(“abc”) 和 newNumber(42)。
3.2 内部属性 [[Class]]
所有 typeof 返回值为 “object”的对象(如数组)都包含一个内部属性[[Class]](我们可以把它看作一个内部的分类,而非传统的面向对象意义上的类)。这个属性无法直接访问,一般通过 Object.prototype.toString(..) 来查看。
1 | Object.prototype.toString.call( [1,2,3] ); |
4 强制类型转换
将值从一种类型转换为另一种类型通常称为类型转换(type casting),这是显式的情况;隐式的情况称为强制类型转换(coercion)。
也可以这样来区分:类型转换发生在静态类型语言的编译阶段,而强制类型转换则发生在动态类型语言的运行时(runtime)
1 | var a = 42; |
4.1 JSON.stringify
JSON.stringify(..) 在对象中遇到undefined、function 和 symbol时会自动将其忽略,在数组中则会返回 null(以保证单元位置不变)。
1 | JSON.stringify( undefined ); // undefined |
4.2 ~ 和 ~~ 的作用 (了解)
~x 大致等同于 -(x+1)。
1 | ~42; // -(42+1) ==> -43 |
~~x 能将值截除为一个 32 位整数,x | 0 也可以,而且看起来还更简洁
4.3 parseInt
ES5 之前的 parseInt(..) 有一个坑导致了很多 bug。即如果没有第二个参数来指定转换的
基数(又称为 radix),parseInt(..) 会根据字符串的第一个字符来自行决定基数
将第二个参数设置为 10,即可避免这个问题:
从 ES5 开始 parseInt(..) 默认转换为十进制数,除非另外指定。如果你的代码需要在 ES5
之前的环境运行,请记得将第二个参数设置为 10
4.4 a + “”(隐式)和前面的 String(a)(显式)之间有一个细微的差别
1 | var a = { |
根据ToPrimitive 抽象操作规则,a + “” 会对 a 调用 valueOf() 方法,然后通过 ToString 抽象
操作将返回值转换为字符串。而 String(a) 则是直接调用 ToString()
5 语法
5.1 语句的结果值
如果你用开发控制台(或者 JavaScript REPL——read/evaluate/print/loop 工具)调试过代
码,应该会看到很多语句的返回值显示为 undefined,只是你可能从未探究过其中的原因。
其实控制台中显示的就是语句的结果值。
代码块的结果值就如同一个隐式的返回,即返回最后一个语句的结果值;
5.2 上下文规则
1 标签
但 foo: bar() 这样奇怪的语法:叫作“标签语句”。而 JavaScript 通过标签跳转能够实现 goto的部分功能。continue 和 break 语句都可以带一个标签,因此能够像 goto 那样进行跳转。
5.3 运算符优先级
1 && 运算符先于 || 执行
false && true || true 的执行顺序如下:
1 | false && true || true; // true |
&& 先执行,然后是 ||。
&& 运算符先于 || 执行
2 短路
1 | function doSomething(opts) { |
1 | function doSomething(opts) { |
3 && 运算符的优先级高于 ||,而 || 的优先级又高于 ? :。
1 | a && b || c ? c || b ? a : c && b : a |
4 关联
(1)
a ? b : c ? d : e;
它的组合顺序是
1 | a ? b : (c ? d : e)。 |
(2)
1 | var a, b, c; |
它首先执行 c = 42,然后是 b = ..,最后是 a = ..。因为是右关联,所以它实际上是这样
来处理的:a = (b = (c = 42))。
(3)
1 | var a = 42; |
分解如下
1 | ((a && b) || c) ? ((c || b) ? a : (c && b)) : a |
第二部分
1 省点回调
为了更优雅地处理错误,有些 API设计提供了分离回调(一个用于成功通知,
一个用于出错通知)
1 | function success(data) { |
2 Node 风格
还有一种常见的回调模式叫作“error-first 风格”(有时候也称为“Node 风格”,因为几乎
所有 Node.js API都采用这种风格),其中回调的第一个参数保留用作错误对象(如果有的话)。如果成功的话,这个参数就会被清空 / 置假(后续的参数就是成功数据)。如果产生了错误结果,那么第一个参数就会被置起 / 置真(通常就不会再传递其他结果):
1 | function response(err,data) { |