这可能是 JavaScript 最好的基础书了!每次翻阅都会有新的收获。特此做个完整的读书笔记来巩固基础,加深印象~
全书 700 多页,共 25 章。读书笔记分为上(1-7章)、中(8-16章)、下(17-25章)三个部分。
第 1 章 JavaScript 简介
- JavaScript 诞生于 1995 年。
- 在 Netscape Navigator 2 正式发布前夕,Netscape 为了搭上媒体热炒 Java 的顺风车,临时把 LiveScript 改名为 JavaScript。
- 两个不同的 JavaScript 版本:Netscape Navigator 中的 JavaScript、Internet Explorer 中的 JScript。
- 欧洲计算机制造商协会(ECMA,European Computer Manufacturers Association)。
- 39 号技术委员会(TC39,Technical Committee #39)。
- ECMA-262——定义一种名为 ECMAScript(发音为“ek-ma-script”)的新脚本语言的标准。
- ISO/IEC(International Organization for Standardization and International Electrotechnical Commission,国标标准化组织和国际电工委员会)也采用了 ECMAScript 作为标准(即 ISO/IEC-16262)。
- IE8 是第一个着手实现 ECMA-262 第 5 版的浏览器,并在 IE9 中提供了完整的支持。
- 负责制定 Web 通信标准的 W3C(World Wide Web Consortium,万维网联盟)。
- JavaScript 是一种专为与网页交互而设计的脚本语言,由下列三个不同的部分组成:
- 核心(ECMAScript),由 ECMA-262 定义,提供核心语言功能;
- 文档对象模型(DOM),提供访问和操作网页内容的方法和接口;
- 浏览器对象模型(BOM),提供与浏览器交互的方法和接口。
第 2 章 在 HTML 中使用 JavaScript
- 向 HTML 页面中插入 JavaScript 的主要方法,就是使用
<script>
元素。 - HTML 4.01 为
<script>
定义了下列 6 个属性:async、charset、defer、language、src、type。 - 所有
<script>
元素都会按照它们在页面中出现的先后顺序依次被解析。在不使用 defer 和 async 属性的情况下,只有在解析完前面<script>
元素中的代码之后,才会开始解析后面<script>
元素中的代码。 - 由于浏览器会先解析完不使用 defer 属性的
<script>
元素中的代码,然后再解析后面的内容, 所以一般应该把<script>
元素放在页面最后,即主要内容后面,</body>
标签前面。 - 使用 defer 属性可以让脚本在文档完全呈现之后再执行。延迟脚本总是按照指定它们的顺序执行。
- 使用 async 属性可以表示当前脚本不必等待其他脚本,也不必阻塞文档呈现。不能保证异步脚本按照它们在页面中出现的顺序执行。
- 可扩展超文本标记语言,即 XHTML(Extensible HyperText Markup Language),是将 HTML 作为 XML 的应用而重新定义的一个标准。
- 一般认为最好的做法还是尽可能使用外部文件来包含 JavaScript 代码,优点:可维护性、可缓存、适应未来。
- 三种文档模式:混杂模式(quirks mode)、标准模式(standards mode)、准标准模式(almost standards mode)。
- 使用
<noscript>
元素可以指定在不支持脚本的浏览器中显示的替代内容。
第 3 章 基本概念
- ECMAScript 中的一切(变量、函数名和操作符)都区分大小写。
- 标识符可以是按照下列格式规则组合起来的一或多个字符:
- 第一个字符必须是一个字母、下划线(_)或一个美元符号($);
- 其他字符可以是字母、下划线、美元符号或数字。
- ECMAScript 5 引入了严格模式(strict mode)的概念。在严格模式下,ECMAScript 3 中的一些不确定的行为将得到处理,而且对某些不安全的操作也会抛出错误。
- ECMAScript 的变量是松散类型的,所谓松散类型就是可以用来保存任何类型的数据。换句话说,每个变量仅仅是一个用于保存值的占位符而已。
- ECMAScript 中有 5 种简单数据类型(也称为基本数据类型):Undefined、Null、Boolean、Number 和 String。还有 1 种复杂数据类型——Object,Object 本质上是由一组无序的名值对组成的。ECMAScript 不支持任何创建自定义类型的机制,而所有值最终都将是上述 6 种数据类型之一。
- 对一个值使用 typeof 操作符可能返回下列某个字符串:
- “undefined”——如果这个值未定义;
- “boolean”——如果这个值是布尔值;
- “string”——如果这个值是字符串;
- “number”——如果这个值是数值;
- “object”——如果这个值是对象或 null;
- “function”——如果这个值是函数。
- 调用 typeof null 会返回”object”,因为特殊值 null 被认为是一个空的对象引用。
- 从技术角度讲,函数在 ECMAScript 中是对象,不是一种数据类型。然而,函数也确实有一些特殊的属性,因此通过 typeof 操作符来区分函数和其他对象是有必要的。
- 如果定义的变量准备在将来用于保存对象,那么最好将该变量初始化为 null 而不是其他值。
- 实际上,undefined 值是派生自 null 值的,因此 ECMA-262 规定对它们的相等性测试要返回 true:alert(null == undefined); //true
- 关于浮点数值计算会产生舍入误差的问题,有一点需要明确:这是使用基于 IEEE754 数值的浮点计算的通病。
- 实际上只有 0 除以 0 才会返回 NaN,正数除以 0 返回 Infinity,负数除以 0 返回-Infinity。
- NaN 本身有两个非同寻常的特点。首先,任何涉及 NaN 的操作(例如 NaN/10)都会返回 NaN,这个特点在多步计算中有可能导致问题。其次,NaN 与任何值都不相等,包括 NaN 本身。
- 有 3 个函数可以把非数值转换为数值:Number()、parseInt()和 parseFloat()。
- 不指定基数意味着让 parseInt()决定如何解析输入的字符串,因此为了避免错误的解析,我们建议无论在什么情况下都明确指定基数。
- Object 的每个实例都具有下列属性和方法:
- constructor:保存着用于创建当前对象的函数。
- hasOwnProperty(propertyName):用于检查给定的属性在当前对象实例中(而不是在实例的原型中)是否存在。
- isPrototypeOf(object):用于检查传入的对象是否是传入对象的原型。
- propertyIsEnumerable(propertyName):用于检查给定的属性是否能够使用 for-in 语句来枚举。
- toLocaleString():返回对象的字符串表示,该字符串与执行环境的地区对应。
- toString():返回对象的字符串表示。
- valueOf():返回对象的字符串、数值或布尔值表示。通常与 toString()方法的返回值相同。
- 操作符:
- 一元操作符:递增(++)和递减(–)操作符、一元加(+)和减(-)操作符
- 位操作符:按位非(~)、按位与(&)、按位或(|)、按位异或(^)、左移(<<)、有符号右移(>>)、无符号右移(>>>)
- 布尔操作符:逻辑非(!)、逻辑与(&&)、逻辑或(||)
- 乘性操作符:乘法(*)、除法(/)、求模(%)
- 加性操作符:加法(+)、减法(-)
- 关系操作符:小于(<)、大于(>)、小于等于(<=)、大于等于(>=)
- 相等操作符:相等(
==
)和不相等(!=
)、全等(===
)和不全等(!==
) - 条件操作符:variable = boolean_expression ? true_value : false_value;
- 赋值操作符:简单赋值(=)、复合赋值(+=、-=等)
- 逗号操作符:使用逗号操作符可以在一条语句中执行多个操作
- 同时使用两个逻辑非操作符,实际上就会模拟 Boolean()转型函数的行为。
- 大写字母的字符编码全部小于小写字母的字符编码。
- 两组操作符:相等和不相等——先转换再比较,全等和不全等——仅比较而不转换。
- 语句:
- if语句
- do-while语句
- while语句
- for语句
- for-in语句
- label语句
- break和continue语句
- with语句
- switch语句
- ECMAScript 函数的一个重要特点:命名的参数只提供便利,但不是必需的。
- 关于 arguments 的行为,还有一点比较有意思。那就是它的值永远与对应命名参数的值保持同步。
- ECMAScript 中的所有参数传递的都是值,不可能通过引用传递参数。
- ECMAScript 中也没有函数签名的概念,因为其函数参数是以一个包含零或多个值的数组的形式传递的。由于不存在函数签名的特性,ECMAScript 函数不能重载。
第 4 章 变量、作用域和内存问题
- ECMAScript 变量可能包含两种不同数据类型的值:基本类型值和引用类型值。基本类型值指的是简单的数据段,而引用类型值指那些可能由多个值构成的对象。
- ECMAScript 中所有函数的参数都是按值传递的。也就是说,把函数外部的值复制给函数内部的参数,就和把值从一个变量复制到另一个变量一样。
- 作用域链的用途,是保证对执行环境有权访问的所有变量和函数的有序访问。
- 标识符解析是沿着作用域链一级一级地搜索标识符的过程。搜索过程始终从作用域链的前端开始, 然后逐级地向后回溯,直至找到标识符为止(如果找不到标识符,通常会导致错误发生)。
- 延长作用域链:对 with 语句来说,会将指定的对象添加到作用域链中。对 catch 语句来说,会创建一个新的变量对象,其中包含的是被抛出的错误对象的声明。
- 使用 var 声明的变量会自动被添加到最接近的环境中。如果初始化变量时没有使用 var 声明,该变量会自动被添加到全局环境。
- JavaScript 具有自动垃圾收集机制,也就是说,执行环境会负责管理代码执行过程中使用的内存。
- JavaScript 中最常用的垃圾收集方式是标记清除(mark-and-sweep)。另一种不太常见的垃圾收集策略叫做引用计数(reference counting)。
- 优化内存占用的最佳方式,就是为执行中的代码只保存必要的数据。一旦数据不再有用,最好通过将其值设置为 null 来释放其引用——这个做法叫做解除引用(dereferencing)。
- 确定一个值是哪种基本类型可以使用 typeof 操作符,而确定一个值是哪种引用类型可以使用 instanceof 操作符。
第 5 章 引用类型
- ECMAScript 提供了很多原生引用类型:Object、Array、Date、RegExp、Function、基本包装类型(Boolean、Number、String)、单体内置对象(Global、Math)等。
- Object 是一个基础类型,其他所有类型都从 Object 继承了基本的行为。
- 创建 Object 实例的方式有两种。第一种是使用 new 操作符后跟 Object 构造函数,另一种方式是使用对象字面量表示法。
- 创建数组的基本方式有两种。第一种是使用 Array 构造函数,创建数组的第二种基本方式是使用数组字面量表示法。
- 数组的 length 属性很有特点——它不是只读的。因此,通过设置这个属性,可以从数组的末尾移除项或向数组中添加新项。
- 数组方法:
- 检测数组:value instanceof Array、Array.isArray()
- 转换方法:toLocaleString()、toString()、valueOf()
- 栈方法LIFO(Last-In-First-Out,后进先出):push()和pop()
- 队列方法FIFO(First-In-First-Out,先进先出):shift()和push()、unshift()和pop()
- 重排序方法:reverse()、sort()
- 操作方法:concat()、slice()、splice()
- 位置方法:indexOf()、lastIndexOf()
- 迭代方法:every()、filter()、forEach()、map()、some()
- 归并方法:reduce()、reduceRight()
- sort()方法默认按升序排列数组项,调用每个数组项的 toString()转型方法进行比较。
- splice()的主要用途是向数组的中部插入项,常用于删除、插入、替换。
- Date 类型使用自 UTC(Coordinated Universal Time,国际协调时间)1970 年 1 月 1 日午夜(零时)开始经过的毫秒数来保存日期。
- ECMAScript 5 添加了 Data.now()方法,返回表示调用这个方法时的日期和时间的毫秒数。
- ECMAScript 通过 RegExp 类型来支持正则表达式。
var expression = / pattern / flags ;- 模式(pattern)部分可以是任何简单或复杂的正则表达式,可以包含字符类、限定符、分组、向前查找以及反向引用。
- 每个正则表达式都可带有一或多个标志(flags),用以标明正则表达式的行为。
- 正则表达式的匹配模式支持下列 3 个标志:
- g:表示全局(global)模式,即模式将被应用于所有字符串,而非在发现第一个匹配项时立即停止;
- i:表示不区分大小写(case-insensitive)模式,即在确定匹配项时忽略模式与字符串的大小写;
- m:表示多行(multiline)模式,即在到达一行文本末尾时还会继续查找下一行中是否存在与模式匹配的项。
- 正则表达式模式中使用的所有元字符都必须转义,元字符包括:( [ { \ ^ $ | ) ? * + . ] }。
- 由于 RegExp 构造函数的模式参数是字符串,所以在某些情况下要对字符进行双重转义。所有元字符都必须双重转义,那些已经转义过的字符也是如此。
- RegExp 的每个实例都具有下列属性,通过这些属性可以取得有关模式的各种信息:
- global:布尔值,表示是否设置了 g 标志。
- ignoreCase:布尔值,表示是否设置了 i 标志。
- lastIndex:整数,表示开始搜索下一个匹配项的字符位置,从 0 算起。
- multiline:布尔值,表示是否设置了 m 标志。
- source:正则表达式的字符串表示,按照字面量形式而非传入构造函数中的字符串模式返回。
- RegExp 对象的主要方法是 exec(),该方法是专门为捕获组而设计的。exec()接受一个参数,即要应用模式的字符串,然后返回包含第一个匹配项信息的数组;或者在没有匹配项的情况下返回 null。
- 正则表达式的第二个方法是 test(),它接受一个字符串参数。在模式与该参数匹配的情况下返回 true;否则,返回 false。
- 函数实际上是 Function 类型的实例,因此函数也是对象;而这一点正是 JavaScript 最有特色的地方。
- 由于函数是对象,因此函数名实际上也是一个指向函数对象的指针,不会与某个函数绑定。
- 函数内部属性:
- arguments 还有一个名叫 callee 的属性,该属性是一个指针,指向拥有这个 arguments 对象的函数。
- this 引用的是函数据以执行的环境对象——或者也可以说是 this 值(当在网页的全局作用域中调用函数时, this 对象引用的就是 window)。
- caller 这个属性中保存着调用当前函数的函数的引用,如果是在全局作用域中调用当前函数,它的值为 null。
- 函数属性:
- length 属性表示函数希望接收的命名参数的个数
- 对于 ECMAScript 中的引用类型而言,prototype 是保存它们所有实例方法的真正所在。在 ECMAScript 5 中,prototype 属性是不可枚举的,因此使用 for-in 无法发现。
- 函数方法:
- apply()和 call()。这两个方法的用途都是在特定的作用域中调用函数,实际上等于设置函数体内 this 对象的值。
- call()方法与 apply()方法的作用相同,它们的区别仅在于接收参数的方式不同。在使用 call()方法时,传递给函数的参数必须逐个列举出来。
- 使用 call()或 apply()来扩充作用域的最大好处,就是对象不需要与方法有任何耦合关系。
- bind()这个方法会创建一个函数的实例,其 this 值会被绑定到传给 bind()函数的值。
- toLocaleString()、toString()和valueOf()方法始终都返回函数的代码。返回代码的格式则因浏览器而异。
- 对基本包装类型的实例调用 typeof 会返回”object”,而且所有基本包装类型的对象都会被转换为布尔值 true。
- 使用 new 调用基本包装类型的构造函数,与直接调用同名的转型函数是不一样的。
- 在读取模式下访问基本类型值时,就会创建对应的基本包装类型的一个对象,从而方便了数据操作;操作基本类型值的语句一经执行完毕,就会立即销毁新创建的包装对象。
- String 类型方法:
- 字符方法:charAt()、charCodeAt()
- 字符串操作方法:concat()、slice()、substr()、substring()
- 字符串位置方法:indexOf()、lastIndexOf()
- trim()方法:创建一个字符串的副本,删除前置及后缀的所有空格,然后返回结果。
- 字符串大小写转换方法:toLowerCase()、toLocaleLowerCase()、toUpperCase()、toLocaleUpperCase()
- 字符串的模式匹配方法:match()、search()、replace()、split()
- localeCompare()方法:比较两个字符串在字母表中排序。
- fromCharCode()方法:静态方法,接收一或多个字符编码,然后将它们转换成一个字符串。
- ECMA-262 对内置对象的定义是:“由 ECMAScript 实现提供的、不依赖于宿主环境的对象,这些对象在 ECMAScript 程序执行之前就已经存在了。”
- 事实上,没有全局变量或全局函数;所有在全局作用域中定义的属性和函数,都是 Global 对象的属性。
- encodeURI()与 encodeURIComponent()的区别:
- encodeURI()主要用于整个 URI,而 encodeURIComponent()主要用于对 URI 中的某一段进行编码。
- encodeURI()不会对本身属于 URI 的特殊字符进行编码,例如冒号、正斜杠、 问号和井字号;而 encodeURIComponent()则会对它发现的任何非标准字符进行编码。
- eval()方法就像是一个完整的ECMAScript解析器,它只接受一个参数,即要执行的ECMAScript(或JavaScript) 字符串。
- 特殊的值 undefined、NaN 以及 Infinity 都是 Global 对象的属性。此外,所有原生引用类型的构造函数,像 Object 和 Function,也都是 Global 对象的属性。
- Math 方法:min()、max()、ceil()、floor()、round()、random()等。
第 6 章 面向对象的程序设计
- ECMA-262 把对象定义为:“无序属性的集合,其属性可以包含基本值、对象或者函数。”严格来讲,这就相当于说对象是一组没有特定顺序的值。
- 数据属性包含一个数据值的位置。数据属性有 4 个描述其行为的特性:
- [[Configurable]]:表示能否通过 delete 删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为访问器属性。默认值为 true。
- [[Enumerable]]:表示能否通过 for-in 循环返回属性。默认值为 true。
- [[Writable]]:表示能否修改属性的值。默认值为 true。
- [[Value]]:包含这个属性的数据值。默认值为 undefined。
- 可以多次调用 Object.defineProperty()方法修改同一个属性,但在把 configurable 特性设置为 false 之后就会有限制了。
- 访问器属性不包含数据值;它们包含一对儿 getter 和 setter 函数。访问器属性有如下 4 个特性:
- [[Configurable]]:表示能否通过 delete 删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为数据属性。默认值为 true。
- [[Enumerable]]:表示能否通过 for-in 循环返回属性。默认值为 true。
- [[Get]]:在读取属性时调用的函数。默认值为 undefined。
- [[Set]]:在写入属性时调用的函数。默认值为 undefined。
- 构造函数始终都应该以一个大写字母开头,而非构造函数则应该以一个小写字母开头。
- 任何函数,只要通过 new 操作符来调用,那它就可以作为构造函数;而任何函数,如果不通过 new 操作符来调用,那它跟普通函数也不会有什么两样。
- 我们创建的每个函数都有一个 prototype(原型)属性,这个属性是一个指针,指向一个对象, 而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。prototype 就是通过调用构造函数而创建的那个对象实例的原型对象。
- 当为对象实例添加一个属性时,这个属性就会屏蔽原型对象中保存的同名属性;换句话说,添加这个属性只会阻止我们访问原型中的那个属性,但不会修改那个属性。
- 在使用 for-in 循环时,返回的是所有能够通过对象访问的、可枚举的(enumerated)属性,其中既包括存在于实例中的属性,也包括存在于原型中的属性。
- 创建自定义类型的最常见方式,就是组合使用构造函数模式与原型模式。构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性。
- 所谓稳妥对象(durable objects),指的是没有公共属性,而且其方法也不引用 this 的对象。
- 许多 OO 语言都支持两种继承方式:接口继承和实现继承。接口继承只继承方法签名,而实现继承则继承实际的方法。
- ECMAScript 中描述了原型链的概念,并将原型链作为实现继承的主要方法。其基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。
- 构造函数、原型和实例的关系:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。
- 所有函数的默认原型都是 Object 的实例,因此默认原型都会包含一个内部指针,指向 Object.prototype。这也正是所有自定义类型都会继承 toString()、 valueOf()等默认方法的根本原因。
- 确定原型和实例的关系:1)使用 instanceof 操作符;2)使用 isPrototypeOf()方法。
- 原型链的问题:1)继承的实例属性变成了现在的原型属性,引用类型值的原型属性会被所有实例共享;2)在创建子类型的实例时,不能向超类型的构造函数中传递参数。
- 借用构造函数 (constructor stealing)的技术(有时候也叫做伪造对象或经典继承),即在子类型构造函数的内部调用超类型构造函数。
- 组合继承(combination inheritance),有时候也叫做伪经典继承,指的是将原型链和借用构造函数的技术组合到一块,从而发挥二者之长的一种继承模式。组合继承成为 JavaScript 中最常用的继承模式。
- 原型式继承,可以在不必预先定义构造函数的情况下实现继承,其本质是执行对给定对象的浅复制。而复制得到的副本还可以得到进一步改造。
- 寄生式继承,与原型式继承非常相似,也是基于某个对象或某些信息创建一个对象,然后增强对象,最后返回对象。
- 组合继承最大的问题就是无论什么情况下,都会调用两次超类型构造函数:一次是在创建子类型原型的时候,另一次是在子类型构造函数内部。
- 寄生组合式继承,本质上就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型。开发人员普遍认为寄生组合式继承是引用类型最理想的继承范式。
第 7 章 函数表达式
- 函数声明提升(function declaration hoisting),在执行代码之前会先读取函数声明。
- 匿名函数(anonymous function),因为 function 关键字后面没有标识符。(匿名函数有时候也叫拉姆达函数。)匿名函数的 name 属性是空字符串。
- arguments.callee 是一个指向正在执行的函数的指针,因此可以用它来实现对函数的递归调用。
- 闭包是指有权访问另一个函数作用域中的变量的函数。创建闭包的常见方式,就是在一个函数内部创建另一个函数。
- 由于闭包会携带包含它的函数的作用域,因此会比其他函数占用更多的内存。过度使用闭包可能会导致内存占用过多,我们建议读者只在绝对必要时再考虑使用闭包。
- 作用域链的这种配置机制引出了一个值得注意的副作用,即闭包只能取得包含函数中任何变量的最后一个值。
- JavaScript 没有块级作用域的概念。这意味着在块语句中定义的变量,实际上是在包含函数中而非语句中创建的。
- 匿名函数用来模仿块级作用域(通常称为私有作用域),这种技术经常在全局作用域中被用在函数外部,从而限制向全局作用域中添加过多的变量和函数。
- 任何在函数中定义的变量,都可以认为是私有变量,因为不能在函数的外部访问这些变量。 私有变量包括函数的参数、局部变量和在函数内部定义的其他函数。
- 特权方法(privileged method),指有权访问私有变量和私有函数的公有方法。
- 模块模式(module pattern)通过为单例添加私有变量和特权方法能够使其得到增强。
注:图片来自于Dribbble。