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/1773504513622212608.htm" title="大前端-postcss安装使用指南" target="_blank">大前端-postcss安装使用指南</a> <span class="text-muted">黑夜照亮前行的路</span> <a class="tag" taget="_blank" href="/search/postcss/1.htm">postcss</a> <div>PostCSS是一款强大的CSS处理工具,可以用来自动添加浏览器前缀、代码合并、代码压缩等,提升代码的可读性,并支持使用最新的CSS语法。以下是一份简化的PostCSS安装使用指南:一、安装PostCSS在你的项目目录中,通过npm(NodePackageManager)来安装PostCSS。打开命令行窗口,输入以下命令:bash复制代码npminstallpostcss--save-dev这将把</div> </li> <li><a href="/article/1773504261557125120.htm" title="谷歌浏览器驱动Chromedriver(114-120版本)文件以及驱动下载教程" target="_blank">谷歌浏览器驱动Chromedriver(114-120版本)文件以及驱动下载教程</a> <span class="text-muted">pigerr杨</span> <a class="tag" taget="_blank" href="/search/Python/1.htm">Python</a><a class="tag" taget="_blank" href="/search/python/1.htm">python</a><a class="tag" taget="_blank" href="/search/chrome/1.htm">chrome</a><a class="tag" taget="_blank" href="/search/drivers/1.htm">drivers</a> <div>ChromeDriver官方网站GitHub||GoogleChromeLabs/chrome-for-testingChromeDriver113-125_JSONChromeforTestingavailability123-125zip白月黑羽Python基础|进阶|Qt图形界面|Django|自动化测试|性能测试|JS语言|JS前端|原理与安装</div> </li> <li><a href="/article/1773501994674225152.htm" title="虚拟 DOM 的优缺点有哪些" target="_blank">虚拟 DOM 的优缺点有哪些</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>虚拟DOM(VirtualDOM)技术作为现代前端开发中的重要组成部分,已经成为了众多流行前端框架的核心特性。它的引入为前端开发带来了诸多优势,同时也需要我们认真思考其潜在的考量。下面简单的介绍一下虚拟DOM技术的优势与缺点,深入探讨其在实际应用中的影响。提升性能虚拟DOM的最大优势之一是提升页面性能。通过比较前后两次虚拟DOM树的差异,最小化实际DOM操作,从而减少页面重渲染时的性能消耗。这种优</div> </li> <li><a href="/article/1773495574226599936.htm" title="3、JavaWeb-Ajax/Axios-前端工程化-Element" target="_blank">3、JavaWeb-Ajax/Axios-前端工程化-Element</a> <span class="text-muted">所谓远行Misnearch</span> <a class="tag" taget="_blank" href="/search/%23/1.htm">#</a><a class="tag" taget="_blank" href="/search/JavaWeb/1.htm">JavaWeb</a><a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF/1.htm">前端</a><a class="tag" taget="_blank" href="/search/ajax/1.htm">ajax</a><a class="tag" taget="_blank" href="/search/elementui/1.htm">elementui</a><a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF%E6%A1%86%E6%9E%B6/1.htm">前端框架</a> <div>P34Ajax介绍Ajax:AsynchroousJavaScriptAndXML,异步的JS和XMLJS网页动作,XML一种标记语言,存储数据,作用:数据交换:通过Ajax给服务器发送请求,并获取服务器响应的数据异步交互:在不重新加载整个页面的情况下,与服务器交换数据并实现更新部分网页的技术,例如:搜索联想、用户名是否可用的校验等等。同步与异步:同步:服务器在处理中客户端要处于等待状态,输入域名</div> </li> <li><a href="/article/1773403175781466112.htm" title="Python dict字符串转json对象,小数精度丢失问题" target="_blank">Python dict字符串转json对象,小数精度丢失问题</a> <span class="text-muted">朝如青丝 暮成雪</span> <a class="tag" taget="_blank" href="/search/json/1.htm">json</a><a class="tag" taget="_blank" href="/search/python/1.htm">python</a> <div>一前言JSON(JavaScriptObjectNotation)是一种轻量级的数据交换格式,dict是Python的一种数据格式。本篇介绍一个float数据转换时精度丢失的案例。二问题描述importjsontest_str1='{"π":3.1415926535897932384626433832795028841971}'test_str2='{"value":10.00000}'print</div> </li> <li><a href="/article/1773382031552610304.htm" title="java实体中返回前端的double类型四舍五入(格式化)" target="_blank">java实体中返回前端的double类型四舍五入(格式化)</a> <span class="text-muted">婲落ヽ紅顏誶</span> <a class="tag" taget="_blank" href="/search/java/1.htm">java</a> <div>根据业务,需要通过后端给前端返回部分double类型的数值,一般需要保留两位小数,使用jackson转换对象packagecom.ruoyi.common.core.config;importcom.fasterxml.jackson.core.JsonGenerator;importcom.fasterxml.jackson.databind.JsonSerializer;importcom.f</div> </li> <li><a href="/article/1773360885226602496.htm" title="Django forms组件" target="_blank">Django forms组件</a> <span class="text-muted">在飞行-米龙</span> <a class="tag" taget="_blank" href="/search/Django/1.htm">Django</a><a class="tag" taget="_blank" href="/search/django/1.htm">django</a><a class="tag" taget="_blank" href="/search/python/1.htm">python</a><a class="tag" taget="_blank" href="/search/%E5%90%8E%E7%AB%AF/1.htm">后端</a> <div>【一】引入【1】实现登陆验证功能(1)需求分析登陆验证需要前后端交互,采用form表单提交数据对数据进行校验用户名必须以英文大写字母开头密码必须大于三位数反馈给用户错误的信息除了反馈错误的信息还有保留原始输入内容(2)后端代码使用user_info_dict字典每次刷新存储存储前端发送的信息存储后端进行验证的信息defhome(request):#每次后刷新这个信息字典user_info_dict</div> </li> <li><a href="/article/1773308900838277120.htm" title="Web前端Html的表单" target="_blank">Web前端Html的表单</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/html/1.htm">html</a> <div>表单的关键字:form标签表示一个表单区域action=“后端地址”method=“提交数据方式:get/post”input单行输入框type=“text”文本name=“定义名称名字自定义”向后端提交的键readonly=“readonly”只读,不可修改,但是可以提交disabled=“disabled”禁用组件不可修改,不能提交type=“password”密码框type=“radio”单</div> </li> <li><a href="/article/1773279695408791552.htm" title="Thinkphp - 详细实现网站系统登录功能,附带 Mysql 数据库设置、Web 前端展示界面、信息校验等(详细代码,即设计过程)" target="_blank">Thinkphp - 详细实现网站系统登录功能,附带 Mysql 数据库设置、Web 前端展示界面、信息校验等(详细代码,即设计过程)</a> <span class="text-muted">王佳斌</span> <a class="tag" taget="_blank" href="/search/%2B/1.htm">+</a><a class="tag" taget="_blank" href="/search/Thinkphp/1.htm">Thinkphp</a><a class="tag" taget="_blank" href="/search/mysql/1.htm">mysql</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%95%B0%E6%8D%AE%E5%BA%93/1.htm">数据库</a> <div>前言登录功能,是我们几乎开发每个系统都必须的模块。登录功能设计思路,主要包括几个方面。用户输入网址展示登录页面用户输入用户名,密码等点击登录进行信息校验校验通过之后,记录用户登录信息,跳转指定页面用户校验失败,提示失败信息页面目录具体功能实现为了快速搭建可用、美观的页面,我们采用一个比较成熟的前端框架Bootstrap。下面我们到Bootstrap的官网Bootsrap官网下载bootstrap。</div> </li> <li><a href="/article/1773256158274977792.htm" title="javascript 日期转换为时间戳,时间戳转换为日期的函数" target="_blank">javascript 日期转换为时间戳,时间戳转换为日期的函数</a> <span class="text-muted">cdcdhj</span> <a class="tag" taget="_blank" href="/search/javascript%E5%AD%A6%E4%B9%A0%E6%97%A5%E8%AE%B0/1.htm">javascript学习日记</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>日期转化为时间戳,主要用valueOf()来进行转化为毫秒时间戳,getTime()IOS系统无法解析转换,所以都有valueOf()letgetTimestampOrDate=function(timestamp){lettimeStamp='';constregex=/^\d{4}(-|\/)\d{2}(-|\/)\d{2}$/;constregex2=/^\d{4}(-|\/)\d{2}(-</div> </li> <li><a href="/article/1773198631940194304.htm" title="COMP315 JavaScript Cloud Computing for E Commerce" target="_blank">COMP315 JavaScript Cloud Computing for E Commerce</a> <span class="text-muted">zhuyu0206girl</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>Assignment1:Javascript1IntroductionAcommontaskincloudcomputingisdatacleaning,whichistheprocessoftakinganinitialdatasetthatmaycontainerroneousorincompletedata,andremovingorfixingthoseelementsbeforeform</div> </li> <li><a href="/article/1773191331473063936.htm" title="JSON与AJAX:网页交互的利器" target="_blank">JSON与AJAX:网页交互的利器</a> <span class="text-muted">入冉心</span> <a class="tag" taget="_blank" href="/search/json/1.htm">json</a><a class="tag" taget="_blank" href="/search/ajax/1.htm">ajax</a><a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF/1.htm">前端</a> <div>在现代Web开发中,JSON(JavaScriptObjectNotation)和AJAX(AsynchronousJavaScriptandXML)是两项不可或缺的技术。它们共同为网页提供了动态、实时的数据交互能力,为用户带来了更加流畅和丰富的体验。本文将详细介绍JSON和AJAX的概念、原理,并通过代码示例展示它们在实际开发中的应用。一、JSON:轻量级的数据交换格式JSON是一种轻量级的数据</div> </li> <li><a href="/article/1772795036136701952.htm" title="程序员开发技术整理" target="_blank">程序员开发技术整理</a> <span class="text-muted">laizhixue</span> <a class="tag" taget="_blank" href="/search/%E5%AD%A6%E4%B9%A0/1.htm">学习</a><a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF%E6%A1%86%E6%9E%B6/1.htm">前端框架</a> <div>前端技术:vue-前端框架element-前端框架bootstrap-前端框架echarts-图标组件C#后端技术:webservice:soap架构:简单的通信协议,用于服务通信ORM框架:对象关系映射,如EF:对象实体模型,是ado.net中的应用技术soap服务通讯:xml通讯ado.net:OAuth2:登录授权认证:Token认证:JWT:jsonwebtokenJava后端技术:便捷工</div> </li> <li><a href="/article/1772773132000624640.htm" title="【前端学习——js篇】7.函数缓存" target="_blank">【前端学习——js篇】7.函数缓存</a> <span class="text-muted">笔下无竹墨下有鱼</span> <a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF%E5%AD%A6%E4%B9%A0/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/%E5%AD%A6%E4%B9%A0/1.htm">学习</a><a class="tag" taget="_blank" href="/search/javascript/1.htm">javascript</a> <div>具体见:https://github.com/febobo/web-interview7.函数缓存函数缓存,就是将函数运算过的结果进行缓存本质上就是用空间(缓存存储)换时间(计算过程)常用于缓存数据计算结果和缓存对象。其实现主要通过闭包、柯里化和高阶函数。下面主要介绍下柯里化:①柯里化柯里化(currying)是一种函数式编程的概念,指的是将一个带有多个参数的函数转换成一系列只接受一个参数的函数的</div> </li> <li><a href="/article/1772738346825613312.htm" title="javascript的数据类型及转换" target="_blank">javascript的数据类型及转换</a> <span class="text-muted">田小田txt</span> <div>一、JavaScript数据类型:共有string,number,boolean,object,function五种数据类型;其中Object,Date,Array为对象型;2个不包含任何值的数据类型:null,undefined。二、Typeof查看数据类型:typeof"John"//返回stringtypeof3.14//返回numbertypeofNaN//返回numbertypeoffa</div> </li> <li><a href="/article/1772673815097180160.htm" title="Websocket服务监听收发消息" target="_blank">Websocket服务监听收发消息</a> <span class="text-muted">beiback</span> <a class="tag" taget="_blank" href="/search/Java/1.htm">Java</a><a class="tag" taget="_blank" href="/search/%E6%9C%8D%E5%8A%A1%E5%99%A8%E9%97%AE%E9%A2%98/1.htm">服务器问题</a><a class="tag" taget="_blank" href="/search/websocket/1.htm">websocket</a><a class="tag" taget="_blank" href="/search/%E7%BD%91%E7%BB%9C%E5%8D%8F%E8%AE%AE/1.htm">网络协议</a><a class="tag" taget="_blank" href="/search/%E7%BD%91%E7%BB%9C/1.htm">网络</a> <div>目录1.pom依赖坐标2.项目配置端口和项目包名2.创建处理器3.注册处理器4.前端页面1.pom依赖坐标org.springframework.bootspring-boot-starter-websocket2.项目配置端口和项目包名application.propertiesserver.port=8088//路径规范:为应用的所有servlet提供一个统一的前缀,使URL结构更加清晰和一致</div> </li> <li><a href="/article/1772673816238030848.htm" title="Netty服务器结合WebSocke协议监听和接收数据" target="_blank">Netty服务器结合WebSocke协议监听和接收数据</a> <span class="text-muted">beiback</span> <a class="tag" taget="_blank" href="/search/%E6%9C%8D%E5%8A%A1%E5%99%A8%E9%97%AE%E9%A2%98/1.htm">服务器问题</a><a class="tag" taget="_blank" href="/search/Java/1.htm">Java</a><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/%E8%BF%90%E7%BB%B4/1.htm">运维</a><a class="tag" taget="_blank" href="/search/netty/1.htm">netty</a> <div>目录1.pom依赖2.配置属性3.创建netty服务器4.建立监听和响应5.创建启动器6.前端static下页面7.前端js8.注意异常问题9.创建netty服务器--使用守护线程1.pom依赖io.nettynetty-all4.1.86.Final2.配置属性application.properties#启动端口server.port=8088server.servlet.context-pa</div> </li> <li><a href="/article/1772654174425645056.htm" title="基于SSM+Vue企业销售培训系统 企业人才培训系统 企业课程培训管理系统 企业文化培训班系统Java" target="_blank">基于SSM+Vue企业销售培训系统 企业人才培训系统 企业课程培训管理系统 企业文化培训班系统Java</a> <span class="text-muted">计算机程序老哥</span> <div>作者主页:计算机毕业设计老哥有问题可以主页问我一、开发介绍1.1开发环境开发语言:Java数据库:MySQL系统架构:B/S后端:SSM(Spring+SpringMVC+Mybatis)前端:Vue工具:IDEA或者Eclipse,JDK1.8,Maven二、系统介绍2.1图片展示注册登录页面:登陆.png前端页面功能:首页、培训班、在线学习、企业文化、交流论坛、试卷列表、系统公告、留言反馈、个</div> </li> <li><a href="/article/1772631263593693184.htm" title="javascript实现SM2加密解密" target="_blank">javascript实现SM2加密解密</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%89%8D%E7%AB%AF/1.htm">前端</a><a class="tag" taget="_blank" href="/search/jquery/1.htm">jquery</a> <div>前提JavaWeb环境前端代码window.sm2=function(t){functioni(e){if(r[e])returnr[e].exports;varn=r[e]={i:e,l:!1,exports:{}};returnt[e].call(n.exports,n,n.exports,i),n.l=!0,n.exports}varr={};returni.m=t,i.c=r,i.d=fu</div> </li> <li><a href="/article/1772618299398488064.htm" title="Vue:为什么要使用v-cloak" target="_blank">Vue:为什么要使用v-cloak</a> <span class="text-muted">刻刻帝的海角</span> <a class="tag" taget="_blank" href="/search/vue.js/1.htm">vue.js</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> <div>Vue.js是一种流行的JavaScript框架,它使我们能够构建交互性强大的用户界面。在Vue.js中,v-cloak是一个指令,用于解决在页面加载时出现的闪烁问题。本文将介绍如何使用v-cloak及代码来优化Vue.js应用程序的渲染效果。引言当我们使用Vue.js构建应用程序时,有时会遇到一个问题:在页面加载时,由于Vue.js需要一定的时间来解析和渲染模板,会导致页面上显示出未经处理的Mu</div> </li> <li><a href="/article/1772594886269272064.htm" title="JavaScript快速入门笔记之二(变量、常量、数据类型)" target="_blank">JavaScript快速入门笔记之二(变量、常量、数据类型)</a> <span class="text-muted">eshineLau</span> <a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF%E5%BC%80%E5%8F%91/1.htm">前端开发</a><a class="tag" taget="_blank" href="/search/javascript/1.htm">javascript</a><a class="tag" taget="_blank" href="/search/%E7%AC%94%E8%AE%B0/1.htm">笔记</a><a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF/1.htm">前端</a> <div>JavaScript快速入门笔记之二(变量、常量、数据类型)1、变量何时使用变量:程序中的一切数据都要保存在变量中,反复使用如何使用变量:2种情况:赋值和取值赋值:2步:1.1创建变量:——声明——创建一个新的空变量语法:var变量名;强调:仅声明,未赋值的变量,默认值是undefined命名:1.不能以数字开头2.不能用保留字。3.一般采用驼峰命名1.2赋值:将数据保存到变量中语法:变量名=数据</div> </li> <li><a href="/article/1772541266194661376.htm" title="前端埋点解决方案" target="_blank">前端埋点解决方案</a> <span class="text-muted">zhu_zhu_xia</span> <a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF/1.htm">前端</a> <div>一、前言:基于神策数据的前端埋点解决方案JavaScript快速使用·神策分析使用手册[预览版]二、sdkgitlab下载地址https://github.com/sensorsdata/sa-sdk-javascript/releases或者npm安装npmisa-sdk-javascript三、入门3.1接入sdk以及配置(version1.17.2),入口文件接入sdk以及添加配置(func</div> </li> <li><a href="/article/1772500355632922624.htm" title="vue时间组件:dayjs与moment" target="_blank">vue时间组件:dayjs与moment</a> <span class="text-muted">煸橙干儿~~</span> <a class="tag" taget="_blank" href="/search/VUE%E5%AE%9E%E6%88%98/1.htm">VUE实战</a><a class="tag" taget="_blank" href="/search/vue.js/1.htm">vue.js</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> <div>一、dayjs使用Day.js是一个极简的JavaScript库,可以为现代浏览器解析、验证、操作和显示日期和时间。具体使用可参考:dayjs官网1、本年start=dayjs().startOf('year').format('YYYY-MM-DD')end=dayjs().endOf('year').format('YYYY-MM-DD')console.log(start,end)//['2</div> </li> <li><a href="/article/1772399131164213248.htm" title="如何提出令人爱回答的好问题?" target="_blank">如何提出令人爱回答的好问题?</a> <span class="text-muted">兮若耶</span> <div>我们经常会遇到这样的问题,如我适合做什么?这个名词怎么解释?大部分人面对这样的问题时,要么答非所问,要么无从下手。现在的很多事物都是速成的,只是好的问题并没有那么容易被提出来。而提不好的问题,可能会拿不到想要的信息等等。所以提出一个好问题很重要。01提问的功能我们参加各种聚会、会议时,能听到很多的比喻和新观点,而这些是在书上和网上找不到的。这些新的有用的东西,都在前端被实践着,暂时来不及把知识系统</div> </li> <li><a href="/article/1772378002940821504.htm" title="谈谈对前端性能监控的理解和实践" target="_blank">谈谈对前端性能监控的理解和实践</a> <span class="text-muted">Layla_c</span> <a class="tag" taget="_blank" href="/search/web/1.htm">web</a><a class="tag" taget="_blank" href="/search/jave/1.htm">jave</a><a class="tag" taget="_blank" href="/search/python/1.htm">python</a><a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF/1.htm">前端</a> <div>一、谈谈对前端性能监控的理解和实践前端性能监控是确保网页或应用高效、稳定运行的关键环节,它涉及对前端页面加载速度、资源消耗、错误率等指标的实时监控和预警。通过前端性能监控,开发者和运维团队能够及时发现并解决性能瓶颈,从而提升用户体验和系统稳定性。理解前端性能监控,首先要明确其重要性。在移动互联网时代,用户对网页和应用的响应速度有着极高的要求。如果页面加载缓慢或出现卡顿,用户可能会选择离开,这对企业</div> </li> <li><a href="/article/1772365540988354560.htm" title="mineadmin使用docker启动方式" target="_blank">mineadmin使用docker启动方式</a> <span class="text-muted">qq_38812523</span> <a class="tag" taget="_blank" href="/search/docker/1.htm">docker</a><a class="tag" taget="_blank" href="/search/php/1.htm">php</a><a class="tag" taget="_blank" href="/search/%E5%AE%B9%E5%99%A8/1.htm">容器</a> <div>找个目录,git下来mineadmin代码,在根目录,创建文件名docker-compose.yml然后复制下面代码version:'3'services:#首先下载前端,https://gitee.com/mineadmin/mineadmin-vue#在后端根目录建立mine-ui目录,把前端文件复制过来。#容器内访问宿主机的地址用:host.docker.internal#宿主机也可以在ho</div> </li> <li><a href="/article/1772335078576291840.htm" title="为什么需要使用版本控制工具(如Git)?它如何帮助管理前端开发项目?" target="_blank">为什么需要使用版本控制工具(如Git)?它如何帮助管理前端开发项目?</a> <span class="text-muted">智伴科技</span> <a class="tag" taget="_blank" href="/search/git/1.htm">git</a> <div>版本控制工具(如Git)在前端开发项目中扮演着重要的角色,主要有以下几方面的作用:1.**版本管理**:版本控制工具可以帮助开发团队管理项目的不同版本,记录每次代码变动的历史记录,方便追踪和回溯。开发人员可以通过版本控制工具轻松地查看、对比和恢复以前的版本。2.**协同合作**:多人开发同一个项目时,版本控制工具可以协助团队成员协同工作,避免代码冲突、重复工作和混乱。开发人员可以通过版本控制工具共</div> </li> <li><a href="/article/1772307893329133568.htm" title="低代码与前端开发架构:重塑软件开发的未来" target="_blank">低代码与前端开发架构:重塑软件开发的未来</a> <span class="text-muted">快乐非自愿</span> <a class="tag" taget="_blank" href="/search/%E4%BD%8E%E4%BB%A3%E7%A0%81/1.htm">低代码</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%89%8D%E7%AB%AF/1.htm">前端</a> <div>随着技术的不断进步和数字化转型的深入,软件开发领域正经历着一场革命性的变革。在这场变革中,低代码开发平台和前端开发架构扮演着越来越重要的角色。本文将探讨低代码与前端开发架构之间的关系,并分析它们如何共同推动软件开发的创新与发展。低代码开发平台的崛起低代码开发平台(Low-CodeDevelopmentPlatform,LCDP)是一种新型的软件开发方式,它允许开发者通过图形化界面、预构建的模块和模</div> </li> <li><a href="/article/1772084223755223040.htm" title="APP UI自动化测试思路总结" target="_blank">APP UI自动化测试思路总结</a> <span class="text-muted">程序员老鹰</span> <a class="tag" taget="_blank" href="/search/ui/1.htm">ui</a><a class="tag" taget="_blank" href="/search/%E6%B5%8B%E8%AF%95%E5%B7%A5%E5%85%B7/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><a class="tag" taget="_blank" href="/search/%E8%BD%AF%E4%BB%B6%E6%B5%8B%E8%AF%95/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><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%A8%8B%E5%BA%8F%E5%91%98/1.htm">程序员</a> <div>首先想要说明一下,APP自动化测试可能很多公司不用,但也是大部分自动化测试工程师、高级测试工程师岗位招聘信息上要求的,所以为了更好的待遇,我们还是需要花时间去掌握的,毕竟谁也不会跟钱过不去。接下来,一起总结一下APPUI自动化测试的思路吧。一,开发语言选择通常用于自动化测试的编程语言有:Python、Java、Javascript、Ruby、C#、PHP等。一般我们会选择自己熟悉的编程语言来编写自</div> </li> <li><a href="/article/1772083971480420352.htm" title="接口测试之测试原则、测试用例、测试流程......" target="_blank">接口测试之测试原则、测试用例、测试流程......</a> <span class="text-muted">程序员老鹰</span> <a class="tag" taget="_blank" href="/search/%E6%B5%8B%E8%AF%95%E5%B7%A5%E5%85%B7/1.htm">测试工具</a><a class="tag" taget="_blank" href="/search/%E5%8A%9F%E8%83%BD%E6%B5%8B%E8%AF%95/1.htm">功能测试</a><a class="tag" taget="_blank" href="/search/%E6%B5%8B%E8%AF%95%E7%94%A8%E4%BE%8B/1.htm">测试用例</a><a class="tag" taget="_blank" href="/search/%E6%B5%8B%E8%AF%95%E8%A6%86%E7%9B%96%E7%8E%87/1.htm">测试覆盖率</a><a class="tag" taget="_blank" href="/search/%E7%B3%BB%E7%BB%9F%E5%AE%89%E5%85%A8/1.htm">系统安全</a> <div>一、接口的介绍软件测试中,常说的接口有两种:图形用户接口(GUI,人与程序的接口)、应用程序编程接口(API)。接口(API)是系统与系统之间,模块与模块之间或者服务与服务之间相互调用的入口。它的本质:其实就是一种约定,在开发前期,我们约定接口会接收什么数据;在处理完成后,它又会返回什么数据。开发岗位分为前端和后端,他们相互配合完成工作,会协商接口的定义方法。一般后端定义接口,前端调用接口。前后端</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>