手记

为什么说要学习全新的原生 JavaScript?

开场白

JavaScript 是前端开发工程师最重要的技能,没有之一。在 Vue.js、React.js、Koa、Echarts 等框架风靡一时的背景下,原生的 JavaScript 就可以被抛弃了吗?答案是否定的。

typeof 10n;

请看下这个代码的执行结果是什么?(能一眼看出结果的同学请迅速离场)。

这个正确的直接结果是:bigint

那么 bigint 是用来解决什么问题的呢?从字面意思就能猜出 big 的含义,我们知道 Number 基本类型可以精确表示的最大整数是 2^53,如果超出了这个范围那就会出错了,所以在 ES10 提出了 bigint 数据类型来解决这个问题。

开场白结束了,我的观点也表达完了:原生能力是框架所不能取代的

辩证关系

此时此刻,很多同学还处于朦胧中:原生JavaScript不能被框架所取代,那他俩的关系是什么?需要我二选一吗?

  Javascript框架是指以Javascript语言为基础搭建的编程框架

这是百度百科的定义,换句话说框架本质上还是原生的 JavaScript,如果 JavaScript 不具备的能力,框架也注定无能为力。其次,框架更注重的是效率的提升而非能力。比如 Vue.js 让大家编写组件更容易了让我们写业务的开发效率提升了,但是不能说 Vue.js 让原生 JavaScript 的能力增强了。反过来说原生 JavaScript 能在框架之上做什么?那就是写业务逻辑了或者补充框架能力(插件)。比如我们经常使用 Webpack 框架,如果我们想写一个 Webpack 插件还是需要用原生 JavaScript 来编写的。当然就算我们使用 Vue、React 这样的框架,业务逻辑也是需要原生 JavaScript来写的。

JS升级:ES6

前端开发者一路从 ES3 写到 ES5,2015年发布了 ES6 让原生 JavaScript 能力大幅提升,Class、Proxy、Generator等专用语法和能力标准化,也逐步得到各大浏览器的兼容。对于前端开发者来说我们可以极度精简代码和提升开发效率,不妨看下:

“初始化一个数组,要求数组的长度是 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 的方式来遍历了,浏览器不再报错了。有没有很惊艳?

JS升级:ES7+

其实 ES6 之后 ES7、ES8、ES9、ES10相继诞生,它们让原生 JavaScript 的能力再次提升。

虽然从 ES7 开始没有像 ES6 那样带来大版本的改动,但是能力的提升仍不可忽视。正则表达式是我们日常开发经常使用的技能,从 ES9 开始就支持正则表达式的分组命名捕获。

  在指定的日期字符串中提取年、月、日数据

在ES9之前不得不这样做:

let t = '2019-06-07'.match(/(\d{4})-(\d{2})-(\d{2})/)
console.log(t[1]) // 2019
console.log(t[2]) // 06
console.log(t[3]) // 07

现在就可以这样做了:

console.log('2019-06-07'.match(/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/))
// ["2019-06-07", "2019", "06", "07", index: 0, input: "2019-06-07", groups: {…}]

这段代码的返回值 groups 已经是 Object 了,具体的值是:

groups: {year: "2019", month: "06", day: "07"}

这个 Object 的 key 就是正则表达式中定义的,也就是把捕获分组进行了命名。想获取这些捕获可以这样做:

let t = '2019-06-07'.match(/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/)
// ["2019-06-07", "2019", "06", "07", index: 0, input: "2019-06-07", groups: {…}]
console.log(t.groups.year) // 2019
console.log(t.groups.month) // 06
console.log(t.groups.day) // 07

通过这个简单的实例,不难看出全新的 JavaScript 语法会改变我们之前书写的习惯,用新的能力解决问题;当然还有很多能力能解决之前搞不定的问题,有没有很期待?

················
欢迎关注课程:
《再学JavaScript ES(6-10)全版本语法大全》(新课限时优惠)

59人推荐
随时随地看视频
慕课网APP

热门评论

新课来了。。。哈哈哈,买买买~

新课来了。。。哈哈哈,买买买~

我想知道const是什么鬼,以前写js的时候没有这个呀?

查看全部评论