《JavaScript权威指南》阅读笔记(上) · 易浮的小窝

断续读完了 JavaScript 开发者眼中的圣经,总之,真是厚厚的一本手册,上千页的非常详细的介绍,大部分是基于 API 的细节,因此很多东西其实是没必要做笔记的(没必要的意思是,随着阅历的增加,很多特性你也许已经了然于胸,但不管怎样,圣经就是圣经,我觉得有空多看两遍核心部分是很有裨益的),因此这里只列出一些核心点和容易被疏漏的地方,首先是第一部分,关于语言的核心

JavaScript权威指南

作者 David Flanagan,封面动物:爪哇犀牛 - Javan rhinoceros

词法结构

  • JavaScript 使用 unicode 编码
  • 分号解析规则:缺少分号无法解析代码的时候(因此以 ([ 开始的代码很大可能与前面的语句一起解析)

类型、值和变量

  • 两种数据类型
    • 基本类型(Primitive),也是不可变类型
      • Number
      • String
      • Boolean
      • Null
      • Undefined
      • Symbol
    • 对象(Object),也是可变类型
      • 数组 Array
      • 函数 Function
      • 其他(包括 Date、RegExp 等)
  • 字符串换行技巧

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    var str1 = ''+
    'line1'+
    'line2'
    // ES5
    var str2 = '
    line1
    line2'
    // ES6
    var str3 = `
    line1
    line2
    `
  • 转换为 false 的六个值,其他均为 true

    • undefined
    • null
    • 0
    • -0
    • NaN
    • “”(空字符串)
  • nullundefined
    1
    2
    typeof null === 'object' // true, 表明是一个空对象
    // 而 undefined 则指代一种更深层次的空 => 未定义或不存在

表达式和运算符

  • 前后自增量

    1
    2
    var i = 1, j = ++i // i和j的值都是2,前增量 pre-increment,赋值前自增
    var i = 1, j = i++ // i是2,j是1,后增量 post-increment
  • NaN 与任何值不相等,包括本身

  • in 操作符用来检测对象的属性是否存在

    1
    2
    var data = [7,8,9]
    '0' in data === true // true 因为存在索引0,可以转换成字符串 '0'
  • 通过 delete 操作符删除数组元素并不会修改数组长度

语句

  • 函数或声明不应该放在除函数外的代码块中
  • 在函数中,switch 中的 break 可以用 return 实现
  • break 不同通过标签跳转到函数外部
  • catch 语句中花括号是有块级作用域的

对象

1
2
3
4
5
6
7
8
var obj1 = {
'current age': 26, // 空格或连字符做属性要加引号
'for': 'wife', // 保留字,ES3 中必须加引号,建议一致加引号
name: 'james', // ES3 中此逗号在 IE 中会报错,建议去除
}
//
var obj2 = Object.create(null) // 不会继承任何属性和方法
var obj3 = Object.create(Object.prototype) // 相当于 var obj3 = {}
  • 每一个对象都具有原型(nullObject.prototype 除外)

数组

  • 稀疏数组,数组的长度大于数组项的数量

    1
    2
    3
    4
    5
    6
    7
    // 产生稀疏数组
    var a1 = new Array(5);
    var a2 = []
    a2.length = 5
    var a3 = [, , ,]
    var a4 = [1, 2, 3, 4]
    delete a4[1] // 等同于[1, , 3, 4]
  • 压缩数组

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    var a = [1, undefined, , , 3, null]
    // b === [1, undefined, 3, null], 稀疏数组被压缩
    b = a.filter(function () {
    return true
    })
    // c === [1,3], 进一步压缩 undefined 和 null
    c = a.filter(function (x) {
    return x !== undefined && x !== null
    })
  • 空数组调用 .every() / .some() 总是返回 true / false

  • 如何判断一个未知对象是否为数组(只有两种方法)

    1
    2
    3
    var arr = new Array()
    Array.isArray(arr) //true, ES5+
    Object.prototype.toString(arr) === '[object Array]' // ES3
  • 字符串可看成是只读的类数组对象,方便进行各种操作

函数

  • 不要将函数声明在代码块中(可换成表达式)
  • 非严格模式中,直接调用函数时其内部的 this 指向全局对象,作为对象的方法调用时则指向方法所属对象

    1
    2
    // 判断函数是否在严格模式中运行
    var strict = (function () { return !this }())
  • 方法的链式调用——当方法不需要返回值时,直接返回 this

  • callee 指向当前正在执行的函数,而 caller 则指向调用当前函数的函数(严格模式中禁用)
  • 函数是一种特殊的对象,可以拥有属性(类似构造函数)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // 如下例子,直接使用函数的属性存储外部初始值(每次调用函数都是0), 而省去一次变量声明来存储初始值
    uniqueInterger.counter = 0 // 函数声明提前
    function () {
    return uniqueInterger.counter++
    }
    // 函数同样可以把自己当成一个数组来使用,如下例子
    // 计算阶乘,并将结果缓存到函数的属性中
    function factirual (n) {
    if (!(n in factorial)) { // 如果没有缓存结果
    factorial[n] = n * factorial(n-1) // 计算结果并缓存
    }
    return factorial[n] // 返回缓存的结果
    }
  • 技术角度上讲,所有的函数都是闭包。一般认为闭包是在函数中返回的另一个函数

  • 函数的 length 为期待的形参个数,而 argument.length 则为实际传入实参个数
  • call()apply() 方法类似,第一个参数会绑定为 this 的值,apply参数传入放在一个数组中;而 bind() 是ES5+方法,绑定所有实参到对象
    1
    2
    3
    4
    5
    6
    7
    8
    9
    // f.call(o) 或 f.apply(o) 的实际调用类似下面
    o.m = f; // 将f存储为o的临时方法
    o.m(); // 调用临时方法
    delete o.m; // 将临时方法删除
    // 以对象o的方法调用函数f(), 并传入两个参数
    f.call(o, 1, 2); // 直接传入
    f.apply(o, [1, 2]); // 参数包装成数组形式,方便传入任意数量参数
    // bind 对比 call 和 apply,不仅将第一个实参绑定至 this,也将其他实参绑定至引用对象(柯里化)
1
2
3
4
5
6
7
8
9
10
11
12
13
// 在ES3中实现 array.map() 方法,该实现为懒加载,因此只在初始化生成,避免每次调用 map 都要判断
var map = Array.prototype.map
? function (array, func) { return array.map(func) }
: function (array, func) {
var results = []
for (var i = 0, len = array.length; i < len; i++) {
if (i in array) {
// array.map(item, index. array)
results[i] = func.call(null, array[i], i, array)
}
}
return results
}
  • 函数的 toString() 方法返回定义该函数的源码
  • 一般不用 Function() 构造函数生成函数
  • 高阶函数即操作函数的函数,接收函数作为参数

类和模块

类的所有实例对象都从同一个原型对象上继承属性,原型对象是类的核心

  • 构造函数调用的一个重要特征是,构造函数的原型会被用作新对象的原型
  • 构造函数是类的公共标识,原型对象是类的唯一标识
  • constructor 属性

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    function F () {}
    F.prototype.constructor === F // true
    //
    var o = new F()
    o.constructor === F // true,一般类的实例的 constructor 指向类的构造函数
    //
    function G () {}
    G.prototype = {} // 直接赋值原型会导致 constructor 丢失
    G.prototype.constructor === G // false
    G.prototype = {
    'constructor': G // 显式设置构造函数反式引用修复上面问题
    }
  • instanceof 检测的实际是对象的继承关系,而不是创建对象的构造函数

  • iframe 中一个 Array 的实例不是另一个 iframe 中的 Array 的实例
  • 一般通过闭包的方式实现私有状态
  • 抽象类是指一种在最高祖先级别定义这个类应该具有的方法,但这些方法没有具体实现,如果在子孙类中没有通过重载复写,一般在调用时会触发祖先定义的方法提示
  • ES3 中,对象的读写等属性并不能自行配置,因此不能将自定义类模拟成内置对象(for...in 会遍历到)

犀牛书对于继承这一方面的介绍一开始就非常的深奥难懂,其实不是特别良好的教程,推荐阅读《JavaScript 高级程序设计》中关于原型与继承的部分

正则表达式的模式匹配

JavaScript 的正则是 Perl 的大型子集

  • ES3 中,对于同样的正则表达式直接量,对应的是同一个正则对象。在 ES5 中,则跟对象等引用类似,不会因为同样是空对象就认为是同一个对象。(由于即使是 IE 的实现也符合 ES5 标准,因此实际上只需要考虑 ES5 的规定即可)
  • 对于特殊的字符,需要用反斜杠进行转义
  • 常见字符匹配语法
    • [abc] => 方括号内任意字符(这里是 a,b,c)
    • [^abc] => 除方括号的任意字符(这里指不含 a 或 b 或c的任意一个)
    • . => 除了换行符外的其他字符
    • w => 等价于 [a-zA-Z0-9],指字母和数字
    • W => 上一条的取反
    • s => 一般匹配空格
    • S => 上一条的取反,一般指所有非空字符,范围比 w 大一些
    • d => 等价于 [0-9],匹配数字
    • D => 上一条取反,匹配非数字
  • 重复语法
    • {n, m} => 至少出现 n 次,但最多 m
    • {n,} => 至少出现 n
    • {n} => 只能出现 n
    • ? => 出现1次或不出现,等价于 {0, 1}
    • + => 至少出现1次,等价于 {1,}
    • * => 可以随便出现(不出现,1次或多次),等价于 {0, }
  • 选择分组
    • | 选择符号,区分可选的分组,这些分组会短路判断,只有在前面的分组不匹配时才尝试后面的分组
    • () 分组符号,将模式打包,定义子模式或子表达式(同时后面可以使用快捷写法)
  • 指定位置
    • ^ => 指定作为开头
    • $ => 指定作为结尾
    • b => 匹配边界
    • B => 上一条的取反
  • 修饰符
    • i => 忽略大小写
    • g => 全局匹配
    • m => 多行模式
  • String 方法
    • .search() => 返回匹配字符串所在的位置,若无匹配则返回 -1
    • .replace(reg, string) => 执行检索和替换,参数可以是函数
    • .match() => 全局检索时返回匹配结果的数组,否则返回首次匹配到的带自模式的
    • .split() => 根据匹配项分割字符串
  • 一般推荐使用直接量法构造正则