前言
最新的 ECMAScript 都已经到发布到 2019 版了。
我们应该有的态度是: Stay hungry ! Stay young !
从接触 vue 到工作中用到 vue 将近 2 年了,在开发 vue 项目中用到了很多 es6 的 api ,es6 给我的开发带来了很大便利。
本文只总结小汪在工作和面试中经常遇到的 ES6 及之后的新 api 。
有空就得多总结,一边总结,一边重温学习!!!
正文
1 let 和 const
let 的作用域与 const 命令相同:只在声明所在的块级作用域内有效。且不存在变量提升 。
1.1 let
let 所声明的变量,可以改变。
let a = 123
a = 456
let b = [123]
b = [456]
1.2 const
const 声明一个只读的常量。一旦声明,常量的值就不能改变。
简单类型的数据(数值、字符串、布尔值),不可以变动
const a = 123
a = 456
let b = [123]
b = [456]
复合类型的数据(主要是对象和数组),可以这样子变动
const a = [123]
a.push(456)
const b =
b.name = 'demo'
1.3 不存在变量提升
{
let a = 10;
var b = 1;
}
a
b
所以 for循环的计数器,就很合适使用 let 命令。
let a = [];
for (let i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6]();
1.4 推荐
对于 数值、字符串、布尔值 经常会变的,用 let 声明。
对象、数组和函数用 const 来声明。
export const funA = function(){
}
2 解构(Destructuring)
2.1 数组
一次性声明多个变量:
let [a, b, c] = [1, 2, 3];
console.log(a)
console
.log(b)
console.log(c)
结合扩展运算符:
let [head, ...tail] = [1, 2, 3, 4];
console.log(head)
console.log(tail)
解构赋值允许指定默认值:
let [foo = true] = [];
foo // true
let [x, y = 'b'] = ['a'];
// x='a', y='b'
2.2 对象
解构不仅可以用于数组,还可以用于对象。
let { a, b } = { a: "aaa", b: "bbb" };
a // "aaa"
b // "bbb"
数组中,变量的取值由它
排列的位置
决定;而对象中,变量必须与
属性
同名,才能取到正确的值。
对象的解构也可以指定默认值。
let {x = 3} = {};
x
let {x, y = 5} = {x: 1};
x
y
2.3 字符串
字符串也可以解构赋值。这是因为此时,字符串被转换成了一个类似数组的对象。
const [a, b, c, d, e] = 'hello';
a // "h"
b // "e"
c // "l"
d // "l"
e // "o"
2.4 用途
-
交换变量的值
let x = 1;
let y = 2;
[x, y] = [y, x];
-
从函数返回多个值
function example() {
let [a, b, c] = [1, 2, 3]
return [a, b, c]
}
let [a, b, c] = example();
function example() {
return {
foo: 1,
bar: 2
};
}
let { foo, bar } = example();
-
函数参数的默认值
function
funA (a = 1, b = 2)
funA(3)
funA(3,3)
-
输入模块的指定方法
加载模块时,往往需要指定输入哪些方法。解构赋值使得输入语句非常清晰。
const { SourceMapConsumer, SourceNode } = require("source-map");
在 utils.js 中:
export const function A (){
console.log('A')
}
export const function B (){
console.log('B')
}
export const function C (){
console.log('C')
}
在 组件中引用时:
import { A, B, C } from "./utils.js"
A()
3. 模板字符串(template string)
模板字符串(template string)用反引号(`)标识。
3.1 纯字符串
所有模板字符串的空格和换行,都是被保留的.
console.log(`输出值为 N,
换行`)
// "输出值为 N
换行"
3.2 字符串中加变量
模板字符串中嵌入变量,需要将变量名写在 ${ } 之中
let x = 1;
let y = 2;
console.log(`输出值为:${x}`)
console.log(`输出值为:${x + y}`)
3.3 模板字符串之中还能调用函数。
function fn() {
return "Hello World";
}
console.log(`输出值为:${fn()}`)
4. 字符串函数扩展
-
includes():返回布尔值,表示是否找到了参数字符串。
-
startsWith():返回布尔值,表示参数字符串是否在原字符串的头部。
-
endsWith():返回布尔值,表示参数字符串是否在原字符串的尾部。
let
s = 'Hello world!';
s.startsWith('Hello') // true
s.endsWith('!') // true
s.includes('o') // true
这三个方法都支持第二个参数,表示开始搜索的位置。
let s = 'Hello world!';
s.startsWith('world', 6) // true
s.endsWith('Hello', 5) // true
s.includes('Hello', 6) // false
5. 数值扩展
5.1 指数运算符
ES2016 新增了一个指数运算符(**)。
2 ** 2
2 ** 3
这个运算符的一个特点是右结合,而不是常见的左结合。多个指数运算符连用时,是从最右边开始计算的。
2 ** 3 ** 2
上面代码中,首先计算的是第二个指数运算符,而不是第一个。
指数运算符可以与等号结合,形成一个新的赋值运算符(**=)。
let a = 1.5;
a **= 2;
let b = 4;
b **= 3;
6. 函数的扩展
除了在解构中说到的函数参数的默认值,还有不少经常会用到的方法。
6. 1 rest 参数
ES6 引入 rest 参数(形式为…变量名),用于获取函数的多余参数,这样就不需要使用 arguments 对象了。rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中。
function add(...values) {
let sum = 0;
for (let val of values) {
sum += val;
}
return sum;
}
add(2, 5, 3)
上面代码的 add 函数是一个求和函数,利用 rest 参数,可以向该函数传入任意数目的参数。
注意,rest 参数之后不能再有其他参数(即只能是最后一个参数),否则会报错。
function f(a, ...b, c) {
}
6.2 箭头函数
ES6 允许使用“箭头”(=>)定义函数。
const f = v => v;
console.log('输出值:', f(3))
const f = function
(v) {
return v;
};
如果箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分。
const f = function () { return 5 };
const sum = (num1, num2) => num1 + num2;
const sum = function(num1, num2) {
return num1 + num2;
};
如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用 return 语句返回。
const sum = (num1, num2) => { return num1 + num2; }
箭头函数的一个用处是简化回调函数。
const square = n => n * n;
[1,2,3].map(function (x) {
return x * x;
});
[1,2,3].map(x => x * x);
注意: 函数体内的 this 对象,就是定义时所在的对象,而不是使用时所在的对象。
this 对象的指向是可变的,但是在箭头函数中,它是固定的。
function foo() {
setTimeout(() => {
console.log('id:', this.id);
}, 100);
}
let id = 21;
foo.call({ id: 42 });
上面代码中,setTimeout 的参数是一个箭头函数,这个箭头函数的定义生效是在 foo 函数生成时,而它的真正执行要等到 100 毫秒后。如果是普通函数,执行时 this 应该指向全局对象window,这时应该输出 21。但是,箭头函数导致 this 总是指向函数定义生效时所在的对象(本例是{ id: 42}),所以输出的是 42。
7. 数组的扩展
扩展运算符(spread)是三个点(…)。它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列。
7.1 数组合并的新写法。
const arr1 = ['a', 'b'];
const arr2 = ['c'];
const arr3 = ['d', 'e'];
// ES5 的合并数组
arr1.concat(arr2, arr3);
// [ 'a', 'b', 'c', 'd', 'e' ]
// ES6 的合并数组
[...arr1, ...arr2, ...arr3]
// [ 'a', 'b', 'c', 'd', 'e' ]
7.2 函数调用。
function add(x, y) {
return x + y;
}
const
numbers = [4, 4];
add(...numbers)
7.3 复制数组的简便写法。
const a1 = [1, 2];
const a2 = [...a1];
a2[0] = 2;
a1
const [...a2] = a1;
a2[0] = 2;
a1
上面的两种写法,a2 都是 a1 的克隆,且不会修改原来的数组。
7.4 将字符串转为真正的数组。
[...'hello']
// [ "h", "e", "l", "l", "o" ]
7.5 数组实例的 entries(),keys() 和 values()
用 for…of 循环进行遍历,唯一的区别是 keys() 是对键名的遍历、values() 是对键值的遍历,entries() 是对键值对的遍历。
for (let index of ['a', 'b'].keys()) {
console.log(index);
}
for (let elem of ['a', 'b'].values()) {
console.log(elem);
}
for (let [index, elem] of ['a', 'b'].entries()) {
console.log(index, elem);
}
7.6 includes()
Array.prototype.includes 方法返回一个布尔值,表示某个数组是否包含给定的值,与字符串的 includes 方法类似。ES2016 引入了该方法。
[1, 2, 3].includes(2)
[1, 2, 3].includes(4)
[1, 2, NaN].includes(NaN)
该方法的第二个参数表示搜索的起始位置,默认为 0。如果第二个参数为负数,则表示倒数的位置,如果这时它大于数组长度(比如第二个参数为 -4,但数组长度为 3 ),则会重置为从 0 开始。
[1, 2, 3].includes(3, 3);
[1, 2, 3].includes(3, -1);
8. 对象的扩展
8.1 属性和方法 的简洁表示法
let birth = '2000/01/01';
const Person = {
name: '张三',
birth,
hello() { console
.log('我的名字是', this.name); }
};
8.2 Object.assign()
Object.assign方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。
const target = ;
const source1 = ;
const source2 = ;
Object.assign(target, source1, source2);
target
Object.assign方法的第一个参数是目标对象,后面的参数都是源对象。
注意,如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性。
const target = ;
const source1 = ;
const source2 = ;
Object.assign(target, source1, source2);
target
Object.assign 方法实行的是浅拷贝,而不是深拷贝。
const obj1 = };
const obj2 = Object.assign(, obj1);
obj1.a.b = 2;
obj2.a.b
上面代码中,源对象 obj1 的 a 属性的值是一个对象,Object.assign 拷贝得到的是这个对象的引用。这个对象的任何变化,都会反映到目标对象上面。
9. Set
ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。
Set 本身是一个构造函数,用来生成 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);
}
const array = [1, 1, 2, 3, 4, 4]
[...new Set(array)]
10. Promise 对象
Promise 是异步编程的一种解决方案。
Promise 对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。
Promise 对象的状态改变,只有两种可能:从 pending 变为 fulfilled 和从 pending 变为
rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)
const someAsyncThing = function(flag) {
return new Promise(function(resolve, reject) {
if(flag){
resolve('ok');
}else{
reject('error')
}
});
};
someAsyncThing(true).then((data)=> {
console.log('data:',data);
}).catch((error)=>{
console.log('error:', error);
})
someAsyncThing(false).then((data)=> {
console.log('data:',data);
}).catch((error)=>{
console.log('error:', error);
})
上面代码中,someAsyncThing 函数成功返回 ‘OK’, 失败返回 ‘error’, 只有失败时才会被 catch 捕捉到。
最简单实现:
fetch('/api/todos')
.then(res => res.json())
.then(data => ({ data }))
.catch(err => ({ err }));
来看一道有意思的面试题:
setTimeout(function() {
console.log(1)
}, 0);
new Promise(function executor(resolve) {
console.log(2);
for( var i=0 ; i<10000 ; i++ ) {
i == 9999 && resolve();
}
console.log(3);
}).then(function() {
console.log(4);
});
console.log(5);
这道题应该考察 JavaScript 的运行机制的。
首先先碰到一个 setTimeout,于是会先设置一个定时,在定时结束后将传递这个函数放到任务队列里面,因此开始肯定不会输出 1 。
然后是一个 Promise,里面的函数是直接执行的,因此应该直接输出 2 3 。
然后,Promise 的 then 应当会放到当前 tick 的最后,但是还是在当前 tick 中。
因此,应当先输出 5,然后再输出 4 。
最后在到下一个 tick,就是 1 。
答案:
“2 3 5 4 1”
11. async 函数
ES2017 标准引入了 async 函数,使得异步操作变得更加方便。
async 函数的使用方式,直接在普通函数前面加上 async,表示这是一个异步函数,在要异步执行的语句前面加上 await,表示后面的表达式需要等待。async 是 Generator 的语法糖
async function f() {
return 'hello world';
}
f().then(v => console.log(v))
上面代码中,函数 f 内部 return 命令返回的值,会被 then 方法回调函数接收到。
async