2. HTML 中的 JavaScript

2.1 元素

基本参数:

  • async
  • charset
  • crossorigin
  • defer
  • integrity
  • language
  • type
1<script src="example.js" />

这种方式在 HTML 中不合法,应该采用

1<script src="http://www.somewhere.com/afile.js"></script>

2.1.1 标签的放置

引用 js 时,应该将<script>放置在最后,如下

1<!DOCTYPE html>
2<html>
3  <head>
4    <title>Example HTML Page</title>
5  </head>
6  <body>
7    <!-- content here -->
8    <script src="example1.js"></script>
9    <script src="example2.js"></script>
10  </body>
11</html>

2.1.2 推迟的脚本

使用 defer 参数保证脚步在 DOMContentLoaded 事件之后进行加载。

1<script defer src="example1.js"></script>

但是并非所有浏览器支持 defer,所以最好还是将<script>标签放在最后

2.1.3 异步脚本

脚本加载顺序改变,但是都是在 DOMContentLoaded 事件之后进行加载。

1<script async src="example1.js"></script>

2.1.4 动态脚本加载

为了统一动态脚本加载行为,需要设置 async 参数

1let script = document.createElement('script');
2script.src = 'gibberish.js';
3script.async = false;
4document.head.appendChild(script);

但是这种设置会损害资源获取队列的优先级。解决方法

1<link rel="subresource" href="gibberish.js" />

2.1.5XHTML 中的改变

在 XHTML 中(<)会被识别为标签的开始

  • 一种方法是用(&lt;)代替(<);
  • 另一种方法是使用 CDATA
1<script type="text/javascript">
2  <![CDATA[
3  function compare(a, b) { if (a < b) {
4  console.log("A is less than B");
5  }
6    ]]>
7</script>

但是并非所有 XHTML 解释器能够支持 CDATA,使用 js 注释解决

1<script type="text/javascript">
2//<![CDATA[
3function compare(a, b) { if (a < b) {
4console.log("A is less than B");
5} else if (a > b) { console.log("A is greater than B");
6} else { console.log("A is equal yo B");
7} }
8//]]>
9</script>

2.2 内联代码与外部文件

虽然可以将内联代码嵌入 HTML,但是最好还是使用外部文件的方式。需要注意三点

  • 可维护性——js 代码与 HTML 分离,更好修理 BUG
  • 缓存——如果两个页面加载同一个 js,仅需加载一次,意味着更快的页面加载速度
  • 面向未来——XHTML 和 HTML 都可以使用

基于 SPDY/HTTP2 使用外部文件时,能够减少相同 js 的下载

2.3 文档模式

IE5.5 通过文档类型切换的使用,介绍了文档模式

  • quirks mode:IE 变得像 IE5,通过省略文档开头的 doctype(使用所有)
  • standard mode:更加标准

以下几种 doctype 使用时,使用 standard mode

1<!-- HTML 4.01 Strict -->
2<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
3<!-- XHTML 1.0 Strict -->
4<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
5<!-- HTML5 -->
6<!DOCTYPE html>
  • almost standard mode:这种模式具有许多标准特性,但不是严格。不同之处在于图片的放置

以下几种 doctype 使用时,使用 almost standard mode

1<!-- HTML 4.01 Transitional -->
2<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
3<!-- HTML 4.01 Frameset -->
4<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">
5<!-- XHTML 1.0 Transitional -->
6<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
7<!-- XHTML 1.0 Frameset -->
8<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">

2.4THE <NOSCRIPT> ELEMENT

<noscript>用于显示两种情况

  • 浏览区不支持脚本
  • 浏览器脚本关闭了

例子如下

1<!DOCTYPE html>
2<html>
3  <head>
4  <title>Example HTML Page</title>
5  <script ""defer="defer" src="example1.js"></script> <script ""defer="defer" src="example2.js"></script> </head>
6  <body>
7  <noscript>
8   <p>This page requires a JavaScript-enabled browser.</p> </noscript>
9  </body>
10</html>

3.语言基础

3.1 语法

3.1.1 大小写敏感(Case-Sensitivity)

3.1.2 Identifiers

  • 第一个字符必须是字母,下划线或者$

ECMAScript identifiers use camel case,举例如下

firstSecond

myCar

doSomethingImportant

3.1.3 注释

两种注释方式

// single line comment

/This is a multi-line comment/

3.1.4 严格模式(Strict Mode)

  • 消除 Javascript 语法的一些不合理、不严谨之处,减少一些怪异行为;

  • 消除代码运行的一些不安全之处,保证代码运行的安全;

  • 提高编译器效率,增加运行速度;

  • 为未来新版本的 Javascript 做好铺垫。

1'use strict'; //进入严格模式

3.1.5 陈述(Statements)

  • 使用分号

  • 使用括号

1// preferred
2if (test) {
3  console.log(test);
4}

3.2 关键字与保留字(Keywords and Reserved Words)

  • Always reserved:
    • enum
  • Reserved in strict mode:
    • implements interface let
    • package public protected static private
  • Reserved in module code:
    • await

3.3 变量

3.3.1 var 关键词

1var message = 'hi';
2message = 100; // legal, but not recommended
3.3.1.1 var 的声明域(Declaration Scope
)

移除 var 后变成全局变量

1function test() {
2  var message = 'hi'; // local variable
3}
4test();
5console.log(message); // error!
6
7function test() {
8  message = 'hi'; // global variable
9}
10test();
11console.log(message); // "hi"

严格模式时无法定义名为evelarguments

3.3.1.2 var 声明提升(Declaration Hoisting)

运行以下代码

1function foo() {
2  console.log(age);
3  var age = 26;
4}
5foo(); // undefined

实际的运行顺序

1function foo() {
2  var age;
3  console.log(age);
4  age = 26;
5}
6foo(); // undefined

3.3.2 let 定义

  • let 与 var 的区别在于,let 是 block scoped,而 var 是 function scoped
1if (true) {
2  var name = 'Matt';
3  console.log(name); // Matt
4}
5console.log(name); // Matt
6
7if (true) {
8  let age = 26;
9  console.log(age); //26
10}
11console.log(age); // ReferenceError: age is not defined
  • let 不允许多次定义
1var name;
2var name;
3let age;
4let age; // SyntaxError; identifier 'age' has already been declared
3.3.2.1 暂时死区(Temporal Dead Zone)
1// name is hoisted
2console.log(name); // undefined
3var name = 'Matt';
4
5// age is not hoisted
6console.log(age); //ReferenceError: age is not defined
7let age = 26;
3.3.2.2 全局声明
1var name = 'Matt';
2console.log(window.name); // 'Matt'
3let age = 26;
4console.log(window.age); // undefined
3.3.2.3 条件声明

对于条件声明不使用 let

1if (typeof name !== 'undefined') {
2  let name;
3}
4name = 'Matt';
5// 'name' is restricted to the if {} block scope,
6// so this assignment will act as a global assignment name = 'Matt';

3.3.2 const 定义

声明后无法重定义

1const age = 26;
2age = 36; // TypeError: assignment to a constant

3.3.3 声明风格

  • 不使用 var

使用 let 与 const 能够满足大部分使用场景

  • 使用 const 胜于 let

在浏览器运行的过程中,保证某些变量不变

3.4 数据类型

  • "undefined" if the value is undefined
  • "boolean" if the value is a Boolean
  • "string" if the value is a string
  • "number" if the value is a number
  • "object" if the value is an object (other than a function) or null
  • "function" if the value is a function
  • "symbol" if the value is a Symbol

3.4.1 Undefined 类型

  • 声明变量后未初始化
  • 未声明的变量
1let message;
2console.log(message == undefined); // true
3console.log(typeof age); // "undefined"
4
5if (message) {
6  //undefined is falsy
7  // This block will not execute
8}

3.4.2 Null 类型

null 值是一个空对象指针

1let car = null;
2console.log(typeof car); // "object"

undefined 是 null 的一种派生

1console.log(null == undefined); // true

3.4.3 Boolean 类型

DATA TYPE VALUES CONVERTED TO TRUE VALUES CONVERTED TO FALSE
Boolean true false
String Any nonempty string "" (empty string)
Number Any nonzero number (including infinity) 0, NaN (See the “NaN” section later in this chapter.)
Object Any object null
Undefined n/a undefined

3.4.4 Number 类型

1let intNum = 55; // integer
2
3let octalNum1 = 070; // octal for 56
4
5let hexNum1 = 0xa; // hexadecimal for 10

不要测试某个特定的浮点值

1var a = 0.1,
2  b = 0.2;
3if (a + b == 0.3) {
4  // avoid! console.log("You got 0.3.");
5} // 0.1+0.2 = 0.30000000000000004
3.4.4.1 值域
1var minNumber = Number.MIN_VALUE;
2var maxNumber = Number.MAX_VALUE;
3
4console.log(isFinite(minNumber + maxNumber)); // false
3.4.4.2 NaN (Not a Number)
  • NaN 与任何值都不相等,因此使用 isNaN()进行判断
1console.log(NaN == NaN); // false
2console.log(isNaN(NaN)); // true
3.4.4.3 Number 转换
  • Number()——所有数据类型
  • parseInt()——string=》number
1let num1 = parseInt('1234blue'); // 1234
2
3let num = parseInt('0xAF', 16);
4let num1 = parseInt('AF', 16); // 175 十六进制转为十进制
5let num2 = parseInt('AF'); // NaN
  • parseFloat()——string=》number

3.4.5 String 类型

三种表示方式(")(')(`)

1let firstName = 'John';
2let lastName = 'Jacob';
3let lastName = `Jingleheimerschmidt`;
3.4.5.1 字符原意
  • \xnn——十六进制表示
  • \unnnn——unicode 字符
3.4.5.2 自然字符
1let lang = 'Java';
2lang = lang + 'Script'; //没效率,需要将原字符删除
3.4.5.3 转换为字符串
1let num = 10;
2console.log(num.toString()); // "10"
3console.log(num.toString(2)); // "1010"
4console.log(num.toString(8)); // "12"
5console.log(num.toString(10)); // "10"
6console.log(num.toString(16)); // "a"
3.4.5.4 字面模版(Template Literals)
1let myMultiLineString = 'first line\nsecond line';
2let myMultiLineTemplateLiteral = `first line second line`;
3console.log(myMultiLineString === myMultiLinetemplateLiteral); // true
3.4.5.5 修改(Interpolation)
1let value = 5;
2let exponent = 'second'; // Formerly, interpolation was accomplished as follows:
3let interpolatedString = value + ' to the ' + exponent + ' power is ' + value * value;
4
5// The same thing accomplished with template literals:
6let interpolatedTemplateLiteral = `${value} to the ${exponent} power is ${value * value}`;
3.4.5.6 Template Literal Tag Functions
1let a = 6;
2let b = 9;
3function simpleTag(strings, ...expressions) {
4  console.log(strings);
5  for (const expression of expressions) {
6    console.log(expression);
7  }
8  return 'foobar';
9}
10let taggedResult = simpleTag`${a} + ${b} = ${a + b}; `;
11//[""," + "," = ",""]
12// 6
13// 9
14// 15

3.4.6 Symbol 类型

Symbol 类型创建一个独一无二的值。目的是为了保护独特的描述符,防止 propperty 冲突

  • 无法与 new 联用
1let mySymbol = new Symbol(); // TypeError: Symbol is not a constructor
3.4.6.1 使用全局 Symbol 注册处(Using the Global Symbol Registry)

Symbol.for()检查是否存在相同 Symbol

1let fooGlobalSymbol = Symbol.for('foo');
2console.log(typeof fooGlobalSymbol); // "object"
3
4let globalSymbol = Symbol.for('foo');
5console.log(localSymbol === globalSymbol); // false
3.4.6.2Well-Known Symbols(先跳过)

3.4.7 Object 类型

1let o = new Object();

后续章节细讲

3.5 操作符

3.5.1 一元操作符(跳过)

3.5.2 位操作符

  • ~ NOT
  • & AND
  • | OR
  • ^ XOR
  • << Left Shift
  • >> Signed Right Shift
  • >>>Unsigned Right Shift
1let oldValue = 64; // equal to binary 1000000
2let newValue = oldValue >>> 5; // equal to binary 10 which is decimal 2
3
4let oldValue = -64; // equal to binary 11111111111111111111111111000000
5let newValue = oldValue >>> 5; // equal to decimal 134217726

3.5.3 布尔操作符

  • !逻辑非
  • && 逻辑与
  • || 逻辑或

3.5.6 Multiolicative Operators

- * - / - %

3.5.7 指数操作符

1console.log(Math.pow(3, 2); // 9
2console.log(3 ** 2); // 9

3.5.8 加法类运算符

- + - -

3.5.9 关系操作符(Relational Operators)

1let result = 'Brick' < 'alphabet'; // true
2
3let result = '23' < 3; // false
4
5let result1 = NaN < 3; // false
6let result2 = NaN >= 3; // false

3.5.10 相等操作符

EXPRESSION VALUE
null == undefined true
"NaN" == NaN false
5 == NaN false
NaN == NaN false
NaN != NaN true
false == 0 true
true == 1 true
true == 2 false
undefined == 0 false
null == 0 false
"5" == 5 true

3.5.11 条件操作符

1variable = boolean_expression ? true_value : false_value;

3.5.12 赋值操作符(Assignment Operators)

  • Multiply/assign (*=)
  • Divide/assign (/=)
  • Modulus/assign (%=)
  • Add/assign (+=)
  • Subtract/assign (-=)
  • Left shift/assign (<<=)
  • Signed right shift/assign (>>=)
  • Unsigned right shift/assign (>>>=)

3.5.13 注释(Comma Operator)

1let num = (5, 1, 4, 8, 0); // num becomes 0

3.6 陈述(Statement)

  • if-else
  • do-while
  • while
  • for
  • for-in
1var obj = { a: 1, b: 2, c: 3 };
2
3for (var prop in obj) {
4  console.log('obj.' + prop + ' = ' + obj[prop]);
5}
6
7// Output:
8// "obj.a = 1"
9// "obj.b = 2"
10// "obj.c = 3"
  • for-of
1const array1 = ['a', 'b', 'c'];
2
3for (const element of array1) {
4  console.log(element);
5}
6
7// expected output: "a"
8// expected output: "b"
9// expected output: "c"
  • Labeled Statements

    1let num = 0;
    2outermost: for (let i = 0; i < 10; i++) {
    3  for (let j = 0; j < 10; j++) {
    4    if (i == 5 && j == 5) {
    5      continue outermost;
    6    }
    7    num++;
    8  }
    9}
    10console.log(num); // 95
    • with statement
    1let qs = location.search.substring(1);
    2let hostName = location.hostname;
    3let url = location.href;
    4//rewritten using the with
    5with (location) {
    6  let qs = search.substring(1);
    7  let hostName = hostname;
    8  let url = href;
    9}

3.7 函数

4. 变量、域与内存

4.1 原始值与引用值(PRIMITIVE AND REFERENCE VALUES)

  • 原始值——simple atomic pieces of data,
  • 引用值——made up of multiple values.

4.1.1 动态属性(Dynamic Properties)

1let person = new Object();
2person.name = 'Nicholas';
3console.log(person.name); // "Nicholas"
4
5let name = 'Nicholas';
6name.age = 27;
7console.log(name.age); // undefined

4.1.2 拷贝值(Copying Values)

  • primitive value——深拷贝
  • reference value——浅拷贝

4.1.5 参数传递(Argument Passing)

参数传递的是拷贝值,基本类型传递拷贝的值,引用类型传递地址值

1function addTen(num) {
2  num += 10;
3  return num;
4}
5let count = 20;
6let result = addTen(count);
7console.log(count); // 20 - no change
8console.log(result); // 30
9
10function setName(obj) {
11  obj.name = 'Nicholas';
12}
13let person = new Object();
14setName(person);
15console.log(person.name); // "Nicholas"

4.1.6 确定类型

  • typeof——基本类型

  • instanceof——引用类型

4.2 执行上下文与值域(跳过)

4.3 垃圾回收(GARBAGE COLLECTION)

4.3.1 标记-清除算法 Mark-and-Sweep 机制

这个算法把"对象是否不再需要"简化定义为"对象是否可以获得"。这个算法假定设置一个叫做根 root 的对象(在 Javascript 里,根是全局对象). 定期的, 垃圾回收器将从根开始, 找所有从根开始引用的对象, 然后找这些对象引用的对象, 从根开始,垃圾回收器将找到所有可以获得的对象和所有不能获得的对象.

从 2012 年起, 所有现代浏览器都使用了标记-清除内存回收算法. 所有对 JavaScript 垃圾回收算法的改进都是基于标记-清除算法的改进.

4.3.2 引用计数算法 (Reference Counting)

此算法把“对象是否不再需要”简化定义为“对象有没有其他对象引用到它”. 如果没有引用指向该对象, 对象将被垃圾回收机制回收.

1let arr = [1, 2, 3, 4];
2arr = null; // [1,2,3,4]这时没有被引用, 会被自动回收

5 基本引用类型(Basic Reference Types)(快速过)

Reference types are also sometimes called object definitions because they describe the properties and methods that objects should have.

5.1 Date 类型

Date 类型记录的是 1970 年一月一号为分界线的时间

  • 时间格式的转换 Date.parse()
  • 协调时间时
1// May 5, 2005 at 5:55:55 PM GMT
2let allFives = new Date(Date.UTC(2005, 4, 5, 17, 55, 55));

5.1.1 继承工具

  • toLocaleString() 有 AM/PM
  • toString() 带有时区,24 小时制

5.1.2 时间格式工具

  • toDateString() 时分秒
  • toTimeString() 时期
  • toLocaleDateString()
  • toLocaleTimeString()
  • toUTCString()

5.2 Regexp 类型

  • g: global mode
  • i: case-insensitive
  • m: multiline mode
  • u: Unicode mode
  • y: sticky mode

6 引用类型 (Collection Reference Types)

6.1 Object 类型

  • 两种创建方式
1let person = new Object(); //use new()
2person.name = 'Nicholas';
3person.age = 29;
4
5let person = {
6  //use object literal notation
7  name: 'Nicholas', //"name":"Nicholas" is the same
8  age: 29,
9};
10
11let person = {}; // same as new Object()
12person.name = 'Nicholas';
13person.age = 29;

6.2 Array 类型

6.2.1 创建 Array

  • 两种最基本的方式
1/*the first way*/
2let colors = new Array();
3let colors = new Array(20); // creates an array with an initial length value of 20
4let colors = new Array('red', 'blue', 'green'); // creates an array with three string values
5
6/*the second way []*/
7let colors = ['red', 'blue', 'green']; // Creates an array with three strings
8let names = []; // Creates an empty array
  • 两种附加形式(Array.from)
1alert(Array.from('Matt')); // ["M", "a", "t", "t"]
2
3alert(Array.of(1, 2, 3, 4)); // [1, 2, 3, 4] convert the list of arguments into an array.

6.2.2 Array Holes

逗号省略

1const options = [, , , , ,]; // Creates an array with 5 items alert(options.length); // 5
2alert(options); // [,,,,,]

6.2.3 Indexing into Arrays

index 从 0 开始

1let colors = ['red', 'blue', 'green']; // creates an array with three strings
2colors.length = 2;
3alert(colors[2]); // undefined

6.2.4 Detecting Arrays

在大多数情况下,instanceof Array 就足够了。然而,由于 instanceof Array 在 iframes 和 window 之间不能正常工作,Array.isArray()将是更为可靠的解决方案。

不过,一定要检查浏览器的兼容性。如果您需要支持 IE8 或更低版本,Array.isArray()将无法工作(请参阅 Mozilla 的文档)。

1if (Array.isArray(value)) {
2  // do something on the array
3}

6.2.5 迭代工具(Iterator Methods)

1const a = ['foo', 'bar', 'baz', 'qux'];
2// Because these methods return iterators, you can funnel their contents // into array instances using Array.from()
3const aKeys = Array.from(a.keys());
4const aValues = Array.from(a.values());
5const aEntries = Array.from(a.entries());
6alert(aKeys); // [0, 1, 2, 3]
7alert(aValues); // ["foo", "bar", "baz", "qux"]
8alert(aEntries); // [[0, "foo"], [1, "bar"], [2, "baz"], [3, "qux"]]

6.2.6 复制与填充(Copy and Fill Method)

  • fill()
1const zeroes = [0, 0, 0, 0, 0];
2
3// Fill the entire array with 5
4zeroes.fill(5);
5alert(zeroes); // [5, 5, 5, 5, 5]
6zeroes.fill(0); // reset
7
8// Fill all indices >=3 with 6 zeroes.fill(6, 3);
9alert(zeroes); // [0, 0, 0, 6, 6]
10zeroes.fill(0); // reset
11
12// Fill all indices >= 1 and < 3 with 7
13zeroes.fill(7, 1, 3);
14alert(zeroes); // [0, 7, 7, 0, 0];
15zeroes.fill(0); // reset
16
17// Fill all indices >=1 and < 4 with 8 // (-4 + zeroes.length = 1)
18// (-1 + zeroes.length = 4)
19zeroes.fill(8, -4, -1);
20alert(zeroes); // [0, 8, 8, 8, 0];
  • copyWithin()
1let ints,
2  reset = () => (ints = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
3reset();
4// Copy the contents of ints beginning at index 0 and ending at index 3 to values // beginning at index 4.
5ints.copyWithin(4, 0, 3);
6alert(ints); // [0, 1, 2, 3, 0, 1, 2, 7, 8, 9]
7reset();

6.2.7 转换工具

1let colors = ['red', 'blue', 'green']; // creates an array with three strings
2alert(colors.toString()); // red,blue,green alert(colors.valueOf()); // red,blue,green alert(colors); // red,blue,green

其中 toLocaleString()会根据你机器的本地环境来返回字符串,它和 toString()返回的值在不同的本地环境下使用的符号可能变化;

6.2.8 栈工具

  • pop()
  • push()

6.2.9 队列工具

  • shift()——获取头部

  • unshift()——加入尾部

6.2.10 Recordering Methods

  • reverse()
  • sort()
1let values = [0, 1, 5, 10, 15];
2values.sort((a, b) => a < b ? a > b ? -1 : 0);
3alert(values); // 15,10,5,1,0

6.2.11 操作工具(Manipulation Methods)

  • concat()
1let colors = ['red', 'green', 'blue'];
2let colors2 = colors.concat('yellow', ['black', 'brown']);
3alert(colors); // ["red", "green","blue"]
4alert(colors2); // ["red", "green", "blue", "yellow", "black", "brown"]
5
6let colors = ['red', 'green', 'blue'];
7colors[Symbol.isConcatSpreadable] = false;
8let colors2 = colors.concat('yellow', ['black', 'brown']);
9alert(colors2); // ["red", "green", "blue", "yellow", ["black", "brown"],]
  • slice()
1let colors = ['red', 'green', 'blue', 'yellow', 'purple'];
2let colors2 = colors.slice(1);
3let colors3 = colors.slice(1, 4);
4alert(colors2); // green,blue,yellow,purple
5alert(colors3); // green,blue,yellow
  • splice()
1let colors = ['red', 'green', 'blue'];
2let removed = colors.splice(0, 1); // remove the first item
3alert(colors); // green,blue
4alert(removed); // red - one item array
5
6removed = colors.splice(1, 0, 'yellow', 'orange'); // insert two items at position 1
7alert(colors); // green,yellow,orange,blue
8alert(removed); // empty array

6.2.12 查找定位工具(Search and Location Methods)

  • 严格相等(Strict Equivalence)
1let numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
2alert(numbers.indexOf(4)); // 3
3alert(numbers.indexOf(4, 4)); // 5 从位置4查找4
4alert(numbers.lastIndexOf(4)); // 5
5alert(numbers.includes(4)); // true
  • Predicate Search
1const people = [
2  {
3    name: 'Matt',
4    age: 27,
5  },
6  {
7    name: 'Nicholas',
8    age: 29,
9  },
10];
11alert(people.find((element, index, array) => element.age < 28)); // {name: "Matt", age: 27}
12alert(people.findIndex((element, index, array) => element.age < 28)); // 0

6.2.13 Iterative Methods

  • every()
  • filter()
  • forEach()
  • map()
  • some()

6.3 类型化数组(TYPED ARRAYS)

JavaScript 类型化数组是一种类似数组的对象,并提供了一种用于访问原始二进制数据的机制。

Web 应用程序变得越来越强大,尤其一些新增加的功能例如:音频视频编辑,访问 WebSockets 的原始数据等,很明显有些时候如果使用 JavaScript 代码可以快速方便地通过类型化数组来操作原始的二进制数据将会非常有帮助。

6.3.1 使用 ArrayBuffers

1const buf1 = new ArrayBuffer(16);
2const buf2 = buf1.slice(4, 12);
3alert(buf2.byteLength); // 8

6.3.2 数据视图(DataViews)

  • DataView 是为了文件 IO 与网络 IO 设计
  • DataView 必须必须由已经存在的 ArrayBuffer 创建
1const buf = new ArrayBuffer(16);
2// DataView default to use the entire ArrayBuffer
3const fullDataView = new DataView(buf);
4alert(fullDataView.byteOffset); // 0
5alert(fullDataView.byteLength); // 16
6alert(fullDataView.buffer === buf); // true
7
8// Constructor takes an optional byte offset and byte length
9// byteOffset=0 begins the view at the start of the buffer
10// byteLength=8 restricts the view to the first 8 bytes
11const firstHalfDataView = new DataView(buf, 0, 8);
12alert(firstHalfDataView.byteOffset); // 0
13alert(firstHalfDataView.byteLength); // 8
14alert(firstHalfDataView.buffer === buf); // true
15
16// DataView will use the remainder of the buffer unless specified
17// byteOffset=8 begins the view at the 9th byte of the buffer
18// byteLength default is the remainder of the buffer
19const secondHalfDataView = new DataView(buf, 8);
20alert(secondHalfDataView.byteOffset); // 8
21alert(secondHalfDataView.byteLength); // 8
22alert(secondHalfDataView.buffer === buf); // true
6.3.2.1ElementTyp
ELEMENTTYPE BYTES DESCRIPTION C EQUIVALENT RANGE OF VALUES
Int8 1 8-bit signed integer signed char –128 to 127
Uint8 1 8-bit unsigned integer unsigned char 0 to 255
Int16 2 16-bit signed integer short –32768 to 32767
Uint16 2 16-bit unsigned integer unsigned short 0 to 65535
Int32 4 32-bit signed integer int –2,147,483,648 to 2,147,483,647
Uint32 4 32-bit unsigned integer unsigned int 0 to 4,294,967,295
Float32 4 32-bit IEEE-754 floating point float –3.4E+38 to +3.4E+38
Float64 8 64-bit IEEE-754 floating point double –1.7E+308 to +1.7E+308
  • 初始化为 0
1// Allocate two bytes of memory and declare a DataView
2const buf = new ArrayBuffer(2);
3const view = new DataView(buf);
4// Demonstrate that the entire buffer is indeed all zeroes
5// Check the first and second byte
6alert(view.getInt8(0)); // 0
7alert(view.getInt8(1)); // 0
8// Check the entire buffer
9alert(view.getInt16(0)); // 0
10
11// Set the entire buffer to ones
12// 255 in binary is 11111111 (2^8 – 1)
13view.setUint8(0, 255);
6.3.2.2 大端与小端
1// Allocate two bytes of memory and declare a DataView
2const buf = new ArrayBuffer(2);
3const view = new DataView(buf);
4
5// Fill the buffer so that the first bit and last bit are 1
6view.setUint8(0, 0x80); // Sets leftmost bit to 1
7view.setUint8(1, 0x01); // Sets rightmost bit to 1
8
9// Buffer contents (spaced for readability):
10// 0x8 0x0 0x0 0x1
11// 1000 0000 0000 0001
12
13// Read a big-endian Uint16
14// 0x80 is the high byte, 0x01 is the low byte
15// 0x8001 = 2^15 + 2^0 = 32768 + 1 = 32769
16alert(view.getUint16(0)); // 32769
17
18// Read a little-endian Uint16
19// 0x01 is the high byte, 0x80 is the low byte
20// 0x0180 = 2^8 + 2^7 = 256 + 128 = 384
21alert(view.getUint16(0, true)); // 384
6.3.2.3 Corner Cases

只有在拥有足够缓存空间时,DataView 完成读或写,否则提示 RangeError

1const buf = new ArrayBuffer(6);
2const view = new DataView(buf);
3
4// Attempt to get a value past the end of the buffer
5view.getInt32(8); // RangeError

6.3.3 类型化数组(Typed Arrays)

Typed array 是另一种ArrayBuffer的形式

以下函数不再支持

  • concat()
  • pop()
  • push()
  • shif()
  • unshift()
  • splice()

新添两个函数

  • set()
  • subarray()

6.4 Map 类型

6.4.1 基本借口

  • 创建 Map
1const m = new Map();
2
3// Initialize map with nested arrays
4const m1 = new Map([
5  ['key1', 'val1'],
6  ['key2', 'val2'],
7  ['key3', 'val3'],
8]);
9alert(m1.size); // 3
  • 删除、添加与查询
1const m = new Map();
2alert(m.has('firstName')); // false
3alert(m.get('firstName ')); // undefined
4alert(m.size); // 0
5
6m.delete('firstName'); // deletes only this key/value pair
  • Map 可是使用 javasctipt 的数据结构作为键值
1const m = new Map();
2const functionKey = function () {};
3const symbolKey = Symbol();
4const objectKey = new Object();
5m.set(functionKey, 'functionValue');
6m.set(symbolKey, 'symbolValue');
7m.set(objectKey, 'objectValue');

6.4.2 顺序与迭代

  • 迭代
1const m = new Map([
2  ['key1', 'val1'],
3  ['key2', 'val2'],
4  ['key3', 'val3'],
5]);
6alert(m.entries === m[Symbol.iterator]); // true
7
8for (let pair of m.entries()) {
9  alert(pair);
10}
11// [key1,val1]
12// [key2,val2]
13// [key3,val3]
14
15for (let pair of m[Symbol.iterator]()) {
16  alert(pair);
17}
18// [key1,val1]
19// [key2,val2]
20// [key3,val3]

6.4.3 Obiects 与 Maps 之间选择

  • 内存

    Map 大约会比 Object 多占用 50%

  • 插入效率

    Map 比 Object 快一些

  • 查找效率

    大型项目 Object 快

  • 删除效率

    Map 删除相比于 Object 非常快

6.5 WeakMap 类型

6.5.1 基本接口

基本和 map 一致

6.5.2 Weak 键值

The “weak” designation stems from the fact that keys in a WeakMap are “weakly held,” meaning they are not counted as formal references that would otherwise prevent garbage collection.

原生的 WeakMap 持有的是每个键对象的“弱引用”,这意味着在没有其他引用存在时垃圾回收能正确进行。原生 WeakMap 的结构是特殊且有效的,其用于映射的 key 只有在其没有被回收时才是有效的。

基本上,如果你要往对象上添加数据,又不想干扰垃圾回收机制,就可以使用 WeakMap。

6.5.3 不可迭代键值

WeakMap 的 key 是不可枚举的 (没有方法能给出所有的 key)。如果 key 是可枚举的话,其列表将会受垃圾回收机制的影响,从而得到不确定的结果。因此,如果你想要这种类型对象的 key 值的列表,你应该使用 Map

6.6 Set 类型

6.6.1 基本接口

1const s = new Set();
2alert(s.has('Matt')); // false
3alert(s.size); // 0
4
5s.add('Matt').add('Frisbie');
6
7s.delete('Matt');
  • 使用 SameValueZero 比较

6.7 WeakSet 类型

8 Objects, Classes, and Object-Oriented Programming

8.1 理解 Objects

  • 两种创建模式
1let person = new Object();
2person.name = 'Nicholas';
3person.age = 29;
4person.job = 'Software Engineer';
5person.sayName = function () {
6  console.log(this.name);
7};
8
9let person = {
10  name: 'Nicholas',
11  age: 29,
12  job: 'Software Engineer',
13  sayName() {
14    console.log(this.name);
15  },
16};

8.1.1 属性的类型(Types of Properties)

8.1.1.1 数据属性(Data Properties)

数据属性包含一个单独的数据地址,由这个地址进行读写操作

数据属性拥有四个特性(attribute)

  • [[Configurable]]——描述符是否可以改变
  • [[Enumerable]]——是否可以迭代
  • [[Writable]]——是否可以修改
  • [[Value]]——默认为 undefined
1let person = {};
2Object.defineProperty(person, 'name', {
3  writable: false,
4  value: 'Nicholas',
5});
6console.log(person.name); // "Nicholas"
7person.name = 'Greg';
8console.log(person.name); // "Nicholas"
8.1.1.2 访问器属性
  • [[Configurable]]
  • [[Enumerable]]
  • [[Get]]——属性读取时的返回值
  • [[Set]]
1// Define object with pseudo-private member 'year_'
2// and public member 'edition'
3let book = {
4  year_: 2017,
5  edition: 1,
6};
7Object.defineProperty(book, 'year', {
8  get() {
9    return this.year_;
10  },
11  set(newValue) {
12    if (newValue > 2017) {
13      this.year_ = newValue;
14      this.edition += newValue - 2017;
15    }
16  },
17});
18book.year = 2018;
19console.log(book.edition); // 2

8.1.2 定义多个属性

1let book = {};
2Object.defineProperties(book, {
3  year_: {
4    value: 2017,
5  },
6  edition: {
7    value: 1,
8  },
9  year: {
10    get() {
11      return this.year_;
12    },
13    set(newValue) {
14      if (newValue > 2017) {
15        this.year_ = newValue;
16        this.edition += newValue - 2017;
17      }
18    },
19  },
20});

8.1.3 阅读属性值(Reading Property Attributes)

  • Object. getOwnPropertyDescriptor()查看属性描述符

8.1.4 合并 Objects

  • Object.assign()
1let dest, src, result;
2/**
3 * Simple copy */
4dest = {};
5src = { id: 'src' };
6result = Object.assign(dest, src);
7// Object.assign mutates the destination object
8// and also returns that object after exiting.
9console.log(dest === result); // true
10console.log(dest !== src); // true
11console.log(result); // { id: src}
12console.log(dest); // { id: src}

8.1.5 Object Identity and Equality

  • Object.is()
1console.log(Object.is(true, 1)); // false
2console.log(Object.is({}, {})); // false
3console.log(Object.is('2', 2)); // false
4
5// Correct 0, -0, +0 equivalence/nonequivalence:
6console.log(Object.is(+0, -0)); // false
7console.log(Object.is(+0, 0)); // true
8console.log(Object.is(-0, 0)); // false
9
10// Correct NaN equivalence:
11console.log(Object.is(NaN, NaN)); // true

8.1.6 Enhanced Object Syntax

8.1.6.1 属性值简写(Property Value Shorthand)

ES6 中的新特性,键值与属性值相同时,可以简写

1let name = 'Matt';
2let person = { name };
3console.log(person); // { name: 'Matt' }
8.1.6.2 计算型属性值
1const nameKey = 'name';
2const ageKey = 'age';
3const jobKey = 'job';
4let uniqueToken = 0;
5function getUniqueKey(key) {
6  return '${key}_${uniqueToken++}';
7}
8let person = {
9  [getUniqueKey(nameKey)]: 'Matt',
10  [getUniqueKey(ageKey)]: 27,
11  [getUniqueKey(jobKey)]: 'Software engineer',
12};
13console.log(person); // { name_0: 'Matt', age_1: 27, job_2: 'Software engineer' }
8.1.6.3 简洁工具语法
1let person = {
2  sayName: function (name) {
3    console.log(`My name is ${name}`);
4  },
5};
6
7let person = {
8  sayName(name) {
9    console.log(`My name is ${name}`);
10  },
11};

与以上两种特性结合

1const methodKey = 'sayName';
2let person = {
3  [methodKey](name) {
4    console.log('My name is ${name}');
5  },
6};
7person.sayName('Matt'); // My name is Matt

8.1.7 Object 结构

1// Without object destructuring
2let person = {
3  name: 'Matt',
4  age: 27,
5};
6let personName = person.name,
7  personAge = person.age;
8console.log(personName); // Matt
9console.log(personAge); // 27
10
11// With object destructuring
12let person = {
13  name: 'Matt',
14  age: 27,
15};
16let { name: personName, age: personAge } = person;
17console.log(personName); // Matt
18console.log(personAge); // 27
8.1.7.1 嵌套解构
1let person = {
2  name: 'Matt',
3  age: 27,
4  job: {
5    title: 'Software engineer',
6  },
7};
8let personCopy = {};
9({ name: personCopy.name, age: personCopy.age, job: personCopy.job } = person);
10
11// Because an object reference was assigned into personCopy, changing a property
12// inside the person.job object will be propagated to personCopy:
13person.job.title = 'Hacker';
14
15console.log(person);
16// { name: 'Matt', age: 27, job: { title: 'Hacker' } }
  • 不完全解构
1let person = {
2  name: 'Matt',
3  age: 27,
4};
5let personName, personBar, personAge;
6try {
7  // person.foo is undefined, so this will throw an error
8  ({
9    name: personName,
10    foo: { bar: personBar },
11    age: personAge,
12  } = person);
13} catch (e) {}
14
15console.log(personName, personBar, personAge);
16// Matt, undefined, undefined
  • 参数上下文匹配
1let person = { name: 'Matt', age: 27 };
2
3function printPerson(foo, { name, age }, bar) {
4  console.log(arguments);
5  console.log(name, age);
6}
7function printPerson2(foo, { name: personName, age: personAge }, bar) {
8  console.log(arguments);
9  console.log(personName, personAge);
10}
11
12printPerson('1st', person, '2nd');
13// ['1st', { name: 'Matt', age: 27 }, '2nd'] // 'Matt', 27
14
15printPerson2('1st', person, '2nd');
16// ['1st', { name: 'Matt', age: 27 }, '2nd'] // 'Matt', 27

8.2 Object 创建

8.2.1 工厂方法(The Factory Pattern)

工厂方式以用于软件工程抽象特定 object 的设计模式而闻名

1function createPerson(name, age, job) {
2  let o = new Object();
3  o.name = name;
4  o.age = age;
5  o.job = job;
6  o.sayName = function () {
7    console.log(this.name);
8  };
9  return o;
10}
11
12let person1 = createPerson('Nicholas', 29, 'Software Engineer');
13let person2 = createPerson('Greg', 27, 'Doctor');

8.2.2 函数构建模式(The Function Constructor Pattern)

1function Person(name, age, job) {
2  this.name = name;
3  this.age = age;
4  this.job = job;
5  this.sayName = function () {
6    console.log(this.name);
7  };
8}
9let person1 = new Person('Nicholas', 29, 'Software Engineer');
10let person2 = new Person('Greg', 27, 'Doctor');
11person1.sayName(); // Nicholas
12person2.sayName(); // Greg
8.2.2.1 构造函数(Constructors as Functions)
1// use as a constructor
2let person = new Person('Nicholas', 29, 'Software Engineer');
3person.sayName(); // "Nicholas"
4
5// call as a function
6Person('Greg', 27, 'Doctor'); // adds to window
7window.sayName(); // "Greg"
8
9// call in the scope of another object let o = new Object();
10Person.call(o, 'Kristen', 25, 'Nurse');
11o.sayName(); // "Kristen"
8.2.2.2 构造函数的问题

构造函数在 ECMASScript 中同样是 Object

1function Person(name, age, job) {
2  this.name = name;
3  this.age = age;
4  this.job = job;
5  this.sayName = sayName;
6}
7function sayName() {
8  console.log(this.name);
9}
10let person1 = new Person('Nicholas', 29, 'Software Engineer');
11let person2 = new Person('Greg', 27, 'Doctor');
12person1.sayName(); // Nicholas
13person2.sayName(); // Greg

8.2.3 原型模式

1let Person = function () {};
2Person.prototype.name = 'Nicholas';
3Person.prototype.age = 29;
4Person.prototype.job = 'Software Engineer';
5Person.prototype.sayName = function () {
6  console.log(this.name);
7};
8let person1 = new Person();
9person1.sayName(); // "Nicholas"
10
11let person2 = new Person();
12person2.sayName(); // "Nicholas"
13
14console.log(person1.sayName == person2.sayName); // true
8.2.3.1 原型工作原理

对象具有属性__proto__,可称为隐式原型,一个对象的隐式原型指向构造该对象的构造函数的原型。不像每个对象都有__proto__属性来标识自己所继承的原型,只有函数才有 prototype 属性。

  • hasOwnProperty() ignores inherited properties
1'constructor' in obj; // true
2'__proto__' in obj; // true
3'hasOwnProperty' in obj; // true
4
5obj.hasOwnProperty('constructor'); // false
6obj.hasOwnProperty('__proto__'); // false
7obj.hasOwnProperty('hasOwnProperty'); // false

8.3 继承

js 中只有 Implementation inheritance ,因为 Interface inheritance is not possible in ECMAScript because functions do not have signatures.

8.3.1 原型链

1function SuperType() {
2  this.property = true;
3}
4SuperType.prototype.getSuperValue = function () {
5  return this.property;
6};
7function SubType() {
8  this.subproperty = false;
9}
10// inherit from SuperType
11SubType.prototype = new SuperType();
12SubType.prototype.getSubValue = function () {
13  return this.subproperty;
14};
15let instance = new SubType();
16console.log(instance.getSuperValue()); // true
8.3.1.1 默认原型

所有引用类型默认由 Object 继承,因此拥有 Object.prototype 的函数

8.3.1.2 原型与实例的关系
1console.log(instance instanceof Object); // true
2console.log(instance instanceof SuperType); // true
3console.log(instance instanceof SubType); // true
4
5console.log(Object.prototype.isPrototypeOf(instance)); // true
6console.log(SuperType.prototype.isPrototypeOf(instance)); // true
7console.log(SubType.prototype.isPrototypeOf(instance)); // true
8.3.1.2 Working with Methods

函数重载

1function SuperType() {
2  this.property = true;
3}
4SuperType.prototype.getSuperValue = function () {
5  return this.property;
6};
7function SubType() {
8  this.subproperty = false;
9}
10
11// inherit from SuperType
12SubType.prototype = new SuperType();
13
14// new method
15SubType.prototype.getSubValue = function () {
16  return this.subproperty;
17};
18
19// override existing method
20SubType.prototype.getSuperValue = function () {
21  return false;
22};
23
24let instance = new SubType();
25console.log(instance.getSuperValue()); // false

8.3.2 借用构造函数(Constructor Stealing)

  • 避免了引用类型的属性被所有实例共享
1function SuperType() {
2  this.colors = ['red', 'blue', 'green'];
3}
4function SubType() {
5  // inherit from SuperType
6  SuperType.call(this);
7}
8
9let instance1 = new SubType();
10instance1.colors.push('black');
11console.log(instance1.colors); // "red,blue,green,black"
12
13let instance2 = new SubType();
14console.log(instance2.colors); // "red,blue,green"
  • 可以在 Child 中向 Parent 传参
1function SuperType(name) {
2  this.name = name;
3}
4function SubType() {
5  // inherit from SuperType passing in an argument
6  SuperType.call(this, 'Nicholas');
7
8  // instance property
9  this.age = 29;
10}
11
12let instance = new SubType();
13console.log(instance.name); // "Nicholas";
14console.log(instance.age); // 29

缺点:方法都在构造函数中定义,每次创建实例都会创建一遍方法。

8.3.3 组合继承

原型链继承和经典继承双剑合璧。

1function Parent(name) {
2  this.name = name;
3  this.colors = ['red', 'blue', 'green'];
4}
5
6Parent.prototype.getName = function () {
7  console.log(this.name);
8};
9
10function Child(name, age) {
11  Parent.call(this, name);
12
13  this.age = age;
14}
15
16Child.prototype = new Parent();
17Child.prototype.constructor = Child;
18
19var child1 = new Child('kevin', '18');
20
21child1.colors.push('black');
22
23console.log(child1.name); // kevin
24console.log(child1.age); // 18
25console.log(child1.colors); // ["red", "blue", "green", "black"]
26
27var child2 = new Child('daisy', '20');
28
29console.log(child2.name); // daisy
30console.log(child2.age); // 20
31console.log(child2.colors); // ["red", "blue", "green"]

8.3.4 原型继承

1function object(o) {
2  function F() {}
3  F.prototype = o;
4  return new F();
5}
6
7let person = {
8  name: 'Nicholas',
9  friends: ['Shelby', 'Court', 'Van'],
10};
11let anotherPerson = object(person);
12anotherPerson.name = 'Greg';
13anotherPerson.friends.push('Rob');
14
15let yetAnotherPerson = object(person);
16yetAnotherPerson.name = 'Linda';
17yetAnotherPerson.friends.push('Barbie');
18
19console.log(person.friends); // "Shelby,Court,Van,Rob,Barbie"

就是 ES5 Object.create 的模拟实现,将传入的对象作为创建的对象的原型。

缺点:

包含引用类型的属性值始终都会共享相应的值,这点跟原型链继承一样。

8.3.5 寄生继承

创建一个仅用于封装继承过程的函数,该函数在内部以某种形式来做增强对象,最后返回对象。

1function createObj(o) {
2  var clone = Object.create(o);
3  clone.sayName = function () {
4    console.log('hi');
5  };
6  return clone;
7}

缺点:跟借用构造函数模式一样,每次创建对象都会创建一遍方法。

8.3.6 寄生组合继承

为了方便大家阅读,在这里重复一下组合继承的代码:

1function Parent(name) {
2  this.name = name;
3  this.colors = ['red', 'blue', 'green'];
4}
5
6Parent.prototype.getName = function () {
7  console.log(this.name);
8};
9
10function Child(name, age) {
11  Parent.call(this, name);
12  this.age = age;
13}
14
15Child.prototype = new Parent();
16
17var child1 = new Child('kevin', '18');
18
19console.log(child1);

组合继承最大的缺点是会调用两次父构造函数。

一次是设置子类型实例的原型的时候:

1Child.prototype = new Parent();

一次在创建子类型实例的时候:

1var child1 = new Child('kevin', '18');

回想下 new 的模拟实现,其实在这句中,我们会执行:

1Parent.call(this, name);

在这里,我们又会调用了一次 Parent 构造函数。

所以,在这个例子中,如果我们打印 child1 对象,我们会发现 Child.prototype 和 child1 都有一个属性为colors,属性值为['red', 'blue', 'green']

那么我们该如何精益求精,避免这一次重复调用呢?

如果我们不使用 Child.prototype = new Parent() ,而是间接的让 Child.prototype 访问到 Parent.prototype 呢?

看看如何实现:

1function Parent(name) {
2  this.name = name;
3  this.colors = ['red', 'blue', 'green'];
4}
5
6Parent.prototype.getName = function () {
7  console.log(this.name);
8};
9
10function Child(name, age) {
11  Parent.call(this, name);
12  this.age = age;
13}
14
15// 关键的三步
16var F = function () {};
17
18F.prototype = Parent.prototype;
19
20Child.prototype = new F();
21
22var child1 = new Child('kevin', '18');
23
24console.log(child1);

最后我们封装一下这个继承方法:

1function object(o) {
2  function F() {}
3  F.prototype = o;
4  return new F();
5}
6
7function prototype(child, parent) {
8  var prototype = object(parent.prototype);
9  prototype.constructor = child;
10  child.prototype = prototype;
11}
12
13// 当我们使用的时候:
14prototype(Child, Parent);

引用《JavaScript 高级程序设计》中对寄生组合式继承的夸赞就是:

这种方式的高效率体现它只调用了一次 Parent 构造函数,并且因此避免了在 Parent.prototype 上面创建不必要的、多余的属性。与此同时,原型链还能保持不变;因此,还能够正常使用 instanceof 和 isPrototypeOf。开发人员普遍认为寄生组合式继承是引用类型最理想的继承范式。

8.4 Class

8.4.1 class 定义基础

1// class declaration
2class Person {}
3// class expression
4const Animal = class {};
  • function 声明挂起,class 声明不会
1console.log(FunctionExpression); // undefined
2var FunctionExpression = function () {};
3console.log(FunctionExpression); // function() {}
4
5console.log(FunctionDeclaration); // FunctionDeclaration() {}
6function FunctionDeclaration() {}
7console.log(FunctionDeclaration); // FunctionDeclaration() {}
8
9console.log(ClassExpression); // undefined
10var ClassExpression = class {};
11console.log(ClassExpression); // class {}
12
13console.log(ClassDeclaration); // ReferenceError: ClassDeclaration is not defined
14class ClassDeclaration {}
15console.log(ClassDeclaration); // class ClassDeclaration {}
8.4.1.1 class 构成
1// Valid empty class definition
2class Foo {}
3
4// Valid class definition with constructor
5class Bar {
6  constructor() {}
7}
8
9// Valid class definition with getter
10class Baz {
11  get myBaz() {}
12}
13
14// Valid class definition with static method
15class Qux {
16  static myQux() {}
17}

8.4.2 class 构造函数

8.4.2.1 Instantiation
  1. a new object 创建在内存
  2. [[Prototype]]指针被分配到构造函数原型
  3. 构造函数的 this 被分配到 new object
  4. 构造函数执行
  5. 如果构造函数返回 object,就返回 object;否则被生成的 new object 返回
1class Animal {}
2class Person {
3  constructor() {
4    console.log('person ctor');
5  }
6}
7class Vegetable {
8  constructor() {
9    this.color = 'orange';
10  }
11}
12
13let a = new Animal();
14let p = new Person(); // person ctor
15
16let v = new Vegetable();
17console.log(v.color); // orange
8.4.2.2 将 class 看作特殊函数
1class Person {}
2console.log(Person); // class Person {}
3console.log(typeof Person); // function
  • class 标识符拥有 prototype 属性,属性拥有 constructor 属性又引用 class 本身
1class Person {}
2console.log(Person.prototype); // { constructor: f() }
3console.log(Person === Person.prototype.constructor); // true
  • 构造函数与 class 的关系
1class Person {}
2let p1 = new Person();
3console.log(p1.constructor === Person); // true
4console.log(p1 instanceof Person); // true
5console.log(p1 instanceof Person.constructor); // false
6
7let p2 = new Person.constructor();
8console.log(p2.constructor === Person); // false
9console.log(p2 instanceof Person); // false
10console.log(p2 instanceof Person.constructor); // true

8.4.3 实例,原型与 class 成员

8.4.3.1 示例成员(Instance Members)
1class Person {
2  constructor() {
3    // For this example, define a string with object wrapper // as to check object equality between instances below
4    this.name = new String('Jack');
5    this.sayName = () => console.log(this.name);
6    this.nicknames = ['Jake', 'J-Dog'];
7  }
8}
9let p1 = new Person(),
10  p2 = new Person();
11
12p1.sayName(); // Jack
13p2.sayName(); // Jack
14
15console.log(p1.name === p2.name);
16console.log(p1.sayName === p2.sayName);
17console.log(p1.nicknames === p2.nicknames); // false
18
19p1.name = p1.nicknames[0];
20p2.name = p2.nicknames[1];
21
22p1.sayName(); // Jake
23p2.sayName(); // J-Dog
8.4.3.2 原型与访问器
  • primitives and objects 不可以直接加入到 class 内的原型
1class Person {
2  name: 'Jake';
3}
4// Uncaught SyntaxError: Unexpected token :
  • get set
1class Person {
2  set name(newName) {
3    this.name_ = newName;
4  }
5  get name() {
6    return this.name_;
7  }
8}
9let p = new Person();
10p.name = 'Jake';
11console.log(p.name); // Jake
8.4.3.3 静态 class 方法与访问器
1class Person {
2  constructor() {
3// Everything added to 'this' will exist on each individual instance
4    this.locate = () => console.log('instance', this); }
5// Defined on the class prototype object locate() {
6  console.log('prototype', this); }
7// Defined on the class
8  static locate() {
9    console.log('class', this);
10  }
11}
12
13let p = new Person();
14
15p.locate(); // instance, Person {}
16Person.prototype.locate(); // prototype, {constructor: ... }
17Person.locate(); // class, class Person {}
8.4.3.4 无函数原型与 class 成员
1class Person {
2  sayName() {
3    console.log('${Person.greeting} ${this.name}');
4  }
5}
6// Define data member on class
7Person.greeting = 'My name is';
8
9// Define data member on prototype
10Person.prototype.name = 'Jake';
11
12let p = new Person();
13p.sayName(); // My name is Jake
8.4.3.5 迭代与生成
1class Person {
2  constructor() {
3    this.nicknames = ['Jack', 'Jake', 'J-Dog'];
4  }
5  *[Symbol.iterator]() {
6    yield* this.nicknames.entries();
7  }
8}
9let p = new Person();
10for (let [idx, nickname] of p) {
11  console.log(nickname);
12}
13// Jack // Jake // J-Dog

8.4.4 继承

8.4.4.1 继承基础
1class Vehicle {}
2
3// Inherit from class
4class Bus extends Vehicle {}
5
6let b = new Bus();
7console.log(b instanceof Bus); // true
8console.log(b instanceof Vehicle); // true
9
10function Person() {}
11
12// Inherit from function constructor
13class Engineer extends Person {}
14
15let e = new Engineer();
16console.log(e instanceof Engineer); // true
17console.log(e instanceof Person); // true
8.4.4.2 构造函数、Homeobjects 与 super()
  • super()用于子类的构造函数中或者静态方法中
1class Vehicle {
2  constructor() {
3    this.hasEngine = true;
4  }
5}
6
7class Bus extends Vehicle {
8  constructor() {
9    // Cannot reference 'this' before super(), will throw ReferenceError
10    super(); // same as super.constructor()
11    console.log(this instanceof Vehicle); // true
12    console.log(this); // Bus { hasEngine: true }
13  }
14}
15
16new Bus();
8.4.4.3 抽象基类(Abstract Base Classes)
1// Abstract base class
2class Vehicle {
3  constructor() {
4    console.log(new.target);
5    if (new.target === Vehicle) {
6      throw new Error('Vehicle cannot be directly instantiated');
7    }
8  }
9}
10
11// Derived class
12class Bus extends Vehicle {}
13
14new Bus(); // class Bus {}
15new Vehicle(); // class Vehicle {}
16// Error: Vehicle cannot be directly instantiated
8.4.4.4 由已经建类型继承(Inheriting from Built-in Types)
1class SuperArray extends Array {
2  shuffle() {
3    // Fisher-Yates shuffle
4    for (let i = this.length - 1; i > 0; i--) {
5      const j = Math.floor(Math.random() * (i + 1));
6      [this[i], this[j]] = [this[j], this[i]];
7    }
8  }
9}
10
11let a = new SuperArray(1, 2, 3, 4, 5);
12
13console.log(a instanceof Array); // true
14console.log(a instanceof SuperArray); // true
15
16console.log(a); // [1, 2, 3, 4, 5]
17a.shuffle();
18console.log(a); // [3, 1, 4, 5, 2]
8.4.4.5 类掺合(Class Mixins)

10 函数

function 是 Object 类型,属于 Function 的实例。因此函数名仅仅是指向 function objects 的指针,对于函数本身不是必要的。

1function sum(num1, num2) {
2  return num1 + num2;
3}
4
5let sum = function (num1, num2) {
6  return num1 + num2;
7};
8
9//arrow function
10let sum = (num1, num2) => {
11  return num1 + num2;
12};
13
14//use the Function consrtuctor
15let sum = new Function('num1', 'num2', 'return num1 + num2'); // not recommended

10.1 箭头函数(Arrow Function)

  • 当一个参数时箭头函数不需要括号
1// Both are valid
2let double = (x) => {
3  return 2 * x;
4};
5let triple = (x) => {
6  return 3 * x;
7};
8
9// Zero parameters require an empty pair of parentheses
10let getRandom = () => {
11  return Math.random();
12};
  • 只有一行代码,可省略花括号
1// Both are valid and will return the value
2let double = (x) => {
3  return 2 * x;
4};
5let triple = (x) => 3 * x;

10.2 函数名

  • 函数名只是指向函数的指针,可以有很多指向该函数的指针

10.3 理解 Argument

  • argument 内在表示为 array,因此函数不关心传入参数的数量
1function sayHi(name, message) {
2  console.log('Hello ' + name + ', ' + message);
3}
4
5//rewritten version
6function sayHi() {
7  console.log('Hello ' + arguments[0] + ', ' + arguments[1]);
8}
  • argument 可与 named argument 结合使用
1function doAdd(num1, num2) {
2  if (arguments.length === 1) {
3    console.log(num1 + 10);
4  } else if (arguments.length === 2) {
5    console.log(arguments[0] + num2);
6  }
7}

10.3.1 箭头函数中的 arguments

  • arguments 只可通过 name token 使用
1let bar = () => {
2  console.log(arguments[0]);
3};
4bar(5); // ReferenceError: arguments is not defined
5
6//correct version
7function foo() {
8  let bar = () => {
9    console.log(arguments[0]); // 5
10  };
11  bar();
12}
13foo(5);

10.4 不重载(No Overloading)

1let addSomeNumber = function (num) {
2  return num + 100;
3};
4addSomeNumber = function (num) {
5  return num + 200;
6};
7let result = addSomeNumber(100); // 300

10.5 初始参数值

1function makeKing(name = 'Henry') {
2  return `King ${name} VIII`;
3}
4console.log(makeKing('Louis')); // 'King Louis VIII'
5console.log(makeKing()); // 'King Henry VIII'
  • 使用初始化参数时,argument 不可以反应初始化参数
1function makeKing(name = 'Henry') {
2  name = 'Louis';
3  return `King ${arguments[0]}`;
4}
5console.log(makeKing()); // 'King undefined'
6console.log(makeKing('Louis')); //  'King Louis'

10.5.1 参数初始化范围与暂时死区(Default Parameter Scope and Temporal Dead Zone)

  • 暂时性死区
1// Error
2function makeKing(name = numerals, numerals = 'VIII') {
3  return `King ${name} ${numerals}`;
4}
5
6// Error
7function makeKing(name = 'Henry', numerals = defaultNumeral) {
8  let defaultNumeral = 'VIII';
9  return `King ${name} ${numerals}`;
10}

10.6 参数传递与剩余参数(SPREAD ARGUMENTS AND REST PARAMETERS)

10.6.1 参数传递

  • 分离出 array 中的变量
1let values = [1, 2, 3, 4];
2function getSum() {
3  let sum = 0;
4  for (let i = 0; i < arguments.length; ++i) {
5    sum += arguments[i];
6  }
7  return sum;
8}
9console.log(getSum.apply(null, values)); // 10
10console.log(getSum(...values)); // 10

10.6.2 剩余参数

  • rest parameter 只能使用在在最后
1// Error
2function getProduct(...values, lastValue) {}
3// OK
4function ignoreFirst(firstValue, ...values) {
5console.log(values); }

10.7 函数声明与函数表达

1// Error
2console.log(sum(10, 10));
3let sum = function (num1, num2) {
4  return num1 + num2;
5};
6
7// OK
8console.log(sum(10, 10));
9var sum = function (num1, num2) {
10  return num1 + num2;
11};

10.8 函数与值

  • 从函数返回函数
1function createComparisonFunction(propertyName) {
2  return function (object1, object2) {
3    let value1 = object1[propertyName];
4    let value2 = object2[propertyName];
5    if (value1 < value2) {
6      return -1;
7    } else if (value1 > value2) {
8      return 1;
9    } else {
10      return 0;
11    }
12  };
13}
14
15let data = [
16  { name: 'Zachary', age: 28 },
17  { name: 'Nicholas', age: 29 },
18];
19
20data.sort(createComparisonFunction('name'));
21console.log(data[0].name); // Nicholas
22
23data.sort(createComparisonFunction('age'));
24console.log(data[0].name); // Zachary

10.9 函数内部

10.9.1 this

  • function 的上下文是动态的
  • 箭头函数的上下文在定义是确定
1function King() {
2  this.royaltyName = 'Henry';
3  // 'this' will be the King instance
4  setTimeout(() => console.log(this.royaltyName), 1000);
5}
6
7function Queen() {
8  this.royaltyName = 'Elizabeth';
9
10  // 'this' will be the window object
11  setTimeout(function () {
12    console.log(this.royaltyName);
13  }, 1000);
14}
15
16new King(); // Henry
17new Queen(); // undefined

10.9.2 caller

该特性是非标准的,请尽量不要在生产环境中使用它!

10.9.3 new.target

new.target 属性允许你检测函数或构造方法是否是通过new运算符被调用的。在通过 new 运算符被初始化的函数或构造方法中,new.target 返回一个指向构造方法或函数的引用。在普通的函数调用中,new.target 的值是 undefined。

10.10 函数属性与工具(FUNCTION PROPERTIES AND METHODS)

  • apply()

apply 方法传入两个参数:一个是作为函数上下文的对象,另外一个是作为函数参数所组成的数组。

  • call()

call 方法第一个参数也是作为函数上下文的对象,但是后面传入的是一个参数列表,而不是单个数组。

10.11 函数表达

  • function 声明
1function functionName(arg0, arg1, arg2) {
2  // function body
3}

这种方式有一个关键特性 ——function declaration hoisting

1sayHi();
2function sayHi() {
3  console.log('Hi!');
4}
5//This example doesn’t throw an error because the function declaration is read first before the code begins to execute.
  • function expression
1let functionName = function(arg0, arg1, arg2) {
2  // function body
3};
4
5sayHi(); // Error! function doesn't exist yet let sayHi =
6function() {
7  console.log("Hi!");
8};

对于function declaration hoisting这个特性

1// Never do this!
2if (condition) {
3  function sayHi() {
4    console.log('Hi!');
5  }
6} else {
7  function sayHi() {
8    console.log('Yo!');
9  }
10}
11//大多数浏览器忽视condition的值,只承认第二次定义。但是firefox会依照condition的值
12
13// OK
14let sayHi;
15if (condition) {
16  sayHi = function () {
17    console.log('Hi!');
18  };
19} else {
20  sayHi = function () {
21    console.log('Yo!');
22  };
23}

10.12 递归

1function factorial(num) {
2  if (num <= 1) {
3    return 1;
4  } else {
5    return num * factorial(num - 1);
6  }
7}
8
9function factorial(num) {
10  if (num <= 1) {
11    return 1;
12  } else {
13    return num * arguments.callee(num - 1); // strct mode 下无法使用
14  }
15}

10.13 尾调用优化

尾调用是指一个函数里的最后一个动作是返回一个函数的调用结果的情形,即最后一步新调用的返回值直接被当前函数的返回结果。

"尾调用优化"(Tail call optimization),即只保留内层函数的调用记录。如果所有函数都是尾调用,那么完全可以做到每次执行时,调用记录只有一项,这将大大节省内存。

举个例子

1function outerFunction() {
2  return innerFunction(); // tail call
3}
  • 在 ES6 优化之前
    1. 到达 outerFunction 函数主体,第一栈帧压入中
    2. 执行 outerFunction 主体,到达 return
    3. 到达 innerFunction 函数主体,第二栈帧压入中
    4. 执行 innerFunction 主体,返回值被估计
    5. 返回值传递给 outerFunction
    6. 栈压出
  • 在 ES6 优化后
    1. 到达 outerFunction 函数主体,第一栈帧压入中
    2. 执行 outerFunction 主体,到达 return
    3. 由于 innnerFunction 的返回值也是 outerFunction 的返回值,引擎识别出 first stack frame 可以被安全压出
    4. outerFunction 栈帧压出
    5. 执行到 innerFunction,栈帧压入
    6. 执行 innerFunction 主体,返回值被估计
    7. innerFunction 栈帧压出

两者明显的差别在于第一个执行会引起一个多余的栈帧

ES6 尾调用优化的核心在于调用记录只有一项

10.13.1 尾调用优化的条件

  • 代码在 strct mode 下执行
  • 返回值是调用函数
  • 尾调用之后没有其他执行
  • 尾函数不是闭包
1"use strict";
2// No optimization: tail call is not returned function
3outerFunction() {
4  innerFunction();
5}
6
7// No optimization: tail call is not directly returned function
8outerFunction() {
9  let innerFunctionResult = innerFunction();
10  return innerFunctionResult;
11}
12// No optimization: tail call must be cast as a string after return function
13outerFunction() {
14  return innerFunction().toString();
15}
16// No optimization: tail call is a closure function
17outerFunction() {
18  let foo = 'bar';
19  function innerFunction() { return foo; }
20  return innerFunction();
21}

10.13.2 尾调用优化代码

1function fib(n) {
2  if (n < 2) {
3    return n;
4  }
5  return fib(n – 1) + fib(n – 2);
6}
7
8// use tail call
9"use strict";
10// base case
11function fib(n) {
12  return fibImpl(0, 1, n);
13}
14// recursive case
15function fibImpl(a, b, n) {
16  if (n === 0) { return a;
17}
18  return fibImpl(b, a + b, n - 1);
19}

10.14 闭包(CLOSURES)

匿名函数与闭包两者经常被混淆

闭包就是能够读取其他函数内部变量的函数。在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。

闭包可以用在许多地方。它的最大用处有两个,一个是前面提到的可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。

10.14.1 The this object

在这个上下文(执行环境)中匿名函数并没有绑定到任何一个对象中,意味着 this 指向 window(除非这个上下文(执行环境)是在严格模式下执行的,而严格模式下该 this 指向 undefined)

1window.identity = 'The Window';
2let object = {
3  identity: 'My Object',
4  getIdentityFunc() {
5    return function () {
6      return this.identity;
7    };
8  },
9};
10console.log(object.getIdentityFunc()()); // 'The Window'
1window.identity = 'The Window';
2let object = {
3  identity: 'My Object',
4  getIdentityFunc() {
5    let that = this;
6    return function () {
7      return that.identity;
8    };
9  },
10};
11console.log(object.getIdentityFunc()()); // 'My Object'

10.14.2 内存泄漏

1function assignHandler() {
2  let element = document.getElementById('someElement');
3  element.onclick = () => console.log(element.id);
4}

只要匿名函数存在,element 至少为 1

1function assignHandler() {
2  let element = document.getElementById('someElement');
3  let id = element.id;
4  element.onclick = () => console.log(id);
5  element = null;
6}

10.15 立刻调用函数表达(IMMEDIATELY INVOKED FUNCTION EXPRESSIONS)

10.16 模块模式

模块模式是为单例模式添加私有变量和私有方法,并减少全局变量的使用

本文目录