引言:
JavaScript语言规范始终在与时俱进,除了过于激进的ES4被“废除”之外,ES Next始终在茁
壮发展。如今,TC39( Technical Committe 39, JavaScript委员会)已经明确表示每年更新一个版本,因此用ES Next表示那些“ 正在演进、正在发展”的新特性集。
作为前端开发者,我们该如何看待每年发布一版的ES Next,又该如何保持学习呢?本篇就来谈
一谈ESNext。我认为列举新特性没有价值,这些东西随处可见,更重要的是分析新特性的由来,剖析如何学习新特性,以及如何利用新特性。

一、ES7的includes

includes判断数组是否含有某个元素,返回一个布尔值

简单实现

let arr = [1, 2, 3]

console.log( arr.findIndex(i => i === 2) ); // 1
console.log( arr.find(i => i === 2) ); // 2
console.log( arr.indexOf(2) ); // 1

const cIncludes = (arr, tar) => !!~ arr.indexOf(tar)

console.log( cIncludes(arr, 3) ); // true
console.log( cIncludes(arr, 4) ); // false

既然能简单实现这个功能,为什么要开发这个API呢

以下例子就能展现区别

console.log([NaN].includes(NaN)); // true
 console.log([NaN].indexOf(NaN));  // false

indexof的对比是用的===,所以无法识别NaN,新特性与老特性的区别很多体现在细节上。

二、Object spread 和 Object.assign

Object spread 和 Object.assign在很多情况下做的事情是一致的,太慢都属于ES Next的新特性,{...obj}也和Object.assign({}, obj)是等价的。但是它们之间肯定存在区别。assign会修改它的第一个参数对象。这个修改会触发参数对象的setter,而spread会创建一个对象副本,不会修改任何值

例:

let obj1 ={a: 1,set b(o){ console.log('setter trigger'); }}
let obj2 ={b: 2}
let obj3 = Object.assign(obj1,obj2) // 打印setter trigger
console.log(obj1); // { a: 1, b: [Setter] }
console.log(obj3); // { a: 1, b: [Setter] }

console.log({ ...obj1,...obj2 }); // { a: 1, b: 2 }


let aaa = {a: 1, b: 2}
let bbb = {c: 3}
let ccc = Object.assign(aaa, bbb)
console.log(aaa); // { a: 1, b: 2, c: 3 }
console.log(ccc); // { a: 1, b: 2, c: 3 }

注:assign的第一个参数对象会被修改,属性合并

三、箭头函数不适用的场合

1.开发者习惯用箭头函数来干预this的指向,反之,不需要干预this指向的场景,就不适合用箭头函数

构造函数的原型方法需要通过this获得实例,所以不适合,以下为错误写法

function Person(){}
Person.prototype = () => { ... }

2.箭头函数不具备arguments属性,需要使用arguments时,不适应用箭头函数

3.动态回调时,需要使用this时,如

const btn = document.getElementById('btn')
 btn.addEventListener('click', () => {
   console.log(this === window);
 })

四、Proxy代理

为对象声明一个代理,可以拦截对象各种行为,自定义一些操作,并将结果同步到对象上

new Proxy(target, handler)

handler.getPrototypeOf()

Object.getPrototypeOf 方法的捕捉器。

handler.setPrototypeOf()

Object.setPrototypeOf 方法的捕捉器。

handler.isExtensible()

Object.isExtensible 方法的捕捉器。

handler.preventExtensions()

Object.preventExtensions 方法的捕捉器。

handler.getOwnPropertyDescriptor()

Object.getOwnPropertyDescriptor 方法的捕捉器。

handler.defineProperty()

Object.defineProperty 方法的捕捉器。

handler.has()

in 操作符的捕捉器。

handler.get()

属性读取操作的捕捉器。

handler.set()

属性设置操作的捕捉器。

handler.deleteProperty()

delete 操作符的捕捉器。

handler.ownKeys()

Object.getOwnPropertyNames 方法和 Object.getOwnPropertySymbols 方法的捕捉器。

handler.apply()

函数调用操作的捕捉器。

handler.construct()

new 操作符的捕捉器。







 以下是get的拦截代理

// proxy 示例
const bird = {
  name: 'john'
}

let proxyBird = new Proxy(bird, {
  get(tar, property, receiver){
    if(property === 'name'){
      console.log('bird get name');
    }
  }
})
proxyBird.name = 'tom'
proxyBird.age = 2
console.log(bird.name); // tom
console.log(bird.age); // 2
console.log(proxyBird.name); // undefined

1.使用Proxy防止函数作为非构造函数调用

class Cat{
  constructor (name){
    this.name = name
  }
}

let ProxyCatClass = new Proxy(Cat, {
  apply(tar, context, args){
    throw new Error(`hello: Function ${tar.name} cannot be invoked without 'new'`)
  }
})
ProxyCatClass('cat') // hello: Function Cat cannot be invoked without 'new'

 2.使用Proxy把函数调用强制转为new调用

class Cat{
  constructor (name){
    this.name = name
  }
}


let ProxyCatClass = new Proxy(Cat, {
  apply(tar, context, args){
    return new (tar.bind(context,...args))()
  }
})
console.log(ProxyCatClass('cat')); // Cat { name: 'cat' }

3.前端测试的断言assert

assert使用如下

const lisa = {
  age: 25
}
assert['lisa is order than 24'] = 24 > lisa.age // Error lisa is older than 24

Proxy实现如下:

const assert = new Proxy({}, {
  set(tar, waring, value){
    if(!value){
      console.log(waring);
    }

  }
})

五、Babel的编译

1.let 和 const的编译

Babel编译let 和 const 都是把它们编译成 var,对const声明的变量进行了二次赋值,块级作用域则是在块内给变量换个名字,这样在块外就访问不到了。暂时性死区则用严格模式,变量未声明不允许使用。for循环则用用闭包储存变量

编译如下

// let
var foo = 123
{
  foo = 'abc'
  let foo
}
// babel let
"use strict"
var foo = 123
{
  _foo = 'abc'
}
var _foo

// const

const foo = 0
foo = 1

// babel const
"use strict"
function _readOnlyError(name) {
  throw new Error("\"" + name + "\" is read-only")
}
var foo = 0
foo = (_readOnlyError("a", 1))

// for let
let array = []
for(let i=0; i<10; i++){
  array[i] = function(){
    return i
  }
}
array[6] //6


// babel for let 
let _loop = function _loop(i){
  array[i] = function(){
      return i
  }
}
var array = []
for(var i=0; i<10; i++){
  _loop(i)
}
console.log( array[6]() ); //6

2.箭头函数的编译

// 箭头函数
var obj = {
  prop: 1,
  func: function(){
    var _this = this
    var fan = () => {
      this.prop = 2
    }
    var fbn = function(){
      this.prop = 3
    }
  }
}

// babel 箭头函数
var obj = {
  prop: 1,
  func: function(){
    var _this2 = this
    var _this = this
    var fan = function(){
      _this2.prop = 2
    }
    var fbn = function(){
      this.prop = 3
    }
  }
}

总结:
JavaScrpt 语言、ES规范总是在不断进步、发展,每个开发者都要做到时刻学习、跟进。在这个过程中,除了解新特性之外,新老知识相结合,融会贯通,不断去思考“是什么”“为什么”
也非常重要。本篇挑选了几个典型的特性,分析了Babel编译结果,内容并不算太深,但却给出了一个很好的切入角度。

希望能够掌握正确的学习“姿势”,保持好的心态,这也是进阶路上至关重要的一点。