es6杂记
es6杂记
let 和 const
let
-
仅在代码块里有效
{ let a = 10; var b = 1; } a // ReferenceError: a is not defined. b // 1
-
for循环比价适合用let
你可能会问,如果每一轮循环的变量i都是重新声明的,那它怎么知道上一轮循环的值,从而计算出本轮循环的值?这是因为 JavaScript 引擎内部会记住上一轮循环的值,初始化本轮的变量i时,就在上一轮循环的基础上进行计算。
-
不存在变量提升
-
暂时性死区 temporal dead zone TDZ
var tmp = 123; if (true) { tmp = 'abc'; // ReferenceError let tmp; }
ES6 明确规定,如果区块中存在let和const命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。
typeof如果跟了let在后面,会报错,如果没有let,只会是undefined
隐蔽TDZ
function bar(x = y, y = 2) { return [x, y]; } bar(); // 报错
上面代码中,调用bar函数之所以报错(某些实现可能不报错),是因为参数x默认值等于另一个参数y,而此时y还没有声明,属于”死区“。如果y的默认值是x,就不会报错,因为此时x已经声明了。
function bar(x = 2, y = x) { return [x, y]; } bar(); // [2, 2]
另外,下面的代码也会报错,与var的行为不同。
// 不报错 var x = x; // 报错 let x = x; // ReferenceError: x is not defined
-
let不允许在相同作用域内,重复声明同一个变量。
function func(arg) { let arg; // 报错 } function func(arg) { { let arg; // 不报错 } } // 报错 function func() { let a = 10; var a = 1; } // 报错 function func() { let a = 10; let a = 1; }
const
-
const声明一个只读的常量。一旦声明,常量的值就不能改变。(一般大写)
-
必须给初值
-
与let一样,仅在代码块里有效
-
也存在TDZ
-
不允许重复声明
-
本质
const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址不得改动
对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。
但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指针,const只能保证这个指针是固定的,至于它指向的数据结构是不是可变的,就完全不能控制了。因此,将一个对象声明为常量必须非常小心。
全局变量与顶层对象脱钩
var a = 1;
// 如果在 Node 的 REPL 环境,可以写成 global.a
// 或者采用通用方法,写成 this.a
window.a // 1
let b = 1;
window.b // undefined
变量的解构赋值
数组的解构赋值
-
基本用法
let [a, b, c] = [1, 2, 3];
一些例子
完全解构 let [foo, [[bar], baz]] = [1, [[2], 3]]; foo // 1 bar // 2 baz // 3 let [ , , third] = ["foo", "bar", "baz"]; third // "baz" let [x, , y] = [1, 2, 3]; x // 1 y // 3 let [head, ...tail] = [1, 2, 3, 4]; head // 1 tail // [2, 3, 4] let [x, y, ...z] = ['a']; x // "a" y // undefined z // []
如果解构不成功,变量的值就等于undefined。
不完全解构 let [x, y] = [1, 2, 3]; x // 1 y // 2 let [a, [b], d] = [1, [2, 3], 4]; a // 1 b // 2 d // 4
-
默认值
let [foo = true] = []; foo // true let [x, y = 'b'] = ['a']; // x='a', y='b' let [x, y = 'b'] = ['a', undefined]; // x='a', y='b'
判断是否===undefined
let [x = 1] = [undefined]; x // 1 let [x = 1] = [null]; x // null
对象的解构赋值
-
基本语法
let { foo, bar } = { foo: "aaa", bar: "bbb" }; foo // "aaa" bar // "bbb"
对象的解构与数组有一个重要的不同。数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。
let { bar, foo } = { foo: "aaa", bar: "bbb" }; foo // "aaa" bar // "bbb" let { baz } = { foo: "aaa", bar: "bbb" }; baz // undefined
字符串的解构赋值
-
基本语法
const [a, b, c, d, e] = 'hello'; a // "h" b // "e" c // "l" d // "l" e // "o" let {length : len} = 'hello'; len // 5
函数解构赋值
解构赋值的用途
-
可以交换变量
let x = 1; let y = 2; [x, y] = [y, x];
-
从函数返回多个值
函数只能返回一个值,如果要返回多个值,只能将它们放在数组或对象里返回。有了解构赋值,取出这些值就非常方便。
// 返回一个数组 function example() { return [1, 2, 3]; } let [a, b, c] = example(); // 返回一个对象 function example() { return { foo: 1, bar: 2 }; } let { foo, bar } = example();
-
函数参数的定义
解构赋值可以方便地将一组参数与变量名对应起来。
-
提取 JSON 数据
let jsonData = { id: 42, status: "OK", data: [867, 5309] }; let { id, status, data: number } = jsonData; console.log(id, status, number); // 42, "OK", [867, 5309]
-
函数参数的默认值
-
输入模块的指定方法
const { SourceMapConsumer, SourceNode } = require("source-map");
字符串的扩展
-
includes(), startsWith(), endsWith()
includes():返回布尔值,表示是否找到了参数字符串。
startsWith():返回布尔值,表示参数字符串是否在原字符串的头部。
endsWith():返回布尔值,表示参数字符串是否在原字符串的尾部。
这三个方法都支持第二个参数,表示开始搜索的位置。
let s = 'Hello world!'; s.startsWith('world', 6) // true s.endsWith('Hello', 5) // true s.includes('Hello', 6) // false
-
repeat()
'x'.repeat(3) // "xxx" 'hello'.repeat(2) // "hellohello" 'na'.repeat(0) // ""
小数会被取整
-
拼接
数值的扩展
-
Number.isFinite(), Number.isNaN()
注意:
Number.isFinite()对于非数值一律返回false, Number.isNaN()只有对于NaN才返回true,非NaN一律返回false。
-
Number.parseInt(), Number.parseFloat()
// ES5的写法 parseInt('12.34') // 12 parseFloat('123.45#') // 123.45 // ES6的写法 Number.parseInt('12.34') // 12 Number.parseFloat('123.45#') // 123.45
-
Number.isInteger()用来判断一个数值是否为整数。
Number.isInteger(25) // true Number.isInteger(25.0) // true Number.isInteger(25.1) // false
如果参数不是数值,Number.isInteger返回false。
Number.isInteger('15') // false
如果数值的精度超过这个限度,第54位及后面的位就会被丢弃,这种情况下,Number.isInteger可能会误判。
对象的扩展
-
默认值
function Point(x = 0, y = 0) { this.x = x; this.y = y; } const p = new Point(); p // { x: 0, y: 0 }
-
rest
-
箭头函数
var f = () => 5; // 等同于 var f = function () { return 5 }; var sum = (num1, num2) => num1 + num2; // 等同于 var sum = function(num1, num2) { return num1 + num2; };
箭头函数有几个使用注意点。
(1)函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。
(2)不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
(3)不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
(4)不可以使用yield命令,因此箭头函数不能用作 Generator 函数。
数组的扩展
-
Array.from(),会返回一个新数组
let arrayLike = { '0': 'a', '1': 'b', '2': 'c', length: 3 }; // ES5的写法 var arr1 = [].slice.call(arrayLike); // ['a', 'b', 'c'] // ES6的写法 let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']
Array.from还可以接受第二个参数,作用类似于数组的map方法,用来对每个元素进行处理,将处理后的值放入返回的数组。
Array.from(arrayLike, x => x * x); // 等同于 Array.from(arrayLike).map(x => x * x); Array.from([1, 2, 3], (x) => x * x) // [1, 4, 9]
-
数组实例的 includes()
[1, 2, 3].includes(2) // true [1, 2, 3].includes(4) // false [1, 2, NaN].includes(NaN) // true
-
使用 for of 来遍历数组中的值
for (let index of ['a', 'b'].keys()) { console.log(index); } // 0 // 1 for (let elem of ['a', 'b'].values()) { console.log(elem); } // 'a' // 'b' for (let [index, elem] of ['a', 'b'].entries()) { console.log(index, elem); } // 0 "a" // 1 "b"
可以用for...of循环进行遍历,唯一的区别是keys()是对键名的遍历、values()是对键值的遍历,entries()是对键值对的遍历。
对象的扩展
-
属性的简洁表示法
const foo = 'bar'; const baz = {foo}; baz // {foo: "bar"} // 等同于 const baz = {foo: foo}; const o = { method() { return "Hello!"; } }; // 等同于 const o = { method: function() { return "Hello!"; } };
Set 和 Map
Set
-
ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。
const s = new Set();
[2, 3, 5, 4, 5, 2, 2].forEach(x => s.add(x));
for (let i of s) {
console.log(i);
}
// 2 3 5 4Set和Array的对比:
1、Set不允许有重复值,但是Array可以 2、Set计算里面的个数,是调用它的size方法,Array用的是length 3、实例方法有 add、delete、has、clear
-
遍历
let set = new Set(['red', 'green', 'blue']); for (let item of set.keys()) { console.log(item); } // red // green // blue for (let item of set.values()) { console.log(item); } // red // green // blue for (let item of set.entries()) { console.log(item); } // ["red", "red"] // ["green", "green"] // ["blue", "blue"]
Set 结构的键名就是键值(两者是同一个值)
可以省略values方法,直接用for...of循环遍历 Set。
let set = new Set(['red', 'green', 'blue']); for (let x of set) { console.log(x); } // red // green // blue
也可以用foreach
set = new Set([1, 4, 9]); set.forEach((value, key) => console.log(key + ' : ' + value)) // 1 : 1 // 4 : 4 // 9 : 9
用途
-
扩展运算符和 Set 结构相结合,就可以去除数组的重复成员。
let arr = [3, 5, 2, 2, 5, 5]; let unique = [...new Set(arr)]; // [3, 5, 2]
-
数组的map和filter方法也可以间接用于 Set 了
let set = new Set([1, 2, 3]); set = new Set([...set].map(x => x * 2)); // 返回Set结构:{2, 4, 6} let set = new Set([1, 2, 3, 4, 5]); set = new Set([...set].filter(x => (x % 2) == 0)); // 返回Set结构:{2, 4}
-
结合filter筛选
let a = new Set([1, 2, 3]); let b = new Set([4, 3, 2]); // 并集 let union = new Set([...a, ...b]); // Set {1, 2, 3, 4} // 交集 let intersect = new Set([...a].filter(x => b.has(x))); // set {2, 3} // 差集 let difference = new Set([...a].filter(x => !b.has(x))); // Set {1}
-
将set结构的数据变成数组
// 方法一 let set = new Set([1, 2, 3]); set = new Set([...set].map(val => val * 2)); // set的值是2, 4, 6 // 方法二 let set = new Set([1, 2, 3]); set = new Set(Array.from(set, val => val * 2)); // set的值是2, 4, 6
-
Map
它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。
注意,0和-0就是一个键,布尔值true和字符串true则是两个不同的键。另外,undefined和null也是两个不同的键。虽然NaN不严格相等于自身,但 Map 将其视为同一个键。
Map 和 对象的比较:
1、对象的key必须是字符串,但是Map的key可以是任何类型
2、map实例方法有 set、get、delete、has、clear
-
基本语法
const m = new Map(); const o = {p: 'Hello World'}; m.set(o, 'content') m.get(o) // "content" m.has(o) // true m.delete(o) // true m.has(o) // false
-
可以接受一个数组
const map = new Map([ ['name', '张三'], ['title', 'Author'] ]); map.size // 2 map.has('name') // true map.get('name') // "张三" map.has('title') // true map.get('title') // "Author"
-
遍历
遍历的顺序就是插入的顺序
const map = new Map([ ['F', 'no'], ['T', 'yes'], ]); for (let key of map.keys()) { console.log(key); } // "F" // "T" for (let value of map.values()) { console.log(value); } // "no" // "yes" for (let item of map.entries()) { console.log(item[0], item[1]); } // "F" "no" // "T" "yes" // 或者 for (let [key, value] of map.entries()) { console.log(key, value); } // "F" "no" // "T" "yes" // 等同于使用map.entries() for (let [key, value] of map) { console.log(key, value); } // "F" "no" // "T" "yes"
-
快速转成数组
Map 结构转为数组结构,比较快速的方法是使用扩展运算符(...)。
const map = new Map([ [1, 'one'], [2, 'two'], [3, 'three'], ]); [...map.keys()] // [1, 2, 3] [...map.values()] // ['one', 'two', 'three'] [...map.entries()] // [[1,'one'], [2, 'two'], [3, 'three']] [...map] // [[1,'one'], [2, 'two'], [3, 'three']]
Promise
所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。
Promise对象有以下两个特点。
(1)对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。
(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。
-
基本用法
Promise对象是一个构造函数,用来生成Promise实例。
const promise = new Promise(function(resolve, reject) { // ... some code if (/* 异步操作成功 */){ resolve(value); } else { reject(error); } });
Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。它们是两个函数,由 JavaScript 引擎提供,不用自己部署。
resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;
reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数。
promise.then(function(value) { // success }, function(error) { // failure });
then方法可以接受两个回调函数作为参数。
第一个回调函数是Promise对象的状态变为resolved时调用
第二个回调函数是Promise对象的状态变为rejected时调用。
二个函数是可选的,不一定要提供。这两个函数都接受Promise对象传出的值作为参数。
简单的例子:
function timeout(ms) { return new Promise((resolve, reject) => { setTimeout(resolve, ms, 'done'); }); } timeout(100).then((value) => { console.log(value); });
-
promise.prototype
promise .then(result => {···}) .catch(error => {···}) .finally(() => {···});
如果该对象状态变为resolved,则会调用then方法指定的回调函数;如果异步操作抛出错误,状态就会变为rejected,就会调用catch方法指定的回调函数,处理这个错误。另外,then方法指定的回调函数,如果运行中抛出错误,也会被catch方法捕获。
//使用promise封装耗时的操作
function promiseTimeOut(time,str){
return new Promise((yes,no)=>{
setTimeout(() => {
yes(str)
}, time);
})
}
promiseTimeOut(2000,"..第一层..").then(data1=>{
console.log(data1)
return promiseTimeOut(2000,'..第二层..')
}).then(data2=>{
console.log(data2)
return promiseTimeOut(2000,'..第三层..')
}).then(data3=>{
console.log(data3)
return promiseTimeOut(2000,'..第四层..')
}).then(data4=>{
console.log(data4)
})
async 函数
作用:
1. 能解决Promise获取值时then过多的问题
2. 通过同步的方式去调用异步的代码
3. 使得异步操作变得更加方便。
语法:
async 函数名称(){
await 异步操作
await 异步操作
}
//使用promise封装耗时的操作
function promiseTimeOut(time,str){
var promise = new Promise((yes,no)=>{
setTimeout(() => {
yes(str)
}, time);
})
return promise
}
function printName(name){
return name
}
async function printStepByStep(){
const result1 = await promiseTimeOut(2000,'。。。第一层。。。')
console.log(result1)
const result2 = await printName("。。。小刘。。。")
console.log(result2)
const result3 = await promiseTimeOut(2000,'。。。第3层。。。')
console.log(result3)
const result4 = await promiseTimeOut(2000,'。。。第四层。。。')
console.log(result4)
}
//调用异步函数
printStepByStep()
注意:
1、我们用async定义的异步函数,必须要调用它,否则不起作用
2、如果await 后面调用的函数,返回的是promise对象,他会自动调用promise的then把结果拿出来,赋值给变量
3、await后面调用的函数里面,除了可以返回promise、也可返回普通的类型