乐趣区

领略原生-JavaScript-ES6ES10-的魅力

作为前端开发工程师,盲目追逐框架似乎有点舍本逐末,要知道基本功才是硬核。JavaScript 的语法这几年一直在更新,不管我们是框架的核心开发者还是业务重塑者,学习下最新的 JavaScript 语法和能力是非常有好处的。下面我们通过几个小示例来看下新语法的强大之处:

“初始化一个数组,要求数组的长度是 5,每个元素的默认值是 0”

这道题看似非常简单,我们可能会这样来写代码:

const arr = []
for(let i = 0; i < 5; i++){arr.push(0)
}

可是如果你学习过 ES6 的语法,就会知道 Array 新增的原型对象方法上有个 fill 的 API,它可以轻松实现这个题目,代码如下:

const arr = Array(5).fill(0)

这就是新的语法赋予 JavaScript 新的能力,如果我们不持续学习新的语法,写出来的代码很难是最简、最优雅、性能最好。当然,阅读其他同学或者开源代码的时候也不一定能看懂。那么 ES6 到底新增或者增强了哪些能力呢?我们来看下图谱:

从这个图谱不能看出 ES6 增加了很多新的语法,比如 Class、Generator、Proxy、Iterator 等。它们可以解决类、异步、代理、自定义遍历等功能。不如我们再来看个小示例:

实现类与继承。

在 ES6 之前实现类与继承都是借助函数来实现的,在继承方面也是利用原型链。代码如下:

function Component () {this.id = Math.random().toString(36).slice(-5)
  Object.defineProperty(this, 'id', {writable: false})
}
const com = new Component()
com.id = 3
console.log(com.id) // jklls

这段代码的含义是定义一个组件类,类定义了一个属性 id,这个 id 是随机、只读的。ES6 有了专门的语法来定义类。

class Component {constructor () {this.id = Math.random().toString(36).slice(-5)
    Object.defineProperty(this, 'id', {writable: false})
  }
}
const com = new Component()
com.id = 3
console.log(com.id)

在语义上看 ES6 的写法更容易读懂。不信,我们在看下继承的写法:

function Component () {this.id = Math.random().toString(36).slice(-5)
  Object.defineProperty(this, 'id', {writable: false})
}
function SubComponent () {Component.call(this)
}
SubComponent.prototype = Component.prototype
const com = new SubComponent()
com.id = 3
console.log(com.id)

class Component {constructor () {this.id = Math.random().toString(36).slice(-5)
    Object.defineProperty(this, 'id', {writable: false})
  }
}

class SubComponent extends Component {

}
const com = new SubComponent()
com.id = 3
console.log(com.id)

上下代码对比可以看出来 ES6 的方式要舒服很多,也更容易阅读。借助这个题我们再来思考 ES6 这个写法还能继续优化吗?比如 Object.defineProperty 方法在构造函数里显得那么格格不入。有没有更优雅的写法呢?不妨试试 ES6 新的语法 Proxy?

class Component {constructor () {
    this.proxy = new Proxy({id: Math.random().toString(36).slice(-5)
    }, {})
  }
  get id () {return this.proxy.id}
}
const com = new Component()
com.id = 3
console.log(com.id)

利用 Proxy 和 Class getter 方式就能保证 id 是只读的,在 proxy 实例化的时候也能保证 id“随机”、“唯一”。有同学会说这个代码有漏洞,proxy 还可以修改会导致 id 也可以被修改。说的没错,但是低估了 proxy 的能力,你再看:

class Component {constructor () {
    this.proxy = new Proxy({id: Math.random().toString(36).slice(-5)
    }, {set (target, key, value) {return false}
    })
  }
  get id () {return this.proxy.id}
}
const com = new Component()
com.proxy.id = 4
com.id = 3
console.log(com.id)

要知道 proxy 下面可以放很多跟 id 一样的内容,这样我们就不会一个一个用 Object.defineProperty 去显示的定义“只读”。用 class getter + proxy 的方式写起来“不露痕迹”,大家是否享受这种写法呢?当然,proxy 还有很多用武之地,比如把保护数据、数据校验等等。

如果大家没过瘾,我们再看一个更强大的功能:自定义遍历。

“我们数据库里存放着很多图书的作者,这些作者按照图书的类别进行分类,现在想遍历所有作者该怎么办?”

let authors = {
  allAuthors: {
    fiction: [
      'Agatha Christie',
      'J. K. Rowling',
      'Dr. Seuss'
    ],
    scienceFiction: [
      'Neal Stephenson',
      'Arthur Clarke',
      'Isaac Asimov',
      'Robert Heinlein'
    ],
    fantasy: [
      'J. R. R. Tolkien',
      'J. K. Rowling',
      'Terry Pratchett'
    ]
  }
}

我们希望可以对 authors 进行遍历并得到所有作者的名单。

for (let author of authors) {console.log(author)
}

本希望可以这做可是浏览器报错了,告诉我们 authors 是不可遍历的。那我们只能通过遍历所有 key 的方式来实现:

for (let key in authors) {let r = []
  for (let k in authors[key]) {r = r.concat(authors[key][k])
  }
  console.log(r)
  // ["Agatha Christie", "J. K. Rowling", "Dr. Seuss", "Neal Stephenson", "Arthur Clarke", "Isaac Asimov", "Robert Heinlein", "J. R. R. Tolkien", "J. K. Rowling", "Terry Pratchett"]
}

虽然用 ES5 的方式实现了,可是我们仍希望用 for…of 的方式来实现,简单便捷。ES6 增加了 Iterator 让任意数据结构可以实现自定义遍历器。直接上代码:

authors[Symbol.iterator] = function () {
  let allAuthors = this.allAuthors
  let keys = Reflect.ownKeys(allAuthors)
  let values = []
  return {next () {if (!values.length) {if (keys.length) {values = allAuthors[keys[0]]
          keys.shift()}
      }
      return {
        done: !values.length,
        value: values.shift()}
    }
  }
}

我们只需要对 authors 这个数据结构增加 Iterator 遍历器接口即可用 for…of 的方式来遍历了,浏览器不再报错了。有没有很惊艳?其实 ES6 之后 ES7、ES8、ES9、ES10 相继诞生,它们让原生 JavaScript 的能力再次提升。

不学习原生 JavaScript 新技能注定会让我们错过更多魅力之城,一起结对学 ES6~ES10。

退出移动版