ES6新特性,都是干货!

87 阅读7分钟

ES6及以后新增的常⽤API解析

一、 let 和 const

1.let的块级作用域

for(var i = 0; i <=3; i++) {
(function (i) {
setTimeout(function () {
console.log(i);
}, 10);
})(i);
}

for(let i=0;i<=3;i++){
setTimeout(function() {
console.log(i)
}, 10);
}

2. let的变量提升

console.log(i)
var i = 1;
console.log(letI)
let letI = 2;

3. const就是在let的基础上, 不可被修改。

二、箭头函数

1. 最大的区别:

箭头函数里的this是定义的时候决定的, 普通函数里的this是使用的时候决定的

const teacher = {
name: 'lubai',
getName: function() {
return `${this.name}`
}
}
console.log(teacher.getName());
const teacher = {
name: 'lubai',
getName: () => {
return `${this.name}`
}
}
console.log(teacher.getName());。

2. 箭头函数简写

const arrowFn = (value) => Number(value);
console.log(arrowFn('aaa'))

3. 注意, 箭头函数不能被用作构造函数

构造函数在new的时候会改变this指向到新实例出来的对象。 箭头函数的this指向是定义的时候决定的。

三、class

class Test {
_name = '';
constructor() {
this.name = 'lubai';
}
static getFormatName() {
return `${this.name} - xixi`;
}
get name() {
return this._name;
}

set name(val) {
console.log('name setter');
this._name = val;
}
}
console.log(new Test().name)
console.log(Test.getFormatName())

四、模板字符串

const b = 'lubai'
const a  = `${b} - xxxx`;
const c = `我是换行 我换行了! 我又换行了!`;

1.面试题:编写render函数, 实现template render功能.

const year = '2021';
const month = '10';
const day = '01';
let template = '![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/de046622db894c77858809dc53ff2c96~tplv-k3u1fbpfcp-zoom-1.image){month}-${day}';
let context = { year, month, day };
const str = render(template)({year,month,day});
console.log(str) // 2021-10-01

function render(template) {
return function(context) {
return template.replace(/${(.*?)}/g, (match, key) => context[key]);
 }
}

五、解构

1. 数组的解构

基础类型解构
let [a, b, c] = [1, 2, 3]
console.log(a, b, c) // 1, 2, 3

2. 对象数组解构

let [a, b, c] = [{name: '1'}, {name: '2'}, {name: '3'}]
console.log(a, b, c) // {name: '1'}, {name: '2'}, {name: '3'}

3. ...解构

let [head, ...tail] = [1, 2, 3, 4]
console.log(head, tail) // 1, [2, 3, 4]

4.嵌套解构

let [a, [b], d] = [1, [2, 3], 4]\
console.log(a, b, d) // 1, 2, 4
// 解构不成功为undefined\
let [a, b, c] = [1]\
console.log(a, b, c) // 1, undefined, undefined

// 1、解构默认赋值
let [a = 1, b = 2] = [3]
console.log(a, b) // 3, 2

2、对象的结构
// 对象属性解构
let { f1, f2 } = { f1: 'test1', f2: 'test2' }\
console.log(f1, f2) // test1, test2

// 可以不按照顺序,这是数组解构和对象解构的区别之一
let { f2, f1 } = { f1: 'test1', f2: 'test2' }
console.log(f1, f2) // test1, test2

六、解构

  1. for in
遍历数组时,key为数组下标字符串;遍历对象,key为对象字段名。
let obj = {a: 'test1', b: 'test2'}\
for (let key in obj) {\
console.log(key, obj[key])\
}
缺点:
-   for in 不仅会遍历当前对象,还包括原型链上的可枚举属性
-   for in 不适合遍历数组,主要应用为对象
  1. for of
可迭代对象(包括 ArrayMapSetStringTypedArrayarguments对象,NodeList对象)上创建一个迭代循环,调用自定义迭代钩子,并为每个不同属性的值执行语句。
let arr = [{age: 1}, {age: 5}, {age: 100}, {age: 34}]\
for(let {age} of arr) {
if (age > 10) {
break // for of 允许中断
}
console.log(age)
}
优点:
-   for of 仅遍历当前对象

六、Object

  1. Object.keys
该方法返回一个给定对象的自身可枚举属性组成的数组。
const obj = { a: 1, b: 2 };
const keys = Object.keys(obj); // [a, b]

手写实现一个函数模拟Object.keys?
function getObjectKeys(obj) {
const result = [];
for (const prop in obj) {
if (obj.hasOwnProperty(prop)) {
result.push(prop);
  }
}
return result;
}
console.log(getObjectKeys({
a: 1,
b: 2
}))
  1. Object.values
该方法返回一个给定对象自身的所有可枚举属性值的数组。
const obj = { a: 1, b: 2 };
const keys = Object.keys(obj); // [1, 2]

手写实现一个函数模拟Object.values?
function getObjectValues(obj) {
const result = [];
for (const prop in obj) {
if (obj.hasOwnProperty(prop)) {
result.push(obj[prop]);
 }
}
return result;
}
console.log(getObjectValues({
a: 1,
b: 2
}))
  1. Object.entries
该方法返回一个给定对象自身可枚举属性的键值对数组。
const obj = { a: 1, b: 2 };\
const keys = Object.entries(obj); // [ [ 'a', 1 ], [ 'b', 2 ] ]
手写实现一个函数模拟Object.entries?
function getObjectEntries(obj) {
const result = [];
for (const prop in obj) {
if (obj.hasOwnProperty(prop)) {
result.push([prop, obj[prop]]);
  }
}
return result;
}
console.log(getObjectEntries({
a: 1,
b: 2
}))
  1. Object.getOwnPropertyNames
该方法返回一个数组,该数组对元素是 obj自身拥有的枚举或不可枚举属性名称字符串。
看一下这段代码会输出什么?
Object.prototype.aa = '1111';
const testData = {
a: 1,
b: 2
}
for (const key in testData) {
console.log(key);
}
console.log(Object.getOwnPropertyNames(testData))
  1. Object.getOwnPropertyDescriptor
什么是descriptor? 对象对应的属性描述符, 是一个对象. 包含以下属性:
-   configurable。 如果为false,则任何尝试删除目标属性或修改属性特性(writable, configurable, enumerable)的行为将被无效化。所以通常属性都有特性时,可以把configurable设置为true即可。
-   writable 是否可写。设置成 false,则任何对该属性改写的操作都无效(但不会报错,严格模式下会报错),默认false。
-   enumerable。是否能在for-in循环中遍历出来或在Object.keys中列举出来。

const object1 = {};
Object.defineProperty(object1, 'p1', {
value: 'lubai',
writable: false
});
object1.p1 = 'not lubai'; // 不可写,修改失败
console.log(object1.p1); // lubai
console.log(Object.getOwnPropertyDescriptor(object1, 'p1')) // {value: 'lubai', writable: false, enumerable: false, configurable: false}

讲到了defineProperty, 那么肯定离不开Proxy.
const obj = new Proxy({}, {
get: function (target, propKey, receiver) {
console.log(`getting ${propKey}`);
return target[propKey];
},
set: function (target, propKey, value, receiver) {
console.log(`setting ${propKey}`);
return Reflect.set(target, propKey, value, receiver);
}
});
obj.something = 1;
console.log(obj.something);

Reflect又是个什么东西?
-   将Object对象的一些明显属于语言内部的方法(比如Object.defineProperty),放到Reflect对象上。现阶段,某些方法同时在ObjectReflect对象上部署,未来的新方法将只部署在Reflect对象上。也就是说,从Reflect对象上可以拿到语言内部的方法
-   让Object操作都变成函数行为。某些Object操作是命令式,比如name in obj和delete obj[name],而Reflect.has(obj, name)和Reflect.deleteProperty(obj, name)让它们变成了函数行为。
-   Reflect对象的方法与Proxy对象的方法一一对应,只要是Proxy对象的方法,就能在Reflect对象上找到对应的方法。这就让Proxy对象可以方便地调用对应的Reflect方法,完成默认行为,作为修改行为的基础。也就是说,不管Proxy怎么修改默认行为,你总可以在Reflect上获取默认行为。
-   
但是要注意, 通过defineProperty设置writable为false的对象, 就不能用Proxyconst target = Object.defineProperties({}, {
foo: {
value: 123,
writable: false,
configurable: false
},
});

const proxy = new Proxy(target, {
get(target, propKey) {
return 'abc';
}
});

proxy.foo
  1. Object.create()
Object.create()方法创建一个新的对象,并以方法的第一个参数作为新对象的**proto**属性的值(根据已有的对象作为原型,创建新的对象。)\
Object.create()方法还有第二个可选参数,是一个对象,对象的每个属性都会作为新对象的自身属性,对象的属性值以descriptor(Object.getOwnPropertyDescriptor(obj, 'key'))的形式出现,且enumerable默认为false

const person = {
isHuman: false,
printIntroduction: function () {
console.log(`My name is ${this.name}. Am I human? ${this.isHuman}`);
}
};
const me = Object.create(person);
me.name = "lubai";
me.isHuman = true;
me.printIntroduction();
console.log(person);
const myObject = Object.create(null)

传入第二个参数是怎么操作的呢?
function Person(name, sex) {
this.name = name;
this.sex = sex;
}

const b = Object.create(Person.prototype, {
name: {
value: 'coco',
writable: true,
configurable: true,
enumerable: true,
},
sex: {
enumerable: true,
get: function () {
return 'hello sex'
},
set: function (val) {
console.log('set value:' + val)
}
}
})
console.log(b.name)
console.log(b.sex)

那么Object.create(null)的意义是什么呢? 平时创建一个对象Object.create({}) 或者 直接声明一个{} 不就够了?
Object.create(null)创建一个对象,但这个对象的原型链为null,即Fn.prototype = null
const b = Object.create(null) // 返回纯{}对象,无prototype
b // {}
b.**proto** // undefined
b.toString() // throw error
所以当你要创建一个非常干净的对象, 没有任何原型链上的属性, 那么就使用Object.create(null). for in 遍历的时候也不需要考虑原型链属性了.

  1. Object.assign
浅拷贝, 类似于 { ...a, ...b };
function shallowClone(source) {
const target = {};
for (const i in source) {
if (source.hasOwnProperty(i)) {
target[i] = source[i];
}
}
return target;
}
const a = {
b: 1,
c: {
d: 111
}
}
const b = shallowClone(a);
b.b = 2222;
b.c.d = 333;
console.log(b)\
console.log(a)
  1. Object.is
const a = {
name: 1
};
const b = a;
console.log(Object.is(a, b))
console.log(Object.is({}, {}))

八、Promise

大部分promise都讲过了, 原理可直接看:王学智:前端Day1:PromiseA+规范和实现
再来复习一下promise.all吧

function PromiseAll(promiseArray) {
return new Promise(function (resolve, reject) {
//判断参数类型
if (!Array.isArray(promiseArray)) {
return reject(new TypeError('arguments muse be an array'))
}
let counter = 0;
let promiseNum = promiseArray.length;
let resolvedArray = [];
for (let i = 0; i < promiseNum; i++) {

这里为什么要用Promise.resolve?
Promise.resolve(promiseArray[i]).then((value) => {
counter++;
resolvedArray[i] = value; // 2. 这里直接Push, 而不是用索引赋值, 有问题吗
if (counter == promiseNum) { // 1. 这里如果不计算counter++, 直接判断resolvedArr.length === promiseNum, 会有问题吗?
// 4. 如果不在.then里面, 而在外层判断, 可以吗?\
resolve(resolvedArray)
}
}).catch(e => reject(e));
}
})
}

// 测试
const pro1 = new Promise((res, rej) => {
setTimeout(() => {
res('1')
}, 1000)
})
const pro2 = new Promise((res, rej) => {
setTimeout(() => {
res('2')
}, 2000)
})
const pro3 = new Promise((res, rej) => {
setTimeout(() => {
res('3')
}, 3000)
})

const proAll = PromiseAll([pro1, pro2, pro3])
.then(res =>
console.log(res) // 3秒之后打印 ["1", "2", "3"]
)
.catch((e) => {
console.log(e)
})

再来写一个Promise.allSeettled, 需要返回所有promise的状态和结果
function PromiseAllSettled(promiseArray) {
return new Promise(function (resolve, reject) {
//判断参数类型
if (!Array.isArray(promiseArray)) {
return reject(new TypeError('arguments muse be an array'))
}
let counter = 0;
const promiseNum = promiseArray.length;
const resolvedArray = [];
for (let i = 0; i < promiseNum; i++) {
Promise.resolve(promiseArray[i]) .then((value) => {
resolvedArray[i] = {
status: 'fulfilled',
value
};
}).catch(reason => {
resolvedArray[i] = {
status: 'rejected',
reason
};
}).finally(() => {
counter++;
if (counter == promiseNum) {
resolve(resolvedArray)
}
})
}
})
}