ES 6语言基础
ES 6(于 2015 年 6 月正式发布)是 JavaScript 语言的下一代标准,相对于 ES 5(于 2011 年 6 月正式发布)新增了一些语法规则和数据结构方法,例如比较典型的 Set
和 Map
数据结构和箭头函数等,可以理解成传统 JavaScript 的升级版,后续还会有 ES 7、ES 8 版本等。Vue 3
发布以来,极力推荐采用 ES 6 的语法来开发代码,另外本书的实战项目将全部采用 ES 6 代码。
由于移动端操作系统和浏览器兼容性问题的限制,虽然大部分机型原生就支持 ES 6 语法的 JavaScript
,但是仍有一部分市场占有率较低的机型无法支持 ES 6 语法,例如 Android
系统 4.4 及以下版本和 iOS
系统 8.4 及以下版本。因此,为了项目的健壮性和更强的适配性,会采用 Node.js 的 Babel
工具来将 ES 6 代码转换成兼容性更强的 ES 5 代码。
由于 ES 6 的语法内容很多,相对复杂,因此本章只会对实战项目中用到的 ES 6 语法结合 ES 5 的写法来对比讲解和演示。
变量声明
let、var 和 const
在 ES 6 语法中,新增了 let
和 const
来声明变量,在 ES 6 之前,ES 5 中只有全局作用域和函数作用域,代码如下:
if(true) {
var a = 'Tom'
}
console.log('a',a) // Tom
作用域是一个独立的地盘,让变量不外泄出去,但是上面的代码中的变量 a
就作为全局作用域外泄了出去,所以此时 JavaScript
没有区块作用域(或称为块级作用域)的概念。
在 ES 6 中加入区块作用域之后,代码如下:
if(true) {
let a = 'Tom'
}
console.log('a',a) // Uncaught ReferenceError: a is not defined
let
和 var
都可以用来声明变量,但是在 ES 6 中,有下面一些区别:
-
使用
var
声明的变量没有区块的概念,可以跨块访问。 -
使用
let
声明的变量只能在区块作用域中访问,不能跨块访问。
在相同的作用域下,使用 var
和 let
具有相同的效果,建议在 ES 6 语法中使用 let
来声明变量,这样可以更加明确该变量所处的作用域。
const
表示声明常量,一般用于一旦声明就不再修改的值,并且 const
声明的常量必须经过初始化,代码如下:
const a = 1
a = 2 // Uncaught TypeError: Assignment to constant variable
const b // Uncaught SyntaxError: Missing initializer in const declaration
总结一下,如果在 ES 5 中习惯了使用 var
来声明变量,在切换到 ES 6 时,就需要思考一下变量的用途和类型,选择合适的 let
和 const
来使代码更加规范和语义化。
箭头函数
ES 6 新增了使用“箭头”(=>
)声明函数,代码如下:
let f = v => v
// 等同于
var f = function (v) {
return v
}
如果箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分,当函数的内容只有返回语句时,可以省去大括号和 return
指令,代码如下:
let f = () => 5
// 等同于
var f = function () { return 5 }
let sum = (num1, num2) => num1 + num2;
// 等同于
var sum = function(num1, num2) {
return num1 + num2
}
如果箭头数的内容部分多于一条语句,就要用大括号将它们括起来,并且使用 return
语句返回,代码如下:
let sum = (num1, num2) => {
let num = 0
return num1 + num2 + num;
}
箭头函数会默认绑定外层的上下文对象 this
的值,因此在箭头函数中,this
的值和外层的 this
是一样的,不需要使用 bind
或者 call
的方法来改变函数中的上下文对象,例如下面的代码:
mounted () {
this.foo = 1
setTimeout(function(){
console.log(this.foo) // 打印出1
}.bind(this),200)
}
//相当于
mounted () {
this.foo = 1
setTimeout(() => {
console.log(this.foo) // 同样打印出1
},200)
}
上面的代码中,在 Vue.js
的 mounted
方法中,this
指向当前的 Vue
组件的上下文对象,如果想要在 setTimeout
的方法中使用 this
来获取当前 Vue
组件的上下文对象,那么非箭头函数需要使用 bind
,箭头函数则不需要。
箭头函数是实战项目中使用最多的 ES 6 语法,所以掌握好其规则和用法是非常重要的。
对象属性和方法的简写
ES 6 允许在大括号中直接写入变量和函数,作为对象的属性和方法,这样的书写更加简洁,代码如下:
const foo = 'bar'
const baz = {foo}
// 等同于
const baz = {foo: foo}
console.log(baz) // {foo: "bar"}
对象中如果含有方法,也可以将 function
关键字省去,代码如下:
{
name: 'item',
data () {
return {
name:'bar'
}
}
mounted () {
},
methods: {
clearSearch () {
}
}
}
// 相当于
{
name: 'item',
data :function() {
return {
name:'bar'
}
}
mounted :function() {
},
methods: {
clearSearch :function() {
}
}
}
在上面的代码中,展示了采用 ES 6 语法来创建 Vue
组件所需的方法和属性,包括 name
属性、mounted
方法、data
方法等,是后面实战项目中经常使用的写法。
对象解构
在 ES 6 中,可以使用解构从数组和对象中提取值并赋给独特的变量,代码如下:
// 数组
const input = [1, 2];
const [first, second] = input;
console.log(first,second) // 1 , 2
// 对象
const o = {
a: "foo",
b: 30,
c: "Johnson"
};
const {a, b, c} = o;
console.log(a,b,c) // foo , 30 , Johnson
在上面的代码中,花括号 “{ }
” 表示被解构的对象,a
、b
和 c
表示要将对象中的属性存储到其中的变量中。
模块化
ES 6模块化概述
在 ES 6 版本之前,JavaScript 一直没有模块(Module
)体系,无法将一个大程序拆分成互相依赖的小文件,再用简单的方法拼装起来。其他语言都有这项功能,比如 Ruby
的 require
、Python
的 import
,甚至就连 CSS
都有 @import
,但是 JavaScript
任何这方面的支持都没有,这对开发大型的、复杂的项目形成了巨大障碍。
好在广大的 JavaScript
程序员自己制定了一些模块加载方案,主要有 CommonJS
和 AMD
两种。前者用于 Node.js
服务器,后者用于浏览器。
import和export
随着 ES 6 的到来,终于原生支持了模块化功能,即 import
和 export
,而且实现得相当简单,完全可以取代 CommonJS
和 AMD
规范成为浏览器和服务器通用的模块化解决方案。
在 ES 6 的模块化系统中,一个模块就是一个独立的文件,模块中的对外接口采用 export
关键字导出,可以将 export
放在任何变量、函数或类声明的前面,从而将它们暴露给外部代码使用,代码如下:
要导出数据,在变量前面加上 export
关键字:
export var name = "小明";
export let age = 20;
// 上面的写法等价于下面的写法
var name = "小明";
let age = 20;
export {
name:name,
age:age
}
// export对象简写的方式
export {name,age}
要导出函数,需要在函数前面加上 export
关键字:
export function sum(num1,num2){
return num1 + num2;
}
// 等价于
let sum = function (num1,num2){
return num1 + num2;
}
export sum
所以,如果没有通过 export
关键字导出,在外部就无法访问该模块的变量或者函数。
有时会在代码中看到使用 export default
,它和 export
具有同样的作用,都是用来导出对外提供接口的,但是它们之间还有一些区别:
-
export default
用于规定模块的默认对外接口,并且一个文件只能有一个export default
,而export
可以有多个。 -
通过
export
方式导出,在导入时要加{ }
,export default
则不需要。
在一个模块中可以采用 import
来导入另一个模块 export
的内容。
导入含有多个 export
的内容,可以采用对象简写的方式,也是现在使用比较多的方式,代码如下:
//other.js
var name = "小明"
let age = 20
// export对象简写的方式
export {name,age}
//import.js
import {name,age} from "other.js"
console.log(name) // 小明
console.log(age) // 20
导入只有一个 export default
的内容,代码如下:
//other.js
export default function sum(num1,num2) {
return num1 + num2;
}
//import.js
import sum from "other.js"
console.log(sum(1,1)) // 2
有时也会在代码中看到 module.exports
的用法,这种用法是从 Node.js 的 CommonJS
演化而来的,它其实就相当于:
module.exports = xxx
// 相当于
export xxx
ES 6 的模块化方案使得原生 JavaScript
的 “拆分” 能力提升了一个大的台阶,几乎成为当下最流行的写法,并且应用在大部分的企业项目中。
Promise和async/await
Promise
Promise
是一种适用于异步操作的机制,比传统的回调函数解决方案更合理和更强大。从语法上说,Promise
是一个对象,从它可以获取异步操作的结果:成功或失败。在 Promise
中,有三种状态:pending
(进行中)、resolved
(已成功)和 rejected
(已失败)。只有异步操作的结果可以决定当前是哪一种状态,无法被 Promise
之外的方式改变。这也是 Promise
这个名字的由来,它的英语意思就是 “承诺”,表示其他手段无法改变。创建一个 Promise
对象,代码如下:
var promise = new Promise(function(resolve, reject) {
...
if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
});
在上面的代码中,创建了一个 Promise
对象,Promise
构造函数接受一个函数作为参数,该函数的两个参数分别是 resolve
和 reject
。这是两个内置函数,resolve
函数的作用是将 Promise
对象的状态变为 “成功”,在异步操作成功时调用,并将异步操作的结果作为参数传递出去;reject
函数的作用是将 Promise
对象的状态变为 “失败”,在异步操作失败时调用,并将异步操作报出的错误作为参数传递出去。当代码中出现错误(Error
)时,就会调用 catch
回调方法,并将错误信息作为参数传递出去。
Promise
对象实例生成后,可以用 then
方法分别指定 resolved
(成功)状态和 rejected
(失败)状态的回调函数以及 catch
方法,比如:
promise.then(function(value) {
// success逻辑
}, function(error) {
// failure逻辑
}).catch(function(){
// error逻辑
});
then()
方法返回的是一个新的 Promise
实例(不是原来那个 Promise
实例)。因此,可以采用链式写法,即 then()
方法后面再调用另一个 then()
方法,比如:
getJSON("/1.json").then(function(post) {
return getJSON(post.nextUrl);
}).then(function (data) {
console.log("resolved: ", data);
}, function (err){
console.log("rejected: ", err);
});
下面是一个用 Promise
对象实现的 Ajax
操作 get
方法的例子。
var getJSON = function(url) {
// 返回一个Promise对象
var promise = new Promise(function(resolve, reject){
var client = new XMLHttpRequest(); //创建XMLHttpRequest对象
client.open("GET", url);
client.onreadystatechange = onreadystatechange;
client.responseType = "json"; //设置返回格式为json
client.setRequestHeader("Accept", "application/json");//设置发送格式为json
client.send();//发送
function onreadystatechange() {
if (this.readyState !== 4) {
return;
}
if (this.status === 200) {
resolve(this.response);
} else {
reject(new Error(this.statusText));
}
};
});
return promise;
};
getJSON("/data.json").then(function(data) {
console.log(data);
}, function(error) {
console.error(error);
});
了解 Promise
的基本知识可以便于后续学习使用服务端渲染。当然,Promise
的应用场合还是比较多的,如果想要深入了解,可以访问网址: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise ,进行系统的学习。
async/await
async/await
语法在 2016 年就已经提出来了,属于 ES 7 中的一个测试标准(目前来看是直接跳过 ES 7,列为 ES 8 的标准了),它主要为了解决下面两个问题:
-
过多的嵌套回调问题。
-
以
Promise
为主的链式回调问题。
前面讲解过 Promise
,虽然 Promise
解决了恐怖的嵌套回调问题,但是解决得并不彻底,过多地使用 Promise
会引发以 then
为主的复杂链式调用问题,同样会让代码阅读起来不那么顺畅,而 async/await
就是它们的救星。
async/await
是两个关键字,主要用于解决异步问题,其中 async
关键字代表后面的函数中有异步操作,await
关键字表示等待一个异步方法执行完成。这两个关键字需要结合使用。
当函数中有异步操作时,可以在声明时在其前面加一个关键字 async
,代码如下:
async function myFunc() {
//异步操作
}
使用 async
声明的函数在被调用时会将返回值转换成一个 Promise
对象,因此 async
函数通过 return
返回的值会进入 Promise
的 resolved
状态,成为 then
方法中回调函数的参数,代码如下:
// myFunc()返回一个Promise对象
async function myFunc() {
return 'hello';
}
// 使用then方法就可以接收到返回值
myFunc().then(value => {
console.log(value); // hello
})
如果不想使用 Promise
的方式接收 myFunc()
的返回值,可以使用 await
关键字更加简洁地获取返回值,代码如下:
async function myFunc() {
return 'hello';
}
let foo = await myFunc(); // hello
await
表示等待一个 Promise
返回,但是 await
后面的 Promise
对象不会总是返回 resolved
状态,如果发生异常,则进入 rejected
状态,那么整个 async
异步函数就会中断执行,为了记录错误的位置和编写异常逻辑的代码,需要使用 try/catch
,代码如下:
try {
let foo = await myFunc(); // hello
} catch (e) {
// 错误逻辑
console.log(e)
}
下面举一个例子,在后面的实战项目开发中,经常会用到数据接口请求数据,接口请求一般是异步操作,例如在 Vue
的 mounted
方法中请求数据,代码如下:
async mounted () {
// 代码编写自上而下,一行一行,以便于阅读
let resp = await ajax.get('weibo/list')
let top = resp[0]
console.log(top)
}
在上面的代码中,ajax.get()
方法会返回一个 Promise
,采用 await
进行了接收,并且 await
必须包含在一个用 async
声明的函数中。
可以看出,在使用了 async/await
之后,整个代码的逻辑更加清晰,没有了复杂的回调和烦琐的换行。
至此,对于实战项目中用到的相关 ES 6 语法基本讲解完毕,如果读者想进一步了解 ES 6 的更多语法知识,可以自行在其官网上学习。