摧枯拉朽,说说ES6的三把火

6,068 阅读6分钟

我是 Jser 我骄傲

JavaScript 如今可谓是屌丝逆袭高富帅的代名词哈,从当初闹着玩似的诞生到现在 Github 上力压群雄的人气王,JS 搭着互联网的顺风车一路高歌,本身也从一个爷爷不疼奶奶不爱的杀马特小脚本蜕变为一门高大上的主流编程语言。运气固然重要,ES6 也让大家看到了这门语言自身的努力和上进,相信 JavaScript 定大有可为。

ES6 发布于 2015 年 6 月,因此也叫 ES2015,距今已有两年多。2015 年是铭心刻骨的一年,这一年,股市崩盘,千古跌停,手里的两个票更是挨了腰斩,宝宝心里苦。同是这一年,ES6 标准落地,作为一名前端开发,宝宝心里乐开了花。

ES6 的新语法很多很碎,如果想系统地学习一遍,强烈推荐阮一峰老师的 ECMAScript 6 入门。本文的目的不是要介绍 ES6 的新语法,而是想谈谈 ES6 为JavaScript 这门语言带来了什么,这也是在咱看来 ES6 最重要最激动人心的特性。

作用域

作用域一直是程序设计语言的基础设施。JavaScript 也有作用域,但它的作用域有点怪,稍不留神就会掉到坑里。

JavaScript 的作用域(scope)有三种类型,分别是 Local,Closure,Global,对应的就是三种作用域变量类型,分别是局部变量,闭包变量和全局变量。

比如下面一段代码:

var a = 'global'
function outer(){
    var b = 'closure'
    return function inner(){
        var c = 'local'

        console.log(a + ':' + b + ':' + c)
    }
}
outer()()

在 Chrome 控制台执行上面代码,在执行 outer()() 时,aouter 都是全局变量,浏览器环境中的全局变量全部挂在全局 window 对象上。b 是闭包变量, 对 temp 函数内定义的函数保持可见。c 是局部变量,只对 inner 函数内部可见。

看上去似乎没什么问题,接下来看点有问题的:

if(true){
    var _a = 1
}
console.log(_a)

正常情况,_a 应该是 undefined 的才对,然而不要以为你以为就是你以为的。if 语句块里定义的变量 _a 直接变成了全局变量。如果是从C语言,Java等主流编程语言转过来的就更加迷惑了,这简直就是TMD不科学。

诸如 forwhileswitch 等可接大括号的代码块都是不能定义局部变量的,定义的通通是全局变量,能够定义局部变量的只有函数这个第一公民。这不科学,不合理,不符合社会主义核心价值观\抓狂。

一声霹雳,ES6 落地,完美地弥补了上面 JavaScript 暴露的缺陷。从此以后你可以以为你以为的就是你以为的了。ES6 为了向后兼容,var 还是原来的 var 保持不变,在此基础上又新添了一个声明变量的关键字 let 和一个声明常量的关键字 const

{
    let a = 1
}
console.log(a)
// Uncaught ReferenceError: a is not defined

完善的作用域机制可有效地避免命名污染,提高程序的可靠性,减少隐晦的bug,尤其是开发大型多人协作项目时作用尤为明显。所以最佳实践就是自始至终地使用 let,而不要再让 var 这个混小子出现在视线里。

模块系统

模块化,模块化,模块化,重要的事情说三遍,这是工程化的第一要务,是控制软件复杂度的最重要措施,没有之一。然而 2015 年之前的 JavaScript 语言是没有模块系统的。C 有 include,Java 有 import,连 CSS 都有个蹩脚的 @import,wrnmmp,JavaScript 却只有 undefined。有条件要上,没条件创造条件也要上。于是乎,JavaScript 社区里的各位勇士造出了各种各样实现模块化的轮子,AMD,CMD,UMD 全蹦出来了。不想用又苦于没有好的解决方案,只能在内心呐喊,这不科学,不合理,不符合···\抓狂。

霹雳哗啦,ES6 落地,带来了原生的模块系统,优雅又简单,丝滑般的体验冲淡了腰斩给咱带来的痛,脸上又洋溢出了久违的笑容。

最基本的使用:

/* add.js */
export function add(a, b){
    return a + b
}

上面定义并导出了一个 ES 模块。接下来就可以在任一个逻辑文件里引用:

/* main.js */
import {add} from './add'

add(1, 3)

模块系统哪家强,ES6 最猖狂。ES6 一出,快刀斩乱麻,直接终结了前端长期的模块化乱象,实现了大一统。唯一的遗憾是 Node.js 对 ES6 模块系统的支持还不是无缝过渡。不过这个还好,毕竟 Node.js 的 CommonJS 用起来还是相当顺手的。

有了语言层面的模块系统,JavaScript 也在语言性质上完成了蜕变,不再是一个打遍互联网酱油的小脚本,而成长为一门举重若轻的主流编程语言。模块系统是大型项目不可或缺的标准配置,对项目的开发和维护都非常重要。所以把它列为 ES6 所带来的最重要特性丝毫不为过。

类(Class)

其实这个特性相比前两个倒是没那么重要,说白了,类(Class)做的就是 JS 原型(prototype)做的那点事——继承,咱觉得这是一个没它也行,有它更好的语法糖功能。之所以把它列在这是因为咱看 prototype 不爽很久了。

JS 基于原型的继承机制借鉴自 Self 语言。正如 JS 的设计者所言“它是 C 语言和 Self 语言一夜情的产物”。原型继承是很不直观的,或者可以说是非主流的。面向对象程序设计中典型的继承是基于类的继承,例如 Java,C++ 等主流编程语言实现的均是基于类的继承。

使用 prototype 会出现 jQuery 的 jQuery.prototype.init.prototype = jQuery.prototype 这种让人瞬间石化的代码。况且咱左看右看,上看下看也没看出来原型继承相较于类式继承有什么优点可言,反而是越看越拙。咱第一眼看到 prototype 这货就很不顺眼,也许是因为从 C++ 开发过来的缘故吧。

好在 ES6 带来了 class,不知道大家怎么看,反正咱心里是乐坏了,有一种久别重逢,相见恨晚感觉。

来看个最简单示例:

/* Point.js */
export class Shape{
    this.x = 0
    this.y = 0
}

英雄联盟里的盲僧说过“如果类不是为了继承,那将毫无意义”。

import {Point} from "./Point"

class Triangle extends Point{
    constructor(){
        super()
        this.sides = 3
    }
}
let a = new Triangle()

这代码看着多直观,多大方,多舒坦。面向对象的概念已经在前端被越来越广泛的传播开来,咱也是非常推崇 class 的继承方式的,不知道喜欢用哪个,反正在咱的代码里已经是看不到 prototype 的影子了。