UP | HOME

JavaScript 编程语言精粹

Table of Contents

1 基础知识

1.1 常量和变量

定义常量使用 const 关键字,变量有 varlet ,其中 var 是旧版本的定 义变量方法,它没有 scope,但是 let 是有 scope 的,所以无论如何 定义变量请 使用 let 关键字

> if (true) {
... var v1 = true;
... }
> v1
true
> if (true) {
... let v2 = true;
... }
> v2
Thrown:
ReferenceError: v2 is not defined
>

1.2 null 和 undefined

null 在 JavaScript 中表示空值, undefined 通常是为没有赋值的变量。通常变 量为空的时候要使用 null ,尽量避免 undefined

> null == undefined
true
> null === undefined
false
> undefined == undefined
true
> typeof undefined
'undefined'
> typeof null
'object'
> typeof true
'boolean'
> typeof 0
'number'
>

1.3 类型

1.3.1 基本类型

JavaScript 的基本类型包括: string, number, boolean, symbol, nullundefined

  • 普通对象都有相应的方法
  • nullundefined 没有方法

JavaScript 的基本类型有以下 包装类 ,例如: String, Number, BooleanSymbol

1.3.2 类型转换

转换成字符串

> String(true)
'true'
> String(null)
'null'
> String(23)
'23'
>String(undefined)
'undefined'
> '' + 21
'21'
> 2 + 2 + '1'
'41'

转换成数值

> "6" / "3"
2
> Number("234")
234
> Number("str")
NaN
> Number(null)
0
> Number(undefined)
NaN
> Number(true)
1
> Number(false)
0
> +'12.3'
12.3
> parseInt('23')
23
> parseFloat('3.14')
3.14

2 控制流

2.1 if 条件, switch case 多层条件控制

if (year < 2015) {
  console.log('Too early...');
} else if (year > 2015) {
  console.log('Too late');
} else {
  console.log('Exactly!');
}

let a = 2 + 2;

switch (a) {
  case 3:
    console.log('Too small');
    break;
  case 4:
    console.log('Exactly!');
    break;
  case 5:
    console.log('Too large');
    break;
  default:
    console.log( "I don't know such values" );
}

2.2 while for 循环

let i = 0;
while (i < 3) { // shows 0, then 1, then 2
  console.log( i );
  i++;
}

do {
  console.log( i );
  i++;
} while (i < 3);

for (let i = 0; i < 3; i++) { // shows 0, then 1, then 2
  console.log(i);
}

2.3 try/catch 处理异常

异常一般用到的比较少,这里先记上

try {
  console.log('try');
  if (confirm('Make an error?')) BAD_CODE();
} catch (e) {
  console.log('catch');
} finally {
  console.log('finally');
}

3 函数

3.1 函数定义

函数是 JavaScript 的重要工具,定义函数有以下几种方法

function sum(a, b) {
  let result = a + b;
  return result;
}
let sum = function(a, b) {
  let result = a + b;
  return result;
}

// expression at the right side
let sum = (a, b) => a + b;
// or multi-line syntax with { ... }, need return here:
let sum = (a, b) => {
  // ...
  return a + b;
}

// without arguments
let sayHi = () => console.log("Hello");
// with a single argument
let double = n => n * 2;

箭头函数绑定了上下文,所以箭头函数有以下特点:

  • 没有 this
  • 没有 arguments
  • 不能使用 new 来创建对象
  • 没有 super

3.2 带默认值的函数

function showMessage(from, text) {
  console.log(from + ': ' + text);
}
function showMessage2(from, text='hello') {
  console.log(from + ': ' + text);
}

3.3 Rest 参数和 ... 操作符

3.3.1 Rest 参数

JavaScript 中的三点操作符 ... 可以定义变长的参数

function sumAll(...args) { // args is the name for the array
  let sum = 0;
  for (let arg of args) sum += arg;
  return sum;
}

sumAll(1); // 1
sumAll(1, 2); // 3
sumAll(1, 2, 3); // 6

3.3.2 arguments 变量

JavaScript 的每个函数中都默认传入一个 Array-like 的 arguments 变量,用于处 理函数的对象。使用 arguments 主要是兼容以前 JavaScript 不支持 Rest 参数

function showName() {
  console.log(arguments.length);
  console.log(arguments[0]);
  console.log(arguments[1]);

  // it's iterable
  // for(let arg of arguments) console.log(arg);
}

// shows: 2, Julius, Caesar
showName("Julius", "Caesar");
// shows: 1, Ilya, undefined (no second argument)
showName("Ilya");

3.3.3 ... 操作符

... 可以将数组中作为变长参数传入函数中

> arr = [1, 2, 5]
[ 1, 2, 5 ]
> Math.max(1,2,5)
5
> Math.max(arr)
NaN
> Math.max(...arr)
5

... 可以合并数组

> let a = [1, 3, 5];
> let b = [2, 4, 6];
> [...a, ...b]
[ 1, 3, 5, 2, 4, 6 ]
> Math.max(9, ...a, ...b)
9
>

3.4 函数对象和 NFE

JavaScript 中函数也是一种对象,所以函数都是 Function 的实例。NFE 是命名函数 表达式(Named Function Expression)。

3.4.1 函数对象

  • 函数对象都包含 name 属性,表示函数的名称
  • 函数对象包含 length 属性,表示函数的参数的长度
> function sayHi() { console.log('hello'); }
> sayHi.name
'sayHi'
> sayHi.length
0
>

3.4.2 NFE

NFE 在函数定义赋值前重新添加一个函数名称,具体如下:

let sayHi = function func(who) { // additional func as name
  console.log(`Hello, ${who}`);
};

这样定义的好处是:

  • func 函数名对函数内部可见
  • func 函数名对函数外部不可见
let sayHi = function func(who) {
  if (who) {
    console.log(`Hello, ${who}`);
  } else {
    func("Guest"); // use func to re-call itself
  }
};

sayHi(); // Hello, Guest
// But this won't work:
func(); // Error, func is not defined (not visible outside of the function)

3.5 调度 setTimeout/setInterval

  • setTimeout 设置一定计时后运行
  • setInterval 设置一定周期运行函数
  • clearTimeout/clearInterval 取消计时器

3.5.1 setTimeout

let timerId = setTimeout(func|code, delay[, arg1, arg2...])
  • func 回调函数
  • delay 延迟毫秒数
  • arg1, arg2, ... 回调函数的参数
function sayHi() {
  console.log('Hello');
}

setTimeout(sayHi, 1000);

setTimeout(func,0) 是在定义后里面运行函数,这个可以实现异步调用,例如下面 的例子中会在显示 Hello 过后立马显示 World

setTimeout(() => console.log("World"), 0);

console.log("Hello");

另外使用 setTimeout(func,0) 可以将高消耗 CPU 的工作放在初始化过后进行

3.5.2 clearTimeout

let timerId = setTimeout(...);
clearTimeout(timerId);

3.5.3 setInterval

let timerId = setInterval(func|code, delay[, arg1, arg2...])

参数和 setTimeout 类似

// repeat with the interval of 2 seconds
let timerId = setInterval(() => console.log('tick'), 2000);

// after 5 seconds stop
setTimeout(() => { clearInterval(timerId); console.log('stop'); }, 5000);

3.6 函数 this 绑定

  • func.bind(context, ...args) 绑定 func 的 this 到 context,详见 bind
  • func.call(context, ...args) 绑定 func 的 this 到 context, 然后调用函数 func,将 ...args 作为参数传入,详见 call
  • func.apply(context, args) 绑定 func 的 this 到 context, 然后调用函数 func,将 Array-like 的 args 作为参数传入,详见 apply

3.7 Currying

Currying 将 f(a,b,c) 转化成 f(a)(b)(c) 这样的调用。一个简单的 curry 实现如 下:

function curry(func) {

  return function curried(...args) {
    if (args.length >= func.length) {
      return func.apply(this, args);
    } else {
      return function(...args2) {
        return curried.apply(this, args.concat(args2));
      }
    }
  };

}

lodash 库提供 _.curry 实现的工具函数

var abc = function(a, b, c) {
  return [a, b, c];
};

var curried = _.curry(abc);

curried(1)(2)(3);
// => [1, 2, 3]

curried(1, 2)(3);
// => [1, 2, 3]

curried(1, 2, 3);
// => [1, 2, 3]

// Curried with placeholders.
curried(1)(_, 3)(2);
// => [1, 2, 3]

4 标准内置对象

4.1 Number 数字

4.1.1 转化成数字

  • parseInt(str, base) 转化成整数
  • parseFloat(str) 转化成浮点数

4.1.2 数字截断

主要有: Math.floor , Math.ceil, Math.round, Math.trunc 这些方法,例 子见下表

  Math.floor Math.ceil Math.round Math.trunc
3.1 3 4 3 3
3.6 3 4 4 3
-1.1 -2 -1 -1 -1
-1.6 -2 -1 -2 -1

4.1.3 其它函数

  • Math.random() 取 0 到 1 之间的随机数
  • Math.max(a, b, c, ..)Math.min(a, b, c, ..) 取最值
  • Math.pow(n, power)

4.1.4 特殊数字测试

  • isFinite 如果不是无穷大,返回真;否则返回假。 Infinity-Infinity 表示无穷大和无穷小
  • isNaN 测试是否是一个数字
> isFinite(NaN)
false
> isFinite(-Infinity)
false
> isFinite(Infinity)
false
> isFinite(12)
true
> isNaN(NaN)
true
> isNaN(2)
false
> isNaN("ss")
true
>

4.2 String 字符串

String 对象在 MDN 中有详细的介绍

4.2.1 定义字符串

注意使用 ${...} 表达式

> "Double quote"
'Double quote'
> 'Single quote'
'Single quote'
> let name = 'Jack'
> `Hi, ${name}`
'Hi, Jack'
> `1 + 2 = ${1 + 2}`
'1 + 2 = 3'

4.2.2 字符串长度

str.length ,字符串长度是一个对象中的属性

> 'hello'.length
5

4.2.3 获取子字符串

  • []str.charAt(idx): 通过下标索引获取字符
  • str.slice(beginIndex[,endIndex]) 获取子字符串,如果 beginIndex 或 endIndex 是负表示反向索引
  • str.substring(indexStart[,indexEnd]) 和 slice 和类似,但是 substring 支 持 indexEnd 小于 indexStart
> 'abcd'.substring(1,3)
'bc'
> 'abcd'.substring(3,1)
'bc'
> 'abcd'.slice(1,3)
'bc'
> 'abcd'.slice(3,1)
''
>

4.2.4 字符串查找

  • str.indexOf(searchValue,fromIndex) : 查找字符串中是否包含 searchValue, 并返回下标
  • str.lastIndexOf(searchValue,fromIndex) : 反向查找字符串中是否包含 searchValue,并返回下标
  • str.includes(searchString,[position]) : 查找字符串,返回布尔值
  • str.startsWith(searchString[,position])str.endsWith(searchString[,length])
> 'abaa'.indexOf('a')
0
> 'abaa'.indexOf('b')
1
> 'abaa'.lastIndexOf('a')
3
> 'abaa'.includes('b')
true
> 'hello'.startsWith('h')
true
> 'hello'.startsWith('a')
false
> 'hello'.endsWith('a')
false
>

4.2.5 字符串替换/正则表达式

  • str.replace(regexp|substr,newSubstr|function)
    • str.replace(/a/g, 'b') 将 str 中所有的 a 替换成 b
  • str.match(regexp) : 匹配 regexp 返回数组,如果 regexp 是 null 则返回空
  • str.search(regexp)
> 'abaa'.replace('a', '$')
'$baa'
> 'abaa'.replace(/a/g, '$')
'$b$$'
>
> 'abaa'.match(/a/g)
[ 'a', 'a', 'a' ]
> 'abaa'.match(/a/)
[ 'a', index: 0, input: 'abaa', groups: undefined ]
> 'abaa'.match(/^a/)
[ 'a', index: 0, input: 'abaa', groups: undefined ]
> 'For more information, see Chapter 3.4.5.1'.match(/see (chapter \d+(\.\d)*)/i)
[ 'see Chapter 3.4.5.1',
  'Chapter 3.4.5.1',
  '.1',
  index: 22,
  input: 'For more information, see Chapter 3.4.5.1',
  groups: undefined ]
> 'abaa'.match(null)
null

创建正则表达式的方法

// 标准创建方法
let regexp = new RegExp("pattern", "flags");
// 语法糖
let regexp1 = /pattern/; // no flags
let regexp2 = /pattern/gmi; // with flags g,m and i
  • re.test(str) 测试 str 是否满足正则表达式 re
  • re.exec(str)

常用的字符组如下:

  • \d digitals, \D non-digitals
  • \s spaces, \S non-spaces
  • \w words, \W non-words
  • \b word boundary
> /^\d*$/.test('1231a23')
false
> /^\d*$/.test('123123')
true
> "Hello, Java!".match(/\bJava\b/)
[ 'Java', index: 7, input: 'Hello, Java!', groups: undefined ]
> "Hello, JavaScript!".match(/\bJava\b/)
null
  • Lazy 匹配模式使用 ?
  • 分组使用小括号,在替换中引用使用 $n, 在正则表达式中引用使用 \n 。其中 n 是数字
> /\d+/.exec('121-a-88-122')
[ '121', index: 0, input: '121-a-88-122', groups: undefined ]
> /\d+?/.exec('121-a-88-122') // lazy mode
[ '1', index: 0, input: '121-a-88-122', groups: undefined ]
> 'Jinghui Hu'.replace(/(\w+) (\w+)/i, "$2, $1")
'Hu, Jinghui'
> /(\w+) (\w+)/i.exec('Jinghui Hu')
[ 'Jinghui Hu',
  'Jinghui',
  'Hu',
  index: 0,
  input: 'Jinghui Hu',
  groups: undefined ]
> 'He said: "She is the one!".'.match(/(['"])(.*?)\1/g)
[ '"She is the one!"' ]
>

4.2.6 其它字符串函数

  • str.padStart(targetLength [,padString]), str.padEnd(targetLength [,padString])
  • str.repeat()
  • str.trim(), str.trimStart(), str.trimEnd()
  • str.toUpperCase(), str.toLowerCase()
  • str.split()
> 'abaa'.split()
[ 'abaa' ]
> 'abaa'.split('')
[ 'a', 'b', 'a', 'a' ]
> 'apple'.padStart(1)
'apple'
> 'apple'.padStart(1, '+')
'apple'
> 'apple'.padStart(10, '+')
'+++++apple'
> 'apple'.padStart(10)
'     apple'
>

4.3 Array 数组

数组是具有固定长度 (arr.length) 的同一类元素的集合,具体参考 MDN 中的定义。

4.3.1 创建

> let fruits = ['Apple', 'Banana', 'Orange']
undefined
> fruits.length
3

4.3.2 修改元素:添加/删除

  • push(...items) : 添加元素到数组结尾
  • pop() : 移除结尾的元素
  • shift() : 删除起始的元素
  • unshift(...items) : 添加元素到数组起始
> fruits
[ 'Apple', 'Banana', 'Orange' ]
> fruits.push('Grape')
4
> fruits
[ 'Apple', 'Banana', 'Orange', 'Grape' ]
> fruits.pop()
'Grape'
> fruits
[ 'Apple', 'Banana', 'Orange' ]
> fruits.shift()
'Apple'
> fruits
[ 'Banana', 'Orange' ]
> fruits.unshift('Apple')
3
> fruits
[ 'Apple', 'Banana', 'Orange' ]
>
  • splice(pos, deleteCount, ...items) : 在 pos 位置删除 deleteCount 个元 素然后插入 items
  • slice(start, end) : 创建一个新的数组, 复制 startend (不包含) 到新 的数组中
  • concat(...items) : 返回一个新的数组: 拷贝当前数组的所有元素然后添加 items 到新的数组中. 如果任何 items 是一个数组, 数组里面的元素都会添加的 新的数组中
> fruits
[ 'Apple', 'Banana', 'Orange' ]
> let new_fruits = fruits.slice(0, 2)
> new_fruits
[ 'Apple', 'Banana' ]
> fruits.splice(1, 1)
[ 'Banana' ]
> fruits
[ 'Apple', 'Orange' ]
> fruits
[ 'Apple', 'Orange' ]
> fruits.splice(1, 0, 'Pear')
[]
> fruits
[ 'Apple', 'Pear', 'Orange' ]
> fruits.concat('Banana')
[ 'Apple', 'Pear', 'Orange', 'Banana' ]
> fruits
[ 'Apple', 'Pear', 'Orange' ]
>

4.3.3 查找

  • indexOf/lastIndexOf(item, pos) : 查找 item , 起始位置是~pos~ , 如果没有 找到则返回 -1
  • includes(value) : 返回 true 如果数组包含 value , 否则 false
  • find/filter(func) : 使用谓词函数过滤数组, 返回第一个/所有的使得谓词函数成 立的元素
  • findIndex(func) : 和 find 相似, 但是返回下标索引而不是数组元素
> fruits
[ 'Apple', 'Pear', 'Orange' ]
> fruits.indexOf('Pear')
1
> fruits.indexOf('Banana')
-1
> fruits.find(function (e) { return e.length >= 5;})
'Apple'
> fruits.filter(function (e) { return e.length >= 5;})
[ 'Apple', 'Orange' ]
> fruits.includes('Banana')
false
> fruits.includes('Pear')
true
> 'Banana' in fruits
false
> 'Pear' in fruits
false

4.3.4 迭代

  • forEach(func) : 调用 func 处理所有的数组里的元素, 但是不返回
['Apple', 'Banana', 'Orange'].forEach(function(e, i, arr) {
  console.log(i + " : " + e)
})

4.3.5 数组变换

  • map(func) : 调用 func 处理所有数组里的元素,返回处理结果集构成的新数组
  • sort(func) : 使用 func 排序数组,然后返回
  • reverse() : 返回逆序的数组
  • split/join : 在字符串和数组之间转换
  • reduce(func, initial) : 计算得出一个值, 通过调用 func 函数处理起始值和 中间值
> fruits
[ 'Apple', 'Pear', 'Orange' ]
> fruits.map(function(e) { return e.toUpperCase();})
[ 'APPLE', 'PEAR', 'ORANGE' ]
> fruits.reduce(function(a, e) { return a+':'+e;}, '')
':Apple:Pear:Orange'
> fruits.join(':')
'Apple:Pear:Orange'
> 'Apple:Pear:Orange'.split(':')
[ 'Apple', 'Pear', 'Orange' ]
> fruits.sort()
[ 'Apple', 'Orange', 'Pear' ]
> fruits
[ 'Apple', 'Orange', 'Pear' ]
> fruits.sort(function(a,b) {return a.length>b.length?1:-1;})
[ 'Pear', 'Apple', 'Orange' ]
> fruits.reverse()
[ 'Orange', 'Apple', 'Pear' ]
>

4.3.6 其它

  • Array.isArray(arr) : 检查 arr 是否是数组对象
  • arr.some(fn) / arr.every(fn) : 调用 fn 作用于数组所有元素,如果任何/ 所有的都返回 true, 则返回 true, 否则返回 false
  • arr.fill(value, start, end) : 使用 value 填充数组 startend 位置 的元素
  • arr.copyWithin(target, start, end) : 复制数组 startend 位置中的元 素到自身 target 位置中, 注意会覆盖已存在的元素

4.4 迭代器/数组类似物

4.4.1 Iterable 和 Array-like

  • Iterable 是实现了 Symbol.iterator 方法的对象
    • Symbol.iterator 必须包含 next() 方法
    • next() 方法必须返回像 {done: Boolean, value: any} 这样的对象,done 为 true 表示迭代器结束
    • Iterable 主要是使用 for..of 迭代器进行迭代
  • Array-like 是含义 length 属性和可索引的对象
let arrayLike = { // has indexes and length => array-like
  0: "Hello",
  1: "World",
  length: 2
};

let iterable = {
  from: 1,
  to: 5,
  // 1. call to for..of initially calls this
  [Symbol.iterator]() {
    this.current = this.from;
    // 2. ...it returns the iterator:
    return this;
  },
  // 3. next() is called on each iteration by the for..of loop
  next() {
    // 4. it should return the value as an object {done:.., value :...}
    if (this.current <= this.to) {
      return { done: false, value: this.current++ };
    } else {
      return { done: true };
    }
  }
  }
}

for (let num of iterable) { console.log(num); } // => prints 1, 2, 3, 4, 5

4.4.2 Array.from

Array.from(arrayLike[,mapFn[,thisArg]]) 通过 Array-like 的对象来新建一个真 的 Array,例如字符串 'foo' 是 Array-like 对象,因此可以建立一个新的数组

> Array.from('foo');
[ 'f', 'o', 'o' ]

4.5 Map/Set 等标准内置对象

Map , WeakMap, Set, WeakSet 等都是常用的 Javascript 对象,具体参考 MDN 中的定 义 Standard built-in objects

Map 对象操作

let map = new Map();

map.set('1', 'str1');   // a string key
map.set(1, 'num1');     // a numeric key
map.set(true, 'bool1'); // a boolean key

// remember the regular Object? it would convert keys to string
// Map keeps the type, so these two are different:
console.log( map.get(1)   ); // 'num1'
console.log( map.get('1') ); // 'str1'

console.log( map.size ); // 3

let recipeMap = new Map([
  ['cucumber', 500],
  ['tomatoes', 350],
  ['onion',    50]
]);

// iterate over keys (vegetables)
for (let vegetable of recipeMap.keys()) {
  console.log(vegetable); // cucumber, tomatoes, onion
}

// iterate over values (amounts)
for (let amount of recipeMap.values()) {
  console.log(amount); // 500, 350, 50
}

// iterate over [key, value] entries
for (let entry of recipeMap) { // the same as of recipeMap.entries()
  console.log(entry); // cucumber,500 (and so on)
}

Set 集合函数

let set = new Set();

let john = { name: "John" };
let pete = { name: "Pete" };
let mary = { name: "Mary" };

// visits, some users come multiple times
set.add(john);
set.add(pete);
set.add(mary);
set.add(john);
set.add(mary);

// set keeps only unique values
console.log( set.size ); // 3

for (let user of set) {
  console.log(user.name); // John (then Pete and Mary)
}

let set = new Set(["oranges", "apples", "bananas"]);

for (let value of set) console.log(value);

// the same with forEach:
set.forEach((value, valueAgain, set) => {
  console.log(value);
});

4.6 Object 对象

Object 是 JavaScript 中的对象

4.6.1 定义对象

> let user1 = new Object()
> user1
{}
> let user2 = {}
> user2
{}
> let user3 = {name: 'Tom', age: 22}
> user3
{ name: 'Tom', age: 22 }
> user3.name
'Tom'
> user3['name']
'Tom'
>

4.6.2 对象基本操作

  • 引用对象中的值
    • 点方式: obj.key
    • 方括号方式: obj['key']
  • 删除对象中的值 delete obj.key
  • 判断对象中是否存在键 'key' in obj
let usr = {name: 'Tom', age: 22}
> 'name' in usr
true
> 'birthday' in usr
false
>

4.6.3 对象引用/复制

= 赋值是引用

> obj1 = {a:'Apple', b:1}
{ a: 'Apple', b: 1 }
> obj2 = obj1
{ a: 'Apple', b: 1 }
> obj2.b = 7
7
> obj1
{ a: 'Apple', b: 7 }
>

Object.assign(...) 可以实现复制传值,或者使用 lodash 库中的 _.cloneDeep(obj)

> obj3 = Object.assign({}, obj1)
{ a: 'Apple', b: 7 }
> obj3.b = 9
9
> obj1
{ a: 'Apple', b: 7 }
>

下面是更多的传值的示例

> usr
{ name: 'Tom', age: 22, foo: undefined }
> usr1 = usr
{ name: 'Tom', age: 22, foo: undefined }
> usr1 == usr
true
> usr1 === usr
true
> let _usr = {}
> for (k in usr) { _usr[k] = usr[k]; }
> _usr
{ name: 'Tom', age: 22, foo: undefined }
> _usr == usr
false
> _usr === usr
false
> usr
{ name: 'Jackson', age: 22, foo: undefined }
> delete usr.foo
true
> usr
{ name: 'Jackson', age: 22 }
> 'foo' in usr
false
>

4.6.4 更新对象

Object.assign(dest[, src1, src2, src3...]) 可以批量更新对象中的数值。

> usr
{ name: 'Tom', age: 22, foo: undefined }
> Object.assign(usr, {name: 'Jackson'})
{ name: 'Jackson', age: 22, foo: undefined }
>

4.6.5 对象的键和值

  • Object.keys(obj) 返回 obj 的键
  • Object.values(obj) 返回 obj 的值
  • Object.entries(obj) 返回 obj 的 [key, value] 对
> let o1 = {apple:1, banana:2, orange:3}
> o1
{ apple: 1, banana: 2, orange: 3 }
> Object.keys(o1)
[ 'apple', 'banana', 'orange' ]
> o1.keys()
TypeError: o1.keys is not a function
> Object.values(o1)
[ 1, 2, 3 ]
> Object.entries(o1)
[ [ 'apple', 1 ], [ 'banana', 2 ], [ 'orange', 3 ] ]
>

4.7 Date 时间和日期

Date 是表示时间和日期的内置对象。个人感觉 JavaScript 原始提供的时间日期操作函数 并不是很好用,如果有条件的话可以移步时间工具库 momentjs

4.7.1 创建日期对象

new Date(), new Date(milliseconds)new Date(date_str) 可以创建日期 对象

> new Date()
2019-07-30T06:58:04.792Z
> new Date(123124123)
1970-01-02T10:12:04.123Z
> new Date("2017-01-26")
2017-01-26T00:00:00.000Z

4.7.2 时间函数

具体见下面的样例

> let now = new Date()
> now.getFullYear()
2019
> now.getYear()
119
> now.getMonth()
6
> now.getDate()
30
> now.getHours()
15
> now.getMinutes()
5
> now.getSeconds()
21
> now.getMilliseconds()
725
> now.getDay()
2
>

4.7.3 时间戳

时间戳有以下函数

// get timestamp
> now
2019-07-30T07:05:21.725Z
> now.getTime()
1564470321725
> +now
1564470321725
> Date.now()
1564470538021

// time diff
> new Date() - now
260547

4.7.4 字符串和 Date 互转

Date.parse(str), str 应满足 YYYY-MM-DDTHH:mm:ss.sssZ 格式

> Date.parse('2012-01-26T13:51:50.417-07:00');
1327611110417

Date 转字符串有以下方法

> now.toJSON()
'2019-07-30T07:05:21.725Z'
> now.toDateString()
'Tue Jul 30 2019'
> now.toISOString()
'2019-07-30T07:05:21.725Z'
> now.toString()
'Tue Jul 30 2019 15:05:21 GMT+0800 (GMT+08:00)'
> now.toTimeString()
'15:05:21 GMT+0800 (GMT+08:00)'
> now.toUTCString()
'Tue, 30 Jul 2019 07:05:21 GMT'
>

4.8 JSON

  • JSON.stringify(obj) 将数组或对象转成 JSON 字符串
  • JSON.parse(str) 解析 JSON 字符串
> let student = {
...   name: 'John',
...   age: 30,
...   isAdmin: false,
...   courses: ['html', 'css', 'js'],
...   wife: null
... };
undefined
> JSON.stringify(student)
'{"name":"John","age":30,"isAdmin":false,"courses":["html","css","js"],"wife":null}'
> JSON.stringify([1,2,3])
'[1,2,3]'
> JSON.parse('[1,2,3,2,3]')
[ 1, 2, 3, 2, 3 ]
>

5 面向对象知识

5.1 new 关键字和构造器

5.1.1 构造器

JavaScript 中没有所谓的构造器,它的构造器实际上是一个函数,该函数满足以下两 点:

  1. 函数名称使用 Pascal case 命名
  2. 函数只能使用 new 操作符来执行

例如:

function User(name) {
  this.name = name;
  this.isAdmin = false;
}

let user = new User("Jack");

console.log(user.name); // Jack
console.log(user.isAdmin); // false

5.1.2 new 操作符

new 操作符做了以下的事情:

  1. 新建了函数作用域的 this 对象
  2. 并且将新创建的 this 对象返回

该过程大致如下面代码所示:

function User(name) {
  // this = {};  (implicitly)

  // add properties to this
  this.name = name;
  this.isAdmin = false;

  // return this;  (implicitly)
}

5.2 属性的 flag 和描述符

Object.getOwnPropertyDescriptor(obj,propertyName) 方法可以获取对象属性的描 述符

> let user = { name: 'Jinghui', age: 18}
> user
{ name: 'Jinghui', age: 18 }
> Object.getOwnPropertyDescriptor(user, 'name')
{ value: 'Jinghui',
  writable: true,
  enumerable: true,
  configurable: true }
>
  • writable 如果为真,则为可写属性
  • enumerable 如果为真,则在循环中可以被迭代到
  • configurable 如果为真,则可以使用 delete 删除

Object.defineProperty(obj,propertyName,descriptor) 方法可以修改对象属性描述 符的属性值

> user.name = 'XiaoHu'
'XiaoHu'
> user
{ name: 'XiaoHu', age: 18 }
> Object.defineProperty(user, 'name', {writable:false})
{ name: 'XiaoHu', age: 18 }
> Object.getOwnPropertyDescriptor(user, 'name')
{ value: 'XiaoHu',
  writable: false,
  enumerable: true,
  configurable: true }
> user.name = 'Jinghu Hu'
'Jinghu Hu'
> user
{ name: 'XiaoHu', age: 18 }
>

Object.getOwnPropertyDescriptors(obj) 方法可以批量获取属性的描述符

> user
{ name: 'XiaoHu', age: 18 }
> Object.getOwnPropertyDescriptors(user)
{ name:
   { value: 'XiaoHu',
     writable: false,
     enumerable: true,
     configurable: true },
  age:
   { value: 18,
     writable: true,
     enumerable: true,
     configurable: true } }
>

Object.defineProperties(obj,descriptors) 方法可以批量定义对象描述符的属性值

Object.defineProperties(user, {
  name: { value: "John", writable: false },
  surname: { value: "Smith", writable: false },
  // ...
});

5.3 属性的 Getter 和 Setter

let user = {
  name: "John",
  surname: "Smith",

  get fullName() {
    return `${this.name} ${this.surname}`;
  },

  set fullName(value) {
    [this.name, this.surname] = value.split(" ");
  }
};

// set fullName is executed with the given value.
user.fullName = "Alice Cooper";

5.4 原型 [[Prototype]]

JavaScript 的对象中都一个隐藏的 [[Prototype]] 属性,该属性被用来设置对象的原 型关系和一些面向对象的方法

5.4.1 对象的原型 obj.__proto__

可以通过 obj.__proto__ 来访问它 [[Prototype]] 。JavaScript 的继承关系都是 靠 __proto__ 来实现的

> let animal = { eats: true };
> let rabbit = { jumps: true};
> rabbit.__proto__ = animal;
{ eats: true }
> rabbit.eats
true
> rabbit.jumps
true
>

__proto__ 的值需要满足以下两点:

  • __proto__ 要么是一个对象,要么是 null
  • __proto__ 不能循环依赖

5.4.2 函数的原型 F.prototype

现代的 JavaScript 可以使用 new F() 来创建对象, F.prototypeobj.__proto__ 功能类似(略有不同)

> let animal = { eats: true };
> function Rabbit(name) { this.name = name; }
> Rabbit.prototype = animal;
{ eats: true }
> let r = new Rabbit('White Rabbit');
> r
{ name: 'White Rabbit' }
> r.eats
true
> r.constructor === Rabbit
false

F.prototype 需要注意以下几点:

  • F.prototypeobj.__proto__ 相似,但是在 new 对象的时候设置了对象的 [[Prototype]] 属性
  • F.prototype 要么是一个对象,要么为 null
  • F.prototype 除了设置了 obj.__proto__ 之外还设置了对象的构造器,即对象 的 obj.constructor 属性

5.4.3 原型的 Getter/Setter 方法

原型也有相应的 Getter 和 Setter 方法

  • Object.create(proto[,descriptors]) 创建一个空的对象,并设置原型为 proto
  • Object.getPrototypeOf(obj) 获取 obj 的原型
  • Object.setPrototypeOf(obj,proto) – 设置 obj 的原型为 proto
let animal = { eats: true };

// create a new object with animal as a prototype
let rabbit = Object.create(animal);
rabbit.eats // true

// get the prototype of rabbit
Object.getPrototypeOf(rabbit) === animal

// change the prototype of rabbit to {}
Object.setPrototypeOf(rabbit, {});

下面是一些可以获取对象属性的函数

  • Object.getOwnPropertySymbols(obj)
  • Object.getOwnPropertyNames(obj)
  • Reflect.ownKeys(obj)
> animal
{ eats: true }
> rabbit
{ jumps: true }
> Object.getOwnPropertySymbols(rabbit)
[]
> Object.getOwnPropertyNames(rabbit)
[ 'jumps' ]
> Object.keys(rabbit)
[ 'jumps' ]
> Reflect.ownKeys(rabbit)
[ 'jumps' ]
> rabbit.__proto__
{ eats: true }
> r.__proto__
{ eats: true }
> rabbit.hasOwnProperty('jumps')
true
> rabbit.hasOwnProperty('eats')
false
>

5.5

5.5.1 类定义

JavaScript 中没有类,所有的类都是通过对象来模拟得到的,如示例

function User(name) {
  this.sayHi = function() {
    console.log(name);
  };
}

let user = new User("John");
user.sayHi(); // John
class User {

  constructor(name) {
    this.name = name;
  }

  sayHi() {
    console.log(this.name);
  }

}

let user = new User("John");
user.sayHi();

5.5.2 基于原型的类定义

JavaScript 中的原型被用于定义类,为了保证子类方法在调用时可以共享父类中的方 法,类的方法都被定义在 prototype 中,下面是典型是示例

function Animal(name) { this.name = name; }
Animal.prototype.eat = function() { console.log(`${this.name} eats.`); };

function Rabbit(name) { this.name = name; }
Rabbit.prototype.jump = function() { console.log(`${this.name} jumps!`);};

// 1. 将子类的 prototype.__proto__ 设置成父类的 prototype
Rabbit.prototype.__proto__ = Animal.prototype;

let rabbit = new Rabbit("White Rabbit");
// 2. 子类在调用 eat() 是根据原型链向上查找得到 eat 方法
rabbit.eat();
rabbit.jump();

5.5.3 继承

继承使用 extends 关键字

class Animal {
  constructor(name) {
    this.speed = 0;
    this.name = name;
  }
  // ...
}

class Rabbit extends Animal {
  constructor(name, earLength) {
    super(name);
    this.earLength = earLength;
  }
  // ...
}

let rabbit = new Rabbit("White Rabbit", 10);
console.log(rabbit.name); // White Rabbit
console.log(rabbit.earLength); // 10
  • super.method() 调用父类方法
  • super() 调用父类构造器
class Animal {

  constructor(name) {
    this.speed = 0;
    this.name = name;
  }

  run(speed) {
    this.speed += speed;
    console.log(`${this.name} runs with speed ${this.speed}.`);
  }

  stop() {
    this.speed = 0;
    console.log(`${this.name} stopped.`);
  }

}

class Rabbit extends Animal {
  hide() {
    console.log(`${this.name} hides!`);
  }

  stop() {
    super.stop(); // call parent stop
    this.hide(); // and then hide
  }
}

let rabbit = new Rabbit("White Rabbit");

rabbit.run(5); // White Rabbit runs with speed 5.
rabbit.stop(); // White Rabbit stopped. White rabbit hides!

instanceof 检测 obj 是否是 Class 的一个实例

obj instanceof Class

5.5.4 Minix

混入 (Mixin) 是一个比较常用的技巧,基本思路是建一个对象,然后将所需混入的对 象的 prototype 通过 Object.assign(...) 方法放入 Mixin 的属性。下面是一个例 子

let sayHiMixin = {
  sayHi() {
    console.log(`Hello ${this.name}`);
  },
  sayBye() {
    console.log(`Bye ${this.name}`);
  }
};

// usage:
class User {
  constructor(name) {
    this.name = name;
  }
}

Object.assign(User.prototype, sayHiMixin);

new User("Dude").sayHi(); // Hello Dude!

6 浏览器知识

大部分 JavaScript 的应用是运行于浏览器中的,浏览器包括如下基本对象

  • DOM: Document Object Model
  • BOM: Browser Object Model, navigator, screen, location, frames, history, XMLHttpRequest, …
  • JavaScript: Object, Array, Function, …

6.1 Document

6.1.1 节点类型及相应对应关系

DOM 中定义了 12 种节点,在实践中常用的有以下四种:

  • DOCUMENT_NODE 文档根节点, document
  • ELEMENT_NODE 元素节点
  • ATTRIBUTE_NODE 属性节点
  • TEXT_NODE 文本节点

节点 DOM 和 HTML 对应关系表格

DOM HTML
<html> document.documentElement
<body> document.body
<head> document.head
<tr> table.rows
  tr.cells
  tr.rowIndex
  tr.sectionRowIndex
<td> td.cellIndex
<caption>, <thead>, <tfoot> table.caption/tHead/tFoot
<tbody> table.tBodies

6.1.2 节点工具函数

DOM 提供了一些可以遍历 html 节点的函数,具体入下

  • 针对于所有类型节点方法: parentNode, childNodes, firstChild, lastChild, previousSibling, nextSibling.
  • 仅仅针对元素节点: parentElement, children, firstElementChild, lastElementChild, previousElementSibling, nextElementSibling

6.1.3 节点选择器

getElementsBy*querySelector* 是选取 DOM 元素的基本方法,它的介绍见下表

方法名 传入参数 元素能否调用 返回实时结果
getElementById id - -
getElementsByName name - yes
getElementsByTagName tag or '*' yes yes
getElementsByClassName class yes yes
querySelector CSS-selector yes -
querySelectorAll CSS-selector yes -

6.1.4 节点属性

节点的类的继承见下图,其中 EventTarget 是节点的抽象基类

dom-node-class.png

  • nodeType 属性表明了 DOM 节点的属性,它是一个整数值:
    • 1 表示元素节点
    • 3 表示文本节点
    • 9 表示 document 对象
  • nodeName/tagName
  • innerHTML
  • outerHTML
  • nodeValue/data
  • textContent
  • hidden 如果为 true,和 CSS 的 display:none 表示同一意思

6.1.5 属性: HTML Attributes 和 DOM Properties

Attribute 存在于 HTML 中,大小写不敏感,必须是字符串;Property 存在于 DOM 对 象中,大小写敏感,可以是任何基础的值,一般使用点操作符来引用。例如:

  • elem.hasAttribute(name) – to check for existence.
  • elem.getAttribute(name) – to get the value.
  • elem.setAttribute(name, value) – to set the value.
  • elem.removeAttribute(name) – to remove the attribute.
  • elem.attributes is a collection of all attributes.

6.1.6 创建/插入/删除 节点

  • document.createElement(tag) 通过 tag 创建新的节点
  • document.createTextNode(value) 创建文本节点
  • elem.cloneNode(deep) 克隆节点
  • parent.appendChild(node)
  • parent.insertBefore(node, nextSibling)
  • parent.removeChild(node)
  • parent.replaceChild(newElem, node)
  • node.append(...nodes or strings)
  • node.prepend(...nodes or strings)
  • node.before(...nodes or strings)
  • node.after(...nodes or strings)
  • node.replaceWith(...nodes or strings)
  • node.remove()
  • elem.insertAdjacentHTML(where, html)
    • where: 可以是这些字符串值 beforebegin, afterbegin, beforeend, afterend

6.1.7 样式和 css 类

  • 在 DOM properties 管理样式有两种方法
    • elem.className 字符串,方便管理全部的属性
    • elem.classList 对象,方便使用 add/remove/toggle 方法
  • css 样式的命名到 DOM 中的 properties 的命名有一点点区别例如:
    • background-color 对应 elem.style.backgroundColor
    • z-index 对应 elem.style.zIndex
    • border-left-width 对应 elem.style.borderLeftWidth
    • -webkit-border-radius 对应 button.style.WebkitBorderRadius
  • style.cssText 全部样式的字符串
  • 计算后的样式值通过调用 getComputedStyle(element[,pseudo]) 函数获取,例如: getComputedStyle(document.body)
    • element 读取的元素名
    • pseudo 伪元素名称,例如: ::before

6.1.8 元素/窗口的几何属性和滚动

DOM 元素位置的几何属性主要有以下:

  • offsetParent
  • offsetLeft/offsetTop
  • offsetWidth/offsetHeight
  • clientLeft/clientTop
  • clientWidth/clientHeight
  • scrollWidth/scrollHeight
  • scrollLeft/scrollTop

窗口的几何属性如下:

  • document.documentElement.clientHeight/Width
  • 滚动的高度 scrollHeight

    let scrollHeight = Math.max(
      document.body.scrollHeight, document.documentElement.scrollHeight,
      document.body.offsetHeight, document.documentElement.offsetHeight,
      document.body.clientHeight, document.documentElement.clientHeight
    );
    
  • 读取当前滚动: window.pageYOffset/pageXOffset
  • 改变当前滚动
    • window.scrollTo(pageX, pageY)
    • window.scrollBy(x,y)
    • elem.scrollIntoView(top/bottom)

6.2 事件

6.2.1 绑定事件

绑定时间主要有 3 种方式:

  1. HTML 标签

    <input value="Click me" onclick="alert('Click!')" type="button">
    
  2. DOM property

    <input id="elem" type="button" value="Click me">
    <script>
      elem.onclick = function() {
        alert('Thank you');
      };
    </script>
    
  3. addEventListener/removeEventListener 添加和移除事件
    • element.addEventListener(event, handler[, options]);
    • element.removeEventListener(event, handler[, options]);
    • event 事件的名称,例如 "click"
    • handler 回调的事件处理函数
    • options 可选项,
      • once 如果真,当事件调用一次自动移除
      • capture 布尔值。默认为假,表示 bubbling 阶段的事件;真表示 capturing 阶段的事件
      • passive 如果真,则不会有 preventDefault()

绑定的处理事件的回调函数传入一个 Event 对象作为参数

elem.onclick = function(event) {
  // ...
}

Event 对象有以下基本属性:

  • event.type 事件类型,例如: "click"
  • event.currentTarget 指向事件所处理的当前 DOM 元素
  • event.clientX / event.clientY 对应鼠标事件,这两个值表示鼠标的坐标

6.2.2 事件冒泡(bubbing)和捕获(capturing)

  1. 事件冒泡

    事件冒泡指的是当 HTML 节点嵌套时,子节点的事件依次引发父节点的相应事件。例如: 在下面的代码中,当点击 p 标签时会触发 divform 的点击事件

    <form onclick="alert('form')">FORM
      <div onclick="alert('div')">DIV
        <p onclick="alert('p')">P</p>
      </div>
    </form>
    

    除了 focus 以外, 几乎所有的事件都是冒泡的

    • event.target 指向最先触发事件的 DOM 元素,主要和 event.currentTarget 的区别
    • event.eventPhase 当前的阶段。 capturing=1, bubbing=3
    • event.stopPropagation() 可以阻止事件冒泡,注意: 如果不是必要的话,千万 不要阻止事件冒泡
    • event.preventDefault() 阻止浏览器默认的事件,方便改变浏览器的行为。例如: 阻止 <input type="submit"> 的行为,可以自己实现提交
    • event.defaultPrevented 如果为真表示浏览器默认事件被阻止了

    下面是一个阻止 a 标签打开链接的例子

    <!-- 老方法 -->
    <a href="/" onclick="return false">Click here</a>
    
    <!-- 新方法 -->
    <a href="/" onclick="event.preventDefault()">here</a>
    
  2. 事件捕获

    事件捕获在实际代码中用的比较少,但是有时非常好用。在 DOM 事件的处理中,分成以 下三个阶段:

    1. Capturing 阶段:父节点捕获到事件时,将事件从父节点传到子节点时期
    2. Target 阶段:事件到达目标节点时期
    3. Bubbling 阶段:子节点处理好事件后,将事件从子节点回传到父节点时期

    dom-capturing-target-bubbling.png

  3. 事件代理

    Capturing 和 Bubbling 使得可以使用事件代理, 减少重复代码 ,如下可以使用 代理点击事件来高亮表格的单元格

    let selectedTd;
    
    table.onclick = function(event) {
      let target = event.target;
      if (target.tagName != 'TD') return;
      highlight(target);
    };
    
    function highlight(td) {
      if (selectedTd) {
        selectedTd.classList.remove('highlight');
      }
      selectedTd = td;
      selectedTd.classList.add('highlight');
    }
    

    在父节点中代理事件时,子节点一定不能阻止事件冒泡 event.stopPropagation()

  4. 事件分发

    W3C 中定义的事件列表参考 uievents

    • Event 的构造器 new Event(event[, options]);
      • event 事件类型
      • options 选项
        • bubbles 是否冒泡
        • cancelable 如果真,则可以调用 event.preventDefault() ,否则调用无效
    • 事件中嵌套事件时同步的,即在事件回调函数中使用 addEventListener 定义事 件不能达到异步执行的效果
    • 分发事件调用 elem.dispatchEvent(event)
    <button id="elem" onclick="alert('Click!');">Autoclick</button>
    
    <script>
      let event = new Event("click");
      elem.dispatchEvent(event);
    </script>
    
  5. 常见事件
    1. 鼠标事件
      • 简单事件
        • mousedown/mouseup
        • mouseover/mouseout
        • mousemove
      • 复杂事件
        • click
        • contextmenu
        • dblclick
      • 鼠标事件参数
        • which 鼠标左键(1),鼠标中键(2),鼠标右键(3)
        • 修饰键 shiftKey, altKey, ctrlKey, metaKey
        • 坐标,window 的相对坐标 clientX/clientY, document 的相对坐标 pageX/pageY
      button.onclick = function(event) {
        if (event.altKey && event.shiftKey) {
          alert('Hooray!');
        }
      };
      
    2. 键盘事件
      • 键盘事件 keydown/keyup
      • event.code/event.key 按下 Z 键: code="KeyZ", key=z;同时按下 Shift 和 Z 键:code="KeyZ", key=Z。
      document.addEventListener('keydown', function(event) {
        if (event.code == 'KeyZ' && (event.ctrlKey || event.metaKey)) {
          alert('Undo!')
        }
      });
      
    3. 滚动事件
      • 滚动事件 onscroll
      • 阻止滚动行为: onscroll="event.preventDefault()"
      window.addEventListener('scroll', function() {
        document.getElementById('showScroll').innerHTML = pageYOffset + 'px';
      });
      
    4. 页面生命周期中的事件
      • DOMContentLoaded: 当初始的 HTML 文档被完全加载和解析完成之后触发
      • windows.onload/onbeforeload/onunload
        • document.readyState: "loading", "interactive", "complete"
      function work() { /*...*/ }
      
      if (document.readyState == 'loading') {
        document.addEventListener('DOMContentLoaded', work);
      } else {
        work();
      }
      

6.3 表单

6.3.1 表单属性和方法

基本的表单见下面的示例

<form name="my">
  <input name="one" value="1">
  <input name="two" value="2">
</form>

<script>
  // get the form
  let form = document.forms.my; // <form name="my"> element

  // get the element
  let elem = form.elements.one; // <input name="one"> element

  alert(elem.value); // 1
</script>
  • document.forms 获取页面中的所有表单
  • input.valuetextarea.value 获取值
  • selectoption
    • select.options 获取 select 下面所有的 option 的集合
    • select.value 获取选取的 option 的值
    • select.selectedIndex 获取选取的下标
  • focusblur 捕获焦点和失去焦点的事件,当前聚焦的元素可以通过 document.activeElement 来获取
<select id="select">
  <option value="apple">Apple</option>
  <option value="pear">Pear</option>
  <option value="banana">Banana</option>
</select>

<script>
  // all three lines do the same thing
  select.options[2].selected = true;
  select.selectedIndex = 2;
  select.value = 'banana';
</script>

6.3.2 表单提交

触发表单提交主要有几种方法:

  1. 点击 <input type="submit"><input type="image">
  2. 在 input 标签中按回车键
  3. 调用 form.submit()
<form onsubmit="alert('submit!');return false">
  First: Enter in the input field <input type="text" value="text"><br>
  Second: Click "submit": <input type="submit" value="Submit">
</form>

7 异步

7.1 XMLHttpRequest 和 AJAX

传统的异步请求是使用 AJAX 技术来实现,AJAX 使用到一个 XMLHttpRequest 对象

let xhr = new XMLHttpRequest();
xhr.open('GET', '/my/url');
xhr.send();

xhr.onload = function() {
  // we can check
  // status, statusText - for response HTTP status
  // responseText, responseXML (when content-type: text/xml) - for the response

  if (this.status != 200) {
    // handle error
    alert('error: ' + this.status);
    return;
  }
  // get the response from this.responseText
};

xhr.onerror = function() {
  // handle error
};
  • xhr.open(method,URL,async,user,password)
    • method, URL HTTP 方法和资源 URL
    • async 默认为假,表示同步请求
    • user, password 用户名和密码
  • xhr.send([body]) 发送请求
  • xhr.abort() 终止请求,也可以设置超时 xhr.timeout=10000
  • 异步请求的相关事件: load, error, timeout, progress
  • 相应结果:
    • status 状态码,例如:200, 404
    • statusText 返回状态短消息,例如: "OK", "Not Found"
    • responseText 返回的文本
  • setRequestHeader(name,value) / getResponseHeader(name) 设置/获取消息头, xhr.setRequestHeader('Content-Type','application/json');

7.2 Promise

7.2.1 Promise

Promise 对象可以用来解决 JavaScript 的 callback hell 的窘境

let promise = new Promise(function(resolve, reject) {
  // executor (the producing code, "singer")
});

Promise 对象有两个重要属性:

  • [[PromiseStatus]] Promise 对象当前的状态
  • [[PromiseValue]] Promise 的初始值

满足如下状态图:

promise-states.png

7.2.2 Promise.then/catch(handler)

Promise 有 then/catch(handler) 两个成员函数

  • then 方法返回一个 Promise 对象,可以用来实现链式调用,有一点比较有用的是 then 的行为会根据 then handler 的返回值类型改变
    • 当 handler 返回普通值时, then 方法会立马返回处理到下一个 handler
    • 当 handler 抛出异常时, then 相当发生错误,调用 catch 方法的 handler 来进行错误处理
    • 当 handler 返回值是 Promise 对象时,直到等待 Promise 的异步调用完毕后 才返回结果
  • catch 方法用来错误处理
// 串行逻辑的 then 调用
new Promise(function(resolve, reject) {
  setTimeout(() => resolve(1), 1000);
}).then(function(result) {
  alert(result); // 1
  return result * 2;
}).then(function(result) {
  alert(result); // 2
  return result * 2;
}).then(function(result) {
  alert(result); // 4
  return result * 2;
});

// 并行版本的 then 调用
let promise = new Promise(function(resolve, reject) {
  setTimeout(() => resolve(1), 1000);
});
promise.then(function(result) {
  alert(result); // 1
  return result * 2;
});
promise.then(function(result) {
  alert(result); // 1
  return result * 2;
});
promise.then(function(result) {
  alert(result); // 1
  return result * 2;
});

7.2.3 fetch 请求服务器

fetch 函数可以请求远端的服务器,然后返回一个 Promise 对象。在 Chrome 浏览器中 请求的样例如下:

fetch-github-api.png

fetch('/article/promise-chaining/user.json')
  .then(response => response.json())
  .then(user => fetch(`https://api.github.com/users/${user.name}`))
  .then(response => response.json())
  .then(githubUser => new Promise(function(resolve, reject) {
    let img = document.createElement('img');
    img.src = githubUser.avatar_url;
    img.className = "promise-avatar-example";
    document.body.append(img);
    setTimeout(() => {
      img.remove();
      resolve(githubUser);
    }, 3000);
  }))
  .then(githubUser => alert(`Finished showing ${githubUser.name}`));

7.2.4 Promise API

Promise 的调用接口是一些静态成员方法:

  • Promise.resolve(value)
  • Promise.reject(error)
  • Promise.all(promises) 等待所有的 Promise 完成后继续:
    • promises 传入一个数组
    • 如果任何一个 Promise 被拒绝了,则会返回 error 状态,并且后续的 Promise 会被 忽略
  • Promise.race(promises) 同时请求所有的 Promise,当第一个 Promise 返回后, 就相当于请求成功,进行后续操作
Promise.all([
  new Promise((resolve, reject) => {
    setTimeout(() => resolve(1), 1000)
  }),
  2, // treated as Promise.resolve(2)
  3  // treated as Promise.resolve(3)
]).then(alert); // 1, 2, 3

7.3 异步函数 async/await

  • 异步函数的定义是在普通函数前面添加 async 关键字
  • 当调用异步函数是返回一个 Promise 对象
  • 可以是函数里面使用 await 关键字
> async function f() { return 1; }
> f
[AsyncFunction: f]
> f()
Promise {
  1,
  domain:
   Domain { ... }}
> f().then(console.log)
Promise {
  <pending>,
  domain:
   Domain { ... }}
1
>
  • 在异步函数中使用 await 可以等待返回结果, let res = await promise
  • await 只能用于异步函数中
  • await 不能用于在顶层调用, 例如下面的调用时错误的

    // 错误调用方式
    let response = await  fetch('/article/promise-chaining/user.json');
    let user = await response.json();
    

7.4 RxJS

RxJS 是一个事件驱动的异步调用函数库,官网上宣称是 事件版本的 Lodash 库 , RxJS 库的核心概念如下:

  • Observable 表示可以被唤起的值和事件的数据结构
  • Observer 回调函数集合,当回调函数返回值后将结果传递给 Observable
  • Subscription 表示 Observable 的执行
  • Operator 操作符,提供函数式编程的接口
  • Subject 和事件发射器相似,用于多播异步回调函数的返回值到 Observer 对象中
  • Scheduler 并发操作的中心分发器

7.4.1 基本使用示例

Observable.fromEvent(target, eventName, options, selector) 可以将事件回调 函数绑定到 DOM 元素中

Rx.Observable.fromEvent(button, 'click')
  .subscribe(() => console.log('Clicked!'));

7.4.2 创建 Observable 对象

  • of(...items) 通过 Array-like 对象创建 Observable
  • from(iter) 通过迭代器创建 Observable
Rx.Observable.of(1,2,3);
Rx.Observable.from([1,2,3]);

7.4.3 订阅 Observable 实例

  • observable.subscribe(res => ..., err => ..., () => ...) 方法可以订阅 Observable 实例
  • subscribe 中有三个参数,第一个是正常调用的 handler;第二个是错误处理的 handler;第三个是完成后的 handler
myObservable.subscribe({
  next(num) { console.log('Next num: ' + num)},
  error(err) { console.log('Received an errror: ' + err)}
});

8 参考链接

Last Updated 2020-02-22 Sat 20:08. Created by Jinghui Hu at 2019-07-02 Tue 11:04.