ES6 备忘

1 篇文章 0 订阅
1 篇文章 0 订阅

一,let和const命令:

二,变量的解构赋值:

三,字符串的扩展:

a. includes(),startsWith(),endsWith();方法:

str.includes()//返回布尔值,表示是否找到了参数字符串

str.startsWith()//返回布尔值,表示参数字符串是否在原字符串的头部

str.endsWith()//返回布尔值,表示参数字符串是否在原字符串的尾部

ps:支持传入第二个参数,表示开始搜索的位置

b. repeat()方法:返回一个新字符串,表示将原字符串重复N次

'sure'.repeat(2);//'suresure';

ps:传入小数会向下取整,0~-1之间的小数等同于0,负数会报错,NaN等同于0

c. padStart(),padEnd();方法:字符串补全长度

四,数值的扩展:

a. Number.isFinite(),Number.isNaN();方法:用来检测一个数值不是Infunity;检测一个数值是否为NaN

b. Number.parseInt(),Number.parseFloat();方法:保留整数与浮点数;

c. Number.isInteger()方法:判断一个数值是否为整数,返回布尔值;

d. Math对象的扩展:

- Math.trunc():去除一个数的小数部分,返回整数部分

- Math.sign():判断一个数到底是正整数,负数,还是零。对于非数值会现将其转换为数值

它会返回五种值:

-- 正数,返回:+1;

-- 负数,返回:-1;

-- 0,返回:0;

-- -0,返回:-0;

-- 其他值,返回:NaN;

- Math.cbrt():返回一个数的立方根;

- Math.clz32():返回一个数的32位无符号整数形式有多少个前导 0;

- Math.imul():返回两个数以 32 位带符号整数形式相乘的结果;

- Math.fround():返回一个数的32位单精度浮点数形式;

五,函数的扩展:

a. 函数参数的默认值:

-- 使用参数默认值时,函数不能有同名参数;

-- 参数默认值不是传值的,而是每次都重新计算默认值表达式的值。也就是说,参数默认值是惰性求值的;

b. 可与解构赋值默认值结合使用:

c.参数默认值位置:

d.函数的length属性:

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

-- rest参数也不会计入length属性;

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

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

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

e.作用域:

-- 一旦设置了参数的默认值,函数进行声明初始化时,参数就会形成一个单独的作用域。等到初始化结束,这个作用域就会消失,这种语法行为在不设置参数默认值时,是不会出现的;

 

f.name属性:

function foo(){}; foo,name//'foo';

var foo = function(){}; foo.name//'foo'

g.箭头函数: http://es6.ruanyifeng.com/#docs/function#%E7%AE%AD%E5%A4%B4%E5%87%BD%E6%95%B0

-- 箭头函数有几个使用注意点。

(1)函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。

(2)不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。

(3)不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。

(4)不可以使用yield命令,因此箭头函数不能用作 Generator 函数。

上面四点中,第一点尤其值得注意。this对象的指向是可变的,但是在箭头函数中,它是固定的。

 

h.双冒号运算符:(改变this指向)

-- foo::bar 等同于 bar.bind(foo)

-- foo::bar(...arguments) 等同于 bar.apply(foo,...arguments);

-- 如果双冒号左边为空,右边是一个对象的方法,则等于将该方法绑定在该对象上面;

 

六,数组的扩展:

a.扩展运算符(...):将一个数组转为用逗号分隔的参数序列。

b.扩展运算符的应用:

(1)复制数组:

const a1 = [1,2];

const a2 = [...a1];

const [...a2] = a1;

(2)合并数组:

let arr1 = [1,2];

let arr2 = [3,4];

let arr3 = [5,6];

let arr4 = [...arr1,...arr2,...arr3];

(3)与解构赋值结合:

let [first,...rest] = [1,2,3,4,5];

first//1;

rest//[2,3,4,5];

ps:扩展运算符只能放在参数的最后一位;

(4)将字符串转化为数组:

[..."hello"']//["h","e","l","l","o"];

(5)实现了Iterator接口的对象:

 

(6)Map,Set 结构,Generator函数:

 

c. Array.from():

(1)将类数组对象转化为真正的数组:

let arrLike = {

'0':'a',

'1':'b',

'2':'c',

length:3

}

let arr = Array.from(arrLike);//['a','b','c'];

ps:实际应用中,常见的类似数组的对象是 DOM 操作返回的 NodeList 集合,以及函数内部的arguments对象。

(2)只要是部署了Iterator接口的数据结构,Array.from都能将其转换为数组:

Array.from('hello');//["h","e","l","l","o"];

let namesSet = new Set(['a','b']);

Array.from(nameSet) // ['a','b'];

PS:任何有length属性的对象,都可以通过Array.from方法转为数组

(3)Array.from的第二个参数:用来对每个元素进行处理,将处理后的值返回数组

Aarry.from([1,2,3],(x) => x + 1);//[2,3,4];

PS:更多例子如下图:

 

d. Array.of():用于将一组值转化为数组

e. 数组实例的copyWithin():

(1)概念:数组实例的copyWithin方法,在当前数组内部,将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组。也就是说,使用这个方法,会修改当前数组。

(2)接收参数:copyWithin(target,start,end):

-- target(必需):从该位置开始替换数据,如果为负值,表示倒数。

-- start(可选):从该位置开始读取数据,默认为0,如果为负值,表示倒数。

-- end(可选):到该位置前停止读取数据,默认等于数组长度,如果为负值,表示倒数。

f. 数组实例的find()和findIndex():

(1) find():用于找出第一个符合条件的数组成员。

-- 参数是一个回调函数,所有数组成员依次执行该回调函数,知道找出第一个返回值为true的成员,然后返回该成员,如果没有符合条件的成员,则返回undefined。

-- 回调函数可接收三个参数find(function(value,index,arr){});参数依次为:

- 当前的值

- 当前的位置

- 原数组

(2) findIndex():用于找出第一个符合条件的数组成员的位置。回调与find方法类似,如果没有符合条件的则返回-1。

(3)这两个方法都可以接收第二个参数,用来绑定回调函数this的对象。

g. 数组实例fill():使用给定值填充一个数组

(1)fill方法用于空数组的初始化非常方便。数组中已有的元素,会被全部抹去。

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

(2) 第二三个参数分别为:填充起始位置,填充结束位置

h. 数组实例的entries(),keys()和values()方法:用于遍历数组

(1) keys():用于遍历键名;

(2) values():用于遍历键值;

(3) entries():用于遍历键值对;

PS:如果不用for...of...循环,可以手动调用遍历对象的next方法进行遍历;

i. 数组实例的includes()方法:Array.prototype.includes方法返回一个布尔值,表示某个数组是否包含给定的值

-- [1,2,3,4].includes(2)//true;

(1)第二个参数:表示搜索的起始位置,默认为0。如果为负数,表示倒数的位置;

PS:[NaN].includes(NaN) // true

j. 数组实例的flat(),flatMap()方法:(实验中方法,浏览器不支持)

(1)flat():用于将嵌套的数组拉平,变成一维数组,该方法返回一个新数组,对原数组没有影响;

-- [1,2,[3,4]].flat() // [1,2,3,4];

七,对像的扩展:

a. 属性的简洁表示法:

b.Object.is()方法:用来比较两个值是否严格相等,与(===)行为基本一致;

(1) 区别之处:

ES5: +0 === -0 //true; NaN === NaN //false;

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

!注意点:

(1)浅拷贝:

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

(2)同名属性的替换:

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

(3)数组的处理:

Object.assign()方法可以用来处理数组,但是会把数组视为对象;

-- Object.assign([1,2,3],[4,5]) // [4,5,3]

(4)取值函数的处理:

Object.assign()方法只能进行值的复制,如果要复制的值是一个取值函数,那么求值后会在复制;

!常见用途:

(1)为对象添加属性:

(2)为对象添加方法:

(3)克隆对象:

(4)合并多个对象:

(5)为属性指定默认值:

c. 属性的可枚举性和遍历:

(1)可枚举性:每个对象都有一个描述对象,用来控制该属性的行为。

-- Object.getOwnPropertDescriptor()方法可以获取该属性的描述对象。描述对象的enumerable属性,称为可枚举性,如果该属性为false,就表示某些操作会忽略当前属性;

目前有四个操作会忽略enumerable为false的属性:

- for...in 循环:只遍历对象自身的和继承的可枚举的属性

- objext.keys():返回对象自身的所有可枚举的属性的键名

- JSON.stringify():只串行化对象自身的可枚举的属性

- Object.assign():只拷贝对象自身的可枚举的属性。

PS:总的来说,操作中引入继承的属性会让问题复杂化,大多数时候,我们只关心对象自身的属性。所以,尽量不要用for...in循环,而用Object.keys()代替。

(2)属性的遍历:ES6一共有5种方法可以遍历对象的属性:

-- for...in:循环遍历对象自身的和继承的可枚举属性(不含 Symbol 属性);

-- Object.keys(obj):回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含 Symbol 属性)的键名;

-- Object.getOwnPropertyNames(obj):返回一个数组,包含对象自身的所有属性(不含 Symbol 属性,但是包括不可枚举属性)的键名;

-- Object.getOwnPropertySymbols(obj):返回一个数组,包含对象自身的所有 Symbol 属性的键名;

-- Reflect.ownKeys(obj):返回一个数组,包含对象自身的所有键名,不管键名是 Symbol 或字符串,也不管是否可枚举;

PS:以上的 5 种方法遍历对象的键名,都遵守同样的属性遍历的次序规则。

  • 首先遍历所有数值键,按照数值升序排列。
  • 其次遍历所有字符串键,按照加入时间升序排列。
  • 最后遍历所有 Symbol 键,按照加入时间升序排列。

d. Object.getOwnPropertyDescriptors()方法:返回指定对象所有自身属性(非继承属性)的描述对象

e. _proto_属性,Object.setPrototypeOf(),Object.getPrototypeOf()方法:

(1)_proto_属性:用来读取设置当前对象的prototype对象;

(2) Object.setPrototypeOf()方法:作用于_proto_相同,用来设置一个对象的prototype对象,返回参数对象本身;

(3) Object.getPrototypeOf()方法:改方法与上方法配套,用于读取一个对象的原型对象;

f. super关键字:指向当前对象的原型对象;

g. Object.keys(),Object.values(),Object.entries()方法:

(1)Object.keys():返回一个数组,成员是参数对象自身的(不含继承的)所有可比遍历属性的键名;

(2)Object.values():返回一个数组,成员是参数对象自身的所有可遍历属性的键值;

(3)Object.entries():返回一个数组,成员是参数对象自身的所有可遍历属性的键值对数组;

八,symbol类型:

a. 属性名的遍历:

Symbol作为属性名,该属性不会出现在for...in,for...of循环中,也不会被Object.keys()、Object.getOwnPropertyNames()、JSON.stringify()返回。但是,它也不是私有属性,有一个Object.getOwnPropertySymbols方法,可以获取指定对象的所有 Symbol 属性名。

-- Object.getOwnPropertySymbols()方法可以获取指定对象的所有Symbol属性名;

-- Reflect.ownKeys()可以返回所有类型的键名,包括常规键名和Symbol键名;

b. Symbol.for(),Symbol.keyFor()方法:

(1)Symbol.for():接受一个字符串作为参数,然后搜索有没有以该参数作为名称的Symbol值,如果有,就返回这个Symbol值,否则就新建并返回一个以该字符串为名称的Symbol值。

-- 例如:

let s1 = Symbol.for('foo');

let s2 = Symbol.for('foo');

s1 === s2//true

PS:Symbol.for()与Symbol()这两种写法,都会生成新的Symbol。区别是,前者会被登记在全局变量中提供搜索,后者不会。换句话说,Symbol.for()会先搜索是否存在该值,若不存在才会新建一个值,而Symbol()每次返回的值都是不同的,也就是Symbol()没有登记机制;

(2)Symbol.keyFor()方法:返回一个已登记的Symbol类型值的key;

(3)需要注意的是,Symbol.for为 Symbol 值登记的名字,是全局环境的,可以在不同的 iframe 或 service worker 中取到同一个值。

c.内置Symbol:除了定义自己使用的 Symbol 值以外,ES6 还提供了 11 个内置的 Symbol 值,指向语言内部使用的方法。

九,Set和Map数据结构:

a. Set

(1)定义:ES6提供了新的数据结构Set,他类似于数组,但是成员的值都是唯一的,没有重复的值。

(2)Set本身是一个构造函数,用来生成Set数据结构。

-- const s = new Set();

(3)Set函数可以接受一个数组(或者具有iterable接口的其他数据结构)作为参数,用来初始化。

b. Set实例的属性和方法:

-- Set.prototype.constructor:构造函数,默认是Set函数;

-- Set.prototype.size:返回Set实例的成员总数;

-- Array.from方法可以将Set结构转为数组;

 

(1)操作方法:

-- add(value):添加某个值,返回Set结构本身;

-- delete(value):删除某个值,返回一个布尔值,表示是否删除成功;

-- has(value):返回一个布尔值,表示该值是否为Set成员;

-- clear(value):清除所有成员,没有返回值;

(2)遍历操作:

-- keys():返回键名的遍历器;

-- values():返回键值的遍历器;

-- entries():返回键值对的遍历器;

-- forEach():使用回调函数遍历每个成员;

c. WeakSet:WeakSet结构与Set结构类似,也是不重复的值的集合。但是,它与Set有两个区别,首先,WeakSet的成员只能是对象,而不能是其他类型的值。

d. Map数据结构:提供了值对值的结构;

(1)实例的属性和操作方法:

-- size属性:返回Map结构成员的总数;

-- set(key,value):设置键名key对应的键值value,然后返回整个Map结构,如果key已经有值,则键值会更新,否则生成新的键值;

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

-- has(key):返回一个布尔值,表示某个键是否在当前Map对象之中;

-- delete(key):删除某个键,返回布尔值;

-- clear():清除所有成员,木有返回值;

 

(2)遍历方法:

-- keys():返回键名的遍历器;

-- values():返回键值的遍历器;

-- entries():返回所有成员的遍历器;

-- forEach():遍历Map的所有成员;

e. WeakMap:与Map结构类似,也是用于生成键值对的集合

(1)与Map的区别:

-- WeakMap只接受对象作为键名(null除外),不接受其他类型的值作为键名;

-- WeakMap的键名所指向的对象,不计入垃圾回收机制;

十,Promise对象:

a. Promise的含义:Promise是异步编程的一种解决方案,简单的说就是一个容器,里面保存着某个未来才会结束的事件,通常为异步操作,从语法上说,Promise是一个对象,从它可以获取异步操作信息。

(1)Promise对象有以下两个特点:

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

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

PS:

1.有了Promise对象,就可以将异步操作以同步的流程表达出来,避免了层层嵌套的回调函数,此外,Promise对象提供统一的接口,使得控制异步操作更加容易。

2.Promise的缺点:首先无法取消Promise,一旦新建他就会立即执行,无法中途取消,其次,如果不设置回调函数,Promise内部抛出错误,不会反应到外部,第三,当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。

b.基本用法:

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

(2)Promise.then():它的作用是为 Promise 实例添加状态改变时的回调函数。前面说过,then方法的第一个参数是resolved状态的回调函数,第二个参数(可选)是rejected状态的回调函数。

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

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

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

(4)Promise.all()方法:用于将多个Promise实例,包装成一个新的Promise实例,需要接受一个具有Iterator接口,且返回的每个成员都是Promise实例的参数,调用者状态有两种:

-- 所有成员的状态都变成fulfilled,调用者的状态才会变成fulfilled,此时所有成员的返回值组成一个数组,传递给调用者的回调函数

-- 只有成员中有一个被rejected,调用者的状态就变成rejected,此时第一个被rejected的实例的返回值,会传递给p的回调函数;

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

-- 所有成员之中有一个实例率先改变,调用者的状态就跟着改变。那个率先改版的Promise实例的返回值,就传递给调用者的回调函数。

(6)Promise.resolve()方法:将现有对象转为Promise对象,Promise.resovle方法就起到这个作用;

Promise.resolve('foo') // 等价于 new Promise(resolve => resolve('foo'))

 

-- Promise.resolve方法的参数分成四种情况:

1. 参数是一个Promise实例:Promise将不做任何改变,原封不动地返回这个实例。

2. 参数是一个thenable对象:thenable对象指的是具有then方法的对象,比如下面这个对象;

let thenable = { then: function(resolve, reject) {

resolve(42); }

};

3. 参数不是具有then方法的对象,或根本就不是对象:如果参数是一个原始值,或者是一个不具有then方法的对象,则Promise.resolve方法返回一个新的 Promise 对象,状态为resolved。

4.不带有任何参数: Promise.resolve方法允许调用时不带参数,直接返回一个resolved状态的 Promise 对象。

(7)Promise.reject()方法:Promise.reject(reason)方法也会返回一个新的 Promise 实例,该实例的状态为rejected。

十一,Iterator 和 for...of 循环:

a.Iterator(遍历器)的概念:

(1)概念:它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署Iterator接口,就可以完成遍历操作。

(2)作用:

1. 为各种数据结构,提供一个统一的,简便的访问接口;

2. 使得数据结构的成员能够按某种次序排列;

3. ES6创造了一种新的遍历命令for...of循环,Iterator接口主要供for...of消费;

(3)遍历过程:

1. 创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。

2. 第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员。

3. 第二次调用指针对象的next方法,指针就指向数据结构的第二个成员。

4.不断调用,直到结束。

ps:每次调用next方法,都会返回当前成员信息,具体来说,就是返回一个包含value和done两个属性的对象,value属性是当前成员的值,done是一个布尔值,表示遍历是否结束。

 

b.默认Iterator接口:

(1)原生具备Iterator接口的数据结构如下:

-- Array -- Map -- Set -- String -- TypedArray -- 函数的arguments对象 -- NodeList对象

 

十二,Generator函数的语法:

a.基本概念:

Generator函数是ES6提供的一种异步编程解决方案,语法行为与传统函数完全不同,首先可以把它理解成,Generator函数是一个状态机,封装了多个内部状态。

执行Generator函数会返回一个遍历器对象,也就是说,Generator函数除了状态机,还是一个遍历器对象函数生成函数。返回的遍历器对象,可以依次遍历Generator函数内部的每一个状态。

形式上,Generator函数是一个普通的函数,但是有两个特征。一是,function关键字与函数名之间有一个星号;二是,函数内部使用yield表达式,定义不同的内部状态(yield在英语里的意思是‘产出’)。

function* helloWorldGenerator() { yield 'hello'; yield 'world'; return 'ending'; } var hw = helloWorldGenerator();

上面代码定义了一个 Generator 函数helloWorldGenerator,它内部有两个yield表达式(hello和world),即该函数有三个状态:hello,world 和 return 语句(结束执行)。

 

b.yield表达式:

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

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

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

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

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

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

 

c.与Iterator接口的关系:

d.next方法的参数:

(1)yield表达式本身没有返回值,或者说总是返回undefined。next方法可以带一个参数,该参数就会被当成上一个yield表达式的返回值。

(2)这个功能有很重要的语法意义。Generator 函数从暂停状态到恢复运行,它的上下文状态(context)是不变的。通过next方法的参数,就有办法在 Generator 函数开始运行之后,继续向函数体内部注入值。也就是说,可以在 Generator 函数运行的不同阶段,从外部向内部注入不同的值,从而调整函数行为。

e.for...of循环:

(1)for...of循环可以自动遍历Grnerator函数时生成的Iterator对象,且此时不在需要调用next方法

f.yield*表达式:用来在一个Generator函数里面执行另一个Generator函数

g:Generator函数的this:

(1)概念总结:Generator函数总是返回一个遍历器,ES6这个遍历器是Generator函数的实例,也继承了Generator函数的prototype对象上的方法。

function* g() {} g.prototype.hello = function () { return 'hi!'; }; let obj = g(); obj instanceof g // true obj.hello() // 'hi!'

上面代码表明,Generator函数g返回的遍历器obj,是g的实例,而继承了g.prototype,但是,把g当做普通的构造函数,并不会生效,因为g返回的总是遍历器对象,而不是this对象。

可以利用call()将this指向Generator函数的prototype

 

h:含义:

(1)Generator与状态机:

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

var clock = function* () { while (true) { console.log('Tick!'); yield; console.log('Tock!'); yield; } };

Generator函数clock每运行一次,就改变一次状态!

(2)Generator与协程:

1. 协程:协程是一种程序运行的方式,可以理解成“协作的线程”或者“协作的函数”。协程既可以单线程实现,也可以用多线程实现。前者是一种特殊的子例程,后者是一种特殊的线程。

2. 协程与子例程的差异:传统的“子例程”(subroutine)采用堆栈式“后进先出”的执行方式,只有当调用的子函数完全执行完毕,才会结束执行父函数。协程与其不同,多个线程(单线程情况下,即多个函数)可以并行执行,但是只有一个线程(或函数)处于正在运行的状态,其他线程(或函数)都处于暂停态(suspended),线程(或函数)之间可以交换执行权。也就是说,一个线程(或函数)执行到一半,可以暂停执行,将执行权交给另一个线程(或函数),等到稍后收回执行权的时候,再恢复执行。这种可以并行执行、交换执行权的线程(或函数),就称为协程。

3. 协程与普通线程的差异:不难看出,协程适合用于多任务运行的环境。在这个意义上,它与普通的线程很相似,都有自己的执行上下文、可以分享全局变量。它们的不同之处在于,同一时间可以有多个线程处于运行状态,但是运行的协程只能有一个,其他协程都处于暂停状态。此外,普通的线程是抢先式的,到底哪个线程优先得到资源,必须由运行环境决定,但是协程是合作式的,执行权由协程自己分配。

由于 JavaScript 是单线程语言,只能保持一个调用栈。引入协程以后,每个任务可以保持自己的调用栈。这样做的最大好处,就是抛出错误的时候,可以找到原始的调用栈。不至于像异步操作的回调函数那样,一旦出错,原始的调用栈早就结束。

Generator 函数是 ES6 对协程的实现,但属于不完全实现。Generator 函数被称为“半协程”(semi-coroutine),意思是只有 Generator 函数的调用者,才能将程序的执行权还给 Generator 函数。如果是完全执行的协程,任何函数都可以让暂停的协程继续执行。

如果将 Generator 函数当作协程,完全可以将多个需要互相协作的任务写成 Generator 函数,它们之间使用yield表达式交换控制权。

(3)Generator与上下文:

Generator 函数执行产生的上下文环境,一旦遇到yield命令,就会暂时退出堆栈,但是并不消失,里面的所有变量和对象会冻结在当前状态。等到对它执行next命令时,这个上下文环境又会重新加入调用栈,冻结的变量和对象恢复执行

 

i. 应用:Generator 可以暂停函数执行,返回任意表达式的值。这种特点使得 Generator 有多种应用场景。

(1)异步操作的同步化表达:

function* main() { var result = yield request("http://some.url"); var resp = JSON.parse(result); console.log(resp.value); } function request(url) { makeAjaxCall(url, function(response){ it.next(response); }); } var it = main(); it.next();

上面代码的main函数,就是通过 Ajax 操作获取数据。可以看到,除了多了一个yield,它几乎与同步操作的写法完全一样。注意,makeAjaxCall函数中的next方法,必须加上response参数,因为yield表达式,本身是没有值的,总是等于undefined。

(2)控制流管理:

Generator函数进行控制流管理,分步执行任务,但必须为同步操作

(3)部署Iterator接口:利用 Generator 函数,可以在任意对象上部署 Iterator 接口。

function* iterEntries(obj) { let keys = Object.keys(obj); for (let i=0; i < keys.length; i++) { let key = keys[i]; yield [key, obj[key]]; } } let myObj = { foo: 3, bar: 7 }; for (let [key, value] of iterEntries(myObj)) { console.log(key, value); } // foo 3 // bar 7

(4)作为数据结构:

 

十三,Generator函数的异步应用:

a.协程:

传统的编程语言,早有异步编程的解决方案(其实是多任务的解决方案)。其中有一种叫做"协程"(coroutine),意思是多个线程互相协作,完成异步任务。

协程有点像函数,又有点像线程。它的运行流程大致如下。

  • 第一步,协程A开始执行。
  • 第二步,协程A执行到一半,进入暂停,执行权转移到协程B。
  • 第三步,(一段时间后)协程B交还执行权。
  • 第四步,协程A恢复执行。

上面流程的协程A,就是异步任务,因为它分成两段(或多段)执行。

b.协程的Generator函数实现:

Generator函数是协程在ES6的实现,最大的特点就是可以交出函数的执行权。

c.Generator函数的数据交换和错误处理:

function* gen(x){ try { var y = yield x + 2; } catch (e){ console.log(e); } return y; } var g = gen(1); g.next(); g.throw('出错了');

d.异步任务的封装:

var fetch = require('node-fetch'); function* gen(){ var url = 'https://api.github.com/users/github'; var result = yield fetch(url); console.log(result.bio); }

var g = gen(); var result = g.next(); result.value.then(function(data){ return data.json(); }).then(function(data){ g.next(data); });

 

e.Thunk函数:Thunk 函数替换的不是表达式,而是多参数函数,将其替换成一个只接受回调函数作为参数的单参数函数

 

十四,Module的语法:

a.严格模式:ES6的模块自动采用严格模式

严格模式主要有以下限制:

1. 变量必须声明后使用;

2. 函数的参数不能有同名属性,否则报错;

3. 不能使用with语句;

4. 不能对只读属性赋值,否则报错;

5. 不能使用前缀0表示八进制数,否则报错;

6. 不能删除变量delete prop,会报错,只能删除属性delete global[prop];

7. eval不会在他的外层作用域引入变量;

8. eval和arguments不能被重新赋值;

9. arguments不会自动反应函数参数变化;

10. 不能使用arguments.callee;

11. 不能使用arguments.caller;

12. 禁止this指向全局对象;

13. 不能使用fn.caller和fn.arguments获取函数调用的堆栈;

14. 增加了保留字;

b.export命令:

(1)模块命令主要由两个命令构成:export 和 import,export命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能。

(2)可以用as关键字重命名;

(3)export是对外接口,必须与模块内部的变量建立一一对应关系;

(4)export命令可以出现在模块的任何位置,只要处于模块顶层就可以。如果处于块级作用域内,就会报错

 

c.import命令:使用export命令定义了模块的对外接口以后,其他 JS 文件就可以通过import命令加载这个模块

(1)同样可以使用as关键字;

(2)import命令输入的变量都是只读的,因为它的本质是输入接口。也就是说,不允许在加载模块的脚本里面,改写接口。如果是输出的是对象,改写对象的属性是允许的;

(3)import后面的from指定模块文件的位置,可以是相对路径,也可以是绝对路径,.js后缀可以省略。如果只是模块名,不带有路径,那么必须有配置文件,告诉 JavaScript 引擎该模块的位置。

(4)import命令具有提升效果,会提升到整个模块的头部,首先执行;

(5)由于import是静态执行,所以不能使用表达式和变量,这些只有在运行时才能得到结果的语法结构;

// 报错 import { 'f' + 'oo' } from 'my_module';

// 报错 let module = 'my_module'; import { foo } from module; // 报错 if (x === 1) { import { foo } from 'module1'; } else { import { foo } from 'module2'; }

(6)如果多次重复执行同一句import语句,那么只会执行一次,而不会执行多次。

(7)除了指定加载某个输出值,还可以使用整体加载,即用星号(*)指定一个对象,所有输出值都加载在这个对象上面。

// circle.js export function area(radius) { return Math.PI * radius * radius; } export function circumference(radius) { return 2 * Math.PI * radius; }

 

import * as circle from './circle'; console.log('圆面积:' + circle.area(4)); console.log('圆周长:' + circle.circumference(14));

注意,模块整体加载所在的那个对象(上例是circle),应该是可以静态分析的,所以不允许运行时改变;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值