JS ES各版本特性

版本历史

ES5 浏览器可用性

https://www.caniuse.com/#search=es5

ES6 浏览器可用性

https://www.caniuse.com/#search=es6

JS包含三个部分:ECMAScript(核心)DOM(文档对象模型)BOM(浏览器对象模型)ECMAScriptJS语言的基础。
ECMAScript的最新版是第六版ECMAScript 6,于2015年6月17日发布,截止发布日期,JavaScript的官方名称是ECMAScript 2015,是当前最新的正式规范。

ECMAScript的各个版本:(从第三版开始说)

  • 第三版ECMAScript3

    新增了对正则表达式、新控制语句、try-catch异常处理的支持,修改了字符处理、错误定义和数值输出等内容。标志着ECMAScript成为了一门真正的编程语言。

  • 第四版于2008年7月发布前被废弃。

  • 第五版ECMAScript5力求澄清第3版中的歧义,并添加了新的功能。

    新功能包括:原生JSON对象、继承的方法、高级属性的定义以及引入严格模式。

  • 第六版ECMAScript6是继ES5之后的一次主要改进。

    增添了许多必要的特性,例如:模块和类以及一些实用特性,Maps、Sets、Promises、生成器(Generators)等。

ES5

支持IE9及以上

最常用的就是JSON对象了,早期的浏览器就要加载js插件来实现。

严格模式

严格模式详解

为整个脚本文件开启严格模式,需要在所有语句之前放一个特定语句 "use strict"; (或 'use strict';

1
2
3
// 整个脚本都开启严格模式的语法
"use strict";
var v = "Hi! I'm a strict mode script!";

严格模式,严格模式,重要其实就是es5中弃用了很多以前版本的语法,你再用就提示错误。

一句话说明了这个的用途,我们只要知道哪些语法被弃用了(es3,es2,es1),就知道哪些会在使用这个模式下报错了。

简单罗列弃用限制和es5不符合语法定义限制:

  1. 不使用var声明变量严格模式中将不通过
  2. 任何使用eval的操作都会被禁止
  3. eval作用域
  4. with被禁用
  5. caller/callee 被禁用
  6. 对禁止扩展的对象添加新属性会报错
  7. 删除系统内置的属性会报错
  8. delete使用var声明的变量或挂在window上的变量报错
  9. delete不可删除属性(isSealed或isFrozen)的对象时报错
  10. 对一个对象的只读属性进行赋值将报错
  11. 对象有重名的属性将报错
  12. 函数有重名的参数将报错
  13. 八进制表示法被禁用
  14. arguments严格定义为参数,不再与形参绑定
  15. 函数必须声明在顶层
  16. ES5里新增的关键字不能当做变量标示符使用,如implements, interface, let, package, private, protected, public, static, yield
  17. call/apply的第一个参数直接传入不包装为对象
  18. call/apply的第一个参数为null/undefined时,this为null/undefined
  19. bind的第一个参数为null/undefined时,this为null/undefined

JSON对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 声明一个“JSON格式”的String 
var jsonStr = '{"a":"1","b":2,"c":"13"}';
// JSON.parse把String转成Object了
var jsonObj = JSON.parse(jsonStr);
console.log(jsonStr);
console.log(jsonObj);
console.log(JSON.stringify(jsonObj));

// 转换JSON时转换类型
var jsonObj2 = JSON.parse(jsonStr, function (key, value) {
if (typeof value == 'string') {
return parseInt(value);
} else {
return value;
}
});
console.log(jsonObj2);

// JSON.stringify 可以用来对Json对象进行简单过滤
var jsonStr2 = JSON.stringify(jsonObj, function (key, value) {
if (value == 13) {
return undefined; //使用undefined,则键值对就不会包含在最终的JSON中
} else {
return value;
}
});
console.log("这里就少了值为13的键值");
console.log(jsonStr2);

// JSON.stringify还可以用来做Json格式化
var jsonStr3 = JSON.stringify(jsonObj, null, 4);
console.log(jsonStr3);

Object对象

方法有很多,但常用的是最后一个

  • Object.getPrototypeOf 返回对象的原型
  • Object.getOwnPropertyDescriptor 返回对象自有属性的属性描述符
  • Object.getOwnPropertyNames 返回一个数组,包括对象所有自有属性名称集合(包括不可枚举的属性)
  • Object.create 创建一个拥有置顶原型和若干个指定属性的对象
  • Object.defineProperty 给对象定义一个新属性,或者修改已有的属性,并返回
  • Object.defineProperties 在一个对象上添加或修改一个或者多个自有属性,并返回该对象
  • Object.seal 锁定对象。阻止修改现有属性的特性,并阻止添加新属性。但是可以修改已有属性的值
  • Object.freeze 冻结对象,阻止对对象的一切操作。冻结对象将永远不可变。
  • Object.preventExtensions 让一个对象变的不可扩展,也就是永远不能再添加新的属性
  • Object.isSealed 判断对象是否被锁定
  • Object.isFrozen 判断对象是否被冻结
  • Object.isExtensible 判断对象是否可以被扩展
  • Object.keys 返回一个由给定对象的所有可枚举自身属性的属性名组成的数组

举个例子怎么用的,这样就可以简单遍历对象了,又贴代码了

1
2
3
4
5
6
7
8
9
10
var obj = {
a: "1",
b: 2,
c: "13"
}
console.log(typeof obj);
let mkeys = Object.keys(obj);
for (var i = 0; i < mkeys.length; i++) {
console.log(obj[mkeys[i]]);
}

bind/apply/call

详情参见

都能起到改变this的效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
"use strict"

function locate(){
console.log(this.location);
}
function Maru(location){
this.location = location;
}
var kitty = new Maru("cardboard box");

var locateMaru = locate.bind(kitty);
locateMaru();
//apply和call 立即执行,而bind如果不调用是不执行的
locate.apply(kitty)
locate.call(kitty)

Array对象

indexOf/lastIndexOf

indexOf 返回根据给定元素找到的第一个索引值,否则返回-1

lastIndexOf 方法返回指定元素在数组中的最后一个的索引,如果不存在则返回 -1

isArray

判断是否为数组

1
2
3
4
5
"use strict"
var str = "aa";
var arr = [1,2,3,4,5,6];
console.info(Array.isArray(str));
console.log(Array.isArray(arr));

forEach

使用foreach遍历数组的话,使用break不能中断循环,使用return也不能返回到外层函数。

遍历循环数组

1
2
3
4
//这样就方便计算数组中数据了
arr.forEach(function(item,index,array){
console.log("索引项:"+index+" 数组项:"+item);
})

map

处理原数组,返回处理后的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
var arrObj = [
{name:"one",age:1},
{name:"two",age:2},
{name:"three",age:3},
{name:"four",age:4},
{name:"five",age:5},
{name:"six",age:6},
{name:"seven",age:7},
];
var ages = arrObj.map(function(obj){
return obj.age;//返回值
});
console.log(ages);

filter

过滤器,根据自己的条件进行过滤,filter和上面的map还是有点小区别的,自己发现吧。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
"use strict"

var arrObj = [
{name:"one",age:1},
{name:"two",age:2},
{name:"three",age:3},
{name:"four",age:4},
{name:"five",age:5},
{name:"six",age:6},
{name:"seven",age:7},
];
var arrFilter = arrObj.filter(function(obj){
return obj.age>5;//返回值的判断条件
});
console.log(arrFilter);

some

判断数组中是否有满足需要的值,返回true/false

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
"use strict"

var arrObj = [
{name:"one",age:1},
{name:"two",age:2},
{name:"three",age:3},
{name:"four",age:4},
{name:"five",age:5},
{name:"six",age:6},
{name:"seven",age:7},
];
var somefour = arrObj.some(function(obj){
return obj.age>4;//判断age有没有大于4的,如果有返回true,没有返回false
});
var someten = arrObj.some(function(obj){
return obj.age>10;//判断age有没有大于10的,如果有返回true,没有返回false
});
console.log("判断有没有大于4的:"+somefour);
console.log("判断有没有大于10的:"+someten);

every

判断数组中是否所有值都满足需要,返回true/false

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
"use strict"

var arrObj = [
{name:"one",age:1},
{name:"two",age:2},
{name:"three",age:3},
{name:"four",age:4},
{name:"five",age:5},
{name:"six",age:6},
{name:"seven",age:7},
];

var everyfour = arrObj.every(function(obj){
return obj>4;
});
var everyten = arrObj.some(function(obj){
return obj.age>10;//判断age有没有大于10的,如果有返回true,没有返回false
});
console.log("判断是不是所有都大于4:"+everyfour);
console.log("判断是不是所有都大于10:"+everyten);

ES6

IE压根不支持,Edge支持

箭头操作符

方便写回调,不改变this的引用。

1
2
3
4
5
6
7
var array = [1, 2, 3];
//传统写法
array.forEach(function(item, index, arr) {
console.log(item);
});
//ES6
array.forEach(item = > console.log(item));

变量定义let和const

  • let声明变量(块级作用域),let是更完美的var,它声明的全局变量不是全局属性widow的变量,这便解决了for循环中变量覆盖的问题

  • const声明常量(块级作用域)

字符串新增方法

1
2
3
4
5
let str = 'happy';
console.log(str.includes('ha')) // true
console.log(str.repeat(3)) // 'happyhappyhappy'
console.log(str.startsWith('ha')); // true,参数2为查找的位置
console.log(str.endsWith('p', 4)); // true,参数2为查找的字符串长度

字符串模版

ES6之前

通过\+来构建模板

1
2
3
$("body").html("This demonstrates the output of HTML \
content to the page, including student's\
" + name + ", " + seatNumber + ", " + sex + " and so on.");

ES6之后

  1. 基本的字符串格式化。将表达式嵌入字符串中进行拼接。用${}来界定;
  2. ES6反引号直接搞定;
1
2
$("body").html(`This demonstrates the output of HTML content to the page, 
including student's ${name}, ${seatNumber}, ${sex} and so on.`);

函数参数默认值

1
2
3
4
5
6
7
8
9
10
11
12
13
// ES6之前,当未传入参数时要实现:text = 'default';
function printText(text) {
text = text || 'default';
console.log(text);
}

// ES6;
function printText(text = 'default') {
console.log(text);
}

printText('hello'); // hello
printText();// default

对象合并Object.assign()

1
2
3
4
5
6
7
8
9
10
const obj1 = {
a: 1
}

const obj2 = {
c: 2
}
const obj = Object.assign(obj1, obj2)
console.log(obj); // {a: 1, c: 2}
console.log(obj === obj1); // true

键值对重名简写

1
2
3
4
5
6
function people(name, age) {
return {
name,
age
};
}

对象字面量简写

1
2
3
4
5
6
const people = {
name: 'lux',
getName () { // 省略冒号(:)和function关键字
console.log(this.name)
}
}

解构(…)

Spread / Rest 操作符指的是...,具体是 Spread 还是 Rest 需要看上下文语境。

当被用于迭代器中时,它是一个 Spread 操作符:

1
2
3
4
5
6
function foo(x,y,z) {
console.log(x,y,z);
}

let arr = [1,2,3];
foo(...arr); // 1 2 3

当被用于函数传参时,是一个 Rest 操作符:当被用于函数传参时,是一个 Rest 操作符:

1
2
3
4
function foo(...args) {
console.log(args);
}
foo( 1, 2, 3, 4, 5); // [1, 2, 3, 4, 5]

对象解构中的 rest 操作符

在对象解构模式下,rest 操作符会将解构源的除了已经在对象字面量中指明的属性之外的,所有可枚举自有属性拷贝到它的运算对象中。

1
2
3
4
5
const obj = {foo: 1, bar: 2, baz: 3};
const {foo, ...rest} = obj;
// Same as:
// const foo = 1;
// const rest = {bar: 2, baz: 3};

如果你正在使用对象解构来处理命名参数,rest 操作符让你可以收集所有剩余参数:

1
2
3
4
function func({param1, param2, ...rest}) { // rest operator
console.log('All parameters: ',{param1, param2, ...rest}); // spread operator
return param1 + param2;
}

对象字面量中的 spread 操作符

对象字面量内部,spread 操作符将自身运算对象的所有可枚举的自有属性,插入到通过字面量创建的对象中:

1
2
3
const obj = {foo: 1, bar: 2, baz: 3};
{...obj, qux: 4}
//{ foo: 1, bar: 2, baz: 3, qux: 4 }

要注意的是顺序问题,即使属性 key 并不冲突,因为对象会记录插入顺序:

1
2
{qux: 4, ...obj}
//{ qux: 4, foo: 1, bar: 2, baz: 3 }

如果 key 出现了冲突,后面的会覆盖前面的属性:

1
2
3
4
5
const obj = {foo: 1, bar: 2, baz: 3};
{...obj, foo: true}
//{ foo: true, bar: 2, baz: 3 }
{foo: true, ...obj}
//{ foo: 1, bar: 2, baz: 3 }

对象 spread 操作符

这一节,我们会看看 spread 操作符的使用场景。

我也会用 Object.assign() 实现一遍,它和 spread 操作符很相似(之后我们会更详细地比较它们)。

拷贝对象

拷贝对象 obj 的可枚举自有属性:

1
2
const clone1 = {...obj};
const clone2 = Object.assign({}, obj);

clone 对象们的原型都是 Object.prototype,它是所有通过对象字面量创建的对象的默认原型:

1
2
3
4
5
6
Object.getPrototypeOf(clone1) === Object.prototype
//true
Object.getPrototypeOf(clone2) === Object.prototype
//true
Object.getPrototypeOf({}) === Object.prototype
//true

拷贝一个对象 obj,包括它的原型:

1
2
3
4
const clone1 = {__proto__: Object.getPrototypeOf(obj), ...obj};
const clone2 = Object.assign(
Object.create(Object.getPrototypeOf(obj)), obj
);

注意,一般来说,对象字面量内部的 proto 只是浏览器内置的特性,并非 JavaScript 引擎所有。

对象的真拷贝

有时候,你需要老老实实地拷贝对象的所有自有属性(properties)和特性(writable, enumerable, …),包括 getters 和 setters。这时候 Object.assign()spread 操作符就回天乏术了。你需要使用属性描述符(property descriptors):

1
2
3
4
5
const clone1 = Object
.defineProperties(
{},
Object.getOwnPropertyDescriptors(obj)
);

如果还希望保留 obj 的原型,可以用 Object.create()

1
2
3
4
const clone2 = Object.create(
Object.getPrototypeOf(obj),
Object.getOwnPropertyDescriptors(obj)
);

对象和数组解构和Rest操作符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 对象解构
const people = {
name: 'xiaoming',
age: 25
}
const {
name,
age
} = people; // 'xiaoming', 25

// rest参数,返回的是一个对象
const obj = {
a: 2,
b: 3,
c: 4,
d: 5
};
const {
a,
...rest
} = obj; // 2 { b: 3, c: 4, d: 5 }

console.log(rest);

// 数组解构
const arr = [1, 3, 4];
const [x, y, z] = arr; // 1, 3, 4
const [x2, ...rest2] = arr; // 1, [3,4]
console.log(rest2);

Promise

非常好用

详情参见

Set

Set作为ES6新的数据解构(类数组),它的成员都是唯一的,因为最直接的使用场景便是去重、并、差、交集的使用。它使用的算法叫做“Same-value-zero equality”,类似精确运算的===,主要是NaN,这里它将两个视为相等。

1
2
3
4
5
6
7
// Set实例的常用方法和属性add,delete,clear,has、size
const s = new Set(['A', 'B', 'C']);
console.log(s); // Set { 'A', 'B', 'C' }
console.log(s.has('A')) // true,bool值
console.log(s.size) // 3
console.log(s.clear()) // Set {}
console.log(s.delete('A')) // true,bool值

同时Set实例配合常用遍历方法,实现并、交、差集。

1
2
3
4
5
6
7
8
const a =[1, 2, 3]
const b = [2, 3, 4];
// 并集
const s = Array.from(new Set([...a, ...b])); // [ 1, 2, 3, 4 ]
// 交集、差集
const bSet = new Set(b);
const interestSet = a.filter(v => bSet.has(v)); // [ 2, 3 ]
const interestSet = a.filter(v => !bSet.has(v)); // [ 1 ]

二进制和八进制字面量

ES6 支持二进制和八进制的字面量,通过在数字前面添加 0o 或者0O 即可将其转换为八进制值:

1
2
3
4
5
let oValue = 0o10;
console.log(oValue); // 8

let bValue = 0b10; // 二进制使用 `0b` 或者 `0B`
console.log(bValue); // 2

for…of 和 for…in

for in遍历的是数组的索引(即键名),而for of遍历的是数组元素值。

for…of:

1
2
3
4
5
let letters = ['a', 'b', 'c'];
for (let letter of letters) {
console.log(letter);
}
// 结果:a,b,c

for…in:

1
2
3
4
5
let letters = ['a', 'b', 'c'];
for (let letter in letters) {
console.log(letter);
}
// 结果:0,1,2

对象超类

ES6 允许在对象中使用 super 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var parent = {
foo() {
console.log("Hello from the Parent");
}
}

var child = {
foo() {
super.foo();
console.log("Hello from the Child");
}
}

Object.setPrototypeOf(child, parent);
child.foo(); // Hello from the Parent
// Hello from the Child

ES6 中支持 class 语法,不过,ES6的class不是新的对象继承模型,它只是原型链的语法糖表现形式。

函数中使用 static 关键词定义构造函数的的方法和属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Student {
constructor() {
console.log("I'm a student.");
}

study() {
console.log('study!');
}

static read() {
console.log("Reading Now.");
}
}

console.log(typeof Student); // function
let stu = new Student(); // "I'm a student."
stu.study(); // "study!"
stu.read(); // "Reading Now."

类中的继承和超集:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Phone {
constructor() {
console.log("I'm a phone.");
}
}

class MI extends Phone {
constructor() {
super();
console.log("I'm a phone designed by xiaomi");
}
}

let mi8 = new MI();

extends 允许一个子类继承父类,需要注意的是,子类的constructor 函数中需要执行 super() 函数。
当然,你也可以在子类方法中调用父类的方法,如super.parentMethodName()。

有几点值得注意的是:

  • 类的声明不会提升(hoisting),如果你要使用某个 Class,那你必须在使用之前定义它,否则会抛出一个 ReferenceError 的错误
  • 在类中定义函数不需要使用 function 关键词

Generator

实际应用中大都用ES7的async/await来替代

yield

由于 Generator 函数返回的遍历器对象,只有调用next方法才会遍历下一个内部状态,所以其实提供了一种可以暂停执行的函数。yield表达式就是暂停标志。

  1. 遇到yield表达式,就暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回的对象的value属性值。
  2. 下一次调用next方法时,再继续往下执行,直到遇到下一个yield表达式。
  3. 如果没有再遇到新的yield表达式,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值,作为返回的对象的value属性值。
  4. 如果该函数没有return语句或者执行完return之后再运行next的时候,则返回的对象的value属性值为undefined,done为true。

需要注意

  • yield表达式只能用在 Generator 函数里面,用在其他地方都会报错。
  • yield表达式如果用在另一个表达式之中,必须放在圆括号里面
  • yield表达式用作函数参数或放在赋值表达式的右边,可以不加括号。
  • yield放在表达式中的时候,let s = (yield 1+2),s其值将会是undefined,
    而1+2这个等于3的值将会作为next返回对象的value的值

next

  • yield表达式本身没有返回值,或者说总是返回undefined。

  • next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值。

由于next方法的参数表示上一个yield表达式的返回值,所以在第一次使用next方法时,传递参数是无效的。
V8 引擎直接忽略第一次使用next方法时的参数,只有从第二次使用next方法开始,参数才是有效的。从语义上讲,第一个next方法用来启动遍历器对象,所以不用带有参数。

yield*

如果在 Generator 函数内部,调用另一个 Generator 函数,默认情况下是没有效果的。
这个就需要用到yield*表达式,用来在一个 Generator 函数里面执行另一个 Generator 函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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"

示例1

不传值

1
2
3
4
5
6
7
8
9
10
function* test() {
var str1 = yield "hello";
var str2 = yield str1 + " world";
return str2;
}

var t = test();
console.log(t.next()); // {value: "hello", done: false}
console.log(t.next()); // {value: "undefined world", done: false}
console.log(t.next()); // {value: undefined, done: true}

传值

1
2
3
4
5
6
7
8
9
10
11
12
13
function* test() {
var str1 = yield "hello";
var str2 = yield str1 + " world";
return str2;
}

var t = test();
var t1 = t.next();
var t2 = t.next(t1.value);
var t3 = t.next(t2.value);
console.log(t1); // {value: "hello", done: false}
console.log(t2); // {value: "hello world", done: false}
console.log(t3); // {value: "hello world", done: true}

上面的两个例子可以发现yield修饰的表达式的返回值为undefined,所以我们一定要把之前的结果传进去。

示例2

1
2
3
4
5
6
7
8
function* test01(x) {
var y = yield x + 1;
return y;
}

var t1 = test01(1);
console.log(t1.next()) // {value: 2, done: false}
console.log(t1.next(3)) // {value: 3, done: true}

第一个 next 方法的 value 属性,返回表达式 x + 1 的值(2)。
第二个 next 方法带有参数3,这个参数可以传入 Generator 函数,作为上个阶段异步任务的返回结果,被函数体内的变量 y 接收。因此,这一步的 value 属性,返回的就是3(变量 y 的值)。

示例3

Generator 函数内部还可以部署错误处理代码,捕获函数体外抛出的错误。

1
2
3
4
5
6
7
8
9
10
11
12
function* test02(x) {
try {
var y = yield x + 2;
} catch (e) {
console.log(e);
}
return y;
}

var t2 = test02(1);
console.log(t2.next());
console.log(t2.throw("出错了"));

上面代码的最后一行,Generator 函数体外,使用指针对象的 throw 方法抛出的错误,可以被函数体内的 try ... catch 代码块捕获。

这意味着,出错的代码与处理错误的代码,实现了时间和空间上的分离,这对于异步编程无疑是很重要的。

示例4

下面看看如何使用 Generator 函数,执行一个真实的异步任务。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function loaddata() {
var request = new XMLHttpRequest();
request.open('GET', 'login.json', false);
request.send(null);
if (request.status === 200) {
return request.responseText;
}
}

function* mytest() {
let data = yield loaddata();
console.log(data);
}

var mtest = mytest();
let mn1 = mtest.next()
console.info(mn1);
let mn2 = mtest.next(mn1.value)
console.info(mn2);

输出的结果

{value: “{“code”: 0, “msg”: “success”}”, done: false}
{ “code”: 0, “msg”: “success”}
{value: undefined, done: true}

这三行的输出分别对应上面的最后三行代码

上面代码中,Generator 函数封装了一个异步操作,该操作先读取一个远程接口,然后从 JSON 格式的数据解析信息。

就像前面说过的,这段代码非常像同步操作,除了加上了 yield 命令。

ES7

async/await

非常好用

详情参见

原来的写法

1
2
3
4
5
update_click:function(){
this.$nextTick(function(){
// ...
});
}

新写法

1
2
3
4
5
async update_click:function(){
// ...
await this.$nextTick();
// ...
}

简单封装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const fetchData = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('fetch data is me')
}, 1000)
})
}

// 抽离成公共方法
const awaitWrap = (promise) => {
return promise
.then(data => [null, data])
.catch(err => [err, null])
}

const [err, data] = await awaitWrap(fetchData())
console.log('err', err)
console.log('data', data)

Promise.all()

Promise.all(promiseArray)方法是Promise对象上的静态方法,该方法的作用是将多个Promise对象实例包装,生成并返回一个新的Promise实例。

参数:promiseArray,是一个Promise实例数组

1
2
3
4
5
6
var p1 = Promise.resolve(1),
p2 = Promise.resolve(2),
p3 = Promise.resolve(3);
Promise.all([p1, p2, p3]).then(function (results) {
console.log(results); // [1, 2, 3]
});

求幂运算符

1
Math.pow(3, 2) === 3 ** 2    // 9

Array.prototype.includes()

数组原型的方法,查找一个数值是否在数组中,只能判断一些简单类型的数据,对于复杂类型的数据无法判断。该方法接受两个参数,分别是查询的数据和初始的查询索引值。

1
2
3
[1, 2, 3].indexOf(3) > -1 // true
// 等同于:
[1, 2, 3].includes(3) // true

两者的优缺点和使用场景

  • 简便性

    includes方法略胜一筹,直接返回bool。indexOf需要返回数组下标,我们需要对下标值在进行操作,进而判断是否在数组中。

  • 精确性

    两者这都是通过===进行数据处理,但是对NaN数值的处理行为不同。includes对NaN的处理不会遵循严格模式去处理,所以返回true。indexOf会按照严格模式去处理,返回-1。

    1
    2
    [1, 2, NaN].includes(NaN)  // true
    [1, 2, NaN].indexOf(NaN) // -1
  • 使用场景
    如果仅仅查找数据是否在数组中,建议使用includes,如果是查找数据的索引位置,建议使用indexOf更好一些

ES8

Object.entries()

该方法会将某个对象的可枚举属性与值按照二维数组的方式返回。(如果目标对象是数组,则会将数组的下标作为键值返回)

1
2
Object.entries({ one: 1, two: 2 })    //[['one', 1], ['two', 2]]
Object.extries([1, 3]) //[['0', 1], ['1', 3]]

Object.values()

它的工作原理和Object.entries()方法很像,但是它只返回键值对中的值,结果是一维数组

1
2
3
Object.values({one: 1, two: 2})    // [1, 2]
Object.values({3: 'a', 1: 'b', 2: 'c'}) // ['b', 'c', 'a']
Object.extries([1, 3]) //[1, 3]

字符串填充

ES8提供了新的字符串填充方法,该方法可以使得字符串达到固定长度。它有两个参数,字符串目标长度和填充内容。

1
2
3
console.info('vue'.padStart(6, 'm')); // mmmvue
console.info('vue'.padEnd(6, 'm')); // vuemmm
console.info('vue'.padStart(3, 'm')); //vue