注:文章末尾有惊喜哟,详查看文章末尾哟!
ECMAScript6 入门
1.块级作用域(以一个{}符号进行区分)
–为什么需要块级作用域?
•ES5只有全局作用于和函数作用域,没有块级作用域。
(1) 场景一:内层变量可能会覆盖外层变量。
(2) 场景二:用来计数的循环变量泄露为全局变量。
2.const
–const 声明一个只读的常量,一旦声明,不能修改。
–const 与 let 的作用域是相同的。
–const 声明的常量是不会提升的,同样存在暂时性死区,只能在声明的位置后面使用。
–const 声明的常量,与let 一样不可重复声明。
3.解构可以解构方法、解构属性、解构参数、解构值
4.要想将0b和0o前缀的字符串数值转为十进制,使用Number()方法即可;
–0b 代表二进制
–0o 代表八进制
Number('ob111') // 7
Number('oo10') // 8
1.数值方法
(1).为数值添加千位分隔符: (12121544876343).toLocaleString()
(2).Number.isInteger()用来判断一个数值是否为整数。
Number.isInteger(25) // true
Number.isInteger(25.0) // true
2.箭头函数需要的注意点:
1.箭头函数没有自己的this 对象
2.箭头函数不可以当构造函数,也就是说,不可以对箭头函数使用new命令,否则会抛出一个错误
3.箭头函数不可以使用arguments 对象,该对象在函数体内不存在。如果要用,可以用rest参数代替
4.箭头函数不可以使用yield 命令,因此箭头函数不能用作Generator函数
3.尾调用
定义:就是某个函数的最后一步是调用另一个函数
优化:函数调用会在内存形成一个“调用记录”,又称“调用帧”(call frame),保存调用位置和内部变量
等信息。如果在函数A的内部调用函数B,那么在A的调用帧上方,还会形成一个B的调用帧。等到B运行结束,
将结果返回到A,B的调用帧才会消失。如果函数B内部还调用函数C,那就还有一个C的调用帧,以此类推。
所有的调用帧,就形成一个“调用栈”(call stack)。
尾调用由于是函数的最后一步操作,所以不需要保留外层函数的调用帧,因为调用位置、内部变量等信息都
不会再用到了,只要直接用内层函数的调用帧,取代外层函数的调用帧就可以了。
4.函数式编程:
柯里化(currying):将多参数的函数转换成单参数的形式。
5.递归:
递归的本质是一种循环操作,纯粹的函数式编程语言没有循环操作命令,所有的循环都用递归来实现,这
就是为什么尾递归对这些语言极其重要。对于其他支持'尾调用优化'的语言(比如Lua,ES6),只需要知道
循环可以用递归代替,而一旦使用递归,最好使用尾递归。
6.Function.prototype.toString()返回函数本身,会省略注释和空格
7.Array.of(item1,....,items) 将一组值转换为数组,
8.运算符的扩展:
•ES6新增指数运算符(**)相当于某个数的第几次方
•ES2020 引入链判运算符(?.)
链判断运算符?.有三种写法。
obj?.prop// 对象属性是否存在
obj?.[expr] // 同上
func?.(...args) // 函数或对象方法是否存在
•逻辑赋值运算符||=、&&=、??=
eg1:
// 老的写法 user.id = user.id || 1; // 新的写法 user.id ||= 1;
eg2:
// 老的写法 function example(opts) { opts.foo = opts.foo ?? 'bar'; opts.baz ?? (opts.baz = 'qux'); } // 新的写法 function example(opts) { opts.foo ??= 'bar'; opts.baz ??= 'qux'; }
1.Set 和 Map 数据结构
•Set 提供了新的数据结构Set,类似于数组,但是成员的值都是唯一的,没有重复的值;
•Set 本身是一个构造函数,用来生成Set数据结构
•Set 的属性和方法
–属性
–Set.prototype.constructor:构造函数,默认就是Set函数
–Set.prototype.size:返回Set实例的成员总数
–方法
–Set.prototype.add(value):添加某个值,返回Set结构本身
–Set.prototype.delete(value):删除某个值,返回一个布尔值,表示删除是否成功
–Set.prototype.has(value):返回一个值,表示改制是否为Set的成员
–Set.prototype.clear():清楚所有成员,没有返回值
–遍历方法
–Set.prototype.keys():返回键名的遍历器
–Set.prototype.values():返回键值的遍历器
–Set.prototype.entries():返回键值对的遍历器
–Set.prototype.forEach():使用回调函数遍历每个成员
•Map JavaScript对象,本质上是键值对的结合(Hash结构)
•Map 的属性和操作方法
–属性
–size:返回Map结构的成员总数
–Map.prototype.set(key,value):返回整个Map结构,如果key已经有值,则键值会被更新,
否则就会生成新值;
•注:set方法也可采用链接写法
let map = new Map() .set(1, 'a') .set(2, 'b') .set(3, 'c');
–Map.prototype.get(key):获取key对应的键值,若找不到key,返回undefined
–Map.prototype.has(key):返回一个布尔值,标志某个值是否在当前Map对象中
–Map.prototype.delete(key):删除某个键,返回布尔值,若删除失败,返回false
–Map.prototype.clear():清除所有成员,没有返回值
–方法
–Map.prototype.keys():返回键名的遍历器。
–Map.prototype.values():返回键值的遍历器。
–Map.prototype.entries():返回所有成员的遍历器。
–Map.prototype.forEach():遍历 Map 的所有成员。
–与其他数据结构的相互转换
–(1) Map 转为数组 (...)
const myMap = new Map() .set(true, 7) .set({foo: 3}, ['abc']); [...myMap] // [ [ true, 7 ], [ { foo: 3 }, [ 'abc' ] ] ]
–(2) 数组转 Map
new Map([ [true, 7], [{foo: 3}, ['abc']] ]) // Map { // true => 7, // Object {foo: 3} => ['abc'] // }
–(3) Map 转对象
function strMapToObj(strMap) { let obj = Object.create(null); // Object.create() 创建一个对象的原型对象,由Object.create() 创建的对象原型, // 都会挂在到创建的对象原型上, for (let [k,v] of strMap) { obj[k] = v; } return obj; } const myMap = new Map() .set('yes', true) .set('no', false); strMapToObj(myMap) // { yes: true, no: false }
–(4) 对象转 Map(Object.entries())
let obj = {"a":1, "b":2}; let map = new Map(Object.entries(obj)); // 实现一个转换函数 function objToStrMap(obj) { let strMap = new Map(); for (let k of Object.keys(obj)) { strMap.set(k, obj[k]); } return strMap; } objToStrMap({yes: true, no: false}) // Map {"yes" => true, "no" => false}
–(5) Map 转 JSON
–Map 转为 JSON 要区分两种情况。一种情况是,Map 的键名都是字符串,这时可以选择转为对象 JSON。
–有一种特殊情况,整个 JSON 就是一个数组,且每个数组成员本身,又是一个有两个成员的数组。
function jsonToMap(jsonStr) { return new Map(JSON.parse(jsonStr)); } jsonToMap('[[true,7],[{"foo":3},["abc"]]]') // Map {true => 7, Object {foo: 3} => ['abc']}
1.Promise
•.then()、.catch()、.finally()
•.then() 当 Promise 的状态发生改变的时候执行的 .then() 回调, .catch() 相反
•.finally() 是不管 Promise 的最后状态如何,都会执行的操作,为ES2018引入的。
•.all()和.race() 将多个 promise 的实例,包装秤一个新的 Promise 实例;
•.allSettled()为了解决Promise.all()可以确定所有请求都成功了,但是只要是有一个请求失败,
Promise.all() 的方法就会报错,而不管其他的请求是否结束了。
注:
1.为了解决上述问题,ES2020 推出了Promise.allSettled()方法,用来确定一组操作是否都结束了
(不管是成功还是失败)。所以,它的名字叫做Settled, 包含了fulfilled和rejected两种情况。
2.Promise.allSettled()方法接收一个数组作为参数,数组的每个成员都是一个Promise对象,
并返回一个新的Promise对象。只有等到参数数组的所有Promise对象都发生状态变更(不管是fulfilled
和rejected),返回的Promise 对象才会发生状态变更。
const promises = [ fetch('/api-1'), fetch('/api-2'), fetch('/api-3'), ]; await Promise.allSettled(promises); removeLoadingIndicator();
// 上面示例中,数组promises包含了三个请求,只有等到这三个请求都结束了(不管请求成功还是失败),
// removeLoadingIndicator()才会执行。
•.any() 方法,改方法接收一组Promise实例作为参数,包装秤一个新的 Promise 实例返回。
注:
1.只要参数实例有一个变成fulfilled状态,包装实例就会变成fulfilled状态;如果所有参数实例都变成
rejected状态,包装实例就会变成rejected状态。
2.Promise.any()跟Promise.race()方法很像,只有一点不同,就是Promise.any()不会因为某个
Promise 变成rejected状态而结束,必须等到所有参数 Promise 变成rejected状态才会结束。
1.Generator 函数的语法
简介
•Generator 函数时ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同。
•Generator 函数有多种理解角度,语法上,可以首先理解为Generator 函数是一个状态机,封装了多个内部状态。
•Generator 函数会返回一个遍历器对象,也就是说,Generator 函数除了状态机,还是一个遍历器对象生成函数。
返回的遍历器对象,可以依次遍历Generator 函数内部的每一个状态。
•Generator 函数一个普通函数,但有两个特征,
–1.function 关键字语函数名只有一个星号
–2.函数体内部使用yield 表达式,定义不同的内部状态(yield在英语里的意思是产出)
•Generator函数语普通函数的相同点和不同点:
1.相同点:也是在函数名后面加上一对圆括号
2.不同点:
•调用Generator函数后,该函数不会执行,返回的也不是函数的运行结果,而是一个指向内部状态的
指针对象,也就是遍历器对象(Iterator Object)
•执行逻辑:
1.如果需要执行Generator 函数,必须调动遍历器对象的next方法,使得指针移向下一个状态。
也就是说,每次调用next 方法,内部指针就从函数头部或上一次停下里的地方开始执行,直到遇到下一个
yield 表达式(return语句)为止。
2.上面的逻辑介绍:简而言之:Generator 函数是分段执行的,yield 表达式是暂停执行的标记,
而 next 方法可以恢复执行。
•逻辑执行总结:
–调用 Generator 函数,返回一个遍历器对象,代表 Generator 函数的内部指针。以后,每次
调用遍历器对象的 next 方法,就会返回一个有 value 和done 的两个属性的对象,value
属性表示当前当前的内部状态的值,是 yield 表达式后面的那个表达式的值;done 属性是一个布尔值,
表示是否遍历结束;
•* 号的规定:
–ES6 没有规定,function 关键字语函数名之间的星号,写在那个位置,这导致下面的写法都能通过。
function * foo(x, y) { ··· } function *foo(x, y) { ··· } function* foo(x, y) { ··· } function*foo(x, y) { ··· }
–由于 Generator 函数仍然是普通函数,所以一版的写法是上面你的第三种,即型号紧跟在 function
关键字后面。
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)的语法功能。
•next 方法的参数:
–yield 表达式本身没有返回值,或者说总是返回 undefined 。next 方法可以带一个参数,
该参数就会被当作上一个 yield 表达式的返回值。
•for....of 循环
•for...of 循环可以自动遍历 Generator 函数运行时生成的 Iterator 对象,且此时不再需要调用 next 方法。
•Generator.prototype.return()
–Generator 函数返回的遍历器对象,还有一个 return() 方法,可以返回给定的值,
并且终结遍历 Generator 函数。
•next()、throw()、return() 的共同点:
–next()、throw()、return() 这三个方法本质上是同一件事,可以放在一起理解。它们的作用都是让
Generator 函数恢复执行,并且使用不同的语句替换 yield 表达式。
–next() 是将 yield 表达式替换成一个值。
const g = function* (x, y) { let result = yield x + y; return result; }; const gen = g(1, 2); gen.next(); // Object {value: 3, done: false} gen.next(1); // Object {value: 1, done: true} // 相当于将 let result = yield x + y // 替换成 let result = 1; // 上面代码中,第二个next(1)方法就相当于将yield表达式替换成一个值1。如果next方法没有参数, // 就相当于替换成undefined。 ``` - `throw()` 是将 `yield` 表达式替换成一个 `throw` 语句。 ```js gen.throw(new Error('出错了')); // Uncaught Error: 出错了 // 相当于将 let result = yield x + y // 替换成 let result = throw(new Error('出错了')); ``` - `return()` 是将 `yield` 表达式替换成一个 `return` 语句。 ```js gen.return(2); // Object {value: 2, done: true} // 相当于将 let result = yield x + y // 替换成 let result = return 2; ```
•yield 表达式
•如果在 Generator 函数内部,调用另一个 Generator 函数。需要在前者的函数体内部,自己手动完成遍历。
function* foo() { yield 'a'; yield 'b'; } function* bar() { yield 'x'; // 手动遍历 foo() for (let i of foo()) { console.log(i); } yield 'y'; } for (let v of bar()){ console.log(v); } // x // a // b // y
注:上面代码中,foo和bar都是 Generator 函数,在bar里面调用foo,就需要手动遍历foo。如果有多个
Generator 函数嵌套,写起来就非常麻烦。
•ES6 提供了 yield* 表达式,作为解决办法,用来在一个 Generator 函数里面执行另一个 Generator 函数。
function* bar() { yield 'x'; yield* foo(); yield 'y'; } // 等同于 function* bar() { yield 'x'; yield 'a'; yield 'b'; yield 'y'; } // 等同于 function* bar() { yield 'x'; for (let v of foo()) { yield v; } yield 'y'; } for (let v of bar()){ console.log(v); } // "x" // "a" // "b" // "y"
再来看一个对比的例子。
function* inner() { yield 'hello!'; } function* outer1() { yield 'open'; yield inner(); yield 'close'; } var gen = outer1() gen.next().value // "open" gen.next().value // 返回一个遍历器对象 gen.next().value // "close" function* outer2() { yield 'open' yield* inner() yield 'close' } var gen = outer2() gen.next().value // "open" gen.next().value // "hello!" gen.next().value // "close"
•从语法角度看,如果 yield 表达式后面跟的是一个遍历器对象,需要在 yield 表达式后面加上星号,
表明它返回的是一个遍历器对象。这被称为 yield* 表达式。
let delegatedIterator = (function* () { yield 'Hello!'; yield 'Bye!'; }()); let delegatingIterator = (function* () { yield 'Greetings!'; yield* delegatedIterator; yield 'Ok, bye.'; }()); for(let value of delegatingIterator) { console.log(value); } // "Greetings! // "Hello!" // "Bye!" // "Ok, bye."
上面代码中,delegatingIterator是代理者,delegatedIterator是被代理者。由于 yield*
delegatedIterator语句得到的值,是一个遍历器,所以要用星号表示。运行结果就是使用一个遍历器,
遍历了多个 Generator 函数,有递归的效果。
•yield* 后面的 Generator 函数 (没有return语句时),等同于在 Generator 函数内部,
部署一个 for...of 循环。
function* concat(iter1, iter2) { yield* iter1; yield* iter2; } // 等同于 function* concat(iter1, iter2) { for (var value of iter1) { yield value; } for (var value of iter2) { yield value; } } /**
上面代码说明,yield*后面的 Generator 函数(没有return语句时),不过是for...of的一种简写形式,
完全可以用后者替代前者。反之,在有return语句时,则需要用var value = yield* iterator的形式获取
return语句的值。
*/
•如果 yield* 后面跟着一个数组,由于数组原生支持遍历器,因此就会遍历数组成员。
function* gen(){ yield* ["a", "b", "c"]; } gen().next() // { value:"a", done:false }
// 上面代码中,yield命令后面如果不加星号,返回的是整个数组,加了星号就表示返回的是数组的遍历器对象。
•Generator 函数返回的是一个遍历器对象,所指向的this 指向遍历器对象,而不是this对象;
function* g() { this.a = 11; } let obj = g(); obj.next(); obj.a // undefined
// 上面代码中,Generator 函数g在this对象上面添加了一个属性a,但是obj对象拿不到这个属性。
•Generator 函数不能语 new 命令一起使用,会报错 new GeneratorFunctionName is not a constructor。