Speaking JavaScript

 26th October 2019 at 9:08am
NameSpeaking JavaScript
AuthorAxel Rauschmayer
Edition1st
Release DateFebruary 2014
ISBN-13978-1-4493-6503-5
Medium电子书
Rating4

看完第八章。后面的内容我觉得不用看了,当参考书翻就好了。重点学习下 ES6 然后开始写代码。

历史原因

JavaScript 参考了很多语言的特性,所以它是个杂揉体。很多时候,学习 JavaScript 不止是学语言特性本身,还有学习它的 common pattern

JavsScript 在 ES3 之前没有异常处理机制,所以它的语言特性里面,遍布了各类隐式转换和 failed silently。比如:

>>> '1' < 3          // true
>>> 1/0              // Infinity
>>> Number("abcd")   // NaN

从规范上看,JS 中的数字都是浮点数(1.0 === 1),但是 JS 解释器为了效率,会在内部实现上尽量用整型。

Closure

A closure is a function plus the connection to the variables of its surrounding scopes.
From Speaking JavaScript

The IIFE Pattern: Introducing a New Scope

IIFE (immediately invoked function expression, pronounced “iffy”),可以用来创建一个 Scope。

var result = [];
for (var i=0; i < 5; i++) {
    result.push(function () { return i });  // (1)
}
console.log(result[1]()); // 5 (not 1)
console.log(result[3]()); // 5 (not 3)

for (var i=0; i < 5; i++) {
    (function () {
        var i2 = i; // copy current i
        result.push(function () { return i2 });
    }());
}

ES6 引入了 let 替代 var 以实现 block scope。

Expressions vs Statements

Expression 是指可以返回一个右值的式子,如 myvar1+2。Statement 表示一个行为,它往往是由 expression 组成的。比如 var salutation; 是一行 statement,它由一个 declaration expression 组成。

Using ambiguous expressions as statements

有些语句可能有歧义:

// 既可以表示一个有 foo 属性的 Object,
// 也可以表示一个 block,里面有一个 label foo 调用了 bar(3, 5)。
{
    foo: bar(3, 5)
}
// 既可以表示一个 Named Function Expression,返回一个函数对象,
// 也可以表示一个函数声明,给 foo 变量赋值为一个函数。
function foo() {}

所以 JS 规范要求,一个 expression statement 必须:

  1. 不以大括号开头
  2. 不以 function 关键字开头

这会有一些影响,比如你向 eval 传递一个 Object literal 时,必须用括号包起来:

> eval('{ foo: 123 }')
123
> eval('({ foo: 123 })')
{ foo: 123 }

比如你用 IIFE (Immediately Invoked function expression) 时,必须这样写:

> (function () { return 'abc' }())
'abc'

// 函数声明需要一个函数名
> function () { return 'abc' }()
SyntaxError: function statement requires a name

// 函数声明不能马上被调用
> function foo() { return 'abc' }()
SyntaxError: Unexpected token )

Automatic Semicolon Insertion

JS 解析器提供了一种自动插入分号的机制,用来使分号变成可选的。它判断是否需要插入分号有这几个依据:

  • A line terminator (e.g., a newline) is followed by an illegal token.
  • A closing brace is encountered.
  • The end of the file has been reached.

比如:

if (a < 0) a = 0
console.log(a)

// a = 0 后不能出现 console
// console 一行是文件尾
if (a < 0) a = 0;
console.log(a);
function add(a,b) { return a+b }
function add(a,b) { return a+b; }    // closing brace

这种机制可能会带来一些问题:

return
{
    name: 'John'
};

// 转换成下面的代码:
// 返回 undefined;然后跟着一个块,块里面有一个 label 叫 name
return;
{
    name: 'John'
};
func()
[ 'ul', 'ol' ].foreach(function (t) { handleTag(t) })

// 转换成下面的代码:
// 'ul', 'ol' 被逗号表达式了,返回 'ol'
// JS 解析器认为 func() 可能返回一个对象,可以取它的 'ul' 属性
func()['ol'].foreach(function (t) { handleTag(t) });

对于 JS 这种语法设计不够精妙的语言来讲,最好是自己加分号,不要相信解析器(它也没办法做到精准)。

Types, Primitives, Wrapping and Unwrapping Primitives

JavaScript 有六种类型,分别是:bool, number, string, object, null, undefined,其中除了 object 其他五种都被叫做 Primitive Values。bool, number, string 有对应的 Wrapper Object Class,分别是 Boolean, Number, String

> String(123)    // number 转换成 string
'123'
> let a = new String(123)     // 定义一个 String 类实例
> a
[String: '123']
> a.valueOf()    // String 类实例 unwrap 成 primitive
'123'
> a === '123'     // primitives 与 object 作比较时永远不相等
false