ES6 速通

《阮一峰 ECMAScript 6 (ES6) 标准入门教程 第三版》 - 书栈网 · BookStack

1. 声明变量方法

var function let const import class
前面两个是es5的,后面是es6的,let const 的好处是块变量:只在声明所在的块级作用域内有效;
const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。

2. 取顶层对象

JavaScript 语言存在一个顶层对象,它提供全局环境(即全局作用域),所有代码都是在这个环境中运行。但是,顶层对象在各种实现里面是不统一的。

  • 浏览器里面,顶层对象是window,但 Node 和 Web Worker 没有window
  • 浏览器和 Web Worker 里面,self也指向顶层对象,但是 Node 没有self
  • Node 里面,顶层对象是global,但其他环境都不支持。

同一段代码为了能够在各种环境,都能取到顶层对象,现在一般是使用this变量,但是有局限性。

  • 全局环境中,this会返回顶层对象。但是,Node 模块和 ES6 模块中,this返回的是当前模块。
  • 函数里面的this,如果函数不是作为对象的方法运行,而是单纯作为函数运行,this会指向顶层对象。但是,严格模式下,这时this会返回undefined
  • 不管是严格模式,还是普通模式,new Function('return this')(),总是会返回全局对象。但是,如果浏览器用了 CSP(Content Security Policy,内容安全策略),那么evalnew Function这些方法都可能无法使用。

综上所述,很难找到一种方法,可以在所有情况下,都取到顶层对象。下面是两种勉强可以使用的方法。

  1. // 方法一

  2. (typeof window !== 'undefined'

  3. ? window

  4. : (typeof process === 'object' &&

  5.   `typeof require === 'function' &&`
    
  6.   `typeof global === 'object')`
    
  7.  `? global`
    
  8.  `: this);`
    
  9. // 方法二

  10. var getGlobal = function () {

  11. if (typeof self !== 'undefined') { return self; }

  12. if (typeof window !== 'undefined') { return window; }

  13. if (typeof global !== 'undefined') { return global; }

  14. throw new Error('unable to locate global object');

  15. };

es6采用的方式是globalThis,都可以从它拿到顶层对象

3. 变量解构

数组字符串解构按顺序

  1. let [a, b, c] = [1, 2, 3];
  2. const [a, b, c, d, e] = 'hello';

对象解构按对象名

  1. let { bar, foo } = { foo: 'aaa', bar: 'bbb' };
  2. let { foo: foo, bar: bar } = { foo: 'aaa', bar: 'bbb' };

数值和布尔值解构

  1. let {toString: s} = 123;

  2. s === Number.prototype.toString // true

  3. let {toString: s} = true;

  4. s === Boolean.prototype.toString // true

  5. let { prop: x } = undefined; // TypeError

  6. let { prop: y } = null; // TypeError

函数解构

  1. [1, undefined, 3].map((x = 'yes') => x);
  2. // [ 1, 'yes', 3 ]

结构默认值:

  1. var {x = 3} = {};

  2. x // 3

  3. var { message: msg = 'Something went wrong' } = {};

  4. msg // "Something went wrong"

这里要注意:

  1. // 错误的写法
  2. let x;
  3. {x} = {x: 1};
  4. // SyntaxError: syntax error

上面代码的写法会报错,因为 JavaScript 引擎会将{x}理解成一个代码块,从而发生语法错误。

  1. // 正确的写法
  2. let x;
  3. ({x} = {x: 1});

不能使用圆括号

(1)变量声明语句
(2)函数参数
(3)赋值语句的模式

4. 字符串拓展

模板字符串(template string)是增强版的字符串,用反引号(`)标识。它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量。

  1. $('#result').append(`
  2. There are ${basket.count} items
  3. in your basket, ${basket.onSale}
  4. are on sale!
  5. `);

模板字符串前后的空格和换行可以使用trim()来处理
` this is a template string `.trim()

“标签模板”的一个重要应用,就是过滤 HTML 字符串,防止用户输入恶意内容。

  1. let a = 5;

  2. let b = 10;

  3. tag`Hello ${ a + b } world ${ a * b }`;

  4. // 等同于

  5. tag(['Hello ', ' world ', ''], 15, 50);

5. 字符串新增方法

ES6 提供了String.fromCodePoint()方法,可以识别大于0xFFFF的字符,弥补了String.fromCharCode()方法的不足。在作用上,正好与下面的codePointAt()方法相反。

  1. String.fromCodePoint(0x20BB7)
  2. // ""
  3. String.fromCodePoint(0x78, 0x1f680, 0x79) === 'x\uD83D\uDE80y'
  4. // true

ES6 还为原生的 String 对象,提供了一个raw()方法。该方法返回一个斜杠都被转义(即斜杠前面再加一个斜杠)的字符串,往往用于模板字符串的处理方法。

  1. // `foo${1 + 2}bar`
  2. // 等同于
  3. String.raw({ raw: ['foo', 'bar'] }, 1 + 2) // "foo3bar"

语调符号和重音符号。为了表示它们,Unicode 提供了两种方法。一种是直接提供带重音符号的字符,比如Ǒ(\u01D1)。另一种是提供合成符号(combining character),即原字符与重音符号的合成,两个字符合成一个字符,比如O(\u004F)和ˇ(\u030C)合成Ǒ(\u004F\u030C)。

  1. '\u01D1'==='\u004F\u030C' //false

  2. '\u01D1'.length // 1

  3. '\u004F\u030C'.length // 2

  4. '\u01D1'.normalize() === '\u004F\u030C'.normalize()

  5. // true

传统上,JavaScript 只有indexOf方法,可以用来确定一个字符串是否包含在另一个字符串中。ES6 又提供了三种新方法。和后面两个和python一致

  • includes():返回布尔值,表示是否找到了参数字符串。
  • startsWith():返回布尔值,表示参数字符串是否在原字符串的头部。
  • endsWith():返回布尔值,表示参数字符串是否在原字符串的尾部。
  1. let s = 'Hello world!';

  2. s.startsWith('world', 6) // true

  3. s.endsWith('Hello', 5) // true

  4. s.includes('Hello', 6) // false

repeat方法返回一个新字符串,表示将原字符串重复n次。小数会向下取整,负数或者Infinity会报错

  1. 'x'.repeat(3) // "xxx"
  2. 'hello'.repeat(2) // "hellohello"
  3. 'na'.repeat(0) // ""

ES2017 引入了字符串补全长度的功能。如果某个字符串不够指定长度,会在头部或尾部补全。padStart()用于头部补全,padEnd()用于尾部补全。

  1. 'x'.padStart(5, 'ab') // 'ababx'

  2. 'x'.padStart(4, 'ab') // 'abax'

  3. 'x'.padEnd(5, 'ab') // 'xabab'

  4. 'x'.padEnd(4, 'ab') // 'xaba'

ES2019 对字符串实例新增了trimStart()trimEnd()这两个方法。它们的行为与trim()一致,trimStart()消除字符串头部的空格,trimEnd()消除尾部的空格。它们返回的都是新字符串,不会修改原始字符串。浏览器还部署了额外的两个方法,trimLeft()trimStart()的别名,trimRight()trimEnd()的别名。

  1. const s = ' abc ';

  2. s.trim() // "abc"

  3. s.trimStart() // "abc "

  4. s.trimEnd() // " abc"

matchAll()方法返回一个正则表达式在当前字符串的所有匹配,详见《正则的扩展》的一章。

6. 正则的拓展

感觉和现在的python差不多,这里要注意是exec进行调用

  1. const RE_OPT_A = /^(?a+)?$/;

  2. const matchObj = RE_OPT_A.exec('');

  3. matchObj.groups.as // undefined

  4. 'as' in matchObj.groups // true

字符串对象共有 4 个方法,可以使用正则表达式:match()replace()search()split()

ES6 将这 4 个方法,在语言内部全部调用RegExp的实例方法,从而做到所有与正则相关的方法,全都定义在RegExp对象上。

  • String.prototype.match 调用 RegExp.prototype[Symbol.match]
  • String.prototype.replace 调用 RegExp.prototype[Symbol.replace]
  • String.prototype.search 调用 RegExp.prototype[Symbol.search]
  • String.prototype.split 调用 RegExp.prototype[Symbol.split]

7. 数值的拓展

ES6 在Number对象上,新提供了Number.isFinite()Number.isNaN()两个方法。它们与传统的全局方法isFinite()isNaN()的区别在于,传统方法先调用Number()将非数值的值转为数值,再进行判断,而这两个新方法只对数值有效,Number.isFinite()对于非数值一律返回falseNumber.isNaN()只有对于NaN才返回true,非NaN一律返回false

ES6 将全局方法parseInt()parseFloat(),移植到Number对象上面,行为完全保持不变。其功能是解析string转化为int或者float

  1. Number.parseInt('12.34') // 12
  2. Number.parseFloat('123.45#') // 123.45

Number.isInteger()用来判断一个数值是否为整数。

  1. Number.isInteger(5E-324) // false
  2. Number.isInteger(5E-325) // true
    如果一个数值的绝对值小于Number.MIN_VALUE(5E-324)会被自动转为 0,上面代码中,5E-325由于值太小,会被自动转为0,因此返回true

ES6 在Number对象上面,新增一个极小的常量Number.EPSILON。根据规格,它表示 1 与大于 1 的最小浮点数之间的差。Number.EPSILON实际上是 JavaScript 能够表示的最小精度。误差如果小于这个值,就可以认为已经没有意义了,即不存在误差了。

  1. 0.1 + 0.2 === 0.3 // false

JavaScript 能够准确表示的整数范围在-2^532^53之间(不含两个端点),超过这个范围,无法精确表示这个值。ES6 引入了Number.MAX_SAFE_INTEGERNumber.MIN_SAFE_INTEGER这两个常量,用来表示这个范围的上下限。Number.isSafeInteger()则是用来判断一个整数是否落在这个范围之内。

JavaScript 所有数字都保存成 64 位浮点数,这给数值的表示带来了两大限制。一是数值的精度只能到 53 个二进制位(相当于 16 个十进制位),大于这个范围的整数,JavaScript 是无法精确表示的,这使得 JavaScript 不适合进行科学和金融方面的精确计算。二是大于或等于2的1024次方的数值,JavaScript 无法表示,会返回Infinity

  1. // 超过 53 个二进制位的数值,无法保持精度

  2. Math.pow(2, 53) === Math.pow(2, 53) + 1 // true

  3. // 超过 2 的 1024 次方的数值,无法表示

  4. Math.pow(2, 1024) // Infinity

8. 函数的拓展

如果有默认值的参数都不是尾参数。这时,无法只省略该参数,而不省略它后面的参数,除非显式输入undefined。ES2017 允许函数的最后一个参数有尾逗号(trailing comma)。

  1. // 例一

  2. function f(x = 1, y) {

  3. return [x, y];

  4. }

  5. f() // [1, undefined]

  6. f(2) // [2, undefined]

  7. f(, 1) // 报错

  8. f(undefined, 1) // [1, 1]

  9. // 例二

  10. function f(x, y = 5, z) {

  11. return [x, y, z];

  12. }

  13. f() // [undefined, 5, undefined]

  14. f(1) // [1, 5, undefined]

  15. f(1, ,2) // 报错

  16. f(1, undefined, 2) // [1, 5, 2]

指定了默认值以后,函数的length属性,将返回没有指定默认值的参数个数。也就是说,指定了默认值后,length属性将失真。

  1. (function (a) {}).length // 1
  2. (function (a = 5) {}).length // 0
  3. (function (a, b, c = 5) {}).length // 2

如果设置了默认值的参数不是尾参数,那么length属性也不再计入后面的参数了。

  1. (function (a = 0, b, c) {}).length // 0
  2. (function (a, b = 1, c) {}).length // 1

ES6 引入 rest 参数(形式为...变量名),用于获取函数的多余参数,这样就不需要使用arguments对象了。rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中。下面是一个 rest 参数代替arguments变量的例子。

  1. // arguments变量的写法

  2. function sortNumbers() {

  3. return Array.prototype.slice.call(arguments).sort();

  4. }

  5. // rest参数的写法

  6. const sortNumbers = (...numbers) => numbers.sort();

arguments对象不是数组,而是一个类似数组的对象。所以为了使用数组的方法,必须使用Array.prototype.slice.call先将其转为数组。rest 参数就不存在这个问题,它就是一个真正的数组,数组特有的方法都可以使用。下面是一个利用 rest 参数改写数组push方法的例子。

注意,rest 参数之后不能再有其他参数(即只能是最后一个参数),否则会报错。

  1. function push(array, ...items) {

  2. items.forEach(function(item) {

  3. array.push(item);

  4. console.log(item);

  5. });

  6. }

  7. var a = [];

  8. push(a, 1, 2, 3)

ES2016 做了一点修改,规定只要函数参数使用了默认值、解构赋值、或者扩展运算符,那么函数内部就不能显式设定为严格模式,否则会报错。这样规定的原因是,函数内部的严格模式,同时适用于函数体和函数参数。但是,函数执行的时候,先执行函数参数,然后再执行函数体。这样就有一个不合理的地方,只有从函数体之中,才能知道参数是否应该以严格模式执行,但是参数却应该先于函数体执行。

  1. function doSomething(a, b) {
  2. 'use strict';
  3. // code
  4. }

函数的name属性,返回该函数的函数名。

  1. const bar = function baz() {};
  2. bar.name // "baz"

箭头函数

ES6 允许使用“箭头”(=>)定义函数。

  1. var f = v => v;

  2. // 等同于

  3. var f = function (v) {

  4. return v;

  5. };

如果箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分。

  1. var f = () => 5;

  2. // 等同于

  3. var f = function () { return 5 };

  4. var sum = (num1, num2) => num1 + num2;

  5. // 等同于

  6. var sum = function(num1, num2) {

  7. return num1 + num2;

  8. };

ES6 的尾调用优化只在严格模式下开启,正常模式是无效的。递归本质上是一种循环操作。纯粹的函数式编程语言没有循环操作命令,所有的循环都用递归实现,这就是为什么尾递归对这些语言极其重要。对于其他支持“尾调用优化”的语言(比如 Lua,ES6),只需要知道循环可以用递归代替,而一旦使用递归,就最好使用尾递归。

9. 数组的拓展

扩展运算符(spread)是三个点(...)。它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列。

  1. console.log(...[1, 2, 3])

  2. // 1 2 3

  3. console.log(1, ...[2, 3, 4], 5)

  4. // 1 2 3 4 5

复制数组

  1. const a1 = [1, 2];
  2. // 写法一
  3. const a2 = [...a1];
  4. // 写法二
  5. const [...a2] = a1;

合并数组

  1. const arr1 = ['a', 'b'];

  2. const arr2 = ['c'];

  3. const arr3 = ['d', 'e'];

  4. // ES5 的合并数组

  5. arr1.concat(arr2, arr3);

  6. // [ 'a', 'b', 'c', 'd', 'e' ]

  7. // ES6 的合并数组

  8. [...arr1, ...arr2, ...arr3]

  9. // [ 'a', 'b', 'c', 'd', 'e' ]

两种转化数组的方式,array.from() array.of()

Array.from方法用于将两类对象转为真正的数组:类似数组的对象(array-like object)和可遍历(iterable)的对象(包括 ES6 新增的数据结构 Set 和 Map)。
Array.of方法用于将一组值,转换为数组。

数组实例的find方法,用于找出第一个符合条件的数组成员。它的参数是一个回调函数,所有数组成员依次执行该回调函数,直到找出第一个返回值为true的成员,然后返回该成员。如果没有符合条件的成员,则返回undefined

  1. [1, 4, -5, 10].find((n) => n < 0)
  2. // -5

上面代码找出数组中第一个小于 0 的成员。

  1. [1, 5, 10, 15].find(function(value, index, arr) {
  2. return value > 9;
  3. }) // 10

数组实例的findIndex方法的用法与find方法非常类似,返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1

  1. [1, 5, 10, 15].findIndex(function(value, index, arr) {
  2. return value > 9;
  3. }) // 2

fill方法使用给定值,填充一个数组。

  1. ['a', 'b', 'c'].fill(7)

  2. // [7, 7, 7]

  3. new Array(3).fill(7)

  4. // [7, 7, 7]

数组也是键值对组成的['a', 'b'] 相当于 {0: 'a', 1: 'b'},其键就是序号,其值是本身;

entries()keys()values()——用于遍历数组。它们都返回一个遍历器对象(详见《Iterator》一章),可以用for...of循环进行遍历,唯一的区别是keys()是对键名的遍历、values()是对键值的遍历,entries()是对键值对的遍历。

数组的成员有时还是数组,Array.prototype.flat()用于将嵌套的数组“拉平”,变成一维的数组。该方法返回一个新数组,对原数据没有影响。

  1. [1, 2, [3, [4, 5]]].flat()

  2. // [1, 2, 3, [4, 5]]

  3. [1, 2, [3, [4, 5]]].flat(2)

  4. // [1, 2, 3, 4, 5]

flatMap()方法对原数组的每个成员执行一个函数(相当于执行Array.prototype.map()),然后对返回值组成的数组执行flat()方法。该方法返回一个新数组,不改变原数组。

  1. // 相当于 [[2, 4], [3, 6], [4, 8]].flat()
  2. [2, 3, 4].flatMap((x) => [x, x * 2])
  3. // [2, 4, 3, 6, 4, 8]

10. 对象的扩展

变量foo直接写在大括号里面。这时,属性名就是变量名, 属性值就是变量值。下面是另一个例子。

  1. let birth = '2000/01/01';

  2. const Person = {

  3. name: '张三',

  4. //等同于birth: birth

  5. birth,

  6. // 等同于hello: function ()...

  7. hello() { console.log('我的名字是', this.name); }

  8. };

ES6 允许字面量定义对象时,用方法二(表达式)作为对象的属性名,即把表达式放在方括号内。

  1. let lastWord = 'last word';

  2. const a = {

  3. 'first word': 'hello',

  4. [lastWord]: 'world'

  5. };

  6. a['first word'] // "hello"

  7. a[lastWord] // "world"

  8. a['last word'] // "world"

注意,属性名表达式如果是一个对象{},默认情况下会自动将对象转为字符串[object Object],这一点要特别小心。

我们知道,this关键字总是指向函数所在的当前对象,ES6 又新增了另一个类似的关键字super,指向当前对象的原型对象。

三种super的用法都会报错,因为对于 JavaScript 引擎来说,这里的super都没有用在对象的方法之中。第一种写法是super用在属性里面,第二种和第三种写法是super用在一个函数里面,然后赋值给foo属性。目前,只有对象方法的简写法可以让 JavaScript 引擎确认,定义的是对象的方法。

  1. // 报错

  2. const obj = {

  3. foo: super.foo

  4. }

  5. // 报错

  6. const obj = {

  7. foo: () => super.foo

  8. }

  9. // 报错

  10. const obj = {

  11. foo: function () {

  12. return super.foo

  13. }

  14. }

对象的拓展运算符

  1. let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
  2. let { x, ...y, ...z } = someObject; // 句法错误
  3. let { ...x, y, z } = someObject; // 句法错误
  4. let { x, ...y, ...z } = someObject; // 句法错误

注意,解构赋值的拷贝是浅拷贝,即如果一个键的值是复合类型的值(数组、对象、函数)、那么解构赋值拷贝的是这个值的引用,而不是这个值的副本。

  1. let obj = { a: { b: 1 } };
  2. let { ...x } = obj;
  3. obj.a.b = 2;
  4. x.a.b // 2

ES2020 引入了“链判断运算符”(optional chaining operator)?.

  1. const firstName = (message

  2. && message.body

  3. && message.body.user

  4. && message.body.user.firstName) || 'default';

  5. const firstName = message?.body?.user?.firstName || 'default';

  6. const fooValue = myForm.querySelector('input[name=foo]')?.value

相等运算符(==)和严格相等运算符(===)ES6 提出“Same-value equality”(同值相等)算法,用来解决这个问题。Object.is就是部署这个算法的新方法。它用来比较两个值是否严格相等,与严格比较运算符(===)的行为基本一致。

  1. Object.is('foo', 'foo')

  2. // true

  3. Object.is({}, {})

  4. // false

  5. +0 === -0 //true

  6. NaN === NaN // false

  7. Object.is(+0, -0) // false

  8. Object.is(NaN, NaN) // true

Object.assign方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。

Object.assign方法实行的是浅拷贝,而不是深拷贝。也就是说,如果源对象某个属性的值是对象,那么目标对象拷贝得到的是这个对象的引用。

  1. const obj1 = {a: {b: 1}};

  2. const obj2 = Object.assign({}, obj1);

  3. obj1.a.b = 2;

  4. obj2.a.b // 2

对于这种嵌套的对象,一旦遇到同名属性,Object.assign的处理方法是替换,而不是添加。

  1. const target = { a: { b: 'c', d: 'e' } }
  2. const source = { a: { b: 'hello' } }
  3. Object.assign(target, source)
  4. // { a: { b: 'hello' } }

Object.assign可以用来处理数组,但是会把数组视为对象。

Object.assign只能进行值的复制,如果要复制的值是一个取值函数,那么将求值后再复制。

  1. const source = {

  2. get foo() { return 1 }

  3. };

  4. const target = {};

  5. Object.assign(target, source)

  6. // { foo: 1 }

ES2017 引入了Object.getOwnPropertyDescriptors()方法,返回指定对象所有自身属性(非继承属性)的描述对象。

  1. const obj = {

  2. foo: 123,

  3. get bar() { return 'abc' }

  4. };

  5. Object.getOwnPropertyDescriptors(obj)

  6. // { foo:

  7. // { value: 123,

  8. // writable: true,

  9. // enumerable: true,

  10. // configurable: true },

  11. // bar:

  12. // { get: [Function: get bar],

  13. // set: undefined,

  14. // enumerable: true,

  15. // configurable: true } }

__proto__属性(前后各两个下划线),用来读取或设置当前对象的原型对象(prototype)。目前,所有浏览器(包括 IE11)都部署了这个属性。但是不推荐使用,实现上,__proto__调用的是Object.prototype.__proto__,具体实现如下。

Object.setPrototypeOf方法的作用与__proto__相同,用来设置一个对象的原型对象(prototype),返回参数对象本身。它是 ES6 正式推荐的设置原型对象的方法。

Object.getPrototypeOf()该方法与Object.setPrototypeOf方法配套,用于读取一个对象的原型对象。

Object.keys(),Object.values(),Object.entries() 作为遍历一个对象的补充手段,供for...of循环使用。

Object.fromEntries()方法是Object.entries()的逆操作,用于将一个键值对数组转为对象。

12. Symbol

ES6 引入了一种新的原始数据类型Symbol,表示独一无二的值。它是 JavaScript 语言的第七种数据类型,前六种是:undefinednull、布尔值(Boolean)、字符串(String)、数值(Number)、对象(Object)。

Symbol 作为属性名,遍历对象的时候,该属性不会出现在for...infor...of循环中,也不会被Object.keys()Object.getOwnPropertyNames()JSON.stringify()返回。

但是,它也不是私有属性,有一个Object.getOwnPropertySymbols()方法,可以获取指定对象的所有 Symbol 属性名。该方法返回一个数组,成员是当前对象的所有用作属性名的 Symbol 值。

  1. const obj = {};

  2. const foo = Symbol('foo');

  3. obj[foo] = 'bar';

  4. for (let i in obj) {

  5. console.log(i); // 无输出

  6. }

  7. Object.getOwnPropertyNames(obj) // []

  8. Object.getOwnPropertySymbols(obj) // [Symbol(foo)]

13. Set 和 Map 数据结构

Set 结构的实例有以下属性。

  • Set.prototype.constructor:构造函数,默认就是Set函数。
  • Set.prototype.size:返回Set实例的成员总数。

Set 实例的方法分为两大类:操作方法(用于操作数据)和遍历方法(用于遍历成员)。下面先介绍四个操作方法。

  • Set.prototype.add(value):添加某个值,返回 Set 结构本身。
  • Set.prototype.delete(value):删除某个值,返回一个布尔值,表示删除是否成功。
  • Set.prototype.has(value):返回一个布尔值,表示该值是否为Set的成员。
  • Set.prototype.clear():清除所有成员,没有返回值。

keys方法、values方法、entries方法返回的都是遍历器对象(详见《Iterator 对象》一章)。由于 Set 结构没有键名,只有键值(或者说键名和键值是同一个值),所以keys方法和values方法的行为完全一致。

WeakSet 结构与 Set 类似,也是不重复的值的集合。但是,它与 Set 有两个区别。首先,WeakSet 的成员只能是对象,而不能是其他类型的值。其次,WeakSet 中的对象都是弱引用,即垃圾回收机制不考虑 WeakSet 对该对象的引用,也就是说,如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象还存在于 WeakSet 之中。

Map是JavaScript 的对象(Object),本质上是键值对的集合(Hash 结构),但是传统上只能用字符串当作键。这给它的使用带来了很大的限制。
Map 结构的实例有以下属性和操作方法。

size属性返回 Map 结构的成员总数。

set方法设置键名key对应的键值为value,然后返回整个 Map 结构。如果key已经有值,则键值会被更新,否则就新生成该键。

set方法返回的是当前的Map对象,因此可以采用链式写法。

get方法读取key对应的键值,如果找不到key,返回undefined

has方法返回一个布尔值,表示某个键是否在当前 Map 对象之中。

delete方法删除某个键,返回true。如果删除失败,返回false

clear方法清除所有成员,没有返回值。

  • Map.prototype.keys():返回键名的遍历器。
  • Map.prototype.values():返回键值的遍历器。
  • Map.prototype.entries():返回所有成员的遍历器。
  • Map.prototype.forEach():遍历 Map 的所有成员。

(1)Map 转为数组

前面已经提过,Map 转为数组最方便的方法,就是使用扩展运算符(...)。

  1. const myMap = new Map()
  2. .set(true, 7)
  3. .set({foo: 3}, ['abc']);
  4. [...myMap]
  5. // [ [ true, 7 ], [ { foo: 3 }, [ 'abc' ] ] ]

(2)数组 转为 Map

将数组传入 Map 构造函数,就可以转为 Map。

  1. new Map([
  2. [true, 7],
  3. [{foo: 3}, ['abc']]
  4. ])
  5. // Map {
  6. // true => 7,
  7. // Object {foo: 3} => ['abc']
  8. // }

(3)Map 转为对象

如果所有 Map 的键都是字符串,它可以无损地转为对象。

  1. function strMapToObj(strMap) {

  2. let obj = Object.create(null);

  3. for (let [k,v] of strMap) {

  4. `obj[k] = v;`
    
  5. }

  6. return obj;

  7. }

  8. const myMap = new Map()

  9. .set('yes', true)

  10. .set('no', false);

  11. strMapToObj(myMap)

  12. // { yes: true, no: false }

如果有非字符串的键名,那么这个键名会被转成字符串,再作为对象的键名。

(4)对象转为 Map

对象转为 Map 可以通过Object.entries()

  1. let obj = {"a":1, "b":2};
  2. let map = new Map(Object.entries(obj));

此外,也可以自己实现一个转换函数。

  1. function objToStrMap(obj) {

  2. let strMap = new Map();

  3. for (let k of Object.keys(obj)) {

  4. `strMap.set(k, obj[k]);`
    
  5. }

  6. return strMap;

  7. }

  8. objToStrMap({yes: true, no: false})

  9. // Map {"yes" => true, "no" => false}

(5)Map 转为 JSON

Map 转为 JSON 要区分两种情况。一种情况是,Map 的键名都是字符串,这时可以选择转为对象 JSON。

  1. function strMapToJson(strMap) {

  2. return JSON.stringify(strMapToObj(strMap));

  3. }

  4. let myMap = new Map().set('yes', true).set('no', false);

  5. strMapToJson(myMap)

  6. // '{"yes":true,"no":false}'

另一种情况是,Map 的键名有非字符串,这时可以选择转为数组 JSON。

  1. function mapToArrayJson(map) {

  2. return JSON.stringify([...map]);

  3. }

  4. let myMap = new Map().set(true, 7).set({foo: 3}, ['abc']);

  5. mapToArrayJson(myMap)

  6. // '[[true,7],[{"foo":3},["abc"]]]'

(6)JSON 转为 Map

JSON 转为 Map,正常情况下,所有键名都是字符串。

  1. function jsonToStrMap(jsonStr) {

  2. return objToStrMap(JSON.parse(jsonStr));

  3. }

  4. jsonToStrMap('{"yes": true, "no": false}')

  5. // Map {'yes' => true, 'no' => false}

但是,有一种特殊情况,整个 JSON 就是一个数组,且每个数组成员本身,又是一个有两个成员的数组。这时,它可以一一对应地转为 Map。这往往是 Map 转为数组 JSON 的逆操作。

  1. function jsonToMap(jsonStr) {

  2. return new Map(JSON.parse(jsonStr));

  3. }

  4. jsonToMap('[[true,7],[{"foo":3},["abc"]]]')

  5. // Map {true => 7, Object {foo: 3} => ['abc']}

WeakMap结构与Map结构类似,也是用于生成键值对的集合。WeakMapMap的区别有两点。首先,WeakMap只接受对象作为键名(null除外),不接受其他类型的值作为键名。其次,WeakMap的键名所指向的对象,不计入垃圾回收机制。

和weakset一样

14. Proxy

Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。

下面是 Proxy 支持的拦截操作一览,一共 13 种。

  • get(target, propKey, receiver):拦截对象属性的读取,比如proxy.fooproxy['foo']
  • set(target, propKey, value, receiver):拦截对象属性的设置,比如proxy.foo = vproxy['foo'] = v,返回一个布尔值。
  • has(target, propKey):拦截propKey in proxy的操作,返回一个布尔值。
  • deleteProperty(target, propKey):拦截delete proxy[propKey]的操作,返回一个布尔值。
  • ownKeys(target):拦截Object.getOwnPropertyNames(proxy)Object.getOwnPropertySymbols(proxy)Object.keys(proxy)for...in循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而Object.keys()的返回结果仅包括目标对象自身的可遍历属性。
  • getOwnPropertyDescriptor(target, propKey):拦截Object.getOwnPropertyDescriptor(proxy, propKey),返回属性的描述对象。
  • defineProperty(target, propKey, propDesc):拦截Object.defineProperty(proxy, propKey, propDesc)Object.defineProperties(proxy, propDescs),返回一个布尔值。
  • preventExtensions(target):拦截Object.preventExtensions(proxy),返回一个布尔值。
  • getPrototypeOf(target):拦截Object.getPrototypeOf(proxy),返回一个对象。
  • isExtensible(target):拦截Object.isExtensible(proxy),返回一个布尔值。
  • setPrototypeOf(target, proto):拦截Object.setPrototypeOf(proxy, proto),返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截。
  • apply(target, object, args):拦截 Proxy 实例作为函数调用的操作,比如proxy(...args)proxy.call(object, ...args)proxy.apply(...)
  • construct(target, args):拦截 Proxy 实例作为构造函数调用的操作,比如new proxy(...args)
  1. var person = {

  2. name: "张三"

  3. };

  4. var proxy = new Proxy(person, {

  5. get: function(target, propKey) {

  6. if (propKey in target) {

  7. return target[propKey];

  8. } else {

  9. throw new ReferenceError("Prop name \"" + propKey + "\" does not exist.");

  10. }

  11. }

  12. });

  13. proxy.name // "张三"

  14. proxy.age // 抛出一个错误

Proxy.revocable()方法返回一个可取消的 Proxy 实例。

Proxy.revocable()方法返回一个对象,该对象的proxy属性是Proxy实例,revoke属性是一个函数,可以取消Proxy实例。上面代码中,当执行revoke函数之后,再访问Proxy实例,就会抛出一个错误。

Proxy.revocable()的一个使用场景是,目标对象不允许直接访问,必须通过代理访问,一旦访问结束,就收回代理权,不允许再次访问。

15. Reflect

Reflect对象与Proxy对象一样,也是 ES6 为了操作对象而提供的新 API。Reflect对象的设计目的有这样几个。

(1) 将Object对象的一些明显属于语言内部的方法(比如Object.defineProperty),放到Reflect对象上。现阶段,某些方法同时在ObjectReflect对象上部署,未来的新方法将只部署在Reflect对象上。也就是说,从Reflect对象上可以拿到语言内部的方法。

(2) 修改某些Object方法的返回结果,让其变得更合理。比如,Object.defineProperty(obj, name, desc)在无法定义属性时,会抛出一个错误,而Reflect.defineProperty(obj, name, desc)则会返回false

(3) 让Object操作都变成函数行为。某些Object操作是命令式,比如name in objdelete obj[name],而Reflect.has(obj, name)Reflect.deleteProperty(obj, name)让它们变成了函数行为。

(4)Reflect对象的方法与Proxy对象的方法一一对应,只要是Proxy对象的方法,就能在Reflect对象上找到对应的方法。这就让Proxy对象可以方便地调用对应的Reflect方法,完成默认行为,作为修改行为的基础。也就是说,不管Proxy怎么修改默认行为,你总可以在Reflect上获取默认行为。

Reflect对象一共有 13 个静态方法。

  • Reflect.apply(target, thisArg, args)
  • Reflect.construct(target, args)
  • Reflect.get(target, name, receiver)
  • Reflect.set(target, name, value, receiver)
  • Reflect.defineProperty(target, name, desc)
  • Reflect.deleteProperty(target, name)
  • Reflect.has(target, name)
  • Reflect.ownKeys(target)
  • Reflect.isExtensible(target)
  • Reflect.preventExtensions(target)
  • Reflect.getOwnPropertyDescriptor(target, name)
  • Reflect.getPrototypeOf(target)
  • Reflect.setPrototypeOf(target, prototype)

上面这些方法的作用,大部分与Object对象的同名方法的作用都是相同的,而且它与Proxy对象的方法是一一对应的。下面是对它们的解释。

16. Promise

Promise对象有以下两个特点。

(1)对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。

(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。

我们可以将图片的加载写成一个Promise,一旦加载完成,Promise的状态就发生变化。

  1. const preloadImage = function (path) {
  2. return new Promise(function (resolve, reject) {
  3. const image = new Image();
  4. image.onload = resolve;
  5. image.onerror = reject;
  6. image.src = path;
  7. });
  8. };

同时要注意:

  1. setTimeout(function () {

  2. console.log('three');

  3. }, 0);

  4. Promise.resolve().then(function () {

  5. console.log('two');

  6. });

  7. console.log('one');

  8. // one

  9. // two

  10. // three

上面代码中,setTimeout(fn, 0)在下一轮“事件循环”开始时执行,Promise.resolve()在本轮“事件循环”结束时执行,console.log('one')则是立即执行,因此最先输出。

实际开发中,经常遇到一种情况:不知道或者不想区分,函数f是同步函数还是异步操作,但是想用 Promise 来处理它。因为这样就可以不管f是否包含异步操作,都用then方法指定下一步流程,用catch方法处理f抛出的错误。一般就会采用下面的写法。

由于Promise.try为所有操作提供了统一的处理机制,所以如果想用then方法管理流程,最好都用Promise.try包装一下,其中一点就是可以更好地管理异常。

  1. Promise.try(() => database.users.get({id: userId}))
  2. .then(...)
  3. .catch(...)

基本操作

ES6 规定,Promise对象是一个构造函数,用来生成Promise实例。

下面代码创造了一个Promise实例。

  1. const promise = new Promise(function(resolve, reject) {

  2. // ... some code

  3. if (/* 异步操作成功 */){

  4. `resolve(value);`
    
  5. } else {

  6. `reject(error);`
    
  7. }

  8. });

Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolvereject。它们是两个函数,由 JavaScript 引擎提供,不用自己部署。

resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。

Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数。

  1. promise.then(function(value) {
  2. // success
  3. }, function(error) {
  4. // failure
  5. });

then方法可以接受两个回调函数作为参数。第一个回调函数是Promise对象的状态变为resolved时调用,第二个回调函数是Promise对象的状态变为rejected时调用。其中,第二个函数是可选的,不一定要提供。这两个函数都接受Promise对象传出的值作为参数。

  1. getJSON("/post/1.json").then(
  2. post => getJSON(post.commentURL)
  3. ).then(
  4. comments => console.log("resolved: ", comments),
  5. err => console.log("rejected: ", err)
  6. );

Promise.prototype.catch()方法是.then(null, rejection).then(undefined, rejection)的别名,用于指定发生错误时的回调函数。

  1. getJSON('/posts.json').then(function(posts) {
  2. // ...
  3. }).catch(function(error) {
  4. // 处理 getJSON 和 前一个回调函数运行时发生的错误
  5. console.log('发生错误!', error);
  6. });

上面代码中,getJSON()方法返回一个 Promise 对象,如果该对象状态变为resolved,则会调用then()方法指定的回调函数;如果异步操作抛出错误,状态就会变为rejected,就会调用catch()方法指定的回调函数,处理这个错误。另外,then()方法指定的回调函数,如果运行中抛出错误,也会被catch()方法捕获。

  1. p.then((val) => console.log('fulfilled:', val))

  2. .catch((err) => console.log('rejected', err));

  3. // 等同于

  4. p.then((val) => console.log('fulfilled:', val))

  5. .then(null, (err) => console.log("rejected:", err));

finally()方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。该方法是 ES2018 引入标准的。

  1. promise
  2. .then(result => {···})
  3. .catch(error => {···})
  4. .finally(() => {···});

上面代码中,不管promise最后的状态,在执行完thencatch指定的回调函数以后,都会执行finally方法指定的回调函数。

下面是一个例子,服务器使用 Promise 处理请求,然后使用finally方法关掉服务器。

  1. server.listen(port)
  2. .then(function () {
  3. `// ...`
    
  4. })
  5. .finally(server.stop);

finally方法的回调函数不接受任何参数,这意味着没有办法知道,前面的 Promise 状态到底是fulfilled还是rejected。这表明,finally方法里面的操作,应该是与状态无关的,不依赖于 Promise 的执行结果。

Promise.all()方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。

  1. const p = Promise.all([p1, p2, p3]);

上面代码中,Promise.all()方法接受一个数组作为参数,p1p2p3都是 Promise 实例,如果不是,就会先调用下面讲到的Promise.resolve方法,将参数转为 Promise 实例,再进一步处理。另外,Promise.all()方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。

p的状态由p1p2p3决定,分成两种情况。

(1)只有p1p2p3的状态都变成fulfilledp的状态才会变成fulfilled,此时p1p2p3的返回值组成一个数组,传递给p的回调函数。

(2)只要p1p2p3之中有一个被rejectedp的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。

Promise.race()方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。

复制代码

  1. const p = Promise.race([p1, p2, p3]);

上面代码中,只要p1p2p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。

Promise.race()方法的参数与Promise.all()方法一样,如果不是 Promise 实例,就会先调用下面讲到的Promise.resolve()方法,将参数转为 Promise 实例,再进一步处理。

下面是一个例子,如果指定时间内没有获得结果,就将 Promise 的状态变为reject,否则变为resolve

  1. const p = Promise.race([

  2. fetch('/resource-that-may-take-a-while'),

  3. new Promise(function (resolve, reject) {

  4. `setTimeout(() => reject(new Error('request timeout')), 5000)`
    
  5. })

  6. ]);

  7. p

  8. .then(console.log)

  9. .catch(console.error);

上面代码中,如果 5 秒之内fetch方法无法返回结果,变量p的状态就会变为rejected,从而触发catch方法指定的回调函数。

Promise.allSettled()

Promise.allSettled()方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。只有等到所有这些参数实例都返回结果,不管是fulfilled还是rejected,包装实例才会结束。该方法由 ES2020 引入。

  1. const promises = [

  2. fetch('/api-1'),

  3. fetch('/api-2'),

  4. fetch('/api-3'),

  5. ];

  6. await Promise.allSettled(promises);

  7. removeLoadingIndicator();

上面代码对服务器发出三个请求,等到三个请求都结束,不管请求成功还是失败,加载的滚动图标就会消失。

该方法返回的新的 Promise 实例,一旦结束,状态总是fulfilled,不会变成rejected。状态变成fulfilled后,Promise 的监听函数接收到的参数是一个数组,每个成员对应一个传入Promise.allSettled()的 Promise 实例。

  1. const resolved = Promise.resolve(42);

  2. const rejected = Promise.reject(-1);

  3. const allSettledPromise = Promise.allSettled([resolved, rejected]);

  4. allSettledPromise.then(function (results) {

  5. console.log(results);

  6. });

  7. // [

  8. // { status: 'fulfilled', value: 42 },

  9. // { status: 'rejected', reason: -1 }

  10. // ]

上面代码中,Promise.allSettled()的返回值allSettledPromise,状态只可能变成fulfilled。它的监听函数接收到的参数是数组results。该数组的每个成员都是一个对象,对应传入Promise.allSettled()的两个 Promise 实例。每个对象都有status属性,该属性的值只可能是字符串fulfilled或字符串rejectedfulfilled时,对象有value属性,rejected时有reason属性,对应两种状态的返回值。

下面是返回值用法的例子。

  1. const promises = [ fetch('index.html'), fetch('https://does-not-exist/') ];

  2. const results = await Promise.allSettled(promises);

  3. // 过滤出成功的请求

  4. const successfulPromises = results.filter(p => p.status === 'fulfilled');

  5. // 过滤出失败的请求,并输出原因

  6. const errors = results

  7. .filter(p => p.status === 'rejected')

  8. .map(p => p.reason);

有时候,我们不关心异步操作的结果,只关心这些操作有没有结束。这时,Promise.allSettled()方法就很有用。如果没有这个方法,想要确保所有操作都结束,就很麻烦。Promise.all()方法无法做到这一点。

  1. const urls = [ /* ... */ ];

  2. const requests = urls.map(x => fetch(x));

  3. try {

  4. await Promise.all(requests);

  5. console.log('所有请求都成功。');

  6. } catch {

  7. console.log('至少一个请求失败,其他请求可能还没结束。');

  8. }

上面代码中,Promise.all()无法确定所有请求都结束。想要达到这个目的,写起来很麻烦,有了Promise.allSettled(),这就很容易了。

Promise.any()方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。只要参数实例有一个变成fulfilled状态,包装实例就会变成fulfilled状态;如果所有参数实例都变成rejected状态,包装实例就会变成rejected状态。该方法目前是一个第三阶段的提案 。

Promise.any()Promise.race()方法很像,只有一点不同,就是不会因为某个 Promise 变成rejected状态而结束。

  1. var resolved = Promise.resolve(42);

  2. var rejected = Promise.reject(-1);

  3. var alsoRejected = Promise.reject(Infinity);

  4. Promise.any([resolved, rejected, alsoRejected]).then(function (result) {

  5. console.log(result); // 42

  6. });

  7. Promise.any([rejected, alsoRejected]).catch(function (results) {

  8. console.log(results); // [-1, Infinity]

  9. });

17. Iterator 和 for … of 循环

扩展运算符

只要某个数据结构部署了 Iterator 接口,就可以对它使用扩展运算符,将其转为数组。

  1. let arr = [...iterable];

yield

yield*后面跟的是一个可遍历的结构,它会调用该结构的遍历器接口。

  1. let generator = function* () {

  2. yield 1;

  3. yield* [2,3,4];

  4. yield 5;

  5. };

  6. var iterator = generator();

  7. iterator.next() // { value: 1, done: false }

  8. iterator.next() // { value: 2, done: false }

  9. iterator.next() // { value: 3, done: false }

  10. iterator.next() // { value: 4, done: false }

  11. iterator.next() // { value: 5, done: false }

  12. iterator.next() // { value: undefined, done: true }

其他场合

由于数组的遍历会调用遍历器接口,所以任何接受数组作为参数的场合,其实都调用了遍历器接口。下面是一些例子。

  • for…of
  • Array.from()
  • Map(), Set(), WeakMap(), WeakSet()(比如new Map([['a',1],['b',2]])
  • Promise.all()
  • Promise.race()

generator 函数

  1. let myIterable = {

  2. [Symbol.iterator]: function* () {

  3. yield 1;

  4. yield 2;

  5. yield 3;

  6. }

  7. }

  8. [...myIterable] // [1, 2, 3]

  9. // 或者采用下面的简洁写法

  10. let obj = {

  11. * [Symbol.iterator]() {

  12. yield 'hello';

  13. yield 'world';

  14. }

  15. };

  16. for (let x of obj) {

  17. console.log(x);

  18. }

  19. // "hello"

  20. // "world"

遍历器对象除了具有next方法,还可以具有return方法和throw方法。如果你自己写遍历器对象生成函数,那么next方法是必须部署的,return方法和throw方法是否部署是可选的。

return方法的使用场合是,如果for...of循环提前退出(通常是因为出错,或者有break语句),就会调用return方法。如果一个对象在完成遍历前,需要清理或释放资源,就可以部署return方法。

  1. function readLinesSync(file) {

  2. return {

  3. `[Symbol.iterator]() {`
    
  4.   `return {`
    
  5.     `next() {`
    
  6.       `return { done: false };`
    
  7.     `},`
    
  8.     `return() {`
    
  9.       `file.close();`
    
  10.       `return { done: true };`
    
  11.     `}`
    
  12.   `};`
    
  13. `},`
    
  14. };

  15. }
    上面代码中,函数readLinesSync接受一个文件对象作为参数,返回一个遍历器对象,其中除了next方法,还部署了return方法。下面的两种情况,都会触发执行return方法。

  16. // 情况一

  17. for (let line of readLinesSync(fileName)) {

  18. console.log(line);

  19. break;

  20. }

  21. // 情况二

  22. for (let line of readLinesSync(fileName)) {

  23. console.log(line);

  24. throw new Error();

  25. }

18. Generator 函数

函数的写法如下:

  1. function* foo(x, y) { ··· }

yield 表达式

由于 Generator 函数返回的遍历器对象,只有调用next方法才会遍历下一个内部状态,所以其实提供了一种可以暂停执行的函数。yield表达式就是暂停标志。

遍历器对象的next方法的运行逻辑如下。

(1)遇到yield表达式,就暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回的对象的value属性值。

(2)下一次调用next方法时,再继续往下执行,直到遇到下一个yield表达式。

(3)如果没有再遇到新的yield表达式,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值,作为返回的对象的value属性值。

(4)如果该函数没有return语句,则返回的对象的value属性值为undefined

需要注意的是,yield表达式后面的表达式,只有当调用next方法、内部指针指向该语句时才会执行,因此等于为 JavaScript 提供了手动的“惰性求值”(Lazy Evaluation)的语法功能。

复制代码

  1. function* gen() {
  2. yield 123 + 456;
  3. }

上面代码中,yield后面的表达式123 + 456,不会立即求值,只会在next方法将指针移到这一句时,才会求值。

yield表达式与return语句既有相似之处,也有区别。相似之处在于,都能返回紧跟在语句后面的那个表达式的值。区别在于每次遇到yield,函数暂停执行,下一次再从该位置继续向后执行,而return语句不具备位置记忆的功能。一个函数里面,只能执行一次(或者说一个)return语句,但是可以执行多次(或者说多个)yield表达式。正常函数只能返回一个值,因为只能执行一次return;Generator 函数可以返回一系列的值,因为可以有任意多个yield。从另一个角度看,也可以说 Generator 生成了一系列的值,这也就是它的名称的来历(英语中,generator 这个词是“生成器”的意思)。

Generator 函数可以不用yield表达式,这时就变成了一个单纯的暂缓执行函数。

  1. function* f() {

  2. console.log('执行了!')

  3. }

  4. var generator = f();

  5. setTimeout(function () {

  6. generator.next()

  7. }, 2000);

上面代码中,函数f如果是普通函数,在为变量generator赋值时就会执行。但是,函数f是一个 Generator 函数,就变成只有调用next方法时,函数f才会执行。

另外需要注意,yield表达式只能用在 Generator 函数里面,用在其他地方都会报错。

Generator 是实现状态机的最佳结构。比如,下面的clock函数就是一个状态机。

  1. var ticking = true;
  2. var clock = function() {
  3. if (ticking)
  4. `console.log('Tick!');`
    
  5. else
  6. `console.log('Tock!');`
    
  7. ticking = !ticking;
  8. }

上面代码的clock函数一共有两种状态(TickTock),每运行一次,就改变一次状态。这个函数如果用 Generator 实现,就是下面这样。

  1. var clock = function* () {
  2. while (true) {
  3. `console.log('Tick!');`
    
  4. `yield;`
    
  5. `console.log('Tock!');`
    
  6. `yield;`
    
  7. }
  8. };

18. Generator 函数的语法 - 应用 - 《阮一峰 ECMAScript 6 (ES6) 标准入门教程 第三版》 - 书栈网 · BookStack

generator 控制流

  1. scheduler(longRunningTask(initialValue));

  2. function scheduler(task) {

  3. var taskObj = task.next(task.value);

  4. // 如果Generator函数未结束,就继续调用

  5. if (!taskObj.done) {

  6. task.value = taskObj.value

  7. scheduler(task);

  8. }

  9. }

  10. let steps = [step1Func, step2Func, step3Func];

  11. function* iterateSteps(steps){

  12. for (var i=0; i< steps.length; i++){

  13. var step = steps[i];

  14. yield step();

  15. }

  16. }

for … of 无法遍历return 对象

for...of循环可以自动遍历 Generator 函数运行时生成的Iterator对象,且此时不再需要调用next方法。

  1. function* foo() {

  2. yield 1;

  3. yield 2;

  4. yield 3;

  5. yield 4;

  6. yield 5;

  7. return 6;

  8. }

  9. for (let v of foo()) {

  10. console.log(v);

  11. }

  12. // 1 2 3 4 5

上面代码使用for...of循环,依次显示 5 个yield表达式的值。这里需要注意,一旦next方法的返回对象的done属性为truefor...of循环就会中止,且不包含该返回对象,所以上面代码的return语句返回的6,不包括在for...of循环之中。

Generator 函数返回的遍历器对象,还有一个return方法,可以返回给定的值,并且终结遍历 Generator 函数。

  1. function* numbers () {
  2. yield 1;
  3. try {
  4. yield 2;
  5. yield 3;
  6. } finally {
  7. yield 4;
  8. yield 5;
  9. }
  10. yield 6;
  11. }
  12. var g = numbers();
  13. g.next() // { value: 1, done: false }
  14. g.next() // { value: 2, done: false }
  15. g.return(7) // { value: 4, done: false }
  16. g.next() // { value: 5, done: false }
  17. g.next() // { value: 7, done: true }

上面代码中,调用return()方法后,就开始执行finally代码块,不执行try里面剩下的代码了,然后等到finally代码块执行完,再返回return()方法指定的返回值。

next()throw()return()这三个方法本质上是同一件事,可以放在一起理解。它们的作用都是让 Generator 函数恢复执行,并且使用不同的语句替换yield表达式。

next()是将yield表达式替换成一个值。

  1. const g = function* (x, y) {

  2. let result = yield x + y;

  3. return result;

  4. };

  5. const gen = g(1, 2);

  6. gen.next(); // Object {value: 3, done: false}

  7. gen.next(1); // Object {value: 1, done: true}

  8. // 相当于将 let result = yield x + y

  9. // 替换成 let result = 1;

上面代码中,第二个next(1)方法就相当于将yield表达式替换成一个值1。如果next方法没有参数,就相当于替换成undefined

throw()是将yield表达式替换成一个throw语句。

  1. gen.throw(new Error('出错了')); // Uncaught Error: 出错了
  2. // 相当于将 let result = yield x + y
  3. // 替换成 let result = throw(new Error('出错了'));

return()是将yield表达式替换成一个return语句。

  1. gen.return(2); // Object {value: 2, done: true}
  2. // 相当于将 let result = yield x + y
  3. // 替换成 let result = return 2;

yield*表达式,用来在一个 Generator 函数里面执行另一个 Generator 函数。

yield*命令可以很方便地取出嵌套数组的所有成员。

复制代码

  1. function* iterTree(tree) {

  2. if (Array.isArray(tree)) {

  3. `for(let i=0; i < tree.length; i++) {`
    
  4.   `yield* iterTree(tree[i]);`
    
  5. `}`
    
  6. } else {

  7. `yield tree;`
    
  8. }

  9. }

  10. const tree = [ 'a', ['b', 'c'], ['d', 'e'] ];

  11. for(let x of iterTree(tree)) {

  12. console.log(x);

  13. }

  14. // a

  15. // b

  16. // c

  17. // d

  18. // e

由于扩展运算符...默认调用 Iterator 接口,所以上面这个函数也可以用于嵌套数组的平铺。

  1. [...iterTree(tree)] // ["a", "b", "c", "d", "e"]

this 结合 generator :

  1. function* F() {

  2. this.a = 1;

  3. yield this.b = 2;

  4. yield this.c = 3;

  5. }

  6. var f = F.call(F.prototype);

  7. f.next(); // Object {value: 2, done: false}

  8. f.next(); // Object {value: 3, done: false}

  9. f.next(); // Object {value: undefined, done: true}

  10. f.a // 1

  11. f.b // 2

  12. f.c // 3

19. Generator 函数的异步应用

Generator 函数可以暂停执行和恢复执行,这是它能封装异步任务的根本原因。除此之外,它还有两个特性,使它可以作为异步编程的完整解决方案:函数体内外的数据交换和错误处理机制。

下面看看如何使用 Generator 函数,执行一个真实的异步任务。

  1. var fetch = require('node-fetch');

  2. function* gen(){

  3. var url = 'https://api.github.com/users/github';

  4. var result = yield fetch(url);

  5. console.log(result.bio);

  6. }

上面代码中,Generator 函数封装了一个异步操作,该操作先读取一个远程接口,然后从 JSON 格式的数据解析信息。就像前面说过的,这段代码非常像同步操作,除了加上了yield命令。

执行这段代码的方法如下。

  1. var g = gen();

  2. var result = g.next();

  3. result.value.then(function(data){

  4. return data.json();

  5. }).then(function(data){

  6. g.next(data);

  7. });

上面代码中,首先执行 Generator 函数,获取遍历器对象,然后使用next方法(第二行),执行异步任务的第一阶段。由于Fetch模块返回的是一个 Promise 对象,因此要用then方法调用下一个next方法。

Thunk 函数是自动执行 Generator 函数的一种方法。

传值调用和传名调用

”传值调用”(call by value),即在进入函数体之前,就计算x + 5的值(等于 6),再将这个值传入函数f。C 语言就采用这种策略。
“传名调用”(call by name),即直接将表达式x + 5传入函数体,只在用到它的时候求值。Haskell 语言采用这种策略。

Thunk 函数真正的威力,在于可以自动执行 Generator 函数。下面就是一个基于 Thunk 函数的 Generator 执行器。

  1. function run(fn) {

  2. var gen = fn();

  3. function next(err, data) {

  4. `var result = gen.next(data);`
    
  5. `if (result.done) return;`
    
  6. `result.value(next);`
    
  7. }

  8. next();

  9. }

  10. function* g() {

  11. // ...

  12. }

  13. run(g);

上面代码的run函数,就是一个 Generator 函数的自动执行器。内部的next函数就是 Thunk 的回调函数。next函数先将指针移到 Generator 函数的下一步(gen.next方法),然后判断 Generator 函数是否结束(result.done属性),如果没结束,就将next函数再传入 Thunk 函数(result.value属性),否则就直接退出。

co 模块是著名程序员 TJ Holowaychuk 于 2013 年 6 月发布的一个小工具,用于 Generator 函数的自动执行。

下面是一个 Generator 函数,用于依次读取两个文件。

  1. var gen = function* () {
  2. var f1 = yield readFile('/etc/fstab');
  3. var f2 = yield readFile('/etc/shells');
  4. console.log(f1.toString());
  5. console.log(f2.toString());
  6. };

co 模块可以让你不用编写 Generator 函数的执行器。

  1. var co = require('co');
  2. co(gen);

co就是把对象转化为promise对象如何层层then

20. async 函数

async 函数是什么?一句话,它就是 Generator 函数的语法糖。

前文有一个 Generator 函数,依次读取两个文件。

  1. const fs = require('fs');

  2. const readFile = function (fileName) {

  3. return new Promise(function (resolve, reject) {

  4. `fs.readFile(fileName, function(error, data) {`
    
  5.   `if (error) return reject(error);`
    
  6.   `resolve(data);`
    
  7. `});`
    
  8. });

  9. };

  10. const gen = function* () {

  11. const f1 = yield readFile('/etc/fstab');

  12. const f2 = yield readFile('/etc/shells');

  13. console.log(f1.toString());

  14. console.log(f2.toString());

  15. };

上面代码的函数gen可以写成async函数,就是下面这样。

  1. const asyncReadFile = async function () {
  2. const f1 = await readFile('/etc/fstab');
  3. const f2 = await readFile('/etc/shells');
  4. console.log(f1.toString());
  5. console.log(f2.toString());
  6. };

一比较就会发现,async函数就是将 Generator 函数的星号(*)替换成async,将yield替换成await,仅此而已。

ad
async函数对 Generator 函数的改进,体现在以下四点。

(1)内置执行器。

Generator 函数的执行必须靠执行器,所以才有了co模块,而async函数自带执行器。也就是说,async函数的执行,与普通函数一模一样,只要一行。

  1. asyncReadFile();

上面的代码调用了asyncReadFile函数,然后它就会自动执行,输出最后结果。这完全不像 Generator 函数,需要调用next方法,或者用co模块,才能真正执行,得到最后结果。

(2)更好的语义。

asyncawait,比起星号和yield,语义更清楚了。async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果。

(3)更广的适用性。

co模块约定,yield命令后面只能是 Thunk 函数或 Promise 对象,而async函数的await命令后面,可以是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时会自动转成立即 resolved 的 Promise 对象)。

(4)返回值是 Promise。

async函数的返回值是 Promise 对象,这比 Generator 函数的返回值是 Iterator 对象方便多了。你可以用then方法指定下一步的操作。

进一步说,async函数完全可以看作多个异步操作,包装成的一个 Promise 对象,而await命令就是内部then命令的语法糖。

async 函数有多种使用形式。

  1. // 函数声明

  2. async function foo() {}

  3. // 函数表达式

  4. const foo = async function () {};

  5. // 对象的方法

  6. let obj = { async foo() {} };

  7. obj.foo().then(...)

  8. // Class 的方法

  9. class Storage {

  10. constructor() {

  11. this.cachePromise = caches.open('avatars');

  12. }

  13. async getAvatar(name) {

  14. const cache = await this.cachePromise;

  15. return cache.match(`/avatars/${name}.jpg`);

  16. }

  17. }

  18. const storage = new Storage();

  19. storage.getAvatar('jake').then(…);

  20. // 箭头函数

  21. const foo = async () => {};

Promise 对象的状态变化

async函数返回的 Promise 对象,必须等到内部所有await命令后面的 Promise 对象执行完,才会发生状态改变,除非遇到return语句或者抛出错误。也就是说,只有async函数内部的异步操作执行完,才会执行then方法指定的回调函数。

下面是一个例子。

  1. async function getTitle(url) {
  2. let response = await fetch(url);
  3. let html = await response.text();
  4. return html.match(/([\s\S]+)<\/title>/i)[1];</code></li> <li><code>}</code></li> <li><code>getTitle('https://tc39.github.io/ecma262/').then(console.log)</code></li> <li><code>// "ECMAScript 2017 Language Specification"</code></li> </ol> <p>上面代码中,函数<code>getTitle</code>内部有三个操作:抓取网页、取出文本、匹配页面标题。只有这三个操作全部完成,才会执行<code>then</code>方法里面的<code>console.log</code>。</p> <p><strong>await 命令</strong></p> <p>正常情况下,<code>await</code>命令后面是一个 Promise 对象,返回该对象的结果。如果不是 Promise 对象,就直接返回对应的值。另一种情况是,<code>await</code>命令后面是一个<code>thenable</code>对象(即定义了<code>then</code>方法的对象),那么<code>await</code>会将其等同于 Promise 对象。</p> <p>任何一个<code>await</code>语句后面的 Promise 对象变为<code>reject</code>状态,那么整个<code>async</code>函数都会中断执行。</p> <p>另一种方法是<code>await</code>后面的 Promise 对象再跟一个<code>catch</code>方法,处理前面可能出现的错误。</p> <p>async 函数的实现原理,就是将 Generator 函数和自动执行器,包装在一个函数里。</p> <ol> <li> <p><code>async function fn(args) {</code></p> </li> <li> <p><code>// ...</code></p> </li> <li> <p><code>}</code></p> </li> <li> <p><code>// 等同于</code></p> </li> <li> <p><code>function fn(args) {</code></p> </li> <li> <p><code>return spawn(function* () {</code></p> </li> <li> <pre><code>`// ...` </code></pre> </li> <li> <p><code>});</code></p> </li> <li> <p><code>}</code></p> </li> </ol> <p>所有的<code>async</code>函数都可以写成上面的第二种形式,其中的<code>spawn</code>函数就是自动执行器。</p> <p>下面给出<code>spawn</code>函数的实现,基本就是前文自动执行器的翻版。</p> <p>复制代码</p> <ol> <li><code>function spawn(genF) {</code></li> <li><code>return new Promise(function(resolve, reject) {</code></li> <li> <pre><code>`const gen = genF();` </code></pre> </li> <li> <pre><code>`function step(nextF) {` </code></pre> </li> <li> <pre><code> `let next;` </code></pre> </li> <li> <pre><code> `try {` </code></pre> </li> <li> <pre><code> `next = nextF();` </code></pre> </li> <li> <pre><code> `} catch(e) {` </code></pre> </li> <li> <pre><code> `return reject(e);` </code></pre> </li> <li> <pre><code> `}` </code></pre> </li> <li> <pre><code> `if(next.done) {` </code></pre> </li> <li> <pre><code> `return resolve(next.value);` </code></pre> </li> <li> <pre><code> `}` </code></pre> </li> <li> <pre><code> `Promise.resolve(next.value).then(function(v) {` </code></pre> </li> <li> <pre><code> `step(function() { return gen.next(v); });` </code></pre> </li> <li> <pre><code> `}, function(e) {` </code></pre> </li> <li> <pre><code> `step(function() { return gen.throw(e); });` </code></pre> </li> <li> <pre><code> `});` </code></pre> </li> <li> <pre><code>`}` </code></pre> </li> <li> <pre><code>`step(function() { return gen.next(undefined); });` </code></pre> </li> <li><code>});</code></li> <li><code>}</code></li> </ol> <p>三种异步的比较</p> <p>我们通过一个例子,来看 async 函数与 Promise、Generator 函数的比较。</p> <p>假定某个 DOM 元素上面,部署了一系列的动画,前一个动画结束,才能开始后一个。如果当中有一个动画出错,就不再往下执行,返回上一个成功执行的动画的返回值。</p> <p>首先是 Promise 的写法。</p> <ol> <li> <p><code>function chainAnimationsPromise(elem, animations) {</code></p> </li> <li> <p><code>// 变量ret用来保存上一个动画的返回值</code></p> </li> <li> <p><code>let ret = null;</code></p> </li> <li> <p><code>// 新建一个空的Promise</code></p> </li> <li> <p><code>let p = Promise.resolve();</code></p> </li> <li> <p><code>// 使用then方法,添加所有动画</code></p> </li> <li> <p><code>for(let anim of animations) {</code></p> </li> <li> <pre><code>`p = p.then(function(val) {` </code></pre> </li> <li> <pre><code> `ret = val;` </code></pre> </li> <li> <pre><code> `return anim(elem);` </code></pre> </li> <li> <pre><code>`});` </code></pre> </li> <li> <p><code>}</code></p> </li> <li> <p><code>// 返回一个部署了错误捕捉机制的Promise</code></p> </li> <li> <p><code>return p.catch(function(e) {</code></p> </li> <li> <pre><code>`/* 忽略错误,继续执行 */` </code></pre> </li> <li> <p><code>}).then(function() {</code></p> </li> <li> <pre><code>`return ret;` </code></pre> </li> <li> <p><code>});</code></p> </li> <li> <p><code>}</code></p> </li> </ol> <p>虽然 Promise 的写法比回调函数的写法大大改进,但是一眼看上去,代码完全都是 Promise 的 API(<code>then</code>、<code>catch</code>等等),操作本身的语义反而不容易看出来。</p> <p>接着是 Generator 函数的写法。</p> <ol> <li> <p><code>function chainAnimationsGenerator(elem, animations) {</code></p> </li> <li> <p><code>return spawn(function*() {</code></p> </li> <li> <pre><code>`let ret = null;` </code></pre> </li> <li> <pre><code>`try {` </code></pre> </li> <li> <pre><code> `for(let anim of animations) {` </code></pre> </li> <li> <pre><code> `ret = yield anim(elem);` </code></pre> </li> <li> <pre><code> `}` </code></pre> </li> <li> <pre><code>`} catch(e) {` </code></pre> </li> <li> <pre><code> `/* 忽略错误,继续执行 */` </code></pre> </li> <li> <pre><code>`}` </code></pre> </li> <li> <pre><code>`return ret;` </code></pre> </li> <li> <p><code>});</code></p> </li> <li> <p><code>}</code></p> </li> </ol> <p>上面代码使用 Generator 函数遍历了每个动画,语义比 Promise 写法更清晰,用户定义的操作全部都出现在<code>spawn</code>函数的内部。这个写法的问题在于,必须有一个任务运行器,自动执行 Generator 函数,上面代码的<code>spawn</code>函数就是自动执行器,它返回一个 Promise 对象,而且必须保证<code>yield</code>语句后面的表达式,必须返回一个 Promise。</p> <p>最后是 async 函数的写法。</p> <ol> <li><code>async function chainAnimationsAsync(elem, animations) {</code></li> <li><code>let ret = null;</code></li> <li><code>try {</code></li> <li> <pre><code>`for(let anim of animations) {` </code></pre> </li> <li> <pre><code> `ret = await anim(elem);` </code></pre> </li> <li> <pre><code>`}` </code></pre> </li> <li><code>} catch(e) {</code></li> <li> <pre><code>`/* 忽略错误,继续执行 */` </code></pre> </li> <li><code>}</code></li> <li><code>return ret;</code></li> <li><code>}</code></li> </ol> <p>可以看到 Async 函数的实现最简洁,最符合语义,几乎没有语义不相关的代码。它将 Generator 写法中的自动执行器,改在语言层面提供,不暴露给用户,因此代码量最少。如果使用 Generator 写法,自动执行器需要用户自己提供。</p> <p><strong>顺序完成异步操作</strong></p> <ol> <li> <p><code>async function logInOrder(urls) {</code></p> </li> <li> <p><code>// 并发读取远程URL</code></p> </li> <li> <p><code>const textPromises = urls.map(async url => {</code></p> </li> <li> <p><code>const response = await fetch(url);</code></p> </li> <li> <p><code>return response.text();</code></p> </li> <li> <p><code>});</code></p> </li> <li> <p><code>// 按次序输出</code></p> </li> <li> <p><code>for (const textPromise of textPromises) {</code></p> </li> <li> <p><code>console.log(await textPromise);</code></p> </li> <li> <p><code>}</code></p> </li> <li> <p><code>}</code></p> </li> </ol> <p>顶层await</p> <ol> <li><code>// awaiting.js</code></li> <li><code>let output;</code></li> <li><code>export default (async function main() {</code></li> <li><code>const dynamic = await import(someMission);</code></li> <li><code>const data = await fetch(url);</code></li> <li><code>output = someProcess(dynamic.default, data);</code></li> <li><code>})();</code></li> <li><code>export { output };</code></li> </ol> <h4>21. Class 的基本语法</h4> <p>原始:</p> <ol> <li> <p><code>function Point(x, y) {</code></p> </li> <li> <p><code>this.x = x;</code></p> </li> <li> <p><code>this.y = y;</code></p> </li> <li> <p><code>}</code></p> </li> <li> <p><code>Point.prototype.toString = function () {</code></p> </li> <li> <p><code>return '(' + this.x + ', ' + this.y + ')';</code></p> </li> <li> <p><code>};</code></p> </li> <li> <p><code>var p = new Point(1, 2);</code></p> </li> </ol> <p>es6改进后:</p> <ol> <li> <p><code>class Point {</code></p> </li> <li> <p><code>constructor(x, y) {</code></p> </li> <li> <p><code>this.x = x;</code></p> </li> <li> <p><code>this.y = y;</code></p> </li> <li> <p><code>}</code></p> </li> <li> <p><code>toString() {</code></p> </li> <li> <p><code>return '(' + this.x + ', ' + this.y + ')';</code></p> </li> <li> <p><code>}</code></p> </li> <li> <p><code>}</code></p> </li> </ol> <p>上面代码定义了一个“类”,定义“类”的方法的时候,前面不需要加上<code>function</code>这个关键字,直接把函数定义放进去了就可以了。另外,方法之间不需要逗号分隔,加了会报错。</p> <p>类的数据类型就是函数,类本身就指向构造函数。</p> <ol> <li> <p><code>class Bar {</code></p> </li> <li> <p><code>doStuff() {</code></p> </li> <li> <p><code>console.log('stuff');</code></p> </li> <li> <p><code>}</code></p> </li> <li> <p><code>}</code></p> </li> <li> <p><code>var b = new Bar();</code></p> </li> <li> <p><code>b.doStuff() // "stuff"</code></p> </li> </ol> <p>构造函数的<code>prototype</code>属性,在 ES6 的“类”上面继续存在。事实上,类的所有方法都定义在类的<code>prototype</code>属性上面。</p> <ol> <li> <p><code>class Point {</code></p> </li> <li> <p><code>constructor() {</code></p> </li> <li> <p><code>// ...</code></p> </li> <li> <p><code>}</code></p> </li> <li> <p><code>toString() {</code></p> </li> <li> <p><code>// ...</code></p> </li> <li> <p><code>}</code></p> </li> <li> <p><code>toValue() {</code></p> </li> <li> <p><code>// ...</code></p> </li> <li> <p><code>}</code></p> </li> <li> <p><code>}</code></p> </li> <li> <p><code>// 等同于</code></p> </li> <li> <p><code>Point.prototype = {</code></p> </li> <li> <p><code>constructor() {},</code></p> </li> <li> <p><code>toString() {},</code></p> </li> <li> <p><code>toValue() {},</code></p> </li> <li> <p><code>};</code></p> </li> </ol> <p><code>prototype</code>对象的<code>constructor</code>属性,直接指向“类”的本身</p> <ol> <li><code>Point.prototype.constructor === Point // true</code></li> </ol> <p>类是有函数构造的q</p> <ol> <li> <p><code>//定义类</code></p> </li> <li> <p><code>class Point {</code></p> </li> <li> <p><code>constructor(x, y) {</code></p> </li> <li> <p><code>this.x = x;</code></p> </li> <li> <p><code>this.y = y;</code></p> </li> <li> <p><code>}</code></p> </li> <li> <p><code>toString() {</code></p> </li> <li> <p><code>return '(' + this.x + ', ' + this.y + ')';</code></p> </li> <li> <p><code>}</code></p> </li> <li> <p><code>}</code></p> </li> <li> <p><code>var point = new Point(2, 3);</code></p> </li> <li> <p><code>point.toString() // (2, 3)</code></p> </li> <li> <p><code>point.hasOwnProperty('x') // true</code></p> </li> <li> <p><code>point.hasOwnProperty('y') // true</code></p> </li> <li> <p><code>point.hasOwnProperty('toString') // false</code></p> </li> <li> <p><code>point.__proto__.hasOwnProperty('toString') // true</code></p> </li> </ol> <p>类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上<code>static</code>关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。</p> <ol> <li> <p><code>class Foo {</code></p> </li> <li> <p><code>static classMethod() {</code></p> </li> <li> <pre><code>`return 'hello';` </code></pre> </li> <li> <p><code>}</code></p> </li> <li> <p><code>}</code></p> </li> <li> <p><code>Foo.classMethod() // 'hello'</code></p> </li> <li> <p><code>var foo = new Foo();</code></p> </li> <li> <p><code>foo.classMethod()</code></p> </li> <li> <p><code>// TypeError: foo.classMethod is not a function</code></p> </li> </ol> <p><code>Foo</code>类的<code>classMethod</code>方法前有<code>static</code>关键字,表明该方法是一个静态方法,可以直接在<code>Foo</code>类上调用(<code>Foo.classMethod()</code>),而不是在<code>Foo</code>类的实例上调用。如果在实例上调用静态方法,会抛出一个错误,表示不存在该方法。</p> <ol> <li> <p><code>class Foo {</code></p> </li> <li> <p><code>static bar() {</code></p> </li> <li> <pre><code>`this.baz();` </code></pre> </li> <li> <p><code>}</code></p> </li> <li> <p><code>static baz() {</code></p> </li> <li> <pre><code>`console.log('hello');` </code></pre> </li> <li> <p><code>}</code></p> </li> <li> <p><code>baz() {</code></p> </li> <li> <pre><code>`console.log('world');` </code></pre> </li> <li> <p><code>}</code></p> </li> <li> <p><code>}</code></p> </li> <li> <p><code>Foo.bar() // hello</code><br> 上面代码中,静态方法<code>bar</code>调用了<code>this.baz</code>,这里的<code>this</code>指的是<code>Foo</code>类,而不是<code>Foo</code>的实例,等同于调用<code>Foo.baz</code>。另外,从这个例子还可以看出,静态方法可以与非静态方法重名。</p> </li> <li> <p><code>class Foo {</code></p> </li> <li> <p><code>static classMethod() {</code></p> </li> <li> <pre><code>`return 'hello';` </code></pre> </li> <li> <p><code>}</code></p> </li> <li> <p><code>}</code></p> </li> <li> <p><code>class Bar extends Foo {</code></p> </li> <li> <p><code>}</code></p> </li> <li> <p><code>Bar.classMethod() // 'hello'</code></p> </li> </ol> <p>实例属性的新写法:可以不使用constructor, 而是直接写在顶层</p> <ol> <li> <p><code>class IncreasingCounter {</code></p> </li> <li> <p><code>constructor() {</code></p> </li> <li> <pre><code>`this._count = 0;` </code></pre> </li> <li> <p><code>}</code></p> </li> <li> <p><code>get value() {</code></p> </li> <li> <pre><code>`console.log('Getting the current value!');` </code></pre> </li> <li> <pre><code>`return this._count;` </code></pre> </li> <li> <p><code>}</code></p> </li> <li> <p><code>increment() {</code></p> </li> <li> <pre><code>`this._count++;` </code></pre> </li> <li> <p><code>}</code></p> </li> <li> <p><code>}</code></p> </li> <li> <p><code>class IncreasingCounter {</code></p> </li> <li> <p><code>_count = 0;</code></p> </li> <li> <p><code>get value() {</code></p> </li> <li> <pre><code>`console.log('Getting the current value!');` </code></pre> </li> <li> <pre><code>`return this._count;` </code></pre> </li> <li> <p><code>}</code></p> </li> <li> <p><code>increment() {</code></p> </li> <li> <pre><code>`this._count++;` </code></pre> </li> <li> <p><code>}</code></p> </li> <li> <p><code>}</code></p> </li> </ol> <p>静态属性</p> <ol> <li> <p><code>class Foo {</code></p> </li> <li> <p><code>}</code></p> </li> <li> <p><code>Foo.prop = 1;</code></p> </li> <li> <p><code>Foo.prop // 1</code></p> </li> <li> <p><code>class MyClass {</code></p> </li> <li> <p><code>static myStaticProp = 42;</code></p> </li> <li> <p><code>constructor() {</code></p> </li> <li> <pre><code>`console.log(MyClass.myStaticProp); // 42` </code></pre> </li> <li> <p><code>}</code></p> </li> <li> <p><code>}</code></p> </li> </ol> <p>可以在constructor定义属性的前面添加static 设置静态属性</p> <p>私有属性和私有方法,外部不能访问</p> <ol> <li><code>class Foo {</code></li> <li><code>#a;</code></li> <li><code>#b;</code></li> <li><code>constructor(a, b) {</code></li> <li> <pre><code>`this.#a = a;` </code></pre> </li> <li> <pre><code>`this.#b = b;` </code></pre> </li> <li><code>}</code></li> <li><code>#sum() {</code></li> <li> <pre><code>`return #a + #b;` </code></pre> </li> <li><code>}</code></li> <li><code>printSum() {</code></li> <li> <pre><code>`console.log(this.#sum());` </code></pre> </li> <li><code>}</code></li> <li><code>}</code></li> </ol> <p><code>new</code>是从构造函数生成实例对象的命令。ES6 为<code>new</code>命令引入了一个<code>new.target</code>属性,该属性一般用在构造函数之中,返回<code>new</code>命令作用于的那个构造函数。如果构造函数不是通过<code>new</code>命令或<code>Reflect.construct()</code>调用的,<code>new.target</code>会返回<code>undefined</code>,因此这个属性可以用来确定构造函数是怎么调用的。</p> <ol> <li> <p><code>function Person(name) {</code></p> </li> <li> <p><code>if (new.target !== undefined) {</code></p> </li> <li> <pre><code>`this.name = name;` </code></pre> </li> <li> <p><code>} else {</code></p> </li> <li> <pre><code>`throw new Error('必须使用 new 命令生成实例');` </code></pre> </li> <li> <p><code>}</code></p> </li> <li> <p><code>}</code></p> </li> <li> <p><code>// 另一种写法</code></p> </li> <li> <p><code>function Person(name) {</code></p> </li> <li> <p><code>if (new.target === Person) {</code></p> </li> <li> <pre><code>`this.name = name;` </code></pre> </li> <li> <p><code>} else {</code></p> </li> <li> <pre><code>`throw new Error('必须使用 new 命令生成实例');` </code></pre> </li> <li> <p><code>}</code></p> </li> <li> <p><code>}</code></p> </li> <li> <p><code>var person = new Person('张三'); // 正确</code></p> </li> <li> <p><code>var notAPerson = Person.call(person, '张三'); // 报错</code></p> </li> </ol> <p>new.target 是用来检测是否是由new构成的,区别于call构成</p> <p><code>new.target</code>会返回子类。</p> <p>用法:</p> <ol> <li> <p><code>class Shape {</code></p> </li> <li> <p><code>constructor() {</code></p> </li> <li> <pre><code>`if (new.target === Shape) {` </code></pre> </li> <li> <pre><code> `throw new Error('本类不能实例化');` </code></pre> </li> <li> <pre><code>`}` </code></pre> </li> <li> <p><code>}</code></p> </li> <li> <p><code>}</code></p> </li> <li> <p><code>class Rectangle extends Shape {</code></p> </li> <li> <p><code>constructor(length, width) {</code></p> </li> <li> <pre><code>`super();` </code></pre> </li> <li> <pre><code>`// ...` </code></pre> </li> <li> <p><code>}</code></p> </li> <li> <p><code>}</code></p> </li> <li> <p><code>var x = new Shape(); // 报错</code></p> </li> <li> <p><code>var y = new Rectangle(3, 4); // 正确</code></p> </li> </ol> <h4>22. Class 的继承</h4> <ol> <li> <p><code>class Point {</code></p> </li> <li> <p><code>}</code></p> </li> <li> <p><code>class ColorPoint extends Point {</code></p> </li> <li> <p><code>}</code></p> </li> </ol> <p>子类在constructor中必须使用super() 可以调用父类的constructor</p> <ol> <li> <p><code>class ColorPoint extends Point {</code></p> </li> <li> <p><code>constructor(x, y, color) {</code></p> </li> <li> <pre><code>`super(x, y); // 调用父类的constructor(x, y)` </code></pre> </li> <li> <pre><code>`this.color = color;` </code></pre> </li> <li> <p><code>}</code></p> </li> <li> <p><code>toString() {</code></p> </li> <li> <pre><code>`return this.color + ' ' + super.toString(); // 调用父类的toString()` </code></pre> </li> <li> <p><code>}</code></p> </li> <li> <p><code>}</code></p> </li> </ol> <p>如果子类没有定义constructor方法,这个方法会被默认添加;</p> <ol> <li> <p><code>class ColorPoint extends Point {</code></p> </li> <li> <p><code>}</code></p> </li> <li> <p><code>// 等同于</code></p> </li> <li> <p><code>class ColorPoint extends Point {</code></p> </li> <li> <p><code>constructor(...args) {</code></p> </li> <li> <pre><code>`super(...args);` </code></pre> </li> <li> <p><code>}</code></p> </li> <li> <p><code>}</code></p> </li> </ol> <p>同时需要注意的是只有在使用super后才可以使用this关键字</p> <ol> <li> <p><code>let cp = new ColorPoint(25, 8, 'green');</code></p> </li> <li> <p><code>cp instanceof ColorPoint // true</code></p> </li> <li> <p><code>cp instanceof Point // true</code></p> </li> </ol> <p>实例对象<code>cp</code>同时是子类和父类<code>ColorPoint</code>和<code>Point</code>两个类的实例</p> <p><code>Object.getPrototypeOf</code>方法可以用来从子类上获取父类。</p> <p>super() 只能放在 constructor中</p> <ol> <li> <p><code>class A {</code></p> </li> <li> <p><code>p() {</code></p> </li> <li> <pre><code>`return 2;` </code></pre> </li> <li> <p><code>}</code></p> </li> <li> <p><code>}</code></p> </li> <li> <p><code>class B extends A {</code></p> </li> <li> <p><code>constructor() {</code></p> </li> <li> <pre><code>`super();` </code></pre> </li> <li> <pre><code>`console.log(super.p()); // 2` </code></pre> </li> <li> <p><code>}</code></p> </li> <li> <p><code>}</code></p> </li> <li> <p><code>let b = new B();</code></p> </li> </ol> <p>上面代码中,子类<code>B</code>当中的<code>super.p()</code>,就是将<code>super</code>当作一个对象使用。这时,<code>super</code>在普通方法之中,指向<code>A.prototype</code>,所以<code>super.p()</code>就相当于<code>A.prototype.p()</code>。</p> <ol> <li> <p><code>class A {</code></p> </li> <li> <p><code>constructor() {</code></p> </li> <li> <pre><code>`this.x = 1;` </code></pre> </li> <li> <p><code>}</code></p> </li> <li> <p><code>print() {</code></p> </li> <li> <pre><code>`console.log(this.x);` </code></pre> </li> <li> <p><code>}</code></p> </li> <li> <p><code>}</code></p> </li> <li> <p><code>class B extends A {</code></p> </li> <li> <p><code>constructor() {</code></p> </li> <li> <pre><code>`super();` </code></pre> </li> <li> <pre><code>`this.x = 2;` </code></pre> </li> <li> <p><code>}</code></p> </li> <li> <p><code>m() {</code></p> </li> <li> <pre><code>`super.print();` </code></pre> </li> <li> <p><code>}</code></p> </li> <li> <p><code>}</code></p> </li> <li> <p><code>let b = new B();</code></p> </li> <li> <p><code>b.m() // 2</code></p> </li> </ol> <p>上面代码中,<code>super.print()</code>虽然调用的是<code>A.prototype.print()</code>,但是<code>A.prototype.print()</code>内部的<code>this</code>指向子类<code>B</code>的实例,导致输出的是<code>2</code>,而不是<code>1</code>。也就是说,实际上执行的是<code>super.print.call(this)</code>。</p> <p>由于<code>this</code>指向子类实例,所以如果通过<code>super</code>对某个属性赋值,这时<code>super</code>就是<code>this</code>,赋值的属性会变成子类实例的属性。</p> <ol> <li> <p><code>class A {</code></p> </li> <li> <p><code>constructor() {</code></p> </li> <li> <pre><code>`this.x = 1;` </code></pre> </li> <li> <p><code>}</code></p> </li> <li> <p><code>}</code></p> </li> <li> <p><code>class B extends A {</code></p> </li> <li> <p><code>constructor() {</code></p> </li> <li> <pre><code>`super();` </code></pre> </li> <li> <pre><code>`this.x = 2;` </code></pre> </li> <li> <pre><code>`super.x = 3;` </code></pre> </li> <li> <pre><code>`console.log(super.x); // undefined` </code></pre> </li> <li> <pre><code>`console.log(this.x); // 3` </code></pre> </li> <li> <p><code>}</code></p> </li> <li> <p><code>}</code></p> </li> <li> <p><code>let b = new B();</code></p> </li> </ol> <p>上面代码中,<code>super.x</code>赋值为<code>3</code>,这时等同于对<code>this.x</code>赋值为<code>3</code>。而当读取<code>super.x</code>的时候,读的是<code>A.prototype.x</code>,所以返回<code>undefined</code>。</p> <p>如果<code>super</code>作为对象,用在静态方法之中,这时<code>super</code>将指向父类,而不是父类的原型对象。</p> <ol> <li> <p><code>class Parent {</code></p> </li> <li> <p><code>static myMethod(msg) {</code></p> </li> <li> <pre><code>`console.log('static', msg);` </code></pre> </li> <li> <p><code>}</code></p> </li> <li> <p><code>myMethod(msg) {</code></p> </li> <li> <pre><code>`console.log('instance', msg);` </code></pre> </li> <li> <p><code>}</code></p> </li> <li> <p><code>}</code></p> </li> <li> <p><code>class Child extends Parent {</code></p> </li> <li> <p><code>static myMethod(msg) {</code></p> </li> <li> <pre><code>`super.myMethod(msg);` </code></pre> </li> <li> <p><code>}</code></p> </li> <li> <p><code>myMethod(msg) {</code></p> </li> <li> <pre><code>`super.myMethod(msg);` </code></pre> </li> <li> <p><code>}</code></p> </li> <li> <p><code>}</code></p> </li> <li> <p><code>Child.myMethod(1); // static 1</code></p> </li> <li> <p><code>var child = new Child();</code></p> </li> <li> <p><code>child.myMethod(2); // instance 2</code></p> </li> </ol> <p>(1)子类的<code>__proto__</code>属性,表示构造函数的继承,总是指向父类。</p> <p>(2)子类<code>prototype</code>属性的<code>__proto__</code>属性,表示方法的继承,总是指向父类的<code>prototype</code>属性。</p> <ol> <li> <p><code>class A {</code></p> </li> <li> <p><code>}</code></p> </li> <li> <p><code>class B extends A {</code></p> </li> <li> <p><code>}</code></p> </li> <li> <p><code>B.__proto__ === A // true</code></p> </li> <li> <p><code>B.prototype.__proto__ === A.prototype // true</code></p> </li> </ol> <p>继承原生构造函数:</p> <ul> <li>Boolean()</li> <li>Number()</li> <li>String()</li> <li>Array()</li> <li>Date()</li> <li>Function()</li> <li>RegExp()</li> <li>Error()</li> <li>Object()</li> </ul> <ol> <li> <p><code>class MyArray extends Array {</code></p> </li> <li> <p><code>constructor(...args) {</code></p> </li> <li> <pre><code>`super(...args);` </code></pre> </li> <li> <p><code>}</code></p> </li> <li> <p><code>}</code></p> </li> <li> <p><code>var arr = new MyArray();</code></p> </li> <li> <p><code>arr[0] = 12;</code></p> </li> <li> <p><code>arr.length // 1</code></p> </li> <li> <p><code>arr.length = 0;</code></p> </li> <li> <p><code>arr[0] // undefined</code></p> </li> </ol> <p>Mixin 指的是多个对象合成一个新的对象,新对象具有各个组成成员的接口。它的最简单实现如下。</p> <ol> <li><code>const a = {</code></li> <li><code>a: 'a'</code></li> <li><code>};</code></li> <li><code>const b = {</code></li> <li><code>b: 'b'</code></li> <li><code>};</code></li> <li><code>const c = {...a, ...b}; // {a: 'a', b: 'b'}</code></li> </ol> <h4>23. Module的语法</h4> <p>ES6 模块不是对象,而是通过<code>export</code>命令显式指定输出的代码,再通过<code>import</code>命令输入。</p> <ol> <li><code>// ES6模块</code></li> <li><code>import { stat, exists, readFile } from 'fs';</code></li> </ol> <p>下面是<code>import()</code>的一些适用场合。</p> <p>(1)按需加载。</p> <p><code>import()</code>可以在需要的时候,再加载某个模块。</p> <ol> <li><code>button.addEventListener('click', event => {</code></li> <li><code>import('./dialogBox.js')</code></li> <li><code>.then(dialogBox => {</code></li> <li> <pre><code>`dialogBox.open();` </code></pre> </li> <li><code>})</code></li> <li><code>.catch(error => {</code></li> <li> <pre><code>`/* Error handling */` </code></pre> </li> <li><code>})</code></li> <li><code>});</code></li> </ol> <p>上面代码中,<code>import()</code>方法放在<code>click</code>事件的监听函数之中,只有用户点击了按钮,才会加载这个模块。</p> <p>(2)条件加载</p> <p><code>import()</code>可以放在<code>if</code>代码块,根据不同的情况,加载不同的模块。</p> <ol> <li><code>if (condition) {</code></li> <li><code>import('moduleA').then(...);</code></li> <li><code>} else {</code></li> <li><code>import('moduleB').then(...);</code></li> <li><code>}</code></li> </ol> <p>上面代码中,如果满足条件,就加载模块 A,否则加载模块 B。</p> <p>(3)动态的模块路径</p> <p><code>import()</code>允许模块路径动态生成。</p> <ol> <li><code>import(f())</code></li> <li><code>.then(...);</code></li> </ol> <p>上面代码中,根据函数<code>f</code>的返回结果,加载不同的模块。</p> <h5>注意点</h5> <p><code>import()</code>加载模块成功以后,这个模块会作为一个对象,当作<code>then</code>方法的参数。因此,可以使用对象解构赋值的语法,获取输出接口。</p> <ol> <li><code>import('./myModule.js')</code></li> <li><code>.then(({export1, export2}) => {</code></li> <li><code>// ...·</code></li> <li><code>});</code></li> </ol> <p>上面代码中,<code>export1</code>和<code>export2</code>都是<code>myModule.js</code>的输出接口,可以解构获得。</p> <p>如果模块有<code>default</code>输出接口,可以用参数直接获得。</p> <ol> <li><code>import('./myModule.js')</code></li> <li><code>.then(myModule => {</code></li> <li><code>console.log(myModule.default);</code></li> <li><code>});</code></li> </ol> <p>上面的代码也可以使用具名输入的形式。</p> <ol> <li><code>import('./myModule.js')</code></li> <li><code>.then(({default: theDefault}) => {</code></li> <li><code>console.log(theDefault);</code></li> <li><code>});</code></li> </ol> <p>如果想同时加载多个模块,可以采用下面的写法。</p> <ol> <li><code>Promise.all([</code></li> <li><code>import('./module1.js'),</code></li> <li><code>import('./module2.js'),</code></li> <li><code>import('./module3.js'),</code></li> <li><code>])</code></li> <li><code>.then(([module1, module2, module3]) => {</code></li> <li><code>···</code></li> <li><code>});</code></li> </ol> <p><code>import()</code>也可以用在 async 函数之中。</p> <ol> <li><code>async function main() {</code></li> <li><code>const myModule = await import('./myModule.js');</code></li> <li><code>const {export1, export2} = await import('./myModule.js');</code></li> <li><code>const [module1, module2, module3] =</code></li> <li> <pre><code>`await Promise.all([` </code></pre> </li> <li> <pre><code> `import('./module1.js'),` </code></pre> </li> <li> <pre><code> `import('./module2.js'),` </code></pre> </li> <li> <pre><code> `import('./module3.js'),` </code></pre> </li> <li> <pre><code>`]);` </code></pre> </li> <li><code>}</code></li> <li><code>main();</code></li> </ol> <h5>严格模式:</h5> <p>ES6 的模块自动采用严格模式,不管你有没有在模块头部加上<code>"use strict";</code>。</p> <p>严格模式主要有以下限制。</p> <ul> <li>变量必须声明后再使用</li> <li>函数的参数不能有同名属性,否则报错</li> <li>不能使用<code>with</code>语句</li> <li>不能对只读属性赋值,否则报错</li> <li>不能使用前缀 0 表示八进制数,否则报错</li> <li>不能删除不可删除的属性,否则报错</li> <li>不能删除变量<code>delete prop</code>,会报错,只能删除属性<code>delete global[prop]</code></li> <li><code>eval</code>不会在它的外层作用域引入变量</li> <li><code>eval</code>和<code>arguments</code>不能被重新赋值</li> <li><code>arguments</code>不会自动反映函数参数的变化</li> <li>不能使用<code>arguments.callee</code></li> <li>不能使用<code>arguments.caller</code></li> <li>禁止<code>this</code>指向全局对象</li> <li>不能使用<code>fn.caller</code>和<code>fn.arguments</code>获取函数调用的堆栈</li> <li>增加了保留字(比如<code>protected</code>、<code>static</code>和<code>interface</code>)</li> </ul> <p>上面这些限制,模块都必须遵守。由于严格模式是 ES5 引入的,不属于 ES6,所以请参阅相关 ES5 书籍,本书不再详细介绍了。</p> <p>其中,尤其需要注意<code>this</code>的限制。ES6 模块之中,顶层的<code>this</code>指向<code>undefined</code>,即不应该在顶层代码使用<code>this</code>。</p> <p>export:</p> <p>模块功能主要由两个命令构成:<code>export</code>和<code>import</code>。<code>export</code>命令用于规定模块的对外接口,<code>import</code>命令用于输入其他模块提供的功能。</p> <p>一个模块就是一个独立的文件。该文件内部的所有变量,外部无法获取。如果你希望外部能够读取模块内部的某个变量,就必须使用<code>export</code>关键字输出该变量。下面是一个 JS 文件,里面使用<code>export</code>命令输出变量。</p> <ol> <li><code>// profile.js</code></li> <li><code>export var firstName = 'Michael';</code></li> <li><code>export var lastName = 'Jackson';</code></li> <li><code>export var year = 1958;</code></li> </ol> <p>上面代码是<code>profile.js</code>文件,保存了用户信息。ES6 将其视为一个模块,里面用<code>export</code>命令对外部输出了三个变量。</p> <p><code>export</code>的写法,除了像上面这样,还有另外一种。</p> <ol> <li> <p><code>// profile.js</code></p> </li> <li> <p><code>var firstName = 'Michael';</code></p> </li> <li> <p><code>var lastName = 'Jackson';</code></p> </li> <li> <p><code>var year = 1958;</code></p> </li> <li> <p><code>export { firstName, lastName, year };</code></p> </li> <li> <p><code>function v1() { ... }</code></p> </li> <li> <p><code>function v2() { ... }</code></p> </li> <li> <p><code>export {</code></p> </li> <li> <p><code>v1 as streamV1,</code></p> </li> <li> <p><code>v2 as streamV2,</code></p> </li> <li> <p><code>v2 as streamLatestVersion</code></p> </li> <li> <p><code>};</code></p> </li> <li> <p><code>// 写法一</code></p> </li> <li> <p><code>export var m = 1;</code></p> </li> <li> <p><code>// 写法二</code></p> </li> <li> <p><code>var m = 1;</code></p> </li> <li> <p><code>export {m};</code></p> </li> <li> <p><code>// 写法三</code></p> </li> <li> <p><code>var n = 1;</code></p> </li> <li> <p><code>export {n as m};</code></p> </li> <li> <p><code>// 报错</code></p> </li> <li> <p><code>function f() {}</code></p> </li> <li> <p><code>export f;</code></p> </li> <li> <p><code>// 正确</code></p> </li> <li> <p><code>export function f() {};</code></p> </li> <li> <p><code>// 正确</code></p> </li> <li> <p><code>function f() {}</code></p> </li> <li> <p><code>export {f};</code></p> </li> </ol> <p>另外,<code>export</code>语句输出的接口,与其对应的值是动态绑定关系,即通过该接口,可以取到模块内部实时的值。</p> <p>最后,<code>export</code>命令可以出现在模块的任何位置,只要处于模块顶层就可以。如果处于块级作用域内,就会报错,下一节的<code>import</code>命令也是如此。这是因为处于条件代码块之中,就没法做静态优化了,违背了 ES6 模块的设计初衷。</p> <ol> <li> <p><code>function foo() {</code></p> </li> <li> <p><code>export default 'bar' // SyntaxError</code></p> </li> <li> <p><code>}</code></p> </li> <li> <p><code>foo()</code><br> 上面代码中,<code>export</code>语句放在函数之中,结果报错。</p> </li> <li> <p><code>// main.js</code></p> </li> <li> <p><code>import { firstName, lastName, year } from './profile.js';</code></p> </li> <li> <p><code>function setName(element) {</code></p> </li> <li> <p><code>element.textContent = firstName + ' ' + lastName;</code></p> </li> <li> <p><code>}</code></p> </li> </ol> <p>如果想为输入的变量重新取一个名字,<code>import</code>命令要使用<code>as</code>关键字,将输入的变量重命名。</p> <ol> <li><code>import { lastName as surname } from './profile.js';</code></li> </ol> <p><code>import</code>命令输入的变量都是只读的,因为它的本质是输入接口。也就是说,不允许在加载模块的脚本里面,改写接口。</p> <ol> <li> <p><code>import {a} from './xxx.js'</code></p> </li> <li> <p><code>a = {}; // Syntax Error : 'a' is read-only;</code></p> </li> </ol> <p>上面代码中,脚本加载了变量<code>a</code>,对其重新赋值就会报错,因为<code>a</code>是一个只读的接口。但是,如果<code>a</code>是一个对象,改写<code>a</code>的属性是允许的。</p> <ol> <li> <p><code>import {a} from './xxx.js'</code></p> </li> <li> <p><code>a.foo = 'hello'; // 合法操作</code></p> </li> </ol> <p>由于<code>import</code>是静态执行,所以不能使用表达式和变量,这些只有在运行时才能得到结果的语法结构。</p> <ol> <li> <p><code>// 报错</code></p> </li> <li> <p><code>import { 'f' + 'oo' } from 'my_module';</code></p> </li> <li> <p><code>// 报错</code></p> </li> <li> <p><code>let module = 'my_module';</code></p> </li> <li> <p><code>import { foo } from module;</code></p> </li> <li> <p><code>// 报错</code></p> </li> <li> <p><code>if (x === 1) {</code></p> </li> <li> <p><code>import { foo } from 'module1';</code></p> </li> <li> <p><code>} else {</code></p> </li> <li> <p><code>import { foo } from 'module2';</code></p> </li> <li> <p><code>}</code></p> </li> <li> <p><code>import * as circle from './circle';</code></p> </li> <li> <p><code>console.log('圆面积:' + circle.area(4));</code></p> </li> <li> <p><code>console.log('圆周长:' + circle.circumference(14));</code></p> </li> </ol> <p>注意,模块整体加载所在的那个对象(上例是<code>circle</code>),应该是可以静态分析的,所以不允许运行时改变。下面的写法都是不允许的。</p> <ol> <li> <p><code>import * as circle from './circle';</code></p> </li> <li> <p><code>// 下面两行都是不允许的</code></p> </li> <li> <p><code>circle.foo = 'hello';</code></p> </li> <li> <p><code>circle.area = function () {};</code></p> </li> </ol> <p>export default:</p> <ol> <li> <p><code>// modules.js</code></p> </li> <li> <p><code>function add(x, y) {</code></p> </li> <li> <p><code>return x * y;</code></p> </li> <li> <p><code>}</code></p> </li> <li> <p><code>export {add as default};</code></p> </li> <li> <p><code>// 等同于</code></p> </li> <li> <p><code>// export default add;</code></p> </li> <li> <p><code>// app.js</code></p> </li> <li> <p><code>import { default as foo } from 'modules';</code></p> </li> <li> <p><code>// 等同于</code></p> </li> <li> <p><code>// import foo from 'modules';</code></p> </li> </ol> <p>有了<code>export default</code>命令,输入模块时就非常直观了,以输入 lodash 模块为例。</p> <ol> <li><code>import _ from 'lodash';</code></li> </ol> <p>如果想在一条<code>import</code>语句中,同时输入默认方法和其他接口,可以写成下面这样。</p> <ol> <li><code>import _, { each, forEach } from 'lodash';</code></li> </ol> <p>export 与 import 的复合写法</p> <ol> <li> <p><code>export { foo, bar } from 'my_module';</code></p> </li> <li> <p><code>// 可以简单理解为</code></p> </li> <li> <p><code>import { foo, bar } from 'my_module';</code></p> </li> <li> <p><code>export { foo, bar };</code></p> </li> </ol> <p>上面代码中,<code>export</code>和<code>import</code>语句可以结合在一起,写成一行。但需要注意的是,写成一行以后,<code>foo</code>和<code>bar</code>实际上并没有被导入当前模块,只是相当于对外转发了这两个接口,导致当前模块不能直接使用<code>foo</code>和<code>bar</code>。</p> <ol> <li> <p><code>// 接口改名</code></p> </li> <li> <p><code>export { foo as myFoo } from 'my_module';</code></p> </li> <li> <p><code>// 整体输出</code></p> </li> <li> <p><code>export * from 'my_module';</code></p> </li> <li> <p><code>// 默认接口</code></p> </li> <li> <p><code>export { default } from 'foo';</code></p> </li> </ol> <p>具名接口改为默认接口的写法如下。</p> <ol> <li> <p><code>export { es6 as default } from './someModule';</code></p> </li> <li> <p><code>// 等同于</code></p> </li> <li> <p><code>import { es6 } from './someModule';</code></p> </li> <li> <p><code>export default es6;</code></p> </li> </ol> <p>模块的继承:</p> <ol> <li> <p><code>// circleplus.js</code></p> </li> <li> <p><code>export * from 'circle';</code></p> </li> <li> <p><code>export var e = 2.71828182846;</code></p> </li> <li> <p><code>export default function(x) {</code></p> </li> <li> <p><code>return Math.exp(x);</code></p> </li> <li> <p><code>}</code></p> </li> </ol> <p>上面代码中的<code>export *</code>,表示再输出<code>circle</code>模块的所有属性和方法。注意,<code>export *</code>命令会忽略<code>circle</code>模块的<code>default</code>方法。然后,上面代码又输出了自定义的<code>e</code>变量和默认方法。</p> <p>如果要使用的常量非常多,可以建一个专门的<code>constants</code>目录,将各种常量写在不同的文件里面,保存在该目录下。</p> <ol> <li> <p><code>// constants/db.js</code></p> </li> <li> <p><code>export const db = {</code></p> </li> <li> <p><code>url: 'http://my.couchdbserver.local:5984',</code></p> </li> <li> <p><code>admin_username: 'admin',</code></p> </li> <li> <p><code>admin_password: 'admin password'</code></p> </li> <li> <p><code>};</code></p> </li> <li> <p><code>// constants/user.js</code></p> </li> <li> <p><code>export const users = ['root', 'admin', 'staff', 'ceo', 'chief', 'moderator'];</code></p> </li> </ol> <h4>24. Module 的加载实现</h4> <p>HTML 网页中,浏览器通过<code><script></code>标签加载 JavaScript 脚本。</p> <ol> <li> <p><code><!-- 页面内嵌的脚本 --></code></p> </li> <li> <p><code><script type="application/javascript"></code></p> </li> <li> <p><code>// module code</code></p> </li> <li> <p><code></script></code></p> </li> <li> <p><code><!-- 外部脚本 --></code></p> </li> <li> <p><code><script type="application/javascript" src="path/to/myModule.js"></code></p> </li> <li> <p><code></script></code></p> </li> </ol> <p>上面代码中,由于浏览器脚本的默认语言是 JavaScript,因此<code>type="application/javascript"</code>可以省略。</p> <p>默认情况下,浏览器是同步加载 JavaScript 脚本,即渲染引擎遇到<code><script></code>标签就会停下来,等到执行完脚本,再继续向下渲染。如果是外部脚本,还必须加入脚本下载的时间。</p> <p>如果脚本体积很大,下载和执行的时间就会很长,因此造成浏览器堵塞,用户会感觉到浏览器“卡死”了,没有任何响应。这显然是很不好的体验,所以浏览器允许脚本异步加载,下面就是两种异步加载的语法。</p> <ol> <li><code><script src="path/to/myModule.js" defer></script></code></li> <li><code><script src="path/to/myModule.js" async></script></code></li> </ol> <p><code>defer</code>与<code>async</code>的区别是:<code>defer</code>要等到整个页面在内存中正常渲染结束(DOM 结构完全生成,以及其他脚本执行完成),才会执行;<code>async</code>一旦下载完,渲染引擎就会中断渲染,执行这个脚本以后,再继续渲染。一句话,<code>defer</code>是“渲染完再执行”,<code>async</code>是“下载完就执行”。另外,如果有多个<code>defer</code>脚本,会按照它们在页面出现的顺序加载,而多个<code>async</code>脚本是不能保证加载顺序的。</p> <h5>加载规则</h5> <p>浏览器加载 ES6 模块,也使用<code><script></code>标签,但是要加入<code>type="module"</code>属性。</p> <ol> <li><code><script type="module" src="./foo.js"></script></code></li> </ol> <p>上面代码在网页中插入一个模块<code>foo.js</code>,由于<code>type</code>属性设为<code>module</code>,所以浏览器知道这是一个 ES6 模块。</p> <p>浏览器对于带有<code>type="module"</code>的<code><script></code>,都是异步加载,不会造成堵塞浏览器,即等到整个页面渲染完,再执行模块脚本,等同于打开了<code><script></code>标签的<code>defer</code>属性。</p> <ol> <li><code><script type="module"></code></li> <li><code>import $ from "./jquery/src/jquery.js";</code></li> <li><code>$('#message').text('Hi from jQuery!');</code></li> <li><code></script></code></li> </ol> <p>对于外部的模块脚本(上例是<code>foo.js</code>),有几点需要注意。</p> <ul> <li>代码是在模块作用域之中运行,而不是在全局作用域运行。模块内部的顶层变量,外部不可见。</li> <li>模块脚本自动采用严格模式,不管有没有声明<code>use strict</code>。</li> <li>模块之中,可以使用<code>import</code>命令加载其他模块(<code>.js</code>后缀不可省略,需要提供绝对 URL 或相对 URL),也可以使用<code>export</code>命令输出对外接口。</li> <li>模块之中,顶层的<code>this</code>关键字返回<code>undefined</code>,而不是指向<code>window</code>。也就是说,在模块顶层使用<code>this</code>关键字,是无意义的。</li> <li>同一个模块如果加载多次,将只执行一次。</li> </ul> <p>下面是一个示例模块。</p> <ol> <li> <p><code>import utils from 'https://example.com/js/utils.js';</code></p> </li> <li> <p><code>const x = 1;</code></p> </li> <li> <p><code>console.log(x === window.x); //false</code></p> </li> <li> <p><code>console.log(this === undefined); // true</code></p> </li> </ol> <p>利用顶层的<code>this</code>等于<code>undefined</code>这个语法点,可以侦测当前代码是否在 ES6 模块之中。</p> <ol> <li><code>const isNotModuleScript = this !== undefined;</code></li> </ol> <p>讨论 Node.js 加载 ES6 模块之前,必须了解 ES6 模块与 CommonJS 模块完全不同。</p> <p>它们有两个重大差异。</p> <ul> <li>CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。</li> <li>CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。</li> </ul> <p>CommonJS 模块输出的是值的拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。请看下面这个模块文件<code>lib.js</code>的例子。</p> <ol> <li><code>// lib.js</code></li> <li><code>var counter = 3;</code></li> <li><code>function incCounter() {</code></li> <li><code>counter++;</code></li> <li><code>}</code></li> <li><code>module.exports = {</code></li> <li><code>counter: counter,</code></li> <li><code>incCounter: incCounter,</code></li> <li><code>};</code></li> </ol> <p>上面代码输出内部变量<code>counter</code>和改写这个变量的内部方法<code>incCounter</code>。然后,在<code>main.js</code>里面加载这个模块。</p> <ol> <li> <p><code>// main.js</code></p> </li> <li> <p><code>var mod = require('./lib');</code></p> </li> <li> <p><code>console.log(mod.counter); // 3</code></p> </li> <li> <p><code>mod.incCounter();</code></p> </li> <li> <p><code>console.log(mod.counter); // 3</code></p> </li> </ol> <p>上面代码说明,<code>lib.js</code>模块加载以后,它的内部变化就影响不到输出的<code>mod.counter</code>了。这是因为<code>mod.counter</code>是一个原始类型的值,会被缓存。除非写成一个函数,才能得到内部变动后的值。</p> <h5>Node.js 加载</h5> <p>Node.js 对 ES6 模块的处理比较麻烦,因为它有自己的 CommonJS 模块格式,与 ES6 模块格式是不兼容的。目前的解决方案是,将两者分开,ES6 模块和 CommonJS 采用各自的加载方案。从 v13.2 版本开始,Node.js 已经默认打开了 ES6 模块支持。</p> <p>Node.js 要求 ES6 模块采用<code>.mjs</code>后缀文件名。也就是说,只要脚本文件里面使用<code>import</code>或者<code>export</code>命令,那么就必须采用<code>.mjs</code>后缀名。Node.js 遇到<code>.mjs</code>文件,就认为它是 ES6 模块,默认启用严格模式,不必在每个模块文件顶部指定<code>"use strict"</code>。</p> <p>如果不希望将后缀名改成<code>.mjs</code>,可以在项目的<code>package.json</code>文件中,指定<code>type</code>字段为<code>module</code>。</p> <ol> <li><code>{</code></li> <li><code>"type": "module"</code></li> <li><code>}</code></li> </ol> <p>一旦设置了以后,该目录里面的 JS 脚本,就被解释用 ES6 模块。</p> <ol> <li><code># 解释成 ES6 模块</code></li> <li><code>$ node my-app.js</code></li> </ol> <p>如果这时还要使用 CommonJS 模块,那么需要将 CommonJS 脚本的后缀名都改成<code>.cjs</code>。如果没有<code>type</code>字段,或者<code>type</code>字段为<code>commonjs</code>,则<code>.js</code>脚本会被解释成 CommonJS 模块。</p> <p>总结为一句话:<code>.mjs</code>文件总是以 ES6 模块加载,<code>.cjs</code>文件总是以 CommonJS 模块加载,<code>.js</code>文件的加载取决于<code>package.json</code>里面<code>type</code>字段的设置。</p> <p>注意,ES6 模块与 CommonJS 模块尽量不要混用。<code>require</code>命令不能加载<code>.mjs</code>文件,会报错,只有<code>import</code>命令才可以加载<code>.mjs</code>文件。反过来,<code>.mjs</code>文件里面也不能使用<code>require</code>命令,必须使用<code>import</code>。</p> <h4>25. 编程风格</h4> <p>尽量不要使用var,而是使用let和const,在let和const之间优选使用const</p> <p>静态字符串一律使用单引号或反引号,不使用双引号。动态字符串使用反引号。</p> <p>使用数组成员对变量赋值时,优先使用解构赋值。</p> <p>单行定义的对象,最后一个成员不以逗号结尾。多行定义的对象,最后一个成员以逗号结尾。</p> <p>对象尽量静态化,一旦定义,就不得随意添加新的属性。如果添加属性不可避免,要使用<code>Object.assign</code>方法。</p> <p>使用扩展运算符(…)拷贝数组。</p> <p>使用 Array.from 方法,将类似数组的对象转为数组。</p> <p>立即执行函数可以写成箭头函数的形式。</p> <p>那些使用匿名函数当作参数的场合,尽量用箭头函数代替。因为这样更简洁,而且绑定了 this。</p> <p>注意区分 Object 和 Map,只有模拟现实世界的实体对象时,才使用 Object。如果只是需要<code>key: value</code>的数据结构,使用 Map 结构。因为 Map 有内建的遍历机制。</p> <ol> <li> <p><code>let map = new Map(arr);</code></p> </li> <li> <p><code>for (let key of map.keys()) {</code></p> </li> <li> <p><code>console.log(key);</code></p> </li> <li> <p><code>}</code></p> </li> <li> <p><code>for (let value of map.values()) {</code></p> </li> <li> <p><code>console.log(value);</code></p> </li> <li> <p><code>}</code></p> </li> <li> <p><code>for (let item of map.entries()) {</code></p> </li> <li> <p><code>console.log(item[0], item[1]);</code></p> </li> <li> <p><code>}</code></p> </li> </ol> <p>总是用 Class,取代需要 prototype 的操作。因为 Class 的写法更简洁,更易于理解。</p> <p>使用<code>extends</code>实现继承,因为这样更简单,不会有破坏<code>instanceof</code>运算的危险。</p> <p>首先,Module 语法是 JavaScript 模块的标准写法,坚持使用这种写法。使用<code>import</code>取代<code>require</code>。</p> <p>使用<code>export</code>取代<code>module.exports</code>。</p> <p>如果模块只有一个输出值,就使用<code>export default</code>,如果模块有多个输出值,就不使用<code>export default</code>,<code>export default</code>与普通的<code>export</code>不要同时使用。</p> <p>不要在模块输入中使用通配符。因为这样可以确保你的模块之中,有一个默认输出(export default)。</p> <ol> <li> <p><code>// bad</code></p> </li> <li> <p><code>import * as myObject from './importModule';</code></p> </li> <li> <p><code>// good</code></p> </li> <li> <p><code>import myObject from './importModule';</code></p> </li> </ol> <p>如果模块默认输出一个函数,函数名的首字母应该小写。</p> <p>如果模块默认输出一个对象,对象名的首字母应该大写。</p> <h5>语法规则和代码风格的检查工具</h5> <p>ESLint 是一个语法规则和代码风格的检查工具,可以用来保证写出语法正确、风格统一的代码。</p> <p>首先,安装 ESLint。</p> <ol> <li><code>$ npm i -g eslint</code></li> </ol> <p>然后,安装 Airbnb 语法规则,以及 import、a11y、react 插件。</p> <ol> <li><code>$ npm i -g eslint-config-airbnb</code></li> <li><code>$ npm i -g eslint-plugin-import eslint-plugin-jsx-a11y eslint-plugin-react</code></li> </ol> <p>最后,在项目的根目录下新建一个<code>.eslintrc</code>文件,配置 ESLint。</p> <ol> <li><code>{</code></li> <li><code>"extends": "eslint-config-airbnb"</code></li> <li><code>}</code></li> </ol> <h4>26. 读懂规格</h4> <blockquote> <ol> <li>Let <code>O</code> be <code>ToObject(this value)</code>.</li> <li><code>ReturnIfAbrupt(O)</code>.</li> <li>Let <code>len</code> be <code>ToLength(Get(O, "length"))</code>.</li> <li><code>ReturnIfAbrupt(len)</code>.</li> <li>If <code>IsCallable(callbackfn)</code> is <code>false</code>, throw a TypeError exception.</li> <li>If <code>thisArg</code> was supplied, let <code>T</code> be <code>thisArg</code>; else let <code>T</code> be <code>undefined</code>.</li> <li>Let <code>A</code> be <code>ArraySpeciesCreate(O, len)</code>.</li> <li><code>ReturnIfAbrupt(A)</code>.</li> <li>Let <code>k</code> be 0.</li> <li>Repeat, while <code>k</code> < <code>len</code> <ol> <li>Let <code>Pk</code> be <code>ToString(k)</code>.</li> <li>Let <code>kPresent</code> be <code>HasProperty(O, Pk)</code>.</li> <li><code>ReturnIfAbrupt(kPresent)</code>.</li> <li>If <code>kPresent</code> is <code>true</code>, then <ol> <li>Let <code>kValue</code> be <code>Get(O, Pk)</code>.</li> <li><code>ReturnIfAbrupt(kValue)</code>.</li> <li>Let <code>mappedValue</code> be <code>Call(callbackfn, T, «kValue, k, O»)</code>.</li> <li><code>ReturnIfAbrupt(mappedValue)</code>.</li> <li>Let <code>status</code> be <code>CreateDataPropertyOrThrow (A, Pk, mappedValue)</code>.</li> <li><code>ReturnIfAbrupt(status)</code>.</li> </ol> </li> <li>Increase <code>k</code> by 1.</li> </ol> </li> <li>Return <code>A</code>.</li> </ol> </blockquote> <p>翻译如下。</p> <blockquote> <ol> <li>得到当前数组的<code>this</code>对象</li> <li>如果报错就返回</li> <li>求出当前数组的<code>length</code>属性</li> <li>如果报错就返回</li> <li>如果 map 方法的参数<code>callbackfn</code>不可执行,就报错</li> <li>如果 map 方法的参数之中,指定了<code>this</code>,就让<code>T</code>等于该参数,否则<code>T</code>为<code>undefined</code></li> <li>生成一个新的数组<code>A</code>,跟当前数组的<code>length</code>属性保持一致</li> <li>如果报错就返回</li> <li>设定<code>k</code>等于 0</li> <li>只要<code>k</code>小于当前数组的<code>length</code>属性,就重复下面步骤 <ol> <li>设定<code>Pk</code>等于<code>ToString(k)</code>,即将<code>K</code>转为字符串</li> <li>设定<code>kPresent</code>等于<code>HasProperty(O, Pk)</code>,即求当前数组有没有指定属性</li> <li>如果报错就返回</li> <li>如果<code>kPresent</code>等于<code>true</code>,则进行下面步骤 <ol> <li>设定<code>kValue</code>等于<code>Get(O, Pk)</code>,取出当前数组的指定属性</li> <li>如果报错就返回</li> <li>设定<code>mappedValue</code>等于<code>Call(callbackfn, T, «kValue, k, O»)</code>,即执行回调函数</li> <li>如果报错就返回</li> <li>设定<code>status</code>等于<code>CreateDataPropertyOrThrow (A, Pk, mappedValue)</code>,即将回调函数的值放入<code>A</code>数组的指定位置</li> <li>如果报错就返回</li> </ol> </li> <li><code>k</code>增加 1</li> </ol> </li> <li>返回<code>A</code></li> </ol> </blockquote> <p>仔细查看上面的算法,可以发现,当处理一个全是空位的数组时,前面步骤都没有问题。进入第 10 步中第 2 步时,<code>kPresent</code>会报错,因为空位对应的属性名,对于数组来说是不存在的,因此就会返回,不会进行后面的步骤。</p> <h4>27. 异步遍历器</h4> <p>将异步操作包装成 Thunk 函数或者 Promise 对象,即<code>next()</code>方法返回值的<code>value</code>属性是一个 Thunk 函数或者 Promise 对象,等待以后返回真正的值,而<code>done</code>属性则还是同步产生的。</p> <ol> <li> <p><code>function idMaker() {</code></p> </li> <li> <p><code>let index = 0;</code></p> </li> <li> <p><code>return {</code></p> </li> <li> <pre><code>`next: function() {` </code></pre> </li> <li> <pre><code> `return {` </code></pre> </li> <li> <pre><code> `value: new Promise(resolve => setTimeout(() => resolve(index++), 1000)),` </code></pre> </li> <li> <pre><code> `done: false` </code></pre> </li> <li> <pre><code> `};` </code></pre> </li> <li> <pre><code>`}` </code></pre> </li> <li> <p><code>};</code></p> </li> <li> <p><code>}</code></p> </li> <li> <p><code>const it = idMaker();</code></p> </li> <li> <p><code>it.next().value.then(o => console.log(o)) // 1</code></p> </li> <li> <p><code>it.next().value.then(o => console.log(o)) // 2</code></p> </li> <li> <p><code>it.next().value.then(o => console.log(o)) // 3</code></p> </li> <li> <p><code>// ...</code></p> </li> </ol> <p>上面代码中,<code>value</code>属性的返回值是一个 Promise 对象,用来放置异步操作。但是这样写很麻烦,不太符合直觉,语义也比较绕。</p> <p><code>asyncIterator</code>是一个异步遍历器,调用<code>next</code>方法以后,返回一个 Promise 对象。因此,可以使用<code>then</code>方法指定,这个 Promise 对象的状态变为<code>resolve</code>以后的回调函数。回调函数的参数,则是一个具有<code>value</code>和<code>done</code>两个属性的对象,这个跟同步遍历器是一样的。</p> <p>我们知道,一个对象的同步遍历器的接口,部署在<code>Symbol.iterator</code>属性上面。同样地,对象的异步遍历器接口,部署在<code>Symbol.asyncIterator</code>属性上面。不管是什么样的对象,只要它的<code>Symbol.asyncIterator</code>属性有值,就表示应该对它进行异步遍历。</p> <ol> <li> <p><code>const asyncIterable = createAsyncIterable(['a', 'b']);</code></p> </li> <li> <p><code>const asyncIterator = asyncIterable[Symbol.asyncIterator]();</code></p> </li> <li> <p><code>asyncIterator</code></p> </li> <li> <p><code>.next()</code></p> </li> <li> <p><code>.then(iterResult1 => {</code></p> </li> <li> <p><code>console.log(iterResult1); // { value: 'a', done: false }</code></p> </li> <li> <p><code>return asyncIterator.next();</code></p> </li> <li> <p><code>})</code></p> </li> <li> <p><code>.then(iterResult2 => {</code></p> </li> <li> <p><code>console.log(iterResult2); // { value: 'b', done: false }</code></p> </li> <li> <p><code>return asyncIterator.next();</code></p> </li> <li> <p><code>})</code></p> </li> <li> <p><code>.then(iterResult3 => {</code></p> </li> <li> <p><code>console.log(iterResult3); // { value: undefined, done: true }</code></p> </li> <li> <p><code>});</code></p> </li> <li> <p><code>async function f() {</code></p> </li> <li> <p><code>const asyncIterable = createAsyncIterable(['a', 'b']);</code></p> </li> <li> <p><code>const asyncIterator = asyncIterable[Symbol.asyncIterator]();</code></p> </li> <li> <p><code>console.log(await asyncIterator.next());</code></p> </li> <li> <p><code>// { value: 'a', done: false }</code></p> </li> <li> <p><code>console.log(await asyncIterator.next());</code></p> </li> <li> <p><code>// { value: 'b', done: false }</code></p> </li> <li> <p><code>console.log(await asyncIterator.next());</code></p> </li> <li> <p><code>// { value: undefined, done: true }</code></p> </li> <li> <p><code>}</code></p> </li> </ol> <p>面代码中,<code>next</code>方法用<code>await</code>处理以后,就不必使用<code>then</code>方法了。整个流程已经很接近同步处理了。</p> <p>注意,异步遍历器的<code>next</code>方法是可以连续调用的,不必等到上一步产生的 Promise 对象<code>resolve</code>以后再调用。这种情况下,<code>next</code>方法会累积起来,自动按照每一步的顺序运行下去。下面是一个例子,把所有的<code>next</code>方法放在<code>Promise.all</code>方法里面。</p> <ol> <li> <p><code>const asyncIterable = createAsyncIterable(['a', 'b']);</code></p> </li> <li> <p><code>const asyncIterator = asyncIterable[Symbol.asyncIterator]();</code></p> </li> <li> <p><code>const [{value: v1}, {value: v2}] = await Promise.all([</code></p> </li> <li> <p><code>asyncIterator.next(), asyncIterator.next()</code></p> </li> <li> <p><code>]);</code></p> </li> <li> <p><code>console.log(v1, v2); // a b</code></p> </li> </ol> <p>另一种用法是一次性调用所有的<code>next</code>方法,然后<code>await</code>最后一步操作。</p> <ol> <li> <p><code>async function runner() {</code></p> </li> <li> <p><code>const writer = openFile('someFile.txt');</code></p> </li> <li> <p><code>writer.next('hello');</code></p> </li> <li> <p><code>writer.next('world');</code></p> </li> <li> <p><code>await writer.return();</code></p> </li> <li> <p><code>}</code></p> </li> <li> <p><code>runner();</code></p> </li> </ol> <p><code>createAsyncIterable()</code>返回一个拥有异步遍历器接口的对象,<code>for...of</code>循环自动调用这个对象的异步遍历器的<code>next</code>方法,会得到一个 Promise 对象。<code>await</code>用来处理这个 Promise 对象,一旦<code>resolve</code>,就把得到的值(<code>x</code>)传入<code>for...of</code>的循环体。</p> <p><code>for await...of</code>循环的一个用途,是部署了 asyncIterable 操作的异步接口,可以直接放入这个循环。</p> <ol> <li> <p><code>let body = '';</code></p> </li> <li> <p><code>async function f() {</code></p> </li> <li> <p><code>for await(const data of req) body += data;</code></p> </li> <li> <p><code>const parsed = JSON.parse(body);</code></p> </li> <li> <p><code>console.log('got', parsed);</code></p> </li> <li> <p><code>}</code></p> </li> </ol> <p>上面代码中,<code>req</code>是一个 asyncIterable 对象,用来异步读取数据。可以看到,使用<code>for await...of</code>循环以后,代码会非常简洁。</p> <p>如果<code>next</code>方法返回的 Promise 对象被<code>reject</code>,<code>for await...of</code>就会报错,要用<code>try...catch</code>捕捉。</p> <ol> <li><code>async function () {</code></li> <li><code>try {</code></li> <li> <pre><code>`for await (const x of createRejectingIterable()) {` </code></pre> </li> <li> <pre><code> `console.log(x);` </code></pre> </li> <li> <pre><code>`}` </code></pre> </li> <li><code>} catch (e) {</code></li> <li> <pre><code>`console.error(e);` </code></pre> </li> <li><code>}</code></li> <li><code>}</code></li> </ol> <p>注意,<code>for await...of</code>循环也可以用于同步遍历器。</p> <ol> <li><code>(async function () {</code></li> <li><code>for await (const x of ['a', 'b']) {</code></li> <li> <pre><code>`console.log(x);` </code></pre> </li> <li><code>}</code></li> <li><code>})();</code></li> <li><code>// a</code></li> <li><code>// b</code></li> </ol> <p>异步遍历器的设计目的之一,就是 Generator 函数处理同步操作和异步操作时,能够使用同一套接口。</p> <ol> <li> <p><code>// 同步 Generator 函数</code></p> </li> <li> <p><code>function* map(iterable, func) {</code></p> </li> <li> <p><code>const iter = iterable[Symbol.iterator]();</code></p> </li> <li> <p><code>while (true) {</code></p> </li> <li> <pre><code>`const {value, done} = iter.next();` </code></pre> </li> <li> <pre><code>`if (done) break;` </code></pre> </li> <li> <pre><code>`yield func(value);` </code></pre> </li> <li> <p><code>}</code></p> </li> <li> <p><code>}</code></p> </li> <li> <p><code>// 异步 Generator 函数</code></p> </li> <li> <p><code>async function* map(iterable, func) {</code></p> </li> <li> <p><code>const iter = iterable[Symbol.asyncIterator]();</code></p> </li> <li> <p><code>while (true) {</code></p> </li> <li> <pre><code>`const {value, done} = await iter.next();` </code></pre> </li> <li> <pre><code>`if (done) break;` </code></pre> </li> <li> <pre><code>`yield func(value);` </code></pre> </li> <li> <p><code>}</code></p> </li> <li> <p><code>}</code></p> </li> </ol> <p><code>yield*</code>语句也可以跟一个异步遍历器。</p> <ol> <li> <p><code>async function* gen1() {</code></p> </li> <li> <p><code>yield 'a';</code></p> </li> <li> <p><code>yield 'b';</code></p> </li> <li> <p><code>return 2;</code></p> </li> <li> <p><code>}</code></p> </li> <li> <p><code>async function* gen2() {</code></p> </li> <li> <p><code>// result 最终会等于 2</code></p> </li> <li> <p><code>const result = yield* gen1();</code></p> </li> <li> <p><code>}</code></p> </li> </ol> <h4>28. ArrayBuffer</h4> <p>二进制数组由三类对象组成。</p> <p><strong>(1)<code>ArrayBuffer</code>对象</strong>:代表内存之中的一段二进制数据,可以通过“视图”进行操作。“视图”部署了数组接口,这意味着,可以用数组的方法操作内存。</p> <p><strong>(2)<code>TypedArray</code>视图</strong>:共包括 9 种类型的视图,比如<code>Uint8Array</code>(无符号 8 位整数)数组视图, <code>Int16Array</code>(16 位整数)数组视图, <code>Float32Array</code>(32 位浮点数)数组视图等等。</p> <p><strong>(3)<code>DataView</code>视图</strong>:可以自定义复合格式的视图,比如第一个字节是 Uint8(无符号 8 位整数)、第二、三个字节是 Int16(16 位整数)、第四个字节开始是 Float32(32 位浮点数)等等,此外还可以自定义字节序。</p> <p>简单说,<code>ArrayBuffer</code>对象代表原始的二进制数据,<code>TypedArray</code>视图用来读写简单类型的二进制数据,<code>DataView</code>视图用来读写复杂类型的二进制数据。</p> <p><code>TypedArray</code>视图支持的数据类型一共有 9 种(<code>DataView</code>视图支持除<code>Uint8C</code>以外的其他 8 种)。</p> <table> <thead> <tr> <th>数据类型</th> <th>字节长度</th> <th>含义</th> <th>对应的 C 语言类型</th> </tr> </thead> <tbody> <tr> <td>Int8</td> <td>1</td> <td>8 位带符号整数</td> <td>signed char</td> </tr> <tr> <td>Uint8</td> <td>1</td> <td>8 位不带符号整数</td> <td>unsigned char</td> </tr> <tr> <td>Uint8C</td> <td>1</td> <td>8 位不带符号整数(自动过滤溢出)</td> <td>unsigned char</td> </tr> <tr> <td>Int16</td> <td>2</td> <td>16 位带符号整数</td> <td>short</td> </tr> <tr> <td>Uint16</td> <td>2</td> <td>16 位不带符号整数</td> <td>unsigned short</td> </tr> <tr> <td>Int32</td> <td>4</td> <td>32 位带符号整数</td> <td>int</td> </tr> <tr> <td>Uint32</td> <td>4</td> <td>32 位不带符号的整数</td> <td>unsigned int</td> </tr> <tr> <td>Float32</td> <td>4</td> <td>32 位浮点数</td> <td>float</td> </tr> <tr> <td>Float64</td> <td>8</td> <td>64 位浮点数</td> <td>double</td> </tr> </tbody> </table> <p><code>ArrayBuffer</code>对象代表储存二进制数据的一段内存,它不能直接读写,只能通过视图(<code>TypedArray</code>视图和<code>DataView</code>视图)来读写,视图的作用是以指定格式解读二进制数据。</p> <p><code>ArrayBuffer</code>也是一个构造函数,可以分配一段可以存放数据的连续内存区域。</p> <ol> <li><code>const buf = new ArrayBuffer(32);</code></li> </ol> <p>上面代码生成了一段 32 字节的内存区域,每个字节的值默认都是 0。可以看到,<code>ArrayBuffer</code>构造函数的参数是所需要的内存大小(单位字节)。</p> <p>为了读写这段内容,需要为它指定视图。<code>DataView</code>视图的创建,需要提供<code>ArrayBuffer</code>对象实例作为参数。</p> <ol> <li><code>const buf = new ArrayBuffer(32);</code></li> <li><code>const dataView = new DataView(buf);</code></li> <li><code>dataView.getUint8(0) // 0</code></li> </ol> <p>上面代码对一段 32 字节的内存,建立<code>DataView</code>视图,然后以不带符号的 8 位整数格式,从头读取 8 位二进制数据,结果得到 0,因为原始内存的<code>ArrayBuffer</code>对象,默认所有位都是 0。</p> <p>另一种<code>TypedArray</code>视图,与<code>DataView</code>视图的一个区别是,它不是一个构造函数,而是一组构造函数,代表不同的数据格式。</p> <ol> <li> <p><code>const buffer = new ArrayBuffer(12);</code></p> </li> <li> <p><code>const x1 = new Int32Array(buffer);</code></p> </li> <li> <p><code>x1[0] = 1;</code></p> </li> <li> <p><code>const x2 = new Uint8Array(buffer);</code></p> </li> <li> <p><code>x2[0] = 2;</code></p> </li> <li> <p><code>x1[0] // 2</code></p> </li> </ol> <p><code>TypedArray</code>视图的构造函数,除了接受<code>ArrayBuffer</code>实例作为参数,还可以接受普通数组作为参数,直接分配内存生成底层的<code>ArrayBuffer</code>实例,并同时完成对这段内存的赋值。</p> <ol> <li> <p><code>const typedArray = new Uint8Array([0,1,2]);</code></p> </li> <li> <p><code>typedArray.length // 3</code></p> </li> <li> <p><code>typedArray[0] = 5;</code></p> </li> <li> <p><code>typedArray // [5, 1, 2]</code></p> </li> </ol> <p><code>ArrayBuffer</code>实例的<code>byteLength</code>属性,返回所分配的内存区域的字节长度。</p> <ol> <li><code>const buffer = new ArrayBuffer(32);</code></li> <li><code>buffer.byteLength</code></li> <li><code>// 32</code></li> </ol> <p><code>ArrayBuffer</code>实例有一个<code>slice</code>方法,允许将内存区域的一部分,拷贝生成一个新的<code>ArrayBuffer</code>对象。</p> <ol> <li><code>const buffer = new ArrayBuffer(8);</code></li> <li><code>const newBuffer = buffer.slice(0, 3);</code></li> </ol> <p><code>ArrayBuffer</code>有一个静态方法<code>isView</code>,返回一个布尔值,表示参数是否为<code>ArrayBuffer</code>的视图实例。这个方法大致相当于判断参数,是否为<code>TypedArray</code>实例或<code>DataView</code>实例。</p> <ol> <li> <p><code>const buffer = new ArrayBuffer(8);</code></p> </li> <li> <p><code>ArrayBuffer.isView(buffer) // false</code></p> </li> <li> <p><code>const v = new Int32Array(buffer);</code></p> </li> <li> <p><code>ArrayBuffer.isView(v) // true</code></p> </li> </ol> <p>普通数组的操作方法和属性,对 TypedArray 数组完全适用。</p> <ul> <li><code>TypedArray.prototype.copyWithin(target, start[, end = this.length])</code></li> <li><code>TypedArray.prototype.entries()</code></li> <li><code>TypedArray.prototype.every(callbackfn, thisArg?)</code></li> <li><code>TypedArray.prototype.fill(value, start=0, end=this.length)</code></li> <li><code>TypedArray.prototype.filter(callbackfn, thisArg?)</code></li> <li><code>TypedArray.prototype.find(predicate, thisArg?)</code></li> <li><code>TypedArray.prototype.findIndex(predicate, thisArg?)</code></li> <li><code>TypedArray.prototype.forEach(callbackfn, thisArg?)</code></li> <li><code>TypedArray.prototype.indexOf(searchElement, fromIndex=0)</code></li> <li><code>TypedArray.prototype.join(separator)</code></li> <li><code>TypedArray.prototype.keys()</code></li> <li><code>TypedArray.prototype.lastIndexOf(searchElement, fromIndex?)</code></li> <li><code>TypedArray.prototype.map(callbackfn, thisArg?)</code></li> <li><code>TypedArray.prototype.reduce(callbackfn, initialValue?)</code></li> <li><code>TypedArray.prototype.reduceRight(callbackfn, initialValue?)</code></li> <li><code>TypedArray.prototype.reverse()</code></li> <li><code>TypedArray.prototype.slice(start=0, end=this.length)</code></li> <li><code>TypedArray.prototype.some(callbackfn, thisArg?)</code></li> <li><code>TypedArray.prototype.sort(comparefn)</code></li> <li><code>TypedArray.prototype.toLocaleString(reserved1?, reserved2?)</code></li> <li><code>TypedArray.prototype.toString()</code></li> <li><code>TypedArray.prototype.values()</code></li> </ul> <p>复合视图:</p> <p>由于视图的构造函数可以指定起始位置和长度,所以在同一段内存之中,可以依次存放不同类型的数据,这叫做“复合视图”。</p> <ol> <li> <p><code>const buffer = new ArrayBuffer(24);</code></p> </li> <li> <p><code>const idView = new Uint32Array(buffer, 0, 1);</code></p> </li> <li> <p><code>const usernameView = new Uint8Array(buffer, 4, 16);</code></p> </li> <li> <p><code>const amountDueView = new Float32Array(buffer, 20, 1);</code></p> </li> </ol> <p><code>DataView</code>视图本身也是构造函数,接受一个<code>ArrayBuffer</code>对象作为参数,生成视图。</p> <ol> <li><code>new DataView(ArrayBuffer buffer [, 字节起始位置 [, 长度]]);</code></li> </ol> <p>下面是一个例子。</p> <ol> <li><code>const buffer = new ArrayBuffer(24);</code></li> <li><code>const dv = new DataView(buffer);</code></li> </ol> <p><code>DataView</code>实例有以下属性,含义与<code>TypedArray</code>实例的同名方法相同。</p> <ul> <li><code>DataView.prototype.buffer</code>:返回对应的 ArrayBuffer 对象</li> <li><code>DataView.prototype.byteLength</code>:返回占据的内存字节长度</li> <li><code>DataView.prototype.byteOffset</code>:返回当前视图从对应的 ArrayBuffer 对象的哪个字节开始</li> </ul> <p><code>DataView</code>实例提供 8 个方法读取内存。</p> <ul> <li><strong><code>getInt8</code></strong>:读取 1 个字节,返回一个 8 位整数。</li> <li><strong><code>getUint8</code></strong>:读取 1 个字节,返回一个无符号的 8 位整数。</li> <li><strong><code>getInt16</code></strong>:读取 2 个字节,返回一个 16 位整数。</li> <li><strong><code>getUint16</code></strong>:读取 2 个字节,返回一个无符号的 16 位整数。</li> <li><strong><code>getInt32</code></strong>:读取 4 个字节,返回一个 32 位整数。</li> <li><strong><code>getUint32</code></strong>:读取 4 个字节,返回一个无符号的 32 位整数。</li> <li><strong><code>getFloat32</code></strong>:读取 4 个字节,返回一个 32 位浮点数。</li> <li><strong><code>getFloat64</code></strong>:读取 8 个字节,返回一个 64 位浮点数。</li> </ul> <p>arraybuffer的应用:</p> <p>传统上,服务器通过 AJAX 操作只能返回文本数据,即<code>responseType</code>属性默认为<code>text</code>。<code>XMLHttpRequest</code>第二版<code>XHR2</code>允许服务器返回二进制数据,这时分成两种情况。如果明确知道返回的二进制数据类型,可以把返回类型(<code>responseType</code>)设为<code>arraybuffer</code>;如果不知道,就设为<code>blob</code>。</p> <ol> <li> <p><code>let xhr = new XMLHttpRequest();</code></p> </li> <li> <p><code>xhr.open('GET', someUrl);</code></p> </li> <li> <p><code>xhr.responseType = 'arraybuffer';</code></p> </li> <li> <p><code>xhr.onload = function () {</code></p> </li> <li> <p><code>let arrayBuffer = xhr.response;</code></p> </li> <li> <p><code>// ···</code></p> </li> <li> <p><code>};</code></p> </li> <li> <p><code>xhr.send();</code></p> </li> </ol> <p>网页<code>Canvas</code>元素输出的二进制像素数据,就是 TypedArray 数组。</p> <ol> <li> <p><code>const canvas = document.getElementById('myCanvas');</code></p> </li> <li> <p><code>const ctx = canvas.getContext('2d');</code></p> </li> <li> <p><code>const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);</code></p> </li> <li> <p><code>const uint8ClampedArray = imageData.data;</code></p> </li> </ol> <p>ES2017 引入<code>SharedArrayBuffer</code>,允许 Worker 线程与主线程共享同一块内存。<code>SharedArrayBuffer</code>的 API 与<code>ArrayBuffer</code>一模一样,唯一的区别是后者无法共享数据。</p> <ol> <li> <p><code>// 主线程</code></p> </li> <li> <p><code>// 新建 1KB 共享内存</code></p> </li> <li> <p><code>const sharedBuffer = new SharedArrayBuffer(1024);</code></p> </li> <li> <p><code>// 主线程将共享内存的地址发送出去</code></p> </li> <li> <p><code>w.postMessage(sharedBuffer);</code></p> </li> <li> <p><code>// 在共享内存上建立视图,供写入数据</code></p> </li> <li> <p><code>const sharedArray = new Int32Array(sharedBuffer);</code></p> </li> </ol> <p>上面代码中,<code>postMessage</code>方法的参数是<code>SharedArrayBuffer</code>对象。</p> <p>Worker 线程从事件的<code>data</code>属性上面取到数据。</p> <ol> <li> <p><code>// Worker 线程</code></p> </li> <li> <p><code>onmessage = function (ev) {</code></p> </li> <li> <p><code>// 主线程共享的数据,就是 1KB 的共享内存</code></p> </li> <li> <p><code>const sharedBuffer = ev.data;</code></p> </li> <li> <p><code>// 在共享内存上建立视图,方便读写</code></p> </li> <li> <p><code>const sharedArray = new Int32Array(sharedBuffer);</code></p> </li> <li> <p><code>// ...</code></p> </li> <li> <p><code>};</code></p> </li> </ol> <p>共享内存也可以在 Worker 线程创建,发给主线程。</p> <p><code>SharedArrayBuffer</code>与<code>ArrayBuffer</code>一样,本身是无法读写的,必须在上面建立视图,然后通过视图读写。</p> <ol> <li> <p><code>// 分配 10 万个 32 位整数占据的内存空间</code></p> </li> <li> <p><code>const sab = new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT * 100000);</code></p> </li> <li> <p><code>// 建立 32 位整数视图</code></p> </li> <li> <p><code>const ia = new Int32Array(sab); // ia.length == 100000</code></p> </li> <li> <p><code>// 新建一个质数生成器</code></p> </li> <li> <p><code>const primes = new PrimeGenerator();</code></p> </li> <li> <p><code>// 将 10 万个质数,写入这段内存空间</code></p> </li> <li> <p><code>for ( let i=0 ; i < ia.length ; i++ )</code></p> </li> <li> <p><code>ia[i] = primes.next();</code></p> </li> <li> <p><code>// 向 Worker 线程发送这段共享内存</code></p> </li> <li> <p><code>w.postMessage(ia);</code></p> </li> </ol> <p>Worker 线程收到数据后的处理如下。</p> <ol> <li><code>// Worker 线程</code></li> <li><code>let ia;</code></li> <li><code>onmessage = function (ev) {</code></li> <li><code>ia = ev.data;</code></li> <li><code>console.log(ia.length); // 100000</code></li> <li><code>console.log(ia[37]); // 输出 163,因为这是第38个质数</code></li> <li><code>};</code></li> </ol> <p>多线程共享内存,最大的问题就是如何防止两个线程同时修改某个地址,或者说,当一个线程修改共享内存以后,必须有一个机制让其他线程同步。SharedArrayBuffer API 提供<code>Atomics</code>对象,保证所有共享内存的操作都是“原子性”的,并且可以在所有线程内同步。</p> <p>共享内存上面的某些运算是不能被打断的,即不能在运算过程中,让其他线程改写内存上面的值。Atomics 对象提供了一些运算方法,防止数据被改写。</p> <ol> <li><code>Atomics.add(sharedArray, index, value)</code></li> </ol> <p><code>Atomics.add</code>用于将<code>value</code>加到<code>sharedArray[index]</code>,返回<code>sharedArray[index]</code>旧的值。</p> <ol> <li><code>Atomics.sub(sharedArray, index, value)</code></li> </ol> <p><code>Atomics.sub</code>用于将<code>value</code>从<code>sharedArray[index]</code>减去,返回<code>sharedArray[index]</code>旧的值。</p> <ol> <li><code>Atomics.and(sharedArray, index, value)</code></li> </ol> <p><code>Atomics.and</code>用于将<code>value</code>与<code>sharedArray[index]</code>进行位运算<code>and</code>,放入<code>sharedArray[index]</code>,并返回旧的值。</p> <ol> <li><code>Atomics.or(sharedArray, index, value)</code></li> </ol> <p><code>Atomics.or</code>用于将<code>value</code>与<code>sharedArray[index]</code>进行位运算<code>or</code>,放入<code>sharedArray[index]</code>,并返回旧的值。</p> <ol> <li><code>Atomics.xor(sharedArray, index, value)</code></li> </ol> <p><code>Atomic.xor</code>用于将<code>vaule</code>与<code>sharedArray[index]</code>进行位运算<code>xor</code>,放入<code>sharedArray[index]</code>,并返回旧的值。</p> <p><strong>(5)其他方法</strong></p> <p><code>Atomics</code>对象还有以下方法。</p> <ul> <li><code>Atomics.compareExchange(sharedArray, index, oldval, newval)</code>:如果<code>sharedArray[index]</code>等于<code>oldval</code>,就写入<code>newval</code>,返回<code>oldval</code>。</li> <li><code>Atomics.isLockFree(size)</code>:返回一个布尔值,表示<code>Atomics</code>对象是否可以处理某个<code>size</code>的内存锁定。如果返回<code>false</code>,应用程序就需要自己来实现锁定。</li> </ul> <p><code>Atomics.compareExchange</code>的一个用途是,从 SharedArrayBuffer 读取一个值,然后对该值进行某个操作,操作结束以后,检查一下 SharedArrayBuffer 里面原来那个值是否发生变化(即被其他线程改写过)。如果没有改写过,就将它写回原来的位置,否则读取新的值,再重头进行一次操作。</p> <h4>29. 最新提案</h4> <p><strong>do 表达式</strong></p> <ol> <li> <p><code>// 等同于 <表达式></code></p> </li> <li> <p><code>do { <表达式>; }</code></p> </li> <li> <p><code>// 等同于 <语句></code></p> </li> <li> <p><code>do { <语句> }</code></p> </li> </ol> <p><code>do</code>表达式的好处是可以封装多个语句,让程序更加模块化,就像乐高积木那样一块块拼装起来。</p> <ol> <li><code>let x = do {</code></li> <li><code>if (foo()) { f() }</code></li> <li><code>else if (bar()) { g() }</code></li> <li><code>else { h() }</code></li> <li><code>};</code></li> </ol> <p>开发者使用一个模块时,有时需要知道模板本身的一些信息(比如模块的路径)。现在有一个提案,为 import 命令添加了一个元属性<code>import.meta</code>,返回当前模块的元信息。</p> <p><code>import.meta</code>只能在模块内部使用,如果在模块外部使用会报错。</p> <p>这个属性返回一个对象,该对象的各种属性就是当前运行的脚本的元信息。具体包含哪些属性,标准没有规定,由各个运行环境自行决定。一般来说,<code>import.meta</code>至少会有下面两个属性。</p> <p><strong>(1)import.meta.url</strong></p> <p><code>import.meta.url</code>返回当前模块的 URL 路径。举例来说,当前模块主文件的路径是<code>https://foo.com/main.js</code>,<code>import.meta.url</code>就返回这个路径。如果模块里面还有一个数据文件<code>data.txt</code>,那么就可以用下面的代码,获取这个数据文件的路径。</p> <ol> <li><code>new URL('data.txt', import.meta.url)</code></li> </ol> <p>注意,Node.js 环境中,<code>import.meta.url</code>返回的总是本地路径,即是<code>file:URL</code>协议的字符串,比如<code>file:///home/user/foo.js</code>。</p> <p><strong>(2)import.meta.scriptElement</strong></p> <p><code>import.meta.scriptElement</code>是浏览器特有的元属性,返回加载模块的那个<code><script></code>元素,相当于<code>document.currentScript</code>属性。</p> <ol> <li> <p><code>// HTML 代码为</code></p> </li> <li> <p><code>// <script type="module" src="my-module.js" data-foo="abc"></script></code></p> </li> <li> <p><code>// my-module.js 内部执行下面的代码</code></p> </li> <li> <p><code>import.meta.scriptElement.dataset.foo</code></p> </li> <li> <p><code>// "abc"</code></p> </li> </ol> <p>函数的部分执行有一些特别注意的地方。</p> <p>(1)函数的部分执行是基于原函数的。如果原函数发生变化,部分执行生成的新函数也会立即反映这种变化。</p> <p>(2)如果预先提供的那个值是一个表达式,那么这个表达式并不会在定义时求值,而是在每次调用时求值。</p> <p>(3)如果新函数的参数多于占位符的数量,那么多余的参数将被忽略。</p> <p>(4)<code>...</code>只会被采集一次,如果函数的部分执行使用了多个<code>...</code>,那么每个<code>...</code>的值都将相同。</p> <p>JavaScript 的管道是一个运算符,写作<code>|></code>。它的左边是一个表达式,右边是一个函数。管道运算符把左边表达式的值,传入右边的函数进行求值。</p> <ol> <li><code>x |> f</code></li> <li><code>// 等同于</code></li> <li><code>f(x)</code></li> </ol> <p>数值分割:</p> <ol> <li> <p><code>123_00 === 12_300 // true</code></p> </li> <li> <p><code>12345_00 === 123_4500 // true</code></p> </li> <li> <p><code>12345_00 === 1_234_500 // true</code></p> </li> </ol> <p>数值分隔符有几个使用注意点。</p> <ul> <li>不能在数值的最前面(leading)或最后面(trailing)。</li> <li>不能两个或两个以上的分隔符连在一起。</li> <li>小数点的前后不能有分隔符。</li> <li>科学计数法里面,表示指数的<code>e</code>或<code>E</code>前后不能有分隔符。</li> </ul> <p><code>Math.sign()</code>用来判断一个值的正负,但是如果参数是<code>-0</code>,它会返回<code>-0</code>。</p> <ol> <li> <p><code>Math.sign(-0) // -0</code></p> </li> <li> <p><code>Math.signbit(2) //false</code></p> </li> <li> <p><code>Math.signbit(-2) //true</code></p> </li> <li> <p><code>Math.signbit(0) //false</code></p> </li> <li> <p><code>Math.signbit(-0) //true</code></p> </li> </ol> <h5>双冒号运算符</h5> <p>箭头函数可以绑定<code>this</code>对象,大大减少了显式绑定<code>this</code>对象的写法(<code>call</code>、<code>apply</code>、<code>bind</code>)。但是,箭头函数并不适用于所有场合,所以现在有一个提案,提出了“函数绑定”(function bind)运算符,用来取代<code>call</code>、<code>apply</code>、<code>bind</code>调用。</p> <p>函数绑定运算符是并排的两个冒号(<code>::</code>),双冒号左边是一个对象,右边是一个函数。该运算符会自动将左边的对象,作为上下文环境(即<code>this</code>对象),绑定到右边的函数上面。</p> <ol> <li> <p><code>foo::bar;</code></p> </li> <li> <p><code>// 等同于</code></p> </li> <li> <p><code>bar.bind(foo);</code></p> </li> <li> <p><code>foo::bar(...arguments);</code></p> </li> <li> <p><code>// 等同于</code></p> </li> <li> <p><code>bar.apply(foo, arguments);</code></p> </li> <li> <p><code>const hasOwnProperty = Object.prototype.hasOwnProperty;</code></p> </li> <li> <p><code>function hasOwn(obj, key) {</code></p> </li> <li> <p><code>return obj::hasOwnProperty(key);</code></p> </li> <li> <p><code>}</code></p> </li> </ol> <p>如果双冒号左边为空,右边是一个对象的方法,则等于将该方法绑定在该对象上面。</p> <ol> <li> <p><code>var method = obj::obj.foo;</code></p> </li> <li> <p><code>// 等同于</code></p> </li> <li> <p><code>var method = ::obj.foo;</code></p> </li> <li> <p><code>let log = ::console.log;</code></p> </li> <li> <p><code>// 等同于</code></p> </li> <li> <p><code>var log = console.log.bind(console);</code></p> </li> </ol> <p>如果双冒号运算符的运算结果,还是一个对象,就可以采用链式写法。</p> <ol> <li> <p><code>import { map, takeWhile, forEach } from "iterlib";</code></p> </li> <li> <p><code>getPlayers()</code></p> </li> <li> <p><code>::map(x => x.character())</code></p> </li> <li> <p><code>::takeWhile(x => x.strength > 100)</code></p> </li> <li> <p><code>::forEach(x => console.log(x));</code></p> </li> </ol> <h4>30. Decorator</h4> <p>装饰器可以用来装饰整个类。</p> <ol> <li> <p><code>function testable(isTestable) {</code></p> </li> <li> <p><code>return function(target) {</code></p> </li> <li> <pre><code>`target.isTestable = isTestable;` </code></pre> </li> <li> <p><code>}</code></p> </li> <li> <p><code>}</code></p> </li> <li> <p><code>@testable(true)</code></p> </li> <li> <p><code>class MyTestableClass {}</code></p> </li> <li> <p><code>MyTestableClass.isTestable // true</code></p> </li> <li> <p><code>@testable(false)</code></p> </li> <li> <p><code>class MyClass {}</code></p> </li> <li> <p><code>MyClass.isTestable // false</code></p> </li> </ol> <p>上面代码中,<code>@testable</code>就是一个装饰器。它修改了<code>MyTestableClass</code>这个类的行为,为它加上了静态属性<code>isTestable</code>。<code>testable</code>函数的参数<code>target</code>是<code>MyTestableClass</code>类本身。</p> <p>装饰器不仅可以装饰类,还可以装饰类的属性。</p> <ol> <li><code>class Person {</code></li> <li><code>@readonly</code></li> <li><code>name() { return `${this.first} ${this.last}` }</code></li> <li><code>}</code></li> </ol> <p>上面代码中,装饰器<code>readonly</code>用来装饰“类”的<code>name</code>方法。</p> <p>装饰器函数<code>readonly</code>一共可以接受三个参数。</p> <ol> <li> <p><code>function readonly(target, name, descriptor){</code></p> </li> <li> <p><code>// descriptor对象原来的值如下</code></p> </li> <li> <p><code>// {</code></p> </li> <li> <p><code>// value: specifiedFunction,</code></p> </li> <li> <p><code>// enumerable: false,</code></p> </li> <li> <p><code>// configurable: true,</code></p> </li> <li> <p><code>// writable: true</code></p> </li> <li> <p><code>// };</code></p> </li> <li> <p><code>descriptor.writable = false;</code></p> </li> <li> <p><code>return descriptor;</code></p> </li> <li> <p><code>}</code></p> </li> <li> <p><code>readonly(Person.prototype, 'name', descriptor);</code></p> </li> <li> <p><code>// 类似于</code></p> </li> <li> <p><code>Object.defineProperty(Person.prototype, 'name', descriptor);</code></p> </li> </ol> <p>装饰器第一个参数是类的原型对象,上例是<code>Person.prototype</code>,装饰器的本意是要“装饰”类的实例,但是这个时候实例还没生成,所以只能去装饰原型(这不同于类的装饰,那种情况时<code>target</code>参数指的是类本身);第二个参数是所要装饰的属性名,第三个参数是该属性的描述对象。</p> <p>另外,上面代码说明,装饰器(readonly)会修改属性的描述对象(descriptor),然后被修改的描述对象再用来定义属性。</p> <p>装饰器只适用于类和类的方法,并不适用于函数</p> <h5>core-decorators.js</h5> <p>core-decorators.js是一个第三方模块,提供了几个常见的装饰器,通过它可以更好地理解装饰器。</p> <p><code>autobind</code>装饰器使得方法中的<code>this</code>对象,绑定原始对象。</p> <p><code>readonly</code>装饰器使得属性或方法不可写。</p> <p><code>override</code>装饰器检查子类的方法,是否正确覆盖了父类的同名方法,如果不正确会报错。</p> <p><code>deprecate</code>或<code>deprecated</code>装饰器在控制台显示一条警告,表示该方法将废除。</p> <p><code>suppressWarnings</code>装饰器抑制<code>deprecated</code>装饰器导致的<code>console.warn()</code>调用。但是,异步代码发出的调用除外。</p> <p>在装饰器的基础上,可以实现<code>Mixin</code>模式。所谓<code>Mixin</code>模式,就是对象继承的一种替代方案,中文译为“混入”(mix in),意为在一个对象之中混入另外一个对象的方法。</p> <p>方法一:</p> <ol> <li> <p><code>const Foo = {</code></p> </li> <li> <p><code>foo() { console.log('foo') }</code></p> </li> <li> <p><code>};</code></p> </li> <li> <p><code>class MyClass {}</code></p> </li> <li> <p><code>Object.assign(MyClass.prototype, Foo);</code></p> </li> <li> <p><code>let obj = new MyClass();</code></p> </li> <li> <p><code>obj.foo() // 'foo'</code></p> </li> </ol> <p>方法二:<br> 部署一个通用脚本<code>mixins.js</code>,将 Mixin 写成一个装饰器。</p> <ol> <li><code>export function mixins(...list) {</code></li> <li><code>return function (target) {</code></li> <li> <pre><code>`Object.assign(target.prototype, ...list);` </code></pre> </li> <li><code>};</code></li> <li><code>}</code></li> </ol> <p>然后,就可以使用上面这个装饰器,为类“混入”各种方法。</p> <ol> <li> <p><code>import { mixins } from './mixins';</code></p> </li> <li> <p><code>const Foo = {</code></p> </li> <li> <p><code>foo() { console.log('foo') }</code></p> </li> <li> <p><code>};</code></p> </li> <li> <p><code>@mixins(Foo)</code></p> </li> <li> <p><code>class MyClass {}</code></p> </li> <li> <p><code>let obj = new MyClass();</code></p> </li> <li> <p><code>obj.foo() // "foo"</code></p> </li> </ol> <p>Trait 也是一种装饰器,效果与 Mixin 类似,但是提供更多功能,比如防止同名方法的冲突、排除混入某些方法、为混入的方法起别名等等。</p> <p>下面采用traits-decorator这个第三方模块作为例子。这个模块提供的<code>traits</code>装饰器,不仅可以接受对象,还可以接受 ES6 类作为参数。</p> <ol> <li> <p><code>import { traits } from 'traits-decorator';</code></p> </li> <li> <p><code>class TFoo {</code></p> </li> <li> <p><code>foo() { console.log('foo') }</code></p> </li> <li> <p><code>}</code></p> </li> <li> <p><code>const TBar = {</code></p> </li> <li> <p><code>bar() { console.log('bar') }</code></p> </li> <li> <p><code>};</code></p> </li> <li> <p><code>@traits(TFoo, TBar)</code></p> </li> <li> <p><code>class MyClass { }</code></p> </li> <li> <p><code>let obj = new MyClass();</code></p> </li> <li> <p><code>obj.foo() // foo</code></p> </li> <li> <p><code>obj.bar() // bar</code></p> </li> </ol> <h4>31. 参考链接</h4> <h5>官方文件</h5> <ul> <li>ECMAScript® 2015 Language Specification: ECMAScript 2015 规格</li> <li>ECMAScript® 2016 Language Specification: ECMAScript 2016 规格</li> <li>ECMAScript® 2017 Language Specification:ECMAScript 2017 规格(草案)</li> <li>ECMAScript Current Proposals: ECMAScript 当前的所有提案</li> <li>ECMAScript Active Proposals: 已经进入正式流程的提案</li> <li>ECMAScript proposals:从阶段 0 到阶段 4 的所有提案列表</li> <li>TC39 meeting agendas: TC39 委员会历年的会议记录</li> <li>ECMAScript Daily: TC39 委员会的动态</li> <li>The TC39 Process: 提案进入正式规格的流程</li> <li>TC39: A Process Sketch, Stages 0 and 1: Stage 0 和 Stage 1 的含义</li> <li>TC39 Process Sketch, Stage 2: Stage 2 的含义</li> </ul> <h5>综合介绍</h5> <ul> <li>Axel Rauschmayer, Exploring ES6: Upgrade to the next version of JavaScript: ES6 的专著,本书的许多代码实例来自该书</li> <li>Sayanee Basu, Use ECMAScript 6 Today</li> <li>Ariya Hidayat, Toward Modern Web Apps with ECMAScript 6</li> <li>Dale Schouten, 10 Ecmascript-6 tricks you can perform right now</li> <li>Colin Toh, Lightweight ES6 Features That Pack A Punch: ES6 的一些“轻量级”的特性介绍</li> <li>Domenic Denicola, ES6: The Awesome Parts</li> <li>Nicholas C. Zakas, Understanding ECMAScript 6</li> <li>Justin Drake, ECMAScript 6 in Node.JS</li> <li>Ryan Dao, Summary of ECMAScript 6 major features</li> <li>Luke Hoban, ES6 features: ES6 新语法点的罗列</li> <li>Traceur-compiler, Language Features: Traceur 文档列出的一些 ES6 例子</li> <li>Axel Rauschmayer, ECMAScript 6: what’s next for JavaScript?: 关于 ES6 新增语法的综合介绍,有很多例子</li> <li>Axel Rauschmayer, Getting started with ECMAScript 6: ES6 语法点的综合介绍</li> <li>Toby Ho, ES6 in io.js</li> <li>Guillermo Rauch, ECMAScript 6</li> <li>Benjamin De Cock, Frontend Guidelines: ES6 最佳实践</li> <li>Jani Hartikainen, ES6: What are the benefits of the new features in practice?</li> <li>kangax, JavaScript quiz. ES6 edition: ES6 小测试</li> <li>Jeremy Fairbank, HTML5DevConf ES7 and Beyond!: ES7 新增语法点介绍</li> <li>Timothy Gu, How to Read the ECMAScript Specification: 如何读懂 ES6 规格</li> </ul> <h5>let 和 const</h5> <ul> <li>Kyle Simpson, For and against let: 讨论 let 命令的作用域</li> <li>kangax, Why typeof is no longer “safe”: 讨论在块级作用域内,let 命令的变量声明和赋值的行为</li> <li>Axel Rauschmayer, Variables and scoping in ECMAScript 6: 讨论块级作用域与 let 和 const 的行为</li> <li>Nicolas Bevacqua, ES6 Let, Const and the “Temporal Dead Zone” (TDZ) in Depth</li> <li>acorn, Function statements in strict mode: 块级作用域对严格模式的函数声明的影响</li> <li>Axel Rauschmayer, ES proposal: global: 顶层对象<code>global</code></li> <li>Mathias Bynens, A horrifying <code>globalThis</code> polyfill in universal JavaScript:如何写 globalThis 的垫片库</li> </ul> <h5>解构赋值</h5> <ul> <li>Nick Fitzgerald, Destructuring Assignment in ECMAScript 6: 详细介绍解构赋值的用法</li> <li>Nicholas C. Zakas, ECMAScript 6 destructuring gotcha</li> </ul> <h5>字符串</h5> <ul> <li>Nicholas C. Zakas, A critical review of ECMAScript 6 quasi-literals</li> <li>Mozilla Developer Network, Template strings</li> <li>Addy Osmani, Getting Literal With ES6 Template Strings: 模板字符串的介绍</li> <li>Blake Winton, ES6 Templates: 模板字符串的介绍</li> <li>Peter Jaszkowiak, How to write a template compiler in JavaScript: 使用模板字符串,编写一个模板编译函数</li> <li>Axel Rauschmayer, ES.stage3: string padding</li> </ul> <h5>正则</h5> <ul> <li>Mathias Bynens, Unicode-aware regular expressions in ES6: 详细介绍正则表达式的 u 修饰符</li> <li>Axel Rauschmayer, New regular expression features in ECMAScript 6:ES6 正则特性的详细介绍</li> <li>Yang Guo, RegExp lookbehind assertions:介绍后行断言</li> <li>Axel Rauschmayer, ES proposal: RegExp named capture groups: 具名组匹配的介绍</li> <li>Mathias Bynens, ECMAScript regular expressions are getting better!: 介绍 ES2018 添加的多项正则语法</li> </ul> <h5>数值</h5> <ul> <li>Nicolas Bevacqua, ES6 Number Improvements in Depth</li> <li>Axel Rauschmayer, ES proposal: arbitrary precision integers</li> <li>Mathias Bynens, BigInt: arbitrary-precision integers in JavaScript</li> </ul> <h5>数组</h5> <ul> <li>Axel Rauschmayer, ECMAScript 6’s new array methods: 对 ES6 新增的数组方法的全面介绍</li> <li>TC39, Array.prototype.includes: 数组的 includes 方法的规格</li> <li>Axel Rauschmayer, ECMAScript 6: holes in Arrays: 数组的空位问题</li> </ul> <h5>函数</h5> <ul> <li>Nicholas C. Zakas, Understanding ECMAScript 6 arrow functions</li> <li>Jack Franklin, Real Life ES6 - Arrow Functions</li> <li>Axel Rauschmayer, Handling required parameters in ECMAScript 6</li> <li>Dmitry Soshnikov, ES6 Notes: Default values of parameters: 介绍参数的默认值</li> <li>Ragan Wald, Destructuring and Recursion in ES6: rest 参数和扩展运算符的详细介绍</li> <li>Axel Rauschmayer, The names of functions in ES6: 函数的 name 属性的详细介绍</li> <li>Kyle Simpson, Arrow This: 箭头函数并没有自己的 this</li> <li>Derick Bailey, Do ES6 Arrow Functions Really Solve “this” In JavaScript?:使用箭头函数处理 this 指向,必须非常小心</li> <li>Mark McDonnell, Understanding recursion in functional JavaScript programming: 如何自己实现尾递归优化</li> <li>Nicholas C. Zakas, The ECMAScript 2016 change you probably don’t know: 使用参数默认值时,不能在函数内部显式开启严格模式</li> <li>Axel Rauschmayer, ES proposal: optional catch binding</li> <li>Cynthia Lee, When you should use ES6 arrow functions — and when you shouldn’t: 讨论箭头函数的适用场合</li> <li>Eric Elliott, What is this?: 箭头函数内部的 this 的解释。</li> </ul> <h5>对象</h5> <ul> <li>Addy Osmani, Data-binding Revolutions with Object.observe(): 介绍 Object.observe()的概念</li> <li>Sella Rafaeli, Native JavaScript Data-Binding: 如何使用 Object.observe 方法,实现数据对象与 DOM 对象的双向绑定</li> <li>Axel Rauschmayer, <code>__proto__</code> in ECMAScript 6</li> <li>Axel Rauschmayer, Enumerability in ECMAScript 6</li> <li>Axel Rauschmayer, ES proposal: Object.getOwnPropertyDescriptors()</li> <li>TC39, Object.getOwnPropertyDescriptors Proposal</li> <li>David Titarenco, How Spread Syntax Breaks JavaScript: 扩展运算符的一些不合理的地方</li> </ul> <h5>Symbol</h5> <ul> <li>Axel Rauschmayer, Symbols in ECMAScript 6: Symbol 简介</li> <li>MDN, Symbol: Symbol 类型的详细介绍</li> <li>Jason Orendorff, ES6 In Depth: Symbols</li> <li>Keith Cirkel, Metaprogramming in ES6: Symbols and why they’re awesome: Symbol 的深入介绍</li> <li>Axel Rauschmayer, Customizing ES6 via well-known symbols</li> <li>Derick Bailey, Creating A True Singleton In Node.js, With ES6 Symbols</li> <li>Das Surma, How to read web specs Part IIa – Or: ECMAScript Symbols: 介绍 Symbol 的规格</li> </ul> <h5>Set 和 Map</h5> <ul> <li>Mozilla Developer Network, WeakSet:介绍 WeakSet 数据结构</li> <li>Dwayne Charrington, What Are Weakmaps In ES6?: WeakMap 数据结构介绍</li> <li>Axel Rauschmayer, ECMAScript 6: maps and sets: Set 和 Map 结构的详细介绍</li> <li>Jason Orendorff, ES6 In Depth: Collections:Set 和 Map 结构的设计思想</li> <li>Axel Rauschmayer, Converting ES6 Maps to and from JSON: 如何将 Map 与其他数据结构互相转换</li> </ul> <h5>Proxy 和 Reflect</h5> <ul> <li>Nicholas C. Zakas, Creating defensive objects with ES6 proxies</li> <li>Axel Rauschmayer, Meta programming with ECMAScript 6 proxies: Proxy 详解</li> <li>Daniel Zautner, Meta-programming JavaScript Using Proxies: 使用 Proxy 实现元编程</li> <li>Tom Van Cutsem, Harmony-reflect: Reflect 对象的设计目的</li> <li>Tom Van Cutsem, Proxy Traps: Proxy 拦截操作一览</li> <li>Tom Van Cutsem, Reflect API</li> <li>Tom Van Cutsem, Proxy Handler API</li> <li>Nicolas Bevacqua, ES6 Proxies in Depth</li> <li>Nicolas Bevacqua, ES6 Proxy Traps in Depth</li> <li>Nicolas Bevacqua, More ES6 Proxy Traps in Depth</li> <li>Axel Rauschmayer, Pitfall: not all objects can be wrapped transparently by proxies</li> <li>Bertalan Miklos, Writing a JavaScript Framework - Data Binding with ES6 Proxies: 使用 Proxy 实现观察者模式</li> <li>Keith Cirkel, Metaprogramming in ES6: Part 2 - Reflect: Reflect API 的详细介绍</li> </ul> <h5>Promise 对象</h5> <ul> <li>Jake Archibald, JavaScript Promises: There and back again</li> <li>Jake Archibald, Tasks, microtasks, queues and schedules</li> <li>Tilde, rsvp.js</li> <li>Sandeep Panda, An Overview of JavaScript Promises: ES6 Promise 入门介绍</li> <li>Dave Atchley, ES6 Promises: Promise 的语法介绍</li> <li>Axel Rauschmayer, ECMAScript 6 promises (2/2): the API: 对 ES6 Promise 规格和用法的详细介绍</li> <li>Jack Franklin, Embracing Promises in JavaScript: catch 方法的例子</li> <li>Ronald Chen, How to escape Promise Hell: 如何使用<code>Promise.all</code>方法的一些很好的例子</li> <li>Jordan Harband, proposal-promise-try: Promise.try() 方法的提案</li> <li>Sven Slootweg, What is Promise.try, and why does it matter?: Promise.try() 方法的优点</li> <li>Yehuda Katz, TC39: Promises, Promises: Promise.try() 的用处</li> </ul> <h5>Iterator</h5> <ul> <li>Mozilla Developer Network, Iterators and generators</li> <li>Mozilla Developer Network, The Iterator protocol</li> <li>Jason Orendorff, ES6 In Depth: Iterators and the for-of loop: 遍历器与 for…of 循环的介绍</li> <li>Axel Rauschmayer, Iterators and generators in ECMAScript 6: 探讨 Iterator 和 Generator 的设计目的</li> <li>Axel Rauschmayer, Iterables and iterators in ECMAScript 6: Iterator 的详细介绍</li> <li>Kyle Simpson, Iterating ES6 Numbers: 在数值对象上部署遍历器</li> </ul> <h5>Generator</h5> <ul> <li>Matt Baker, Replacing callbacks with ES6 Generators</li> <li>Steven Sanderson, Experiments with Koa and JavaScript Generators</li> <li>jmar777, What’s the Big Deal with Generators?</li> <li>Marc Harter, Generators in Node.js: Common Misconceptions and Three Good Use Cases: 讨论 Generator 函数的作用</li> <li>StackOverflow, ES6 yield : what happens to the arguments of the first call next()?: 第一次使用 next 方法时不能带有参数</li> <li>Kyle Simpson, ES6 Generators: Complete Series: 由浅入深探讨 Generator 的系列文章,共四篇</li> <li>Gajus Kuizinas, The Definitive Guide to the JavaScript Generators: 对 Generator 的综合介绍</li> <li>Jan Krems, Generators Are Like Arrays: 讨论 Generator 可以被当作数据结构看待</li> <li>Harold Cooper, Coroutine Event Loops in JavaScript: Generator 用于实现状态机</li> <li>Ruslan Ismagilov, learn-generators: 编程练习,共 6 道题</li> <li>Steven Sanderson, Experiments with Koa and JavaScript Generators: Generator 入门介绍,以 Koa 框架为例</li> <li>Mahdi Dibaiee, ES7 Array and Generator comprehensions:ES7 的 Generator 推导</li> <li>Nicolas Bevacqua, ES6 Generators in Depth</li> <li>Axel Rauschmayer, ES6 generators in depth: Generator 规格的详尽讲解</li> <li>Derick Bailey, Using ES6 Generators To Short-Circuit Hierarchical Data Iteration:使用 for…of 循环完成预定的操作步骤</li> </ul> <h5>异步操作和 Async 函数</h5> <ul> <li>Luke Hoban, Async Functions for ECMAScript: Async 函数的设计思想,与 Promise、Gernerator 函数的关系</li> <li>Jafar Husain, Asynchronous Generators for ES7: Async 函数的深入讨论</li> <li>Nolan Lawson, Taming the asynchronous beast with ES7: async 函数通俗的实例讲解</li> <li>Jafar Husain, Async Generators: 对 async 与 Generator 混合使用的一些讨论</li> <li>Daniel Brain, Understand promises before you start using async/await: 讨论 async/await 与 Promise 的关系</li> <li>Jake Archibald, Async functions - making promises friendly</li> <li>Axel Rauschmayer, ES proposal: asynchronous iteration: 异步遍历器的详细介绍</li> <li>Dima Grossman, How to write async await without try-catch blocks in JavaScript: 除了 try/catch 以外的 async 函数内部捕捉错误的方法</li> <li>Mostafa Gaafa, 6 Reasons Why JavaScript’s Async/Await Blows Promises Away: Async 函数的6个好处</li> <li>Mathias Bynens, Asynchronous stack traces: why await beats Promise#then(): async 函数可以保留错误堆栈</li> </ul> <h5>Class</h5> <ul> <li>Sebastian Porto, ES6 classes and JavaScript prototypes: ES6 Class 的写法与 ES5 Prototype 的写法对比</li> <li>Jack Franklin, An introduction to ES6 classes: ES6 class 的入门介绍</li> <li>Axel Rauschmayer, ECMAScript 6: new OOP features besides classes</li> <li>Axel Rauschmayer, Classes in ECMAScript 6 (final semantics): Class 语法的详细介绍和设计思想分析</li> <li>Eric Faust, ES6 In Depth: Subclassing: Class 语法的深入介绍</li> <li>Nicolás Bevacqua, Binding Methods to Class Instance Objects: 如何绑定类的实例中的 this</li> <li>Jamie Kyle, JavaScript’s new #private class fields:私有属性的介绍。</li> <li>Mathias Bynens, Public and private class fields:实例属性的新写法的介绍。</li> </ul> <h5>Decorator</h5> <ul> <li>Maximiliano Fierro, Declarative vs Imperative: Decorators 和 Mixin 介绍</li> <li>Justin Fagnani, “Real” Mixins with JavaScript Classes: 使用类的继承实现 Mixin</li> <li>Addy Osmani, Exploring ES2016 Decorators: Decorator 的深入介绍</li> <li>Sebastian McKenzie, Allow decorators for functions as well: 为什么修饰器不能用于函数</li> <li>Maximiliano Fierro, Traits with ES7 Decorators: Trait 的用法介绍</li> <li>Jonathan Creamer: Using ES2016 Decorators to Publish on an Event Bus: 使用修饰器实现自动发布事件</li> </ul> <h5>Module</h5> <ul> <li>Jack Franklin, JavaScript Modules the ES6 Way: ES6 模块入门</li> <li>Axel Rauschmayer, ECMAScript 6 modules: the final syntax: ES6 模块的介绍,以及与 CommonJS 规格的详细比较</li> <li>Dave Herman, Static module resolution: ES6 模块的静态化设计思想</li> <li>Jason Orendorff, ES6 In Depth: Modules: ES6 模块设计思想的介绍</li> <li>Ben Newman, The Importance of import and export: ES6 模块的设计思想</li> <li>ESDiscuss, Why is “export default var a = 1;” invalid syntax?</li> <li>Bradley Meck, ES6 Module Interoperability: 介绍 Node 如何处理 ES6 语法加载 CommonJS 模块</li> <li>Axel Rauschmayer, Making transpiled ES modules more spec-compliant: ES6 模块编译成 CommonJS 模块的详细介绍</li> <li>Axel Rauschmayer, ES proposal: import() – dynamically importing ES modules: import() 的用法</li> <li>Node EPS, ES Module Interoperability: Node 对 ES6 模块的处理规格</li> </ul> <h5>二进制数组</h5> <ul> <li>Ilmari Heikkinen, Typed Arrays: Binary Data in the Browser</li> <li>Khronos, Typed Array Specification</li> <li>Ian Elliot, Reading A BMP File In JavaScript</li> <li>Renato Mangini, How to convert ArrayBuffer to and from String</li> <li>Axel Rauschmayer, Typed Arrays in ECMAScript 6</li> <li>Axel Rauschmayer, ES proposal: Shared memory and atomics</li> <li>Lin Clark, Avoiding race conditions in SharedArrayBuffers with Atomics: Atomics 对象使用场景的解释</li> <li>Lars T Hansen, Shared memory - a brief tutorial</li> <li>James Milner, The Return of SharedArrayBuffers and Atomics</li> </ul> <h5>SIMD</h5> <ul> <li>TC39, SIMD.js Stage 2</li> <li>MDN, SIMD</li> <li>TC39, ECMAScript SIMD</li> <li>Axel Rauschmayer, JavaScript gains support for SIMD</li> </ul> <h5>工具</h5> <ul> <li>Babel, Babel Handbook: Babel 的用法介绍</li> <li>Google, traceur-compiler: Traceur 编译器</li> <li>Casper Beyer, ECMAScript 6 Features and Tools</li> <li>Stoyan Stefanov, Writing ES6 today with jstransform</li> <li>ES6 Module Loader, ES6 Module Loader Polyfill: 在浏览器和 node.js 加载 ES6 模块的一个库,文档里对 ES6 模块有详细解释</li> <li>Paul Miller, es6-shim: 一个针对老式浏览器,模拟 ES6 部分功能的垫片库(shim)</li> <li>army8735, JavaScript Downcast: 国产的 ES6 到 ES5 的转码器</li> <li>esnext, ES6 Module Transpiler:基于 node.js 的将 ES6 模块转为 ES5 代码的命令行工具</li> <li>Sebastian McKenzie, BabelJS: ES6 转译器</li> <li>SystemJS, SystemJS: 在浏览器中加载 AMD、CJS、ES6 模块的一个垫片库</li> <li>Modernizr, HTML5 Cross Browser Polyfills: ES6 垫片库清单</li> <li>Facebook, regenerator: 将 Generator 函数转为 ES5 的转码器</li> </ul> <h4>32. Mixin</h4> <p>JavaScript 语言的设计是单一继承,即子类只能继承一个父类,不允许继承多个父类。这种设计保证了对象继承的层次结构是树状的,而不是复杂的网状结构。</p> <p>但是,这大大降低了编程的灵活性。因为实际开发中,有时不可避免,子类需要继承多个父类。举例来说,“猫”可以继承“哺乳类动物”,也可以继承“宠物”。</p> <p>这里使用mixin和trait解决</p> <h4>33. SIMD</h4> <p>SIMD(发音<code>/sim-dee/</code>)是“Single Instruction/Multiple Data”的缩写,意为“单指令,多数据”。它是 JavaScript 操作 CPU 对应指令的接口,你可以看做这是一种不同的运算执行模式。与它相对的是 SISD(“Single Instruction/Single Data”),即“单指令,单数据”。</p> <p>SIMD 的含义是使用一个指令,完成多个数据的运算;SISD 的含义是使用一个指令,完成单个数据的运算,这是 JavaScript 的默认运算模式。显而易见,SIMD 的执行效率要高于 SISD,所以被广泛用于 3D 图形运算、物理模拟等运算量超大的项目之中。</p> <p>总的来说,SIMD 是数据并行处理(parallelism)的一种手段,可以加速一些运算密集型操作的速度。将来与 WebAssembly 结合以后,可以让 JavaScript 达到二进制代码的运行速度。</p> <h4>34. 函数式编程</h4> <p>柯里化(currying)指的是将一个多参数的函数拆分成一系列函数,每个拆分后的函数都只接受一个参数(unary)。</p> <ol> <li> <p><code>function add (a) {</code></p> </li> <li> <p><code>return function (b) {</code></p> </li> <li> <pre><code>`return a + b;` </code></pre> </li> <li> <p><code>}</code></p> </li> <li> <p><code>}</code></p> </li> <li> <p><code>// 或者采用箭头函数写法</code></p> </li> <li> <p><code>const add = x => y => x + y;</code></p> </li> <li> <p><code>const f = add(1);</code></p> </li> <li> <p><code>f(1) // 2</code></p> </li> </ol> <p>函数合成(function composition)指的是,将多个函数合成一个函数。</p> <ol> <li> <p><code>const compose = f => g => x => f(g(x));</code></p> </li> <li> <p><code>const f = compose (x => x * 4) (x => x + 3);</code></p> </li> <li> <p><code>f(2) // 20</code></p> </li> </ol> <p>参数倒置(flip)指的是改变函数前两个参数的顺序。</p> <ol> <li> <p><code>let f = {};</code></p> </li> <li> <p><code>f.flip =</code></p> </li> <li> <p><code>fn =></code></p> </li> <li> <pre><code>`(a, b, ...args) => fn(b, a, ...args.reverse());` </code></pre> </li> <li> <p><code>var divide = (a, b) => a / b;</code></p> </li> <li> <p><code>var flip = f.flip(divide);</code></p> </li> <li> <p><code>flip(10, 5) // 0.5</code></p> </li> <li> <p><code>flip(1, 10) // 10</code></p> </li> <li> <p><code>var three = (a, b, c) => [a, b, c];</code></p> </li> <li> <p><code>var flip = f.flip(three);</code></p> </li> <li> <p><code>flip(1, 2, 3); // => [2, 1, 3]</code></p> </li> </ol> <p>执行边界(until)指的是函数执行到满足条件为止。</p> <ol> <li> <p><code>let f = {};</code></p> </li> <li> <p><code>f.until = (condition, f) =></code></p> </li> <li> <p><code>(...args) => {</code></p> </li> <li> <pre><code>`var r = f.apply(null, args);` </code></pre> </li> <li> <pre><code>`return condition(r) ? r : f.until(condition, f)(r);` </code></pre> </li> <li> <p><code>};</code></p> </li> <li> <p><code>let condition = x => x > 100;</code></p> </li> <li> <p><code>let inc = x => x + 1;</code></p> </li> <li> <p><code>let until = f.until(condition, inc);</code></p> </li> <li> <p><code>until(0) // 101</code></p> </li> <li> <p><code>condition = x => x === 5;</code></p> </li> <li> <p><code>until = f.until(condition, inc);</code></p> </li> <li> <p><code>until(3) // 5</code></p> </li> </ol> <p>Mateo Gianolio, Haskell in ES6: Part 1</p> <p><code>next()</code>、<code>throw()</code>、<code>return()</code>这三个方法本质上是同一件事,可以放在一起理解。它们的作用都是让 Generator 函数恢复执行,并且使用不同的语句替换<code>yield</code>表达式。</p> </div> </div>���� </div> </div> </div> <!--PC和WAP自适应版--> <div id="SOHUCS" sid="1748241585121804288"></div> <script type="text/javascript" src="/views/front/js/chanyan.js"></script> <!-- 文章页-底部 动态广告位 --> <div class="youdao-fixed-ad" id="detail_ad_bottom"></div> </div> <div class="col-md-3"> <div class="row" id="ad"> <!-- 文章页-右侧1 动态广告位 --> <div id="right-1" class="col-lg-12 col-md-12 col-sm-4 col-xs-4 ad"> <div class="youdao-fixed-ad" id="detail_ad_1"> </div> </div> <!-- 文章页-右侧2 动态广告位 --> <div id="right-2" class="col-lg-12 col-md-12 col-sm-4 col-xs-4 ad"> <div class="youdao-fixed-ad" id="detail_ad_2"></div> </div> <!-- 文章页-右侧3 动态广告位 --> <div id="right-3" class="col-lg-12 col-md-12 col-sm-4 col-xs-4 ad"> <div class="youdao-fixed-ad" id="detail_ad_3"></div> </div> </div> </div> </div> </div> </div> <div class="container"> <h4 class="pt20 mb15 mt0 border-top">你可能感兴趣的:(前端,es6,javascript,前端)</h4> <div id="paradigm-article-related"> <div class="recommend-post mb30"> <ul class="widget-links"> <li><a href="/article/1835509897106649088.htm" title="Long类型前后端数据不一致" target="_blank">Long类型前后端数据不一致</a> <span class="text-muted">igotyback</span> <a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF/1.htm">前端</a> <div>响应给前端的数据浏览器控制台中response中看到的Long类型的数据是正常的到前端数据不一致前后端数据类型不匹配是一个常见问题,尤其是当后端使用Java的Long类型(64位)与前端JavaScript的Number类型(最大安全整数为2^53-1,即16位)进行数据交互时,很容易出现精度丢失的问题。这是因为JavaScript中的Number类型无法安全地表示超过16位的整数。为了解决这个问</div> </li> <li><a href="/article/1835498925755297792.htm" title="DIV+CSS+JavaScript技术制作网页(旅游主题网页设计与制作)云南大理" target="_blank">DIV+CSS+JavaScript技术制作网页(旅游主题网页设计与制作)云南大理</a> <span class="text-muted">STU学生网页设计</span> <a class="tag" taget="_blank" href="/search/%E7%BD%91%E9%A1%B5%E8%AE%BE%E8%AE%A1/1.htm">网页设计</a><a class="tag" taget="_blank" href="/search/%E6%9C%9F%E6%9C%AB%E7%BD%91%E9%A1%B5%E4%BD%9C%E4%B8%9A/1.htm">期末网页作业</a><a class="tag" taget="_blank" href="/search/html%E9%9D%99%E6%80%81%E7%BD%91%E9%A1%B5/1.htm">html静态网页</a><a class="tag" taget="_blank" href="/search/html5%E6%9C%9F%E6%9C%AB%E5%A4%A7%E4%BD%9C%E4%B8%9A/1.htm">html5期末大作业</a><a class="tag" taget="_blank" href="/search/%E7%BD%91%E9%A1%B5%E8%AE%BE%E8%AE%A1/1.htm">网页设计</a><a class="tag" taget="_blank" href="/search/web%E5%A4%A7%E4%BD%9C%E4%B8%9A/1.htm">web大作业</a> <div>️精彩专栏推荐作者主页:【进入主页—获取更多源码】web前端期末大作业:【HTML5网页期末作业(1000套)】程序员有趣的告白方式:【HTML七夕情人节表白网页制作(110套)】文章目录二、网站介绍三、网站效果▶️1.视频演示2.图片演示四、网站代码HTML结构代码CSS样式代码五、更多源码二、网站介绍网站布局方面:计划采用目前主流的、能兼容各大主流浏览器、显示效果稳定的浮动网页布局结构。网站程</div> </li> <li><a href="/article/1835497792265613312.htm" title="【加密社】Solidity 中的事件机制及其应用" target="_blank">【加密社】Solidity 中的事件机制及其应用</a> <span class="text-muted">加密社</span> <a class="tag" taget="_blank" href="/search/%E9%97%B2%E4%BE%83/1.htm">闲侃</a><a class="tag" taget="_blank" href="/search/%E5%8C%BA%E5%9D%97%E9%93%BE/1.htm">区块链</a><a class="tag" taget="_blank" href="/search/%E6%99%BA%E8%83%BD%E5%90%88%E7%BA%A6/1.htm">智能合约</a><a class="tag" taget="_blank" href="/search/%E5%8C%BA%E5%9D%97%E9%93%BE/1.htm">区块链</a> <div>加密社引言在Solidity合约开发过程中,事件(Events)是一种非常重要的机制。它们不仅能够让开发者记录智能合约的重要状态变更,还能够让外部系统(如前端应用)监听这些状态的变化。本文将详细介绍Solidity中的事件机制以及如何利用不同的手段来触发、监听和获取这些事件。事件存储的地方当我们在Solidity合约中使用emit关键字触发事件时,该事件会被记录在区块链的交易收据中。具体而言,事件</div> </li> <li><a href="/article/1835496149843275776.htm" title="关于城市旅游的HTML网页设计——(旅游风景云南 5页)HTML+CSS+JavaScript" target="_blank">关于城市旅游的HTML网页设计——(旅游风景云南 5页)HTML+CSS+JavaScript</a> <span class="text-muted">二挡起步</span> <a class="tag" taget="_blank" href="/search/web%E5%89%8D%E7%AB%AF%E6%9C%9F%E6%9C%AB%E5%A4%A7%E4%BD%9C%E4%B8%9A/1.htm">web前端期末大作业</a><a class="tag" taget="_blank" href="/search/javascript/1.htm">javascript</a><a class="tag" taget="_blank" href="/search/html/1.htm">html</a><a class="tag" taget="_blank" href="/search/css/1.htm">css</a><a class="tag" taget="_blank" href="/search/%E6%97%85%E6%B8%B8/1.htm">旅游</a><a class="tag" taget="_blank" href="/search/%E9%A3%8E%E6%99%AF/1.htm">风景</a> <div>⛵源码获取文末联系✈Web前端开发技术描述网页设计题材,DIV+CSS布局制作,HTML+CSS网页设计期末课程大作业|游景点介绍|旅游风景区|家乡介绍|等网站的设计与制作|HTML期末大学生网页设计作业,Web大学生网页HTML:结构CSS:样式在操作方面上运用了html5和css3,采用了div+css结构、表单、超链接、浮动、绝对定位、相对定位、字体样式、引用视频等基础知识JavaScrip</div> </li> <li><a href="/article/1835496148601761792.htm" title="HTML网页设计制作大作业(div+css) 云南我的家乡旅游景点 带文字滚动" target="_blank">HTML网页设计制作大作业(div+css) 云南我的家乡旅游景点 带文字滚动</a> <span class="text-muted">二挡起步</span> <a class="tag" taget="_blank" href="/search/web%E5%89%8D%E7%AB%AF%E6%9C%9F%E6%9C%AB%E5%A4%A7%E4%BD%9C%E4%B8%9A/1.htm">web前端期末大作业</a><a class="tag" taget="_blank" href="/search/web%E8%AE%BE%E8%AE%A1%E7%BD%91%E9%A1%B5%E8%A7%84%E5%88%92%E4%B8%8E%E8%AE%BE%E8%AE%A1/1.htm">web设计网页规划与设计</a><a class="tag" taget="_blank" href="/search/html/1.htm">html</a><a class="tag" taget="_blank" href="/search/css/1.htm">css</a><a class="tag" taget="_blank" href="/search/javascript/1.htm">javascript</a><a class="tag" taget="_blank" href="/search/dreamweaver/1.htm">dreamweaver</a><a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF/1.htm">前端</a> <div>Web前端开发技术描述网页设计题材,DIV+CSS布局制作,HTML+CSS网页设计期末课程大作业游景点介绍|旅游风景区|家乡介绍|等网站的设计与制作HTML期末大学生网页设计作业HTML:结构CSS:样式在操作方面上运用了html5和css3,采用了div+css结构、表单、超链接、浮动、绝对定位、相对定位、字体样式、引用视频等基础知识JavaScript:做与用户的交互行为文章目录前端学习路线</div> </li> <li><a href="/article/1835492740536823808.htm" title="node.js学习" target="_blank">node.js学习</a> <span class="text-muted">小猿L</span> <a class="tag" taget="_blank" href="/search/node.js/1.htm">node.js</a><a class="tag" taget="_blank" href="/search/node.js/1.htm">node.js</a><a class="tag" taget="_blank" href="/search/%E5%AD%A6%E4%B9%A0/1.htm">学习</a><a class="tag" taget="_blank" href="/search/vim/1.htm">vim</a> <div>node.js学习实操及笔记温故node.js,node.js学习实操过程及笔记~node.js学习视频node.js官网node.js中文网实操笔记githubcsdn笔记为什么学node.js可以让别人访问我们编写的网页为后续的框架学习打下基础,三大框架vuereactangular离不开node.jsnode.js是什么官网:node.js是一个开源的、跨平台的运行JavaScript的运行</div> </li> <li><a href="/article/1835448238103162880.htm" title="springboot+vue项目实战一-创建SpringBoot简单项目" target="_blank">springboot+vue项目实战一-创建SpringBoot简单项目</a> <span class="text-muted">苹果酱0567</span> <a class="tag" taget="_blank" href="/search/%E9%9D%A2%E8%AF%95%E9%A2%98%E6%B1%87%E6%80%BB%E4%B8%8E%E8%A7%A3%E6%9E%90/1.htm">面试题汇总与解析</a><a class="tag" taget="_blank" href="/search/spring/1.htm">spring</a><a class="tag" taget="_blank" href="/search/boot/1.htm">boot</a><a class="tag" taget="_blank" href="/search/%E5%90%8E%E7%AB%AF/1.htm">后端</a><a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/%E4%B8%AD%E9%97%B4%E4%BB%B6/1.htm">中间件</a><a class="tag" taget="_blank" href="/search/%E5%BC%80%E5%8F%91%E8%AF%AD%E8%A8%80/1.htm">开发语言</a> <div>这段时间抽空给女朋友搭建一个个人博客,想着记录一下建站的过程,就当做笔记吧。虽然复制zjblog只要一个小时就可以搞定一个网站,或者用cms系统,三四个小时就可以做出一个前后台都有的网站,而且想做成啥样也都行。但是就是要从新做,自己做的意义不一样,更何况,俺就是专门干这个的,嘿嘿嘿要做一个网站,而且从零开始,首先呢就是技术选型了,经过一番思量决定选择-SpringBoot做后端,前端使用Vue做一</div> </li> <li><a href="/article/1835448239864770560.htm" title="JavaScript 中,深拷贝(Deep Copy)和浅拷贝(Shallow Copy)" target="_blank">JavaScript 中,深拷贝(Deep Copy)和浅拷贝(Shallow Copy)</a> <span class="text-muted">跳房子的前端</span> <a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF%E9%9D%A2%E8%AF%95/1.htm">前端面试</a><a class="tag" taget="_blank" href="/search/javascript/1.htm">javascript</a><a class="tag" taget="_blank" href="/search/%E5%BC%80%E5%8F%91%E8%AF%AD%E8%A8%80/1.htm">开发语言</a><a class="tag" taget="_blank" href="/search/ecmascript/1.htm">ecmascript</a> <div>在JavaScript中,深拷贝(DeepCopy)和浅拷贝(ShallowCopy)是用于复制对象或数组的两种不同方法。了解它们的区别和应用场景对于避免潜在的bugs和高效地处理数据非常重要。以下是对深拷贝和浅拷贝的详细解释,包括它们的概念、用途、优缺点以及实现方式。1.浅拷贝(ShallowCopy)概念定义:浅拷贝是指创建一个新的对象或数组,其中包含了原对象或数组的基本数据类型的值和对引用数</div> </li> <li><a href="/article/1835437775344726016.htm" title="博客网站制作教程" target="_blank">博客网站制作教程</a> <span class="text-muted">2401_85194651</span> <a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/maven/1.htm">maven</a> <div>首先就是技术框架:后端:Java+SpringBoot数据库:MySQL前端:Vue.js数据库连接:JPA(JavaPersistenceAPI)1.项目结构blog-app/├──backend/│├──src/main/java/com/example/blogapp/││├──BlogApplication.java││├──config/│││└──DatabaseConfig.java</div> </li> <li><a href="/article/1835428948339683328.htm" title="JavaScript `Map` 和 `WeakMap`详细解释" target="_blank">JavaScript `Map` 和 `WeakMap`详细解释</a> <span class="text-muted">跳房子的前端</span> <a class="tag" taget="_blank" href="/search/JavaScript/1.htm">JavaScript</a><a class="tag" taget="_blank" href="/search/%E5%8E%9F%E7%94%9F%E6%96%B9%E6%B3%95/1.htm">原生方法</a><a class="tag" taget="_blank" href="/search/javascript/1.htm">javascript</a><a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF/1.htm">前端</a><a class="tag" taget="_blank" href="/search/%E5%BC%80%E5%8F%91%E8%AF%AD%E8%A8%80/1.htm">开发语言</a> <div>在JavaScript中,Map和WeakMap都是用于存储键值对的数据结构,但它们有一些关键的不同之处。MapMap是一种可以存储任意类型的键值对的集合。它保持了键值对的插入顺序,并且可以通过键快速查找对应的值。Map提供了一些非常有用的方法和属性来操作这些数据对:set(key,value):将一个键值对添加到Map中。如果键已经存在,则更新其对应的值。get(key):获取指定键的值。如果键</div> </li> <li><a href="/article/1835428317084348416.htm" title="最简单将静态网页挂载到服务器上(不用nginx)" target="_blank">最简单将静态网页挂载到服务器上(不用nginx)</a> <span class="text-muted">全能全知者</span> <a class="tag" taget="_blank" href="/search/%E6%9C%8D%E5%8A%A1%E5%99%A8/1.htm">服务器</a><a class="tag" taget="_blank" href="/search/nginx/1.htm">nginx</a><a class="tag" taget="_blank" href="/search/%E8%BF%90%E7%BB%B4/1.htm">运维</a><a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF/1.htm">前端</a><a class="tag" taget="_blank" href="/search/html/1.htm">html</a><a class="tag" taget="_blank" href="/search/%E7%AC%94%E8%AE%B0/1.htm">笔记</a> <div>最简单将静态网页挂载到服务器上(不用nginx)如果随便弄个静态网页挂在服务器都要用nignx就太麻烦了,所以直接使用Apache来搭建一些简单前端静态网页会相对方便很多检查Web服务器服务状态:sudosystemctlstatushttpd#ApacheWeb服务器如果发现没有安装web服务器:安装Apache:sudoyuminstallhttpd启动Apache:sudosystemctl</div> </li> <li><a href="/article/1835427057752961024.htm" title="补充元象二面" target="_blank">补充元象二面</a> <span class="text-muted">Redstone Monstrosity</span> <a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF/1.htm">前端</a><a class="tag" taget="_blank" href="/search/%E9%9D%A2%E8%AF%95/1.htm">面试</a> <div>1.请尽可能详细地说明,防抖和节流的区别,应用场景?你的回答中不要写出示例代码。防抖(Debounce)和节流(Throttle)是两种常用的前端性能优化技术,它们的主要区别在于如何处理高频事件的触发。以下是防抖和节流的区别和应用场景的详细说明:防抖和节流的定义防抖:在一段时间内,多次执行变为只执行最后一次。防抖的原理是,当事件被触发后,设置一个延迟定时器。如果在这个延迟时间内事件再次被触发,则重</div> </li> <li><a href="/article/1835420753252675584.htm" title="微信小程序开发注意事项" target="_blank">微信小程序开发注意事项</a> <span class="text-muted">jun778895</span> <a class="tag" taget="_blank" href="/search/%E5%BE%AE%E4%BF%A1%E5%B0%8F%E7%A8%8B%E5%BA%8F/1.htm">微信小程序</a><a class="tag" taget="_blank" href="/search/%E5%B0%8F%E7%A8%8B%E5%BA%8F/1.htm">小程序</a> <div>微信小程序开发是一个融合了前端开发、用户体验设计、后端服务(可选)以及微信小程序平台特性的综合性项目。这里,我将详细介绍一个典型的小程序开发项目的全过程,包括项目规划、设计、开发、测试及部署上线等各个环节,并尽量使内容达到或超过2000字的要求。一、项目规划1.1项目背景与目标假设我们要开发一个名为“智慧校园助手”的微信小程序,旨在为学生提供一站式校园生活服务,包括课程表查询、图书馆座位预约、食堂</div> </li> <li><a href="/article/1835419870070665216.htm" title="切换淘宝最新npm镜像源是" target="_blank">切换淘宝最新npm镜像源是</a> <span class="text-muted">hai40587</span> <a class="tag" taget="_blank" href="/search/npm/1.htm">npm</a><a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF/1.htm">前端</a><a class="tag" taget="_blank" href="/search/node.js/1.htm">node.js</a> <div>切换淘宝最新npm镜像源是一个相对简单的过程,但首先需要明确当前淘宝npm镜像源的状态和最新的镜像地址。由于网络环境和服务更新,镜像源的具体地址可能会发生变化,因此,我将基于当前可获取的信息,提供一个通用的切换步骤,并附上最新的镜像地址(截至回答时)。一、了解npm镜像源npm(NodePackageManager)是JavaScript的包管理器,用于安装、更新和管理项目依赖。由于npm官方仓库</div> </li> <li><a href="/article/1835411044768509952.htm" title="字节二面" target="_blank">字节二面</a> <span class="text-muted">Redstone Monstrosity</span> <a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF/1.htm">前端</a><a class="tag" taget="_blank" href="/search/%E9%9D%A2%E8%AF%95/1.htm">面试</a> <div>1.假设你是正在面试前端开发工程师的候选人,面试官让你详细说出你上一段实习过程的收获和感悟。在上一段实习过程中,我获得了宝贵的实践经验和深刻的行业洞察,以下是我的主要收获和感悟:一、专业技能提升框架应用熟练度:通过实际项目,我深入掌握了React、Vue等前端框架的使用,不仅提升了编码效率,还学会了如何根据项目需求选择合适的框架。问题解决能力:在实习期间,我遇到了许多预料之外的技术难题。通过查阅文</div> </li> <li><a href="/article/1835398064727224320.htm" title="前端代码上传文件" target="_blank">前端代码上传文件</a> <span class="text-muted">余生逆风飞翔</span> <a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF/1.htm">前端</a><a class="tag" taget="_blank" href="/search/javascript/1.htm">javascript</a><a class="tag" taget="_blank" href="/search/%E5%BC%80%E5%8F%91%E8%AF%AD%E8%A8%80/1.htm">开发语言</a> <div>点击上传文件import{ElNotification}from'element-plus'import{API_CONFIG}from'../config/index.js'import{UploadFilled}from'@element-plus/icons-vue'import{reactive}from'vue'import{BASE_URL}from'../config/index'i</div> </li> <li><a href="/article/1835385458356482048.htm" title="uniapp实现动态标记效果详细步骤【前端开发】" target="_blank">uniapp实现动态标记效果详细步骤【前端开发】</a> <span class="text-muted">2401_85123349</span> <a class="tag" taget="_blank" href="/search/uni-app/1.htm">uni-app</a> <div>第二个点在于实现将已经被用户标记的内容在下一次获取后刷新它的状态为已标记。这是什么意思呢?比如说上面gif图中的这些人物对象,有一些已被该用户添加为关心,那么当用户下一次进入该页面时,这些已经被添加关心的对象需要以“红心”状态显现出来。这个点的难度还不算大,只需要在每一次获取后端的内容后对标记对象进行状态更新即可。II.动态标记效果实现思路和步骤首先,整体的思路是利用动态类名对不同的元素进行选择。</div> </li> <li><a href="/article/1835383919906746368.htm" title="高性能javascript--算法和流程控制" target="_blank">高性能javascript--算法和流程控制</a> <span class="text-muted">海淀萌狗</span> <div>-for,while和do-while性能相当-避免使用for-in循环,==除非遍历一个属性量未知的对象==es5:for-in遍历的对象便不局限于数组,还可以遍历对象。原因:for-in每次迭代操作会同时搜索实例或者原型属性,for-in循环的每次迭代都会产生更多开销,因此要比其他循环类型慢,一般速度为其他类型循环的1/7。因此,除非明确需要迭代一个属性数量未知的对象,否则应避免使用for-i</div> </li> <li><a href="/article/1835373236217540608.htm" title="360前端星计划-动画可以这么玩" target="_blank">360前端星计划-动画可以这么玩</a> <span class="text-muted">马小蜗</span> <div>动画的基本原理定时器改变对象的属性根据新的属性重新渲染动画functionupdate(context){//更新属性}constticker=newTicker();ticker.tick(update,context);动画的种类1、JavaScript动画操作DOMCanvas2、CSS动画transitionanimation3、SVG动画SMILJS动画的优缺点优点:灵活度、可控性、性能</div> </li> <li><a href="/article/1835368019430305792.htm" title="Vue + Express实现一个表单提交" target="_blank">Vue + Express实现一个表单提交</a> <span class="text-muted">九旬大爷的梦</span> <div>最近在折腾一个cms系统,用的vue+express,但是就一个表单提交就弄了好久,记录一下。环境:Node10+前端:Vue服务端:Express依赖包:vueexpressaxiosexpress-formidableelement-ui(可选)前言:axiosget请求参数是:paramsaxiospost请求参数是:dataexpressget接受参数是req.queryexpresspo</div> </li> <li><a href="/article/1835360244646113280.htm" title="JavaScript中秋快乐!" target="_blank">JavaScript中秋快乐!</a> <span class="text-muted">Q_w7742</span> <a class="tag" taget="_blank" href="/search/javascript/1.htm">javascript</a><a class="tag" taget="_blank" href="/search/%E5%BC%80%E5%8F%91%E8%AF%AD%E8%A8%80/1.htm">开发语言</a><a class="tag" taget="_blank" href="/search/ecmascript/1.htm">ecmascript</a> <div>我们来实现一个简单的祝福网页~主要的难度在于使用canvas绘图当点击canvas时候,跳出“中秋节快乐”字样,需要注册鼠标单击事件和计时器。首先定义主要函数:初始化当点击canvas之后转到onCanvasClick函数,绘图生成灯笼。functiononCanvasClick(){//事件处理函数context.clearRect(0,0,canvas1.width,canvas1.heigh</div> </li> <li><a href="/article/1835359727924637696.htm" title="Nginx从入门到实践(三)" target="_blank">Nginx从入门到实践(三)</a> <span class="text-muted">听你讲故事啊</span> <div>动静分离动静分离是将网站静态资源(JavaScript,CSS,img等文件)与后台应用分开部署,提高用户访问静态代码的速度,降低对后台应用访问。动静分离的一种做法是将静态资源部署在nginx上,后台项目部署到应用服务器上,根据一定规则静态资源的请求全部请求nginx服务器,达到动静分离的目标。rewrite规则Rewrite规则常见正则表达式Rewrite主要的功能就是实现URL的重写,Ngin</div> </li> <li><a href="/article/1835354700392787968.htm" title="Nginx的使用场景:构建高效、可扩展的Web架构" target="_blank">Nginx的使用场景:构建高效、可扩展的Web架构</a> <span class="text-muted">张某布响丸辣</span> <a class="tag" taget="_blank" href="/search/nginx/1.htm">nginx</a><a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF/1.htm">前端</a><a class="tag" taget="_blank" href="/search/%E6%9E%B6%E6%9E%84/1.htm">架构</a> <div>Nginx,作为当今最流行的Web服务器和反向代理软件之一,凭借其高性能、稳定性和灵活性,在众多Web项目中扮演着核心角色。无论是个人博客、中小型网站,还是大型企业级应用,Nginx都能提供强大的支持。本文将探讨Nginx的几个主要使用场景,帮助读者理解如何在实际项目中充分利用Nginx的优势。1.静态文件服务对于包含大量静态文件(如HTML、CSS、JavaScript、图片等)的网站,Ngin</div> </li> <li><a href="/article/1835354447627251712.htm" title="前端知识点" target="_blank">前端知识点</a> <span class="text-muted">ZhangTao_zata</span> <a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF/1.htm">前端</a><a class="tag" taget="_blank" href="/search/javascript/1.htm">javascript</a><a class="tag" taget="_blank" href="/search/css/1.htm">css</a> <div>下面是一个最基本的html代码body{font-family:Arial,sans-serif;margin:20px;}//JavaScriptfunctionthatdisplaysanalertwhencalledfunctionshowMessage(){alert("Hello!Youclickedthebutton.");}MyFirstHTMLPageWelcometoMyPage</div> </li> <li><a href="/article/1835352325032603648.htm" title="第三十一节:Vue路由:前端路由vs后端路由的了解" target="_blank">第三十一节:Vue路由:前端路由vs后端路由的了解</a> <span class="text-muted">曹老师</span> <div>1.认识前端路由和后端路由前端路由相对于后端路由而言的,在理解前端路由之前先对于路由有一个基本的了解路由:简而言之,就是把信息从原地址传输到目的地的活动对于我们来说路由就是:根据不同的url地址展示不同的页面内容1.1后端路由以前咱们接触比较多的后端路由,当改变url地址时,浏览器会向服务器发送请求,服务器根据这个url,返回不同的资源内容后端路由的特点就是前端每次跳转到不同url地址,都会重新访</div> </li> <li><a href="/article/1835350917352878080.htm" title="华雁智科前端面试题" target="_blank">华雁智科前端面试题</a> <span class="text-muted">因为奋斗超太帅啦</span> <a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF%E7%AC%94%E8%AF%95%E9%9D%A2%E8%AF%95%E9%97%AE%E9%A2%98%E6%95%B4%E7%90%86/1.htm">前端笔试面试问题整理</a><a class="tag" taget="_blank" href="/search/javascript/1.htm">javascript</a><a class="tag" taget="_blank" href="/search/%E5%BC%80%E5%8F%91%E8%AF%AD%E8%A8%80/1.htm">开发语言</a><a class="tag" taget="_blank" href="/search/ecmascript/1.htm">ecmascript</a> <div>1.var变量的提升题目:vara=1functionfun(){console.log(b)varb=2}fun()console.log(a)正确输出结果:undefined、1答错了,给一个大嘴巴子,错误答案输出结果为:2,1此题主要考察var定义的变量,作用域提升的问题,相当于varaa=1functionfun(){varbconsole.log(b)b=2}fun()console.l</div> </li> <li><a href="/article/1835350535818014720.htm" title="如何建设数据中台(五)——数据汇集—打破企业数据孤岛" target="_blank">如何建设数据中台(五)——数据汇集—打破企业数据孤岛</a> <span class="text-muted">weixin_47088026</span> <a class="tag" taget="_blank" href="/search/%E5%AD%A6%E4%B9%A0%E8%AE%B0%E5%BD%95%E5%92%8C%E6%80%BB%E7%BB%93/1.htm">学习记录和总结</a><a class="tag" taget="_blank" href="/search/%E4%B8%AD%E5%8F%B0/1.htm">中台</a><a class="tag" taget="_blank" href="/search/%E6%95%B0%E6%8D%AE%E4%B8%AD%E5%8F%B0/1.htm">数据中台</a><a class="tag" taget="_blank" href="/search/%E7%A8%8B%E5%BA%8F%E4%BA%BA%E7%94%9F/1.htm">程序人生</a><a class="tag" taget="_blank" href="/search/%E7%BB%8F%E9%AA%8C%E5%88%86%E4%BA%AB/1.htm">经验分享</a> <div>数据汇集——打破企业数据孤岛要构建企业级数据中台,第一步就是将企业内部各个业务系统的数据实现互通互联,打破数据孤岛,主要通过数据汇聚和交换来实现。企业采集的数据可以是线上采集、线下数据采集、互联网数据采集、内部数据采集等。线上数据采集主要载体分为互联网和移动互联网两种,对应有系统平台、网页、H5、小程序、App等,可以采用前端或后端埋点方式采集数据。线下数据采集主要是通过硬件来采集,例如:WiFi</div> </li> <li><a href="/article/1835343473629294592.htm" title="分布式锁和spring事务管理" target="_blank">分布式锁和spring事务管理</a> <span class="text-muted">暴躁的鱼</span> <a class="tag" taget="_blank" href="/search/%E9%94%81%E5%8F%8A%E4%BA%8B%E5%8A%A1/1.htm">锁及事务</a><a class="tag" taget="_blank" href="/search/%E5%88%86%E5%B8%83%E5%BC%8F/1.htm">分布式</a><a class="tag" taget="_blank" href="/search/spring/1.htm">spring</a><a class="tag" taget="_blank" href="/search/java/1.htm">java</a> <div>最近开发一个小程序遇到一个需求需要实现分布式事务管理业务需求用户在使用小程序的过程中可以查看景点,对景点地区或者城市标记是否想去,那么需要统计一个地点被标记的人数,以及记录某个用户对某个地点是否标记为想去,用两个表存储数据,一个地点表记录改地点被标记的次数,一个用户意向表记录某个用户对某个地点是否标记为想去。由于可能有多个用户同时标记一个地点,每个用户在前端点击想去按钮之后,后台接收到请求,从数据</div> </li> <li><a href="/article/1835340577596600320.htm" title="前端CSS面试常见题" target="_blank">前端CSS面试常见题</a> <span class="text-muted">剑亦未配妥</span> <a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF%E9%9D%A2%E8%AF%95/1.htm">前端面试</a><a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF/1.htm">前端</a><a class="tag" taget="_blank" href="/search/css/1.htm">css</a><a class="tag" taget="_blank" href="/search/%E9%9D%A2%E8%AF%95/1.htm">面试</a> <div>边界塌陷盒模型有两种:W3C盒模型和IE盒模型,区别在于宽度是否包含边框定义:同时给兄弟/父子盒模型设置上下边距,理论上边距值是两者之和,实际上不是注意:浮动和定位不会产生边界塌陷;只有块级元素垂直方向才会产生margin合并margin计算方案margin同为正负:取绝对值大的值一正一负:求和父子元素边界塌陷解决父元素可以通过调整padding处理;设置overflowhidden,触发BFC子</div> </li> <li><a href="/article/1835331376895848448.htm" title="【JS】前端文件读取FileReader操作总结" target="_blank">【JS】前端文件读取FileReader操作总结</a> <span class="text-muted">程序员-张师傅</span> <a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF/1.htm">前端</a><a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF/1.htm">前端</a><a class="tag" taget="_blank" href="/search/javascript/1.htm">javascript</a><a class="tag" taget="_blank" href="/search/%E5%BC%80%E5%8F%91%E8%AF%AD%E8%A8%80/1.htm">开发语言</a> <div>前端文件读取FileReader操作总结FileReader是JavaScript中的一个WebAPI,它允许web应用程序异步读取用户计算机上的文件(或原始数据缓冲区)的内容,例如读取文件以获取其内容,并在不将文件发送到服务器的情况下在客户端使用它。这对于处理图片、文本文件等非常有用,尤其是当你想要在用户界面中即时显示文件内容或进行文件预览时。创建FileReader对象首先,你需要创建一个Fi</div> </li> <li><a href="/article/24.htm" title="tomcat基础与部署发布" target="_blank">tomcat基础与部署发布</a> <span class="text-muted">暗黑小菠萝</span> <a class="tag" taget="_blank" href="/search/Tomcat+java+web/1.htm">Tomcat java web</a> <div>从51cto搬家了,以后会更新在这里方便自己查看。 做项目一直用tomcat,都是配置到eclipse中使用,这几天有时间整理一下使用心得,有一些自己配置遇到的细节问题。 Tomcat:一个Servlets和JSP页面的容器,以提供网站服务。 一、Tomcat安装     安装方式:①运行.exe安装包      &n</div> </li> <li><a href="/article/151.htm" title="网站架构发展的过程" target="_blank">网站架构发展的过程</a> <span class="text-muted">ayaoxinchao</span> <a class="tag" taget="_blank" href="/search/%E6%95%B0%E6%8D%AE%E5%BA%93/1.htm">数据库</a><a class="tag" taget="_blank" href="/search/%E5%BA%94%E7%94%A8%E6%9C%8D%E5%8A%A1%E5%99%A8/1.htm">应用服务器</a><a class="tag" taget="_blank" href="/search/%E7%BD%91%E7%AB%99%E6%9E%B6%E6%9E%84/1.htm">网站架构</a> <div>1.初始阶段网站架构:应用程序、数据库、文件等资源在同一个服务器上 2.应用服务和数据服务分离:应用服务器、数据库服务器、文件服务器 3.使用缓存改善网站性能:为应用服务器提供本地缓存,但受限于应用服务器的内存容量,可以使用专门的缓存服务器,提供分布式缓存服务器架构 4.使用应用服务器集群改善网站的并发处理能力:使用负载均衡调度服务器,将来自客户端浏览器的访问请求分发到应用服务器集群中的任何</div> </li> <li><a href="/article/278.htm" title="[信息与安全]数据库的备份问题" target="_blank">[信息与安全]数据库的备份问题</a> <span class="text-muted">comsci</span> <a class="tag" taget="_blank" href="/search/%E6%95%B0%E6%8D%AE%E5%BA%93/1.htm">数据库</a> <div>       如果你们建设的信息系统是采用中心-分支的模式,那么这里有一个问题   如果你的数据来自中心数据库,那么中心数据库如果出现故障,你的分支机构的数据如何保证安全呢?    是否应该在这种信息系统结构的基础上进行改造,容许分支机构的信息系统也备份一个中心数据库的文件呢?  &n</div> </li> <li><a href="/article/405.htm" title="使用maven tomcat plugin插件debug关联源代码" target="_blank">使用maven tomcat plugin插件debug关联源代码</a> <span class="text-muted">商人shang</span> <a class="tag" taget="_blank" href="/search/maven/1.htm">maven</a><a class="tag" taget="_blank" href="/search/debug/1.htm">debug</a><a class="tag" taget="_blank" href="/search/%E6%9F%A5%E7%9C%8B%E6%BA%90%E7%A0%81/1.htm">查看源码</a><a class="tag" taget="_blank" href="/search/tomcat-plugin/1.htm">tomcat-plugin</a> <div>*首先需要配置好'''maven-tomcat7-plugin''',参见[[Maven开发Web项目]]的'''Tomcat'''部分。 *配置好后,在[[Eclipse]]中打开'''Debug Configurations'''界面,在'''Maven Build'''项下新建当前工程的调试。在'''Main'''选项卡中点击'''Browse Workspace...'''选择需要开发的</div> </li> <li><a href="/article/532.htm" title="大访问量高并发" target="_blank">大访问量高并发</a> <span class="text-muted">oloz</span> <a class="tag" taget="_blank" href="/search/%E5%A4%A7%E8%AE%BF%E9%97%AE%E9%87%8F%E9%AB%98%E5%B9%B6%E5%8F%91/1.htm">大访问量高并发</a> <div>大访问量高并发的网站主要压力还是在于数据库的操作上,尽量避免频繁的请求数据库。下面简 要列出几点解决方案: 01、优化你的代码和查询语句,合理使用索引 02、使用缓存技术例如memcache、ecache将不经常变化的数据放入缓存之中 03、采用服务器集群、负载均衡分担大访问量高并发压力 04、数据读写分离 05、合理选用框架,合理架构(推荐分布式架构)。 </div> </li> <li><a href="/article/659.htm" title="cache 服务器" target="_blank">cache 服务器</a> <span class="text-muted">小猪猪08</span> <a class="tag" taget="_blank" href="/search/cache/1.htm">cache</a> <div>Cache   即高速缓存.那么cache是怎么样提高系统性能与运行速度呢?是不是在任何情况下用cache都能提高性能?是不是cache用的越多就越好呢?我在近期开发的项目中有所体会,写下来当作总结也希望能跟大家一起探讨探讨,有错误的地方希望大家批评指正。   1.Cache   是怎么样工作的?   Cache   是分配在服务器上</div> </li> <li><a href="/article/786.htm" title="mysql存储过程" target="_blank">mysql存储过程</a> <span class="text-muted">香水浓</span> <a class="tag" taget="_blank" href="/search/mysql/1.htm">mysql</a> <div>Description:插入大量测试数据 use xmpl; drop procedure if exists mockup_test_data_sp; create procedure mockup_test_data_sp( in number_of_records int ) begin declare cnt int; declare name varch</div> </li> <li><a href="/article/913.htm" title="CSS的class、id、css文件名的常用命名规则" target="_blank">CSS的class、id、css文件名的常用命名规则</a> <span class="text-muted">agevs</span> <a class="tag" taget="_blank" href="/search/JavaScript/1.htm">JavaScript</a><a class="tag" taget="_blank" href="/search/UI/1.htm">UI</a><a class="tag" taget="_blank" href="/search/%E6%A1%86%E6%9E%B6/1.htm">框架</a><a class="tag" taget="_blank" href="/search/Ajax/1.htm">Ajax</a><a class="tag" taget="_blank" href="/search/css/1.htm">css</a> <div>  CSS的class、id、css文件名的常用命名规则     (一)常用的CSS命名规则   头:header   内容:content/container   尾:footer   导航:nav   侧栏:sidebar   栏目:column   页面外围控制整体布局宽度:wrapper   左右中:left right </div> </li> <li><a href="/article/1040.htm" title="全局数据源" target="_blank">全局数据源</a> <span class="text-muted">AILIKES</span> <a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/tomcat/1.htm">tomcat</a><a class="tag" taget="_blank" href="/search/mysql/1.htm">mysql</a><a class="tag" taget="_blank" href="/search/jdbc/1.htm">jdbc</a><a class="tag" taget="_blank" href="/search/JNDI/1.htm">JNDI</a> <div>实验目的:为了研究两个项目同时访问一个全局数据源的时候是创建了一个数据源对象,还是创建了两个数据源对象。 1:将diuid和mysql驱动包(druid-1.0.2.jar和mysql-connector-java-5.1.15.jar)copy至%TOMCAT_HOME%/lib下;2:配置数据源,将JNDI在%TOMCAT_HOME%/conf/context.xml中配置好,格式如下:&l</div> </li> <li><a href="/article/1167.htm" title="MYSQL的随机查询的实现方法" target="_blank">MYSQL的随机查询的实现方法</a> <span class="text-muted">baalwolf</span> <a class="tag" taget="_blank" href="/search/mysql/1.htm">mysql</a> <div>MYSQL的随机抽取实现方法。举个例子,要从tablename表中随机提取一条记录,大家一般的写法就是:SELECT * FROM tablename ORDER BY RAND() LIMIT 1。但是,后来我查了一下MYSQL的官方手册,里面针对RAND()的提示大概意思就是,在ORDER BY从句里面不能使用RAND()函数,因为这样会导致数据列被多次扫描。但是在MYSQL 3.23版本中,</div> </li> <li><a href="/article/1294.htm" title="JAVA的getBytes()方法" target="_blank">JAVA的getBytes()方法</a> <span class="text-muted">bijian1013</span> <a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/eclipse/1.htm">eclipse</a><a class="tag" taget="_blank" href="/search/unix/1.htm">unix</a><a class="tag" taget="_blank" href="/search/OS/1.htm">OS</a> <div>    在Java中,String的getBytes()方法是得到一个操作系统默认的编码格式的字节数组。这个表示在不同OS下,返回的东西不一样!      String.getBytes(String decode)方法会根据指定的decode编码返回某字符串在该编码下的byte数组表示,如: byte[] b_gbk = "</div> </li> <li><a href="/article/1421.htm" title="AngularJS中操作Cookies" target="_blank">AngularJS中操作Cookies</a> <span class="text-muted">bijian1013</span> <a class="tag" taget="_blank" href="/search/JavaScript/1.htm">JavaScript</a><a class="tag" taget="_blank" href="/search/AngularJS/1.htm">AngularJS</a><a class="tag" taget="_blank" href="/search/Cookies/1.htm">Cookies</a> <div>        如果你的应用足够大、足够复杂,那么你很快就会遇到这样一咱种情况:你需要在客户端存储一些状态信息,这些状态信息是跨session(会话)的。你可能还记得利用document.cookie接口直接操作纯文本cookie的痛苦经历。         幸运的是,这种方式已经一去不复返了,在所有现代浏览器中几乎</div> </li> <li><a href="/article/1548.htm" title="[Maven学习笔记五]Maven聚合和继承特性" target="_blank">[Maven学习笔记五]Maven聚合和继承特性</a> <span class="text-muted">bit1129</span> <a class="tag" taget="_blank" href="/search/maven/1.htm">maven</a> <div>Maven聚合   在实际的项目中,一个项目通常会划分为多个模块,为了说明问题,以用户登陆这个小web应用为例。通常一个web应用分为三个模块: 1. 模型和数据持久化层user-core, 2. 业务逻辑层user-service以 3. web展现层user-web, user-service依赖于user-core user-web依赖于user-core和use</div> </li> <li><a href="/article/1675.htm" title="【JVM七】JVM知识点总结" target="_blank">【JVM七】JVM知识点总结</a> <span class="text-muted">bit1129</span> <a class="tag" taget="_blank" href="/search/jvm/1.htm">jvm</a> <div>  1. JVM运行模式 1.1 JVM运行时分为-server和-client两种模式,在32位机器上只有client模式的JVM。通常,64位的JVM默认都是使用server模式,因为server模式的JVM虽然启动慢点,但是,在运行过程,JVM会尽可能的进行优化 1.2 JVM分为三种字节码解释执行方式:mixed mode, interpret mode以及compiler </div> </li> <li><a href="/article/1802.htm" title="linux下查看nginx、apache、mysql、php的编译参数" target="_blank">linux下查看nginx、apache、mysql、php的编译参数</a> <span class="text-muted">ronin47</span> <div>在linux平台下的应用,最流行的莫过于nginx、apache、mysql、php几个。而这几个常用的应用,在手工编译完以后,在其他一些情况下(如:新增模块),往往想要查看当初都使用了那些参数进行的编译。这时候就可以利用以下方法查看。 1、nginx [root@361way ~]# /App/nginx/sbin/nginx -V nginx: nginx version: nginx/</div> </li> <li><a href="/article/1929.htm" title="unity中运用Resources.Load的方法?" target="_blank">unity中运用Resources.Load的方法?</a> <span class="text-muted">brotherlamp</span> <a class="tag" taget="_blank" href="/search/unity%E8%A7%86%E9%A2%91/1.htm">unity视频</a><a class="tag" taget="_blank" href="/search/unity%E8%B5%84%E6%96%99/1.htm">unity资料</a><a class="tag" taget="_blank" href="/search/unity%E8%87%AA%E5%AD%A6/1.htm">unity自学</a><a class="tag" taget="_blank" href="/search/unity/1.htm">unity</a><a class="tag" taget="_blank" href="/search/unity%E6%95%99%E7%A8%8B/1.htm">unity教程</a> <div>问:unity中运用Resources.Load的方法? 答:Resources.Load是unity本地动态加载资本所用的方法,也即是你想动态加载的时分才用到它,比方枪弹,特效,某些实时替换的图像什么的,主张此文件夹不要放太多东西,在打包的时分,它会独自把里边的一切东西都会集打包到一同,不论里边有没有你用的东西,所以大多数资本应该是自个建文件放置 1、unity实时替换的物体即是依据环境条件</div> </li> <li><a href="/article/2056.htm" title="线段树-入门" target="_blank">线段树-入门</a> <span class="text-muted">bylijinnan</span> <a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/%E7%AE%97%E6%B3%95/1.htm">算法</a><a class="tag" taget="_blank" href="/search/%E7%BA%BF%E6%AE%B5%E6%A0%91/1.htm">线段树</a> <div> /** * 线段树入门 * 问题:已知线段[2,5] [4,6] [0,7];求点2,4,7分别出现了多少次 * 以下代码建立的线段树用链表来保存,且树的叶子结点类似[i,i] * * 参考链接:http://hi.baidu.com/semluhiigubbqvq/item/be736a33a8864789f4e4ad18 * @author lijinna</div> </li> <li><a href="/article/2183.htm" title="全选与反选" target="_blank">全选与反选</a> <span class="text-muted">chicony</span> <a class="tag" taget="_blank" href="/search/%E5%85%A8%E9%80%89/1.htm">全选</a> <div>  <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <title>全选与反选</title> </div> </li> <li><a href="/article/2310.htm" title="vim一些简单记录" target="_blank">vim一些简单记录</a> <span class="text-muted">chenchao051</span> <a class="tag" taget="_blank" href="/search/vim/1.htm">vim</a> <div>mac在/usr/share/vim/vimrc linux在/etc/vimrc   1、问:后退键不能删除数据,不能往后退怎么办?       答:在vimrc中加入set backspace=2   2、问:如何控制tab键的缩进?       答:在vimrc中加入set tabstop=4 (任何</div> </li> <li><a href="/article/2437.htm" title="Sublime Text 快捷键" target="_blank">Sublime Text 快捷键</a> <span class="text-muted">daizj</span> <a class="tag" taget="_blank" href="/search/%E5%BF%AB%E6%8D%B7%E9%94%AE/1.htm">快捷键</a><a class="tag" taget="_blank" href="/search/sublime/1.htm">sublime</a> <div>[size=large][/size]Sublime Text快捷键:Ctrl+Shift+P:打开命令面板Ctrl+P:搜索项目中的文件Ctrl+G:跳转到第几行Ctrl+W:关闭当前打开文件Ctrl+Shift+W:关闭所有打开文件Ctrl+Shift+V:粘贴并格式化Ctrl+D:选择单词,重复可增加选择下一个相同的单词Ctrl+L:选择行,重复可依次增加选择下一行Ctrl+Shift+L:</div> </li> <li><a href="/article/2564.htm" title="php 引用(&)详解" target="_blank">php 引用(&)详解</a> <span class="text-muted">dcj3sjt126com</span> <a class="tag" taget="_blank" href="/search/PHP/1.htm">PHP</a> <div>在PHP 中引用的意思是:不同的名字访问同一个变量内容. 与C语言中的指针是有差别的.C语言中的指针里面存储的是变量的内容在内存中存放的地址 变量的引用 PHP 的引用允许你用两个变量来指向同一个内容  复制代码代码如下: <?  $a="ABC";  $b =&$a;  echo</div> </li> <li><a href="/article/2691.htm" title="SVN中trunk,branches,tags用法详解" target="_blank">SVN中trunk,branches,tags用法详解</a> <span class="text-muted">dcj3sjt126com</span> <a class="tag" taget="_blank" href="/search/SVN/1.htm">SVN</a> <div>Subversion有一个很标准的目录结构,是这样的。比如项目是proj,svn地址为svn://proj/,那么标准的svn布局是svn://proj/|+-trunk+-branches+-tags这是一个标准的布局,trunk为主开发目录,branches为分支开发目录,tags为tag存档目录(不允许修改)。但是具体这几个目录应该如何使用,svn并没有明确的规范,更多的还是用户自己的习惯。</div> </li> <li><a href="/article/2818.htm" title="对软件设计的思考" target="_blank">对软件设计的思考</a> <span class="text-muted">e200702084</span> <a class="tag" taget="_blank" href="/search/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/1.htm">设计模式</a><a class="tag" taget="_blank" href="/search/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/1.htm">数据结构</a><a class="tag" taget="_blank" href="/search/%E7%AE%97%E6%B3%95/1.htm">算法</a><a class="tag" taget="_blank" href="/search/ssh/1.htm">ssh</a><a class="tag" taget="_blank" href="/search/%E6%B4%BB%E5%8A%A8/1.htm">活动</a> <div>软件设计的宏观与微观    软件开发是一种高智商的开发活动。一个优秀的软件设计人员不仅要从宏观上把握软件之间的开发,也要从微观上把握软件之间的开发。宏观上,可以应用面向对象设计,采用流行的SSH架构,采用web层,业务逻辑层,持久层分层架构。采用设计模式提供系统的健壮性和可维护性。微观上,对于一个类,甚至方法的调用,从计算机的角度模拟程序的运行情况。了解内存分配,参数传</div> </li> <li><a href="/article/2945.htm" title="同步、异步、阻塞、非阻塞" target="_blank">同步、异步、阻塞、非阻塞</a> <span class="text-muted">geeksun</span> <a class="tag" taget="_blank" href="/search/%E9%9D%9E%E9%98%BB%E5%A1%9E/1.htm">非阻塞</a> <div>同步、异步、阻塞、非阻塞这几个概念有时有点混淆,在此文试图解释一下。   同步:发出方法调用后,当没有返回结果,当前线程会一直在等待(阻塞)状态。 场景:打电话,营业厅窗口办业务、B/S架构的http请求-响应模式。   异步:方法调用后不立即返回结果,调用结果通过状态、通知或回调通知方法调用者或接收者。异步方法调用后,当前线程不会阻塞,会继续执行其他任务。 实现:</div> </li> <li><a href="/article/3072.htm" title="Reverse SSH Tunnel 反向打洞實錄" target="_blank">Reverse SSH Tunnel 反向打洞實錄</a> <span class="text-muted">hongtoushizi</span> <a class="tag" taget="_blank" href="/search/ssh/1.htm">ssh</a> <div>實際的操作步驟: # 首先,在客戶那理的機器下指令連回我們自己的 Server,並設定自己 Server 上的 12345 port 會對應到幾器上的 SSH port ssh -NfR 12345:localhost:22 fred@myhost.com # 然後在 myhost 的機器上連自己的 12345 port,就可以連回在客戶那的機器 ssh localhost -p 1</div> </li> <li><a href="/article/3199.htm" title="Hibernate中的缓存" target="_blank">Hibernate中的缓存</a> <span class="text-muted">Josh_Persistence</span> <a class="tag" taget="_blank" href="/search/%E4%B8%80%E7%BA%A7%E7%BC%93%E5%AD%98/1.htm">一级缓存</a><a class="tag" taget="_blank" href="/search/Hiberante%E7%BC%93%E5%AD%98/1.htm">Hiberante缓存</a><a class="tag" taget="_blank" href="/search/%E6%9F%A5%E8%AF%A2%E7%BC%93%E5%AD%98/1.htm">查询缓存</a><a class="tag" taget="_blank" href="/search/%E4%BA%8C%E7%BA%A7%E7%BC%93%E5%AD%98/1.htm">二级缓存</a> <div>Hibernate中的缓存   一、Hiberante中常见的三大缓存:一级缓存,二级缓存和查询缓存。 Hibernate中提供了两级Cache,第一级别的缓存是Session级别的缓存,它是属于事务范围的缓存。这一级别的缓存是由hibernate管理的,一般情况下无需进行干预;第二级别的缓存是SessionFactory级别的缓存,它是属于进程范围或群集范围的缓存。这一级别的缓存</div> </li> <li><a href="/article/3326.htm" title="对象关系行为模式之延迟加载" target="_blank">对象关系行为模式之延迟加载</a> <span class="text-muted">home198979</span> <a class="tag" taget="_blank" href="/search/PHP/1.htm">PHP</a><a class="tag" taget="_blank" href="/search/%E6%9E%B6%E6%9E%84/1.htm">架构</a><a class="tag" taget="_blank" href="/search/%E5%BB%B6%E8%BF%9F%E5%8A%A0%E8%BD%BD/1.htm">延迟加载</a> <div>形象化设计模式实战     HELLO!架构   一、概念 Lazy Load:一个对象,它虽然不包含所需要的所有数据,但是知道怎么获取这些数据。 延迟加载貌似很简单,就是在数据需要时再从数据库获取,减少数据库的消耗。但这其中还是有不少技巧的。     二、实现延迟加载 实现Lazy Load主要有四种方法:延迟初始化、虚</div> </li> <li><a href="/article/3453.htm" title="xml 验证" target="_blank">xml 验证</a> <span class="text-muted">pengfeicao521</span> <a class="tag" taget="_blank" href="/search/xml/1.htm">xml</a><a class="tag" taget="_blank" href="/search/xml%E8%A7%A3%E6%9E%90/1.htm">xml解析</a> <div>有些字符,xml不能识别,用jdom或者dom4j解析的时候就报错 public static void testPattern() { // 含有非法字符的串 String str =       "Jamey&#52828;&#01;&#02;&#209;&#1282</div> </li> <li><a href="/article/3580.htm" title="div设置半透明效果" target="_blank">div设置半透明效果</a> <span class="text-muted">spjich</span> <a class="tag" taget="_blank" href="/search/css/1.htm">css</a><a class="tag" taget="_blank" href="/search/%E5%8D%8A%E9%80%8F%E6%98%8E/1.htm">半透明</a> <div>为div设置如下样式:   div{filter:alpha(Opacity=80);-moz-opacity:0.5;opacity: 0.5;}        说明: 1、filter:对win IE设置半透明滤镜效果,filter:alpha(Opacity=80)代表该对象80%半透明,火狐浏览器不认2、-moz-opaci</div> </li> <li><a href="/article/3707.htm" title="你真的了解单例模式么?" target="_blank">你真的了解单例模式么?</a> <span class="text-muted">w574240966</span> <a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/%E5%8D%95%E4%BE%8B/1.htm">单例</a><a class="tag" taget="_blank" href="/search/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/1.htm">设计模式</a><a class="tag" taget="_blank" href="/search/jvm/1.htm">jvm</a> <div>    单例模式,很多初学者认为单例模式很简单,并且认为自己已经掌握了这种设计模式。但事实上,你真的了解单例模式了么。   一,单例模式的5中写法。(回字的四种写法,哈哈。)     1,懒汉式           (1)线程不安全的懒汉式 public cla</div> </li> </ul> </div> </div> </div> <div> <div class="container"> <div class="indexes"> <strong>按字母分类:</strong> <a href="/tags/A/1.htm" target="_blank">A</a><a href="/tags/B/1.htm" target="_blank">B</a><a href="/tags/C/1.htm" target="_blank">C</a><a href="/tags/D/1.htm" target="_blank">D</a><a href="/tags/E/1.htm" target="_blank">E</a><a href="/tags/F/1.htm" target="_blank">F</a><a href="/tags/G/1.htm" target="_blank">G</a><a href="/tags/H/1.htm" target="_blank">H</a><a href="/tags/I/1.htm" target="_blank">I</a><a href="/tags/J/1.htm" target="_blank">J</a><a href="/tags/K/1.htm" target="_blank">K</a><a href="/tags/L/1.htm" target="_blank">L</a><a href="/tags/M/1.htm" target="_blank">M</a><a href="/tags/N/1.htm" target="_blank">N</a><a href="/tags/O/1.htm" target="_blank">O</a><a href="/tags/P/1.htm" target="_blank">P</a><a href="/tags/Q/1.htm" target="_blank">Q</a><a href="/tags/R/1.htm" target="_blank">R</a><a href="/tags/S/1.htm" target="_blank">S</a><a href="/tags/T/1.htm" target="_blank">T</a><a href="/tags/U/1.htm" target="_blank">U</a><a href="/tags/V/1.htm" target="_blank">V</a><a href="/tags/W/1.htm" target="_blank">W</a><a href="/tags/X/1.htm" target="_blank">X</a><a href="/tags/Y/1.htm" target="_blank">Y</a><a href="/tags/Z/1.htm" target="_blank">Z</a><a href="/tags/0/1.htm" target="_blank">其他</a> </div> </div> </div> <footer id="footer" class="mb30 mt30"> <div class="container"> <div class="footBglm"> <a target="_blank" href="/">首页</a> - <a target="_blank" href="/custom/about.htm">关于我们</a> - <a target="_blank" href="/search/Java/1.htm">站内搜索</a> - <a target="_blank" href="/sitemap.txt">Sitemap</a> - <a target="_blank" href="/custom/delete.htm">侵权投诉</a> </div> <div class="copyright">版权所有 IT知识库 CopyRight © 2000-2050 E-COM-NET.COM , All Rights Reserved. <!-- <a href="https://beian.miit.gov.cn/" rel="nofollow" target="_blank">京ICP备09083238号</a><br>--> </div> </div> </footer> <!-- 代码高亮 --> <script type="text/javascript" src="/static/syntaxhighlighter/scripts/shCore.js"></script> <script type="text/javascript" src="/static/syntaxhighlighter/scripts/shLegacy.js"></script> <script type="text/javascript" src="/static/syntaxhighlighter/scripts/shAutoloader.js"></script> <link type="text/css" rel="stylesheet" href="/static/syntaxhighlighter/styles/shCoreDefault.css"/> <script type="text/javascript" src="/static/syntaxhighlighter/src/my_start_1.js"></script> </body> </html>