为什么说要学习全新的原生 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"]
}
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)全版本语法大全》(新课限时优惠)
共同学习,写下你的评论
评论加载中...
作者其他优质文章