前奏篇(一):ES 2015 新特性一览


注:如无特别声明,接下来的教程默认都是基于最新版 Laravel 8 作为底层框架进行项目开发。

技术栈选择

前面学院君介绍了从 Laravel 8 开始,自带的前端 UI 脚手架扩展包 Jetstream 不再基于 Vue 作为默认的组件开发技术栈,以便降低后端新手入门前端组件开发的学习成本。不过,如果你已经对 Vue.js 非常熟悉,仍然可以使用纯 Vue 框架进行页面组件开发,如果想要快速上手的话,只需要切换到 laravel/ui 这个 UI 扩展包即可,由于本系列教程讲的是 Vue.js 框架,所以后续教程依然会使用纯 Vue 框架编写页面组件。

至于 CSS 框架,laravel/jetstream 使用的是 Tailwind CSS 框架,laravel/ui 使用的是 Bootstrap CSS 框架,你自己根据自己的喜好进行选择,Bootstrap 想必大家都已经非常熟悉,相关的 UI 包和插件也是非常丰富,所以我们后续项目实战教程,会引入 Tailwind CSS 作为默认 CSS 框架,以便大家顺便学习和了解这个 CSS 框架的使用,更好地拥抱未来的 Laravel 生态。

由于 Vue 框架是一个 JavaScript 框架,掌握最新的 JavaScript 语法可以帮助我们更快更好地编写 Vue 组件,因此作为正式编写 Vue 表单组件的前奏,学院君打算花两个篇幅的教程分别介绍下 ES 2015 的新特性和 Tailwind CSS 的使用入门。

先来看 ES 2015 的主要新特性。

JavaScript 与 ECMAScript

我们知道 JavaScript 的官方标准是 ECMAScript(简称 ES),这门语言其实真正应该叫做 ECMAScript,JavaScript 只是遵循 ECMAScript 标准的方言实现版本,只是业界已经约定俗称将其称之为「JavaScript」。

厘清 JS 与 ES 的关系后,我们再来看 ES 5、ES 6 这些不同的标准版本,这就类似于 PHP 5、PHP 7 这些 PHP 的主版本号,不过,ECMAScript 从 2015 年开始,改为每年制定一次新标准,因此又有了一个按照发布年份的命名,比如 ES6 发布于 2015 年,又被称之为 ES 2015。

目前业界主流的 JavaScript 实现标准还是 ES 5 和 ES 6,你可以在这张兼容表里查看主流浏览器对不同版本 ES 标准的支持情况。

关于 ES 5 学院君在之前发布的读书笔记「JavaScript 权威指南」中已经详细介绍了,这里我们来看看 ES 6 的主要新特性即可,通过使用这些新特性,可以显著提高我们编写代码的效率。

ES 2015 新特性

箭头函数

在 ES 2015 中,支持箭头函数简化匿名函数的编写:

let languages = ['php', 'javascript', 'golang'];

languages.forEach((language) => {
   console.log(language);
});

对于函数体只包含单条语句,可以进一步简化:

languages.forEach((language) => console.log(language));

可变参数

ES 2015 支持在函数定义中使用可变参数:

let languages = ['php', 'javascript', 'golang'];

function print_items(...items) {
    items.forEach((item) => console.log(item));
}

print_items(languages);

字符串函数

ECMAScript 提供了一些字符串函数方便我们对字符串进行处理,比如,要判断一个字符串中是否包含指定内容,可以使用 includes 或者 indexOf 函数:

let name = '学院君';
let message = `
    <div class="alert-success">
    恭喜你,${name},注册成功!
    </div>
`;

console.log(message.includes(name));  // true
console.log(message.indexOf(name) > -1);   // 返回出现位置

还可以使用 startsWithendsWith 判断给定字符串是否以指定内容开头或结尾:

console.log(name.startsWith('学院'));  // true
console.log(name.endsWith('君'));     // true

更多字符串函数,请参考 MDN JavaScript 文档

数组函数

除了字符串之外,另一种日常经常打交道的数据结构就是数组了,ECMAScript 也提供了很多内置的数组函数方便我们对数组进行处理,和字符串函数一样,判断数组中是否包含给定值也可以使用 includesindexOf 函数:

console.log(languages.includes('java'));  // false
console.log(languages.indexOf('javascript'));  // 1

和 PHP 类似,还支持编写自定义的查找函数:

languages.find((language) => {
    return language === 'php';
});

更多数组函数,请参考 MDN JavaScript 文档

面向对象增强

在 ES 5 中,类的继承和扩展是基于原型实现的,与我们日常熟悉的面向对象编程语法差别很大,在 ES 6 中对此进行了调整和增强,我们可以通过熟悉的类定义方式编写面向对象编程代码了:

class Car {
    constructor(brand, logo) {
        this._brand = brand;
        this._logo = logo;
    }

    // Getter/Setter
    get brand() {
        return this._brand;
    }

    set brand(value) {
        this._brand = value;
    }

    get logo() {
        return this._logo;
    }

    set logo(value) {
        this._logo = value;
    }

    // 静态方法
    static drive() {
        console.log('开车了...');
    }
}

let car = new Car('奔驰', '三叉星');
console.log(car.brand);  // 调用对应 getter 方法
console.log(car.logo);   // 调用对应 getter 方法
car.brand = '宝马';       // 调用对应 setter 方法
car.logo = '蓝天白云';     // 调用对应 setter 方法
Car.drive();             // 调用静态方法,输出开车了...

对应代码很简单,这里就不详细解释了,我们可以编写一个继承自该类的子类:

class Audi extends Car {
    constructor() {
        super('奥迪', '四个圈');
    }

    get brand() {
        return super.brand;
    }

    set brand(brand) {
        throw '不支持该操作';
    }

    get logo() {
        return super.logo;
    }

    set logo(logo) {
        throw '不支持该操作';
    }
}

let audi = new Audi();
console.log(audi.brand);
Audi.drive();
audi.logo = '别摸我';

最后一行代码会抛出异常:

-w745 你可以通过 try...catch... 对其进行处理:

try {
    audi.logo = '别摸我';
} catch (e) {
    console.log('运行时报错: ' + e);
}

模块化

在 ES 2015 中,可以通过 export 或者 export default 导出模块:

...

export default Audi;

然后在另一个模块中通过 import 语法引入:

import Audi from './demo'

let audi = new Audi();
console.log(audi.logo);

这其实就是 JavaScript 的包管理机制,由于没有命名空间的概念,所以只能通过这种方式实现,就像 PHP 在没有命名空间之前也是通过 require/include 函数导入其它文件和类一样。

Promise

ES 2015 支持通过 Promise 对象实现优雅的异步编程,Promise 本质上是一个代理对象,被代理的值在 Promise 对象创建时是未知的(比如一个网络请求),然后我们可以在这个代理对象上绑定异步处理成功或失败后的回调函数作为异步处理器,从而完成异步调用的完整回路:

let promise = new Promise(function (resolve, reject) {
    console.log('初始化 Promise...');

    // 3s 后执行,模拟异步处理的耗时任务
    setTimeout(function () {
        console.log('执行完成');

         resolve();  // 调用该方法表示异步处理成功,失败则调用 reject() 方法
    }, 3000);
});

promise.then(function () {
    // 成功的回调
    console.log('异步任务处理完毕');
}).catch(function () {
    // 失败的回调
    console.log('异步任务处理失败');
});

编译和打包工具

由于不同版本、不同厂商的浏览器对 ES 标准的实现和支持并不是同步的,所以对于使用高版本语法编写的代码,需要通过编译工具将其编译为向后兼容的 JavaScript 代码,以便可以在新老版本浏览器中都可以正常运行,Babel 就是这样的一个编译工具。

Babel 这样的编译工具让我们可以使用最新版本的 ES 特性编写代码,而不必担心浏览器的兼容问题。

除此之外,对于现代模块化、组件化开发的 JavaScript 项目,还需要 Webpack 这样的打包工具将不同脚本和样式代码进行打包,方便依赖管理和部署,我们可以在 Webpack 中引入 Babel 插件,将编译打包处理流程化、自动化。

看起来很复杂,不过在 Laravel 项目中,可以使用 Mix 这样的上层封装工具简化编译打包流程。Mix 封装了 Webpack 80% 以上的操作,对于后端开发人员而言,已经足够满足日常使用场景了。


Vote Vote Cancel Collect Collect Cancel

<< 上一篇: 基于 Laravel Jetstream 提供的 Livewire + Blade 技术栈编写表单组件

>> 下一篇: 前奏篇(二):Tailwind 与 Bootstrap 的区别和使用入门