#ES6
##一、简介
ECMAScript 6.0(以下简称 ES6)是 JavaScript 语言的下一代标准,已经在 2015 年 6 月正式发布了。它的目标,是使得 JavaScript 语言可以用来编写复杂的大型应用程序,成为企业级开发语言。ECMAScript 和 JavaScript 的关系是,前者是后者的规格,后者是前者的一种实现。
** 学习参考网址:**
- https://github.com/search?q=es6
- https://www.jianshu.com/p/287e0bb867ae
- http://es6.ruanyifeng.com/
- http://www.runoob.com/w3cnote/es6-setup.html
- https://github.com/tc39/ecma262
##二、语法
###1、 let 与 const
let 声明的变量只在 let 命令所在的代码块内有效。 const 声明一个只读的常量,一旦声明,常量的值就不能改变。
let 特征
代码块内有效,不能重复声明,不存在变量提升。
const 特征
const 声明一个只读变量,声明之后不允许改变。意味着,一但声明必须初始化,否则会报错。
###2、解构赋值
解构赋值是对赋值运算符的扩展。
他是一种针对数组或者对象进行模式匹配,然后对其中的变量进行赋值。
在代码书写上简洁且易读,语义更加清晰明了;也方便了复杂对象中数据字段获取。
数组模型的解构(Array)
基本
let [a, b, c] = [1, 2, 3]; // a = 1 // b = 2 // c = 3[链接文字](http://example.com)
可嵌套
let [a, [[b], c]] = [1, [[2], 3]]; // a = 1 // b = 2 // c = 3
不完全解构
let [a = 1, b] = []; // a = 1, b = undefined
剩余运算符
let [a, ...b] = [1, 2, 3]; //a = 1 //b = [2, 3]
字符串
let [a, b, c, d, e] = 'hello'; // a = 'h' // b = 'e' // c = 'l' // d = 'l' // e = 'o'
解构默认值
let [a = 2] = [undefined]; // a = 2 let [a = 3, b = a] = []; // a = 3, b = 3 let [a = 3, b = a] = [1]; // a = 1, b = 1 let [a = 3, b = a] = [1, 2]; // a = 1, b = 2
对象模型的解构(Object)
基本
let { foo, bar } = { foo: 'aaa', bar: 'bbb' }; // foo = 'aaa' // bar = 'bbb' let { baz : foo } = { baz : 'ddd' }; // foo = 'ddd'
可嵌套可忽略
let obj = {p: ['hello', {y: 'world'}] }; let {p: [x, { y }] } = obj; // x = 'hello' // y = 'world' let obj = {p: ['hello', {y: 'world'}] }; let {p: [x, { }] } = obj; // x = 'hello'
不完全解构
let obj = {p: [{y: 'world'}] }; let {p: [{ y }, x ] } = obj; // x = undefined // y = 'world'
剩余运算符
let {a, b, ...rest} = {a: 10, b: 20, c: 30, d: 40}; // a = 10 // b = 20 // rest = {c: 30, d: 40}
解构默认值
let {a = 10, b = 5} = {a: 3}; // a = 3; b = 5; let {a: aa = 10, b: bb = 5} = {a: 3}; // aa = 3; bb = 5;
###3、Symbol 用法
ES6 引入了一种新的原始数据类型 Symbol ,表示独一无二的值,最大的用法是用来定义对象的唯一属性名。
ES6 数据类型除了 Number 、 String 、 Boolean 、 Objec t、 null 和 undefined ,还新增了 Symbol 。
**基本用法 **
Symbol 函数栈不能用 new 命令,因为 Symbol 是原始数据类型,不是对象。可以接受一个字符串作为参数,为新创建的 Symbol 提供描述,用来显示在控制台或者作为字符串的时候使用,便于区分。
使用场景
作为属性名
由于每一个 Symbol 的值都是不相等的,所以 Symbol 作为对象的属性名,可以保证属性不重名。
let sy = Symbol("key1"); // 写法1 let syObject = {}; syObject[sy] = "kk"; console.log(syObject); // {Symbol(key1): "kk"} // 写法2 let syObject = { [sy]: "kk"}; console.log(syObject); // {Symbol(key1): "kk"} // 写法3 let syObject = {}; Object.defineProperty(syObject, sy, {value: "kk"}); console.log(syObject); // {Symbol(key1): "kk"}
Symbol 作为对象属性名时不能用.运算符,要用方括号。因为.运算符后面是字符串,所以取到的是字符串 sy 属性,而不是 Symbol 值 sy 属性。
let syObject = {}; syObject[sy] = "kk"; syObject[sy]; // "kk" syObject.sy; // undefined
注意点
Symbol 值作为属性名时,该属性是公有属性不是私有属性,可以在类的外部访问。但是不会出现在 for...in 、 for...of 的循环中,也不会被 Object.keys() 、 Object.getOwnPropertyNames() 返回。如果要读取到一个对象的 Symbol 属性,可以通过 Object.getOwnPropertySymbols() 和 Reflect.ownKeys() 取到。
定义常量
Symbol 定义常量可以保证一组常量的值都不相等。
以下为例,Symbol 的值是唯一的,所以不会出现相同值得常量,即可以保证 switch 按照代码预想的方式执行。
const COLOR_RED = Symbol("red"); const COLOR_YELLOW = Symbol("yellow"); const COLOR_BLUE = Symbol("blue"); function getConstantName(color) { switch (color) { case COLOR_RED : return "COLOR_RED"; case COLOR_YELLOW : return "COLOR_YELLOW "; case COLOR_BLUE: return "COLOR_BLUE"; default: throw new Exception('Can't find this color'); } }
Symbol.for()
Symbol.for() 类似单例模式,首先会在全局搜索被登记的 Symbol 中是否有该字符串参数作为名称的 Symbol 值,如果有即返回该 Symbol 值,若没有则新建并返回一个以该字符串参数为名称的 Symbol 值,并登记在全局环境中供搜索。
let yellow = Symbol("Yellow"); let yellow1 = Symbol.for("Yellow"); yellow === yellow1; // false let yellow2 = Symbol.for("Yellow"); yellow1 === yellow2; // true}
Symbol.keyFor()
Symbol.keyFor() 返回一个已登记的 Symbol 类型值的 key ,用来检测该字符串参数作为名称的 Symbol 值是否已被登记。
let yellow1 = Symbol.for("Yellow"); Symbol.keyFor(yellow1); // "Yellow"
###4、Map 与 Set
Map 对象
Map 对象保存键值对。任何值(对象或者原始值) 都可以作为一个键或一个值。
Set 对象
Set 对象允许你存储任何类型的唯一值,无论是原始值或者是对象引用。
###5、ES6 Reflect 与 Proxy
Proxy 与 Reflect 是 ES6 为了操作对象引入的 API 。Proxy 可以对目标对象的读取、函数调用等操作进行拦截,然后进行操作处理。它不直接操作对象,而是像代理模式,通过对象的代理对象进行操作,在进行这些操作时,可以添加一些需要的额外操作。Reflect 可以用于获取目标对象的行为,它与 Object 类似,但是更易读,为操作对象提供了一种更优雅的方式。它的方法与 Proxy 是对应的。
###6、字符串
ES6 之前判断字符串是否包含子串,用 indexOf 方法,ES6 新增了子串的识别方法。
- includes():返回布尔值,判断是否找到参数字符串。
- startsWith():返回布尔值,判断参数字符串是否在原字符串的头部。
- endsWith():返回布尔值,判断参数字符串是否在原字符串的尾部。
let string = "apple,banana,orange"; string.includes("banana"); // true string.startsWith("apple"); // true string.endsWith("apple"); // false string.startsWith("banana",6) // true
字符串重复
repeat():返回新的字符串,表示将字符串重复指定次数返回。
console.log("Hello,".repeat(2)); // "Hello,Hello,"
如果参数是小数,向下取整
console.log("Hello,".repeat(3.2)); // "Hello,Hello,Hello,"
如果参数是 0 至 -1 之间的小数,会进行取整运算,0 至 -1 之间的小数取整得到 -0 ,等同于 repeat 零次
console.log("Hello,".repeat(-0.5)); // ""
如果参数是 NaN,等同于 repeat 零次
console.log("Hello,".repeat(NaN)); // ""
如果参数是负数或者 Infinity ,会报错:
console.log("Hello,".repeat(-1)); // RangeError: Invalid count value console.log("Hello,".repeat(Infinity)); // RangeError: Invalid count value
如果传入的参数是字符串,则会先将字符串转化为数字
console.log("Hello,".repeat("hh")); // "" console.log("Hello,".repeat("2")); // "Hello,Hello,"
字符串补全
-
padStart:返回新的字符串,表示用参数字符串从头部补全原字符串。
-
padEnd:返回新的字符串,表示用参数字符串从头部补全原字符串。
以上两个方法接受两个参数,第一个参数是指定生成的字符串的最小长度,第二个参数是用来补全的字符串。如果没有指定第二个参数,默认用空格填充。
console.log("h".padStart(5,"o")); // "ooooh" console.log("h".padEnd(5,"o")); // "hoooo" console.log("h".padStart(5)); // " h"
如果指定的长度大于或者等于原字符串的长度,则返回原字符串:
console.log("hello".padStart(5,"A")); // "hello"
如果原字符串加上补全字符串长度大于指定长度,则截去超出位数的补全字符串:
console.log("hello".padEnd(10,",world!")); // "hello,worl"
常用于补全位数:
console.log("123".padStart(10,"0")); // "0000000123"
**模板字符串**
模板字符串相当于加强版的字符串,用反引号 `,除了作为普通字符串,还可以用来定义多行字符串,还可以在字符串中加入变量和表达式。
普通字符串
let string =
Hello’\n’world; console.log(string); // "Hello' // 'world"
多行字符串:
let string1 =
Hey,
can you stop angry now?
;
console.log(string1); // Hey, // can you stop angry now?
字符串插入变量和表达式。
变量名写在 {} 中可以放入 JavaScript 表达式。
let name = "Mike"; let age = 27; let info =
``My Name is ${name},I am {f()};
console.log(string2); // Game start,have fun!`
注意要点
模板字符串中的换行和空格都是会被保留的
标签模板
标签模板,是一个函数的调用,其中调用的参数是模板字符串。
当模板字符串中带有变量,会将模板字符串参数处理成多个参数
###7、数值
数值的表示
二进制表示法新写法: 前缀 0b 或 0B 。
console.log(0b11 === 3); // true console.log(0B11 === 3); // true
八进制表示法新写法: 前缀 0o 或 0O 。
console.log(0o11 === 9); // true console.log(0O11 === 9); // true
常量
Number.EPSILON
Number.EPSILON 属性表示 1 与大于 1 的最小浮点数之间的差。
它的值接近于 2.2204460492503130808472633361816E-16,或者 2-52。
测试数值是否在误差范围内:
0.1 + 0.2 === 0.3; // false // 在误差范围内即视为相等 equal = (Math.abs(0.1 - 0.3 + 0.2) < Number.EPSILON); // true
方法
Number 对象新方法
Number.isFinite()
用于检查一个数值是否为有限的( finite ),即不是 Infinity
从全局移植到 Number 对象的方法
逐步减少全局方法,用于全局变量的模块化。
方法的行为没有发生改变。
Number.parseInt()
Math 对象的扩展
Math.cbrt
用于计算一个数的立方根。
Math.imul
两个数以 32 位带符号整数形式相乘的结果,返回的也是一个 32 位的带符号整数。
Math.hypot
用于计算所有参数的平方和的平方根。
Math.clz32
用于返回数字的32 位无符号整数形式的前导0的个数。
** 数字处理 **
Math.trunc
用于返回数字的整数部分。
Math.fround
用于获取数字的32位单精度浮点数形式。
判断
Math.sign
判断数字的符号(正、负、0)
对数方法
Math.expm1()
用于计算 e 的 x 次方减 1 的结果,即 Math.exp(x) - 1 。
Math.log1p(x)
用于计算1 + x 的自然对数,即 Math.log(1 + x) 。
Math.log10(x)
用于计算以 10 为底的 x 的对数。
Math.log2()
用于计算 2 为底的 x 的对数。
双曲函数方法
Math.sinh(x): 用于计算双曲正弦。 Math.cosh(x): 用于计算双曲余弦。 Math.tanh(x): 用于计算双曲正切。 Math.asinh(x): 用于计算反双曲正弦。 Math.acosh(x): 用于计算反双曲余弦。 Math.atanh(x): 用于计算反双曲正切。
指数运算符
1 ** 2; // 1 // 右结合,从右至左计算 2 ** 2 ** 3; // 256 // **= let exam = 2; exam ** = 2; // 4
###8、 对象
对象字面量
属性的简洁表示法
ES6允许对象的属性直接写变量,这时候属性名是变量名,属性值是变量值。
const age = 12; const name = "Amy"; const person = {age, name}; person //{age: 12, name: "Amy"} //等同于 const person = {age: age, name: name}
方法名也可以简写
const person = { sayHi(){ console.log("Hi"); } } person.sayHi(); //"Hi" //等同于 const person = { sayHi:function(){ console.log("Hi"); } } person.sayHi();//"Hi"
如果是Generator 函数,则要在前面加一个星号:
const obj = { *myGenerator() { yield 'hello world'; } }; //等同于 const obj = { myGenerator: function* () { yield 'hello world'; } };
属性名表达式
ES6允许用表达式作为属性名,但是一定要将表达式放在方括号内。
const obj = { ["he"+"llo"](){ return "Hi"; } } obj.hello(); //"Hi"
注意点:属性的简洁表示法和属性名表达式不能同时使用,否则会报错。
const hello = "Hello"; const obj = { [hello] }; obj //SyntaxError: Unexpected token } const hello = "Hello"; const obj = { [hello+"2"]:"world"};obj //{Hello2: "world"}
对象的拓展运算符
拓展运算符(...)用于取出参数对象所有可遍历属性然后拷贝到当前对象。
基本用法
let person = {name: "Amy", age: 15}; let someone = { ...person }; someone; //{name: "Amy", age: 15}
可用于合并两个对象
let age = {age: 15}; let name = {name: "Amy"}; let person = {...age, ...name}; person; //{age: 15, name: "Amy"}
注意点
自定义的属性和拓展运算符对象里面属性的相同的时候:自定义的属性在拓展运算符后面,则拓展运算符对象内部同名的属性将被覆盖掉。
let person = {name: "Amy", age: 15}; let someone = { ...person, name: "Mike", age: 17}; someone; //{name: "Mike", age: 17}
自定义的属性在拓展运算度前面,则变成设置新对象默认属性值。
let person = {name: "Amy", age: 15}; let someone = {name: "Mike", age: 17, ...person}; someone; //{name: "Amy", age: 15}
拓展运算符后面是空对象,没有任何效果也不会报错。
let a = {...{}, a: 1, b: 2}; a; //{a: 1, b: 2}
拓展运算符后面是null或者undefined,没有效果也不会报错。
let b = {...null, ...undefined, a: 1, b: 2}; b; //{a: 1, b: 2}
对象的新方法
用于将源对象的所有可枚举属性复制到目标对象中
基本用法
let target = {a: 1}; let object2 = {b: 2}; let object3 = {c: 3}; Object.assign(target,object2,object3); // 第一个参数是目标对象,后面的参数是源对象 target; // {a: 1, b: 2, c: 3
如果目标对象和源对象有同名属性,或者多个源对象有同名属性,则后面的属性会覆盖前面的属性。
如果该函数只有一个参数,当参数为对象时,直接返回该对象;当参数不是对象时,会先将参数转为对象然后返回。
注意点
assign 的属性拷贝是浅拷贝:
let sourceObj = { a: { b: 1}}; let targetObj = {c: 3}; Object.assign(targetObj, sourceObj); targetObj.a.b = 2; sourceObj.a.b; // 2
Object.is(value1, value2)
用来比较两个值是否严格相等,与(===)基本类似。
###9、数组
数组创建
1、
Array.of()
将参数中所有值作为元素形成数组。
console.log(Array.of(1, 2, 3, 4)); // [1, 2, 3, 4] // 参数值可为不同类型 console.log(Array.of(1, '2', true)); // [1, '2', true] // 参数为空时返回空数组 console.log(Array.of()); // []
2、Array.from()
将类数组对象或可迭代对象转化为数组。
// 参数为数组,返回与原数组一样的数组 console.log(Array.from([1, 2])); // [1, 2] // 参数含空位 console.log(Array.from([1, , 3])); // [1, undefined, 3]
3、mapFn
可选,map 函数,用于对每个元素进行处理,放入数组的是处理后的元素。
console.log(Array.from([1, 2, 3], (n) => n * 2)); // [2, 4, 6]
4、thisArg
可选,用于指定 map 函数执行时的 this 对象。
let map = { do: function(n) { return n * 2; } } let arrayLike = [1, 2, 3]; console.log(Array.from(arrayLike, function (n){ return this.do(n); }, map)); // [2, 4, 6]
类数组对象
1、一个类数组对象必须含有 length 属性,且元素属性名必须是数值或者可转换为数值的字符
let arr = Array.from({ 0: '1', 1: '2', 2: 3, length: 3 }); console.log(); // ['1', '2', 3] // 没有 length 属性,则返回空数组 let array = Array.from({ 0: '1', 1: '2', 2: 3, }); console.log(array); // [] // 元素属性名不为数值且无法转换为数值,返回长度为 length 元素值为 undefined 的数组 let array1 = Array.from({ a: 1, b: 2, length: 2 }); console.log(array1); // [undefined, undefined]
2、转换可迭代对象
转换 map
let map = new Map(); map.set('key0', 'value0'); map.set('key1', 'value1'); console.log(Array.from(map)); // [['key0', 'value0'],['key1', // 'value1']]
3、转换 set
let arr = [1, 2, 3]; let set = new Set(arr); console.log(Array.from(set)); // [1, 2, 3]
4、转换字符串
let str = 'abc'; console.log(Array.from(str)); // ["a", "b", "c"]
扩展的方法
1、查找
find()
查找数组中符合条件的元素,若有多个符合条件的元素,则返回第一个元素。
let arr = Array.of(1, 2, 3, 4); console.log(arr.find(item => item > 2)); // 3 // 数组空位处理为 undefined console.log([, 1].find(n => true)); // undefined
findIndex()
查找数组中符合条件的元素索引,若有多个符合条件的元素,则返回第一个元素索引。
let arr = Array.of(1, 2, 1, 3); // 参数1:回调函数 // 参数2(可选):指定回调函数中的 this 值 console.log(arr.findIndex(item => item = 1)); // 0 // 数组空位处理为 undefined console.log([, 1].findIndex(n => true)); //0
2、填充
fill()
将一定范围索引的数组元素内容填充为单个指定的值。
let arr = Array.of(1, 2, 3, 4); // 参数1:用来填充的值 // 参数2:被填充的起始索引 // 参数3(可选):被填充的结束索引,默认为数组末尾 console.log(arr.fill(0,1,2)); // [1, 0, 3, 4]
copyWithin()
将一定范围索引的数组元素修改为此数组另一指定范围索引的元素。
// 参数1:被修改的起始索引 // 参数2:被用来覆盖的数据的起始索引 // 参数3(可选):被用来覆盖的数据的结束索引,默认为数组末尾 console.log([1, 2, 3, 4].copyWithin(0,2,4)); // [3, 4, 3, 4] // 参数1为负数表示倒数 console.log([1, 2, 3, 4].copyWithin(-2, 0)); // [1, 2, 1, 2] console.log([1, 2, ,4].copyWithin(0, 2, 4)); // [, 4, , 4]
3、遍历
entries()
遍历键值对。
for(let [key, value] of ['a', 'b'].entries()){ console.log(key, value); } // 0 "a" // 1 "b" // 不使用 for... of 循环 let entries = ['a', 'b'].entries(); console.log(entries.next().value); // [0, "a"] console.log(entries.next().value); // [1, "b"] // 数组含空位 console.log([...[,'a'].entries()]); // [[0, undefined], [1, "a"]]
keys()
遍历键名。
for(let key of ['a', 'b'].keys()){ console.log(key); } // 0 // 1 // 数组含空位 console.log([...[,'a'].keys()]); // [0, 1] values()
遍历键值。
for(let value of ['a', 'b'].values()){ console.log(value); } // "a" // "b" // 数组含空位 console.log([...[,'a'].values()]); // [undefined, "a"]
4、包含
includes()
数组是否包含指定值。
注意:与 Set 和 Map 的 has 方法区分;Set 的 has 方法用于查找值;Map 的 has 方法用于查找键名。
// 参数1:包含的指定值 [1, 2, 3].includes(1); // true // 参数2:可选,搜索的起始索引,默认为0 [1, 2, 3].includes(1, 2); // false // NaN 的包含判断 [1, NaN, 3].includes(NaN); // true
定型数组
数组缓冲区的特定类型的视图。
可以强制使用特定的数据类型,而不是使用通用的 DataView 对象来操作数组缓冲区。
扩展运算符
复制数组
let arr = [1, 2], arr1 = [...arr]; console.log(arr1); // [1, 2] // 数组含空位 let arr2 = [1, , 3], arr3 = [...arr2]; console.log(arr3); [1, undefined, 3]
合并数组
console.log([...[1, 2],...[3, 4]]); // [1, 2, 3, 4]
###10、 函数
函数参数的扩展
默认参数
基本用法
function fn(name,age=17){ console.log(name+","+age); } fn("Amy",18); // Amy,18 fn("Amy",""); // Amy, fn("Amy"); // Amy,17
注意点:使用函数默认参数时,不允许有同名参数。
// 不报错 function fn(name,name){ console.log(name); } // 报错 //SyntaxError: Duplicate parameter name not allowed in this context function fn(name,name,age=17){ console.log(name+","+age); }
只有在未传递参数,或者参数为 undefined 时,才会使用默认参数,null 值被认为是有效的值传递。
function fn(name,age=17){ console.log(name+","+age); } fn("Amy",null); // Amy,null
函数参数默认值存在暂时性死区,在函数参数默认值表达式中,还未初始化赋值的参数值无法作为其他参数的默认值。
function f(x,y=x){ console.log(x,y); } f(1); // 1 1 function f(x=y){ console.log(x); } f(); // ReferenceError: y is not defined
不定参数
不定参数用来表示不确定参数个数,形如,…变量名,由…加上一个具名参数标识符组成。具名参数只能放在参数组的最后,并且有且只有一个不定参数。
基本用法
function f(...values){ console.log(values.length); } f(1,2); //2 f(1,2,3,4); //4
箭头函数
箭头函数提供了一种更加简洁的函数书写方式。基本语法是:
参数 => 函数体
1、基本用法:
var f = v => v; //等价于 var f = function(a){ return a; } f(1); //1
2、当箭头函数没有参数或者有多个参数,要用 () 括起来。
var f = (a,b) => a+b; f(6,2); //8
3、当箭头函数函数体有多行语句,用 {} 包裹起来,表示代码块,当只有一行语句,并且需要返回结果时,可以省略 {} , 结果会自动返回。
var f = (a,b) => { let result = a+b; return result; } f(6,2); // 8
4、当箭头函数要返回对象的时候,为了区分于代码块,要用 () 将对象包裹起来
// 报错 var f = (id,name) => {id: id, name: name}; f(6,2); // SyntaxError: Unexpected token : // 不报错 var f = (id,name) => ({id: id, name: name}); f(6,2); // {id: 6, name: 2}
5、注意点:没有 this、super、arguments 和 new.target 绑定。
var func = () => { // 箭头函数里面没有 this 对象, // 此时的 this 是外层的 this 对象,即 Window console.log(this) } func(55) // Window var func = () => { console.log(arguments) } func(55); // ReferenceError: arguments is not defined
6、箭头函数体中的 this 对象,是定义函数时的对象,而不是使用函数时的对象。
function fn(){ setTimeout(()=>{ // 定义时,this 绑定的是 fn 中的 this 对象 console.log(this.a); },0) } var a = 20; // fn 的 this 对象为 {a: 19} fn.call({a: 18}); // 18
7、不可以作为构造函数,也就是不能使用 new 命令,否则会报错
适合使用的场景
ES6 之前,JavaScript 的 this 对象一直很令人头大,回调函数,经常看到 var self = this 这样的代码,为了将外部 this 传递到回调函数中,那么有了箭头函数,就不需要这样做了,直接使用 this 就行。所以,当我们需要维护一个 this 上下文的时候,就可以使用箭头函数。
不适合使用的场景
定义函数的方法,且该方法中包含 this;
需要动态 this 的时候。
###11、迭代器
** Iterator**
Iterator 是 ES6 引入的一种新的遍历机制,迭代器有两个核心概念:
1、 迭代器是一个统一的接口,它的作用是使各种数据结构可被便捷的访问,它是通过一个键为Symbol.iterator 的方法来实现。
2、 迭代器是用于遍历数据结构元素的指针(如数据库中的游标)。
迭代过程
迭代的过程如下:
通过 Symbol.iterator 创建一个迭代器,指向当前数据结构的起始位置
随后通过 next 方法进行向下迭代指向下一个位置, next 方法会返回当前位置的对象,对象包含了 value 和 done 两个属性, value 是当前属性的值, done 用于判断是否遍历结束
当 done 为 true 时则遍历结束
下面通过一个简单的例子进行说明:
const items = ["zero", "one", "two"]; const it = items[Symbol.iterator](); it.next(); {value: "zero", done: false} it.next(); {value: "one", done: false} it.next(); {value: "two", done: false} it.next(); {value: undefined, done: true}
可迭代的数据结构
以下是可迭代的值:
- Array
for (let item of ["zero", "one", "two"]) { console.log(item); } // output: // zero // one // two
- String
for (const c of 'z\uD83D\uDC0A') { console.log(c); } // output: // z // \uD83D\uDC0A
- Map
const map = new Map(); map.set(0, "zero"); map.set(1, "one"); for (let item of map) { console.log(item); } // output: // [0, "zero"] // [1, "one"]
注意: WeakMaps 不可迭代 - Set
const set = new Set(); set.add("zero"); set.add("one"); for (let item of set) { console.log(item); } // output: // zero // one
注意: WeakSets 不可迭代 - Dom元素(正在进行中)
arguments
arguments 目前在 ES6 中使用越来越少,但也是可遍历的
function args() { for (let item of arguments) { console.log(item); } } args("zero", "one"); // output: // zero // one
普通对象不可迭代
普通对象是由 object 创建的,不可迭代:
// TypeError for (let item of {}) { console.log(item); }
###12、Class 类
在ES6中,class (类)作为对象的模板被引入,可以通过 class 关键字定义类。
class 的本质是 function。
它可以看作一个语法糖,让对象原型的写法更加清晰、更像面向对象编程的语法。
基础用法
1、类定义
类表达式可以为匿名或命名。
// 匿名类
let Example = class { constructor(a) { this.a = a; } } // 命名类 let Example = class Example { constructor(a) { this.a = a; } }
2、类声明
class Example { constructor(a) { this.a = a; } } 注意要点:不可重复声明。 class Example{} class Example{} // Uncaught SyntaxError: Identifier 'Example' has already been // declared let Example1 = class{} class Example{} // Uncaught SyntaxError: Identifier 'Example' has already been // declared
3、注意要点
类定义不会被提升,这意味着,必须在访问前对类进行定义,否则就会报错。
类中方法不需要 function 关键字。
方法间不能加分号。
new Example();
class Example {}
4、类的主体
属性 prototype
ES6 中,prototype 仍旧存在,虽然可以直接自类中定义方法,但是其实方法还是定义在 prototype 上的。
覆盖方法 / 初始化时 添加方法 Example.prototype={ //methods } 添加方法 Object.assign(Example.prototype,{ //methods })
a、静态属性
静态属性:class 本身的属性,即直接定义在类内部的属性( Class.propname ),不需要实例化。 ES6 中规定,Class 内部只有静态方法,没有静态属性。
class Example { // 新提案 static a = 2; } // 目前可行写法 Example.b = 2;
b、公共属性
class Example{} Example.prototype.a = 2;
c、实例属性
实例属性:定义在实例对象( this )上的属性。
class Example { a = 2; constructor () { console.log(this.a); } }
d、name 属性
返回跟在 class 后的类名(存在时)。
let Example=class Exam { constructor(a) { this.a = a; } } console.log(Example.name); // Exam let Example=class { constructor(a) { this.a = a; } } console.log(Example.name); // Example
5、方法
a、constructor 方法
constructor 方法是类的默认方法,创建类的实例化对象时被调用。
class Example{ constructor(){ console.log('我是constructor'); } } new Example(); // 我是constructor
b、静态方法
class Example{ static sum(a, b) { console.log(a+b); } } Example.sum(1, 2); // 3
c、原型方法
class Example { sum(a, b) { console.log(a + b); } } let exam = new Example(); exam.sum(1, 2); // 3
d、实例方法
class Example { constructor() { this.sum = (a, b) => { console.log(a + b); } } }
类的实例化
new
class 的实例化必须通过 new 关键字。
class Example {} let exam1 = Example(); // Class constructor Example cannot be invoked without 'new'
实例化对象
class Example { constructor(a, b) { this.a = a; this.b = b; console.log('Example'); } sum() { return this.a + this.b; } } let exam1 = new Example(2, 1); let exam2 = new Example(3, 1); console.log(exam1._proto_ == exam2._proto_); // true exam1._proto_.sub = function() { return this.a - this.b; } console.log(exam1.sub()); // 1 console.log(exam2.sub()); // 2
decorator
decorator 是一个函数,用来修改类的行为,在代码编译时产生作用。
类修饰
一个参数
第一个参数 target,指向类本身。
function testable(target) { target.isTestable = true; } @testable class Example {} Example.isTestable; // true 多个参数——嵌套实现 function testable(isTestable) { return function(target) { target.isTestable=isTestable; } } @testable(true) class Example {} Example.isTestable; // true
方法修饰
3个参数:target(类的原型对象)、name(修饰的属性名)、
descriptor(该属性的描述对象)。
class Example { @writable sum(a, b) { return a + b; } } function writable(target, name, descriptor) { descriptor.writable = false; return descriptor; // 必须返回 } 修饰器执行顺序 由外向内进入,由内向外执行。 class Example { @logMethod(1) @logMthod(2) sum(a, b){ return a + b; } } function logMethod(id) { console.log('evaluated logMethod'+id); return (target, name, desctiptor) => console.log('excuted logMethod '+id); } // evaluated logMethod 1 // evaluated logMethod 2 // excuted logMethod 2 // excuted logMethod 1
封装与继承
getter / setter
getter 与 setter 必须同级出现
class Example{ constructor(a, b) { this.a = a; // 实例化时调用 set 方法 this.b = b; } get a(){ console.log('getter'); return this.a; } set a(a){ console.log('setter'); this.a = a; // 自身递归调用 } } let exam = new Example(1,2); // 不断输出 setter ,最终导致 RangeError class Example1{ constructor(a, b) { this.a = a; this.b = b; } get a(){ console.log('getter'); return this._a; } set a(a){ console.log('setter'); this._a = a; } } let exam1 = new Example1(1,2); // 只输出 setter , 不会调用 getter 方法 console.log(exam._a); // 1, 可以直接访问
extends
通过 extends 实现类的继承。
class Child extends Father { ... }
super
子类 constructor 方法中必须有 super ,且必须出现在 this 之前。
class Father { constructor() {} } class Child extends Father { constructor() {} // or // constructor(a) { // this.a = a; // super(); // } } let test = new Child(); // Uncaught ReferenceError: Must call super // constructor in derived class before accessing 'this' or returning // from derived constructor
调用父类构造函数,只能出现在子类的构造函数。
class Father { test(){ return 0; } static test1(){ return 1; } } class Child extends Father { constructor(){ super(); } } class Child1 extends Father { test2() { super(); // Uncaught SyntaxError: 'super' keyword unexpected // here } }
调用父类方法, super 作为对象,在普通方法中,指向父类的原型对象,在静态方法中,指向父类
class Child2 extends Father { constructor(){ super(); // 调用父类普通方法 console.log(super.test()); // 0 } static test3(){ // 调用父类静态方法 return super.test1+2; } } Child2.test3(); // 3
注意要点
不可继承常规对象。
###13、模块
在 ES6 前, 实现模块化使用的是 RequireJS 或者 seaJS(分别是基于 AMD 规范的模块化库, 和基于 CMD 规范的模块化库)。
ES6 引入了模块化,其设计思想是在编译时就能确定模块的依赖关系,以及输入和输出的变量。
ES6 的模块化分为导出(export) @与导入(import)两个模块。
特点
-
ES6 的模块自动开启严格模式,不管你有没有在模块头部加上 use strict;。
-
模块中可以导入和导出各种类型的变量,如函数,对象,字符串,数字,布尔值,类等。
-
每个模块都有自己的上下文,每一个模块内声明的变量都是局部变量,不会污染全局作用域。
-
每一个模块只加载一次(是单例的), 若再去加载同目录下同文件,直接从内存中读取。
export 与 import
a、基本用法
模块导入导出各种类型的变量,如字符串,数值,函数,类。
导出的函数声明与类声明必须要有名称(export default 命令另外考虑)。
不仅能导出声明还能导出引用(例如函数)。
export 命令可以出现在模块的任何位置,但必需处于模块顶层。
import 命令会提升到整个模块的头部,首先执行。
/*-----export [test.js]-----*/ let myName = "Tom"; let myAge = 20; let myfn = function(){ return "My name is" + myName + "! I'm '" + myAge + "years old." } let myClass = class myClass { static a = "yeah!"; } export { myName, myAge, myfn, myClass } /*-----import [xxx.js]-----*/ import { myName, myAge, myfn, myClass } from "./test.js"; console.log(myfn());// My name is Tom! I'm 20 years old. console.log(myAge);// 20 console.log(myName);// Tom console.log(myClass.a );// yeah!
b、as 的用法
export 命令导出的接口名称,须和模块内部的变量有一一对应关系。
导入的变量名,须和导出的接口名称相同,即顺序可以不一致。
/*-----export [test.js]-----*/ let myName = "Tom"; export { myName as exportName } /*-----import [xxx.js]-----*/ import { exportName } from "./test.js"; console.log(exportName);// Tom 使用 as 重新定义导出的接口名称,隐藏模块内部的变量 /*-----export [test1.js]-----*/ let myName = "Tom"; export { myName } /*-----export [test2.js]-----*/ let myName = "Jerry"; export { myName } /*-----import [xxx.js]-----*/ import { myName as name1 } from "./test1.js"; import { myName as name2 } from "./test2.js"; console.log(name1);// Tom console.log(name2);// Jerry
c、import 命令的特点
- 只读属性:不允许在加载模块的脚本里面,改写接口的引用指向,即可以改写 import 变量类型为对象的属性值,不能改写 import 变量类型为基本类型的值。
import {a} from "./xxx.js" a = {}; // error import {a} from "./xxx.js" a.foo = "hello"; // a = { foo : 'hello' }
- 单例模式:多次重复执行同一句 import 语句,那么只会执行一次,而不会执行多次。import 同一模块,声明不同接口引用,会声明对应变量,但只执行一次 import 单例模式:多次重复执行同一句 import 语句,那么只会执行一次,而不会执行多次。import 同一模块,声明不同接口引用,会声明对应变量,但只执行一次 import
import { a } "./xxx.js"; import { a } "./xxx.js"; // 相当于 import { a } "./xxx.js"; import { a } from "./xxx.js"; import { b } from "./xxx.js"; // 相当于 import { a, b } from "./xxx.js";
- 静态执行特性:import 是静态执行,所以不能使用表达式和变量。
import { "f" + "oo" } from "methods"; // error let module = "methods"; import { foo } from module; // error if (true) { import { foo } from "method1"; } else { import { foo } from "method2"; } // error
export default 命令
在一个文件或模块中,export、import 可以有多个,export default 仅有一个。
export default 中的 default 是对应的导出接口变量。
通过 export 方式导出,在导入时要加{ },export default 则不需要。
export default 向外暴露的成员,可以使用任意变量来接收。
var a = "My name is Tom!"; export default a; // 仅有一个 export default var c = "error"; // error,default 已经是对应的导出变量,不能跟着变量声明语句
复合使用
注:import() 是提案,这边暂时不延伸讲解。
export 与 import 可以在同一模块使用,使用特点:
1、可以将导出接口改名,包括 default。
2、复合使用 export 与 import ,也可以导出全部,当前模块导出的接口会覆盖继承导出的。
export { foo, bar } from "methods"; // 约等于下面两段语句,不过上面导入导出方式该模块没有导入 foo 与 bar import { foo, bar } from "methods"; export { foo, bar }; /* ------- 特点 1 --------*/ // 普通改名 export { foo as bar } from "methods"; // 将 foo 转导成 default export { foo as default } from "methods"; // 将 default 转导成 foo export { default as foo } from "methods"; /* ------- 特点 2 --------*/ export * from "methods"; import b from "./xxx.js"; // 不需要加{}, 使用任意变量接收
###14、Promise 对象
是异步编程的一种解决方案。
从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。
Promise 状态
状态的特点
Promise 异步操作有三种状态:pending(进行中)、fulfilled(已成功)和 rejected(已失败)。除了异步操作的结果,任何其他操作都无法改变这个状态。
Promise 对象只有:从 pending 变为 fulfilled 和从 pending 变为 rejected 的状态改变。只要处于 fulfilled 和 rejected ,状态就不会再变了即 resolved(已定型)。
const p1 = new Promise(function(resolve,reject){ resolve('success1'); resolve('success2'); }); const p2 = new Promise(function(){ resolve('success3'); reject('reject'); }); p1.then(function(value){ console.log(value); // success1 }); p2.then(function(value){ console.log(value); // success3 });
状态的缺点
无法取消 Promise ,一旦新建它就会立即执行,无法中途取消。
如果不设置回调函数,Promise 内部抛出的错误,不会反应到外部。
当处于 pending 状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
then 方法
then 方法接收两个函数作为参数,第一个参数是 Promise 执行成功时的回调,第二个参数是 Promise 执行失败时的回调,两个函数只会有一个被调用。
then 方法的特点
-
在 JavaScript 事件队列的当前运行完成之前,回调函数永远不会被调用。
const p = new Promise(function(resolve,reject){ resolve('success'); }); p.then(function(value){ console.log(value); }); onsole.log('first'); // first // success
-
通过 .then 形式添加的回调函数,不论什么时候,都会被调用。
通过多次调用
.then
可以添加多个回调函数,它们会按照插入顺序并且独立运行。
const p = new Promise(function(resolve,reject){ resolve(1); }).then(function(value){ // 第一个then // 1 console.log(value); return value * 2; }).then(function(value){ // 第二个then // 2 console.log(value); }).then(function(value){ // 第三个then // undefined console.log(value); return Promise.resolve('resolve'); }).then(function(value){ // 第四个then // resolve console.log(value); return Promise.reject('reject'); }).then(function(value){ // 第五个then //reject:reject console.log('resolve:' + value); }, function(err) { console.log('reject:' + err); });
-
then 方法将返回一个 resolved 或 rejected 状态的 Promise 对象用于链式调用,且 Promise 对象的值就是这个返回值。
简便的 Promise 链式编程最好保持扁平化,不要嵌套 Promise。
####15、Generator 函数
ES6 新引入了 Generator 函数,可以通过 yield 关键字,把函数的执行流挂起,为改变执行流程提供了可能,从而为异步编程提供解决方案。
Generator 函数组成
Generator 有两个区分于普通函数的部分:
-
在 function 后面,函数名之前有个 * ;
-
函数内部有 yield 表达式。
function* func(){ console.log("one"); yield '1'; console.log("two"); yield '2'; console.log("three"); return '3'; }
执行机制
调用 Generator 函数和调用普通函数一样,在函数名后面加上()即可,但是 Generator 函数不会像普通函数一样立即执行,而是返回一个指向内部状态对象的指针,所以要调用遍历器对象Iterator 的 next 方法,指针就会从函数头部或者上一次停下来的地方开始执行。
f.next(); // one // {value: "1", done: false} f.next(); // two // {value: "2", done: false} f.next(); // three // {value: "3", done: true} f.next(); // {value: undefined, done: true}
函数返回的遍历器对象的方法
next 方法
一般情况下,next 方法不传入参数的时候,yield 表达式的返回值是 undefined 。当 next 传入参数的时候,该参数会作为上一步yield的返回值。
function* sendParameter(){ console.log("strat"); var x = yield '2'; console.log("one:" + x); var y = yield '3'; console.log("two:" + y); console.log("total:" + (x + y)); }
next不传参
var sendp1 = sendParameter(); sendp1.next(); // strat // {value: "2", done: false} sendp1.next(); // one:undefined // {value: "3", done: false} sendp1.next(); // two:undefined // total:NaN // {value: undefined, done: true} next传参 var sendp2 = sendParameter(); sendp2.next(10); // strat // {value: "2", done: false} sendp2.next(20); // one:20 // {value: "3", done: false} sendp2.next(30); // two:30 // total:50 // {value: undefined, done: true}
return 方法
return 方法返回给定值,并结束遍历 Generator 函数。
return 方法提供参数时,返回该参数;不提供参数时,返回 undefined 。
function* foo(){ yield 1; yield 2; yield 3; } var f = foo(); f.next(); // {value: 1, done: false} f.return("foo"); // {value: "foo", done: true} f.next(); // {value: undefined, done: true} throw 方法 throw 方法可以再 Generator 函数体外面抛出异常,再函数体内部捕获。 var g = function* () { try { yield; } catch (e) { console.log('catch inner', e); } }; var i = g(); i.next(); try { i.throw('a'); i.throw('b'); } catch (e) { console.log('catch outside', e); } // catch inner a // catch outside b
yield* 表达式
yield* 表达式表示 yield 返回一个遍历器对象,用于在 Generator 函数内部,调用另一个 Generator 函数。
function* callee() { console.log('callee: ' + (yield)); } function* caller() { while (true) { yield* callee(); } } const callerObj = caller(); callerObj.next(); // {value: undefined, done: false} callerObj.next("a"); // callee: a // {value: undefined, done: false} callerObj.next("b"); // callee: b // {value: undefined, done: false} // 等同于 function* caller() { while (true) { for (var value of callee) { yield value; } } }
使用场景
实现 Iterator
为不具备 Iterator 接口的对象提供遍历方法。
###16、async 函数
async 是 ES7 才有的与异步操作有关的关键字,和 Promise , Generator 有很大关联的。
语法
async function name([param[, param[, ... param]]]) { statements }
- name: 函数名称。
- param: 要传递给函数的参数的名称。
- statements: 函数体语句。
返回值
async 函数返回一个 Promise 对象,可以使用 then 方法添加回调函数。
async function helloAsync(){ return "helloAsync"; } console.log(helloAsync()) // Promise {<resolved>: "helloAsync"} helloAsync().then(v=>{ console.log(v); // helloAsync })
async 函数中可能会有 await 表达式,async 函数执行时,如果遇到 await 就会先暂停执行 ,等到触发的异步操作完成后,恢复 async 函数的执行并返回解析值。
await 关键字仅在 async function 中有效。如果在 async function 函数体外使用 await ,你只会得到一个语法错误
function testAwait(){ return new Promise((resolve) => { setTimeout(function(){ console.log("testAwait"); resolve(); }, 1000); }); } async function helloAsync(){ await testAwait(); console.log("helloAsync"); } helloAsync(); // testAwait // helloAsync
await
await 操作符用于等待一个 Promise 对象, 它只能在异步函数 async function 内部使用。
a、语法
[return_value] = await expression;
expression: 一个 Promise 对象或者任何要等待的值。
b、返回值
返回 Promise 对象的处理结果。如果等待的不是 Promise 对象,则返回该值本身。
如果一个 Promise 被传递给一个 await 操作符,await 将等待 Promise 正常处理完成并返回其处理结果。
function testAwait (x) { return new Promise(resolve => { setTimeout(() => { resolve(x); }, 2000); }); } async function helloAsync() { var x = await testAwait ("hello world"); console.log(x); } helloAsync (); // hello world
正常情况下,await 命令后面是一个 Promise 对象,它也可以跟其他值,如字符串,布尔值,数值以及普通函数。
function testAwait(){ console.log("testAwait"); } async function helloAsync(){ await testAwait(); console.log("helloAsync"); } helloAsync(); // testAwait // helloAsync
c、await针对所跟不同表达式的处理方式:
Promise 对象:await 会暂停执行,等待 Promise 对象 resolve,然后恢复 async 函数的执行并返回解析值。
非 Promise 对象:直接返回对应的值