ECMAScript 6,即所谓的现代JavaScript,具有强大的功能,例如块作用域,类,箭头功能,生成器以及许多其他有用的功能。
您可能会认为您不能使用它们,因为缺少对IE 11的支持。一个好消息是:您实际上可以在Babel和core-js的帮助下使用大多数这些功能, 您在Vue CLI生成的应用中。
以下是这些ES6功能的三类(按浏览器兼容性分类):
-
您可以使用的功能而不必担心兼容性(使用Babel和core-js时)
-
可以使用的功能,但您需要放弃对IE 11(及更低版本)的支持,因为Babel和core-js不会为您转换/填充它们,主要是本机类型的代理和子类化。
-
甚至Chrome和Firefox当前都不支持的功能。
在本文中,我们将专注于第一类:您可以在Vue应用程序中使用的所有必要功能来改善编程体验。
这是我们将要经历的ES6功能列表:
IE 11
在开始之前,让我们先了解一下Vue如何支持Internet Explorer 11。
让我们创建一个新的Vue应用进行演示:
vue create es6-app
从提示中选择Default Vue 2选项。(由于Vue 3不支持IE 11,因为Composition API使用的是Proxy,babel / core-js不支持。)
如果您在package.json文件中进行搜索,则会发现一个名为的字段browserslist
:
"browserslist": [ "> 1%", "last 2 versions", "not dead"]
引擎盖下发生了很多魔术,我们将在另一篇文章中详细介绍转码和polyfill。现在,请注意,这是告诉babel支持哪些浏览器的配置。此默认配置涵盖了许多过时的浏览器,包括IE 11。
如果我们清空"browserslist"
数组,那么如果我们打算使用本文将介绍的任何ES6功能,则将不支持IE 11。实际上,这并非完全正确,IE 11确实支持一些ES6功能,例如let
和const
关键字。
let / const {#let-const}
让我们从ES6最普遍的功能开始:let
和const
。它们无处不在,甚至IE 11也支持它们。
let
就像,var
但是用声明的变量let
在声明它们的块内作用域。("块"是指条件块,for
循环块等。)
例如,let
在条件块中使用将在该块内限定变量的范围,而在其外部将不可用。
if(true){ let foo = 'word'}console.log(foo) // error
错误在这里是一件好事,因为它可以防止生产期间发生潜在的错误。
如果您使用var
(例如在传统的JavaScript代码中)而不是let
上面的示例,则不会出现错误。
const
是另一个ES6关键字,用于声明变量。区别在于const
声明后不能更改由创建的变量。
例如:
const a = 1a = 2 // error
通过几种创建变量的方法,我们应该使用哪种方法?
最佳实践是尽可能使用const
。使用let
只有当你需要有一个变量,要在以后设置的需求,如在一个for
循环。
并避免var
完全使用。
for...of {#for-of}
说到循环,有一种更简单的方法可以for
甚至不用使用ES6语法编写循环let
。
例如,for
像这样的传统循环:
const arr = [1, 2, 3]for(let i=0; i < arr.length; i++){ const item = arr[i] console.log(item)
}
在ES6中,我们可以简单地执行以下操作:
const arr = [1, 2, 3]for(const item of arr){ console.log(item)
}
我们在for..of
这里使用语法。
不要与for..in
语法混淆;他们是完全不同的东西。for..in
将获得数组/对象中的属性,但for..of
将获得您实际打算迭代的数据。
我们可以for..of
在许多不同种类的对象中使用。他们只需要是可迭代的。
Iterable {#iterable}
可迭代对象是实现可迭代协议的任何对象。(协议只是意味着您需要通过在对象内部使用某种具有特定名称的方法来满足要求。)
例如,这是一个实现可迭代协议的对象:
const twice = {
[Symbol.iterator]() { let i = 0; const iterator = { next() { if(i < 2){ return { value: i++, done: false }
} else{ return { value: undefined, done: true };
}
}
} return iterator
}
}
我将稍作解释,但现在我们可以循环使用该twice
对象for..of
:
for(const x of twice){ console.log(x)
}
这将遍历twice
对象两次,分别为0和1。
现在让我们分解代码...为了创建一个可迭代的对象,我们实际上实现了两个协议,可迭代协议和迭代器协议。
为了满足成为可迭代对象的要求,我们需要一个名称为的方法[Symbol.iterator]
。
const twice = {
[Symbol.iterator]() {
...
}
}
方法名称中应用了两个新的ES6技巧。
首先,Symbol.iterator
是内置的Symbol值,而Symbol是ES6中用于创建唯一标签/标识符的原始类型。(我将在下面的专用部分中详细讨论符号类型。)
其次,方括号括起了属性键,使其成为动态计算的键。这里的关键是表达式Symbol.iterator
将要求值的内容,我们通常不在乎实际的求值是什么。这个不重要的细节被抽象掉了。
这就是可迭代的协议。现在我们仍然需要处理迭代器协议以创建可迭代的对象,因为我们必须从[Symbol.iterator]
函数中返回一个迭代器。
迭代器协议更简单。我们只需要一个对象具有一个next
返回带有两个键的对象的方法:value
和done
。当您想停止迭代时,只需返回object即可{ value: undefined, done: true }
。
这是我们示例中的迭代器:
const iterator = { next() { if(i < 2){ return { value: i++, done: false }
} else{ return { value: undefined, done: true };
}
}
}
总之,我们有一个同时满足可迭代协议和迭代器协议的对象。
再次是下面的代码:
const twice = {
[Symbol.iterator]() { let i = 0; const iterator = { next() { if(i < 2){ return { value: i++, done: false }
} else{ return { value: undefined, done: true };
}
}
} return iterator
}
}
另外,可以使用迭代数组和字符串for..of
。这意味着这些内置类型包含[Symbol.iterator]
与上面类似的方法。
Generator {#generator}
与迭代相关的另一个功能是生成器。
上面的可迭代代码依赖于闭包来存储i
变量。使用generator时,我们不必担心自己构造闭包:
function* twiceGen(){ let i = 0
while(i < 2){ yield i
i++
}
}const twice = twiceGen()
该代码实现了与可迭代示例相同的行为,但更为简单。
我们可以通过以下方式完全相同地使用它for..of
:
for(const item of twice){ console.log(item)
}
让我们倒退一点,谈论什么是发电机。
如您所见,它是一个带有星号(*
)声明的函数。它使用yield
关键字像迭代器的next
方法一样逐个抽取值。
Generator是一种多功能工具,基本上,它是一种允许您暂停/恢复功能的机制。我们不必将twice
上面的对象与一起使用for..of
。我们可以调用它的next
方法。
function* twiceGen(){ let i = 0
while(i < 2){ yield i
}
}const twice = twiceGen()
twice.next().value // 0
此时,该twiceGen
功能在while
循环的第一次运行后暂停。如果再次运行相同的操作,它将恢复并播放循环的第二次运行。
twice.next().value // 1
关于generator的很酷的事情是它还创建了一个可迭代的iterator对象。这就是为什么我们能够迭代twice
用for..of
(可迭代振作),并调用它的next
直接方法(迭代器振作)。而且我们免费获得了可迭代的迭代器协议,而又不会弄乱[Symbol.iterator]
。
如我所说,生成器是一种多功能工具。您可以将其用作暂停/恢复机制,可以将其用作关闭的替代方法,还可以将其用作创建可迭代对象的快捷方式。
Symbol {#symbol}
现在让我们回头讨论一下Symbol类型。
要创建一个符号类型的值,我们只需要调用Symbol()
:
const name = Symbol()const version = Symbol()
Symbol值的主要用例是用于对象属性键:
const language = {
[name]: 'ES',
[version]: '6',
}
现在,要检索属性,我们只需要使用正确的Symbol值来访问它:
language[name]
那么,使用Symbol值作为键而不是使用普通字符串有什么好处?
如果我们的描述性键名很长,例如theMostPopularImplementationOfThisLanguage
,我们可以使用它一次来创建属性:
const theMostPopularImplementationOfThisLanguage = Symbol()const language = {
...
[theMostPopularImplementationOfThisLanguage]: 'JavaScript'}
从这一点开始,我们可以将其分配给较短的变量名称:
const impl = theMostPopularImplementationOfThisLanguage
并使用较短的名称代替该属性:
language[impl]
另外,我们也可以在创建Symbol值时将长名称作为参数:
const impl = Symbol('theMostPopularImplementationOfThisLanguage')const language = {
...
[impl]: 'JavaScript'}
(请注意,您可以完全放弃长名称,但是代码不那么可读。其他人在阅读代码时不知道impl
应该是什么。)
默认参数 {#default-parameter}
您可能不会立即创建自己的迭代器,生成器或符号,所以让我们检查一下其他一些ES6技巧,它们可以立即使您的生活更轻松。
就像许多其他编程语言一样,我们现在可以为函数参数分配默认值。
而不是这样做:
function addOne(num){ if(num === undefined){
num = 0
} return num + 1}
addOne()
现在我们可以这样做:
function addOne(num = 0){ return num + 1}
addOne()
解构语法 {#destructuring-syntax}
如果要将对象传递给函数,则可以使用ES6解构语法轻松地选择对象的属性并将它们放在单独的变量中:
function foo({ a, b }){ console.log(a, b) // 1, 2}
foo({ a: 1, b: 2 })
这种解构语法的好处是可以避免创建带有附加代码行的变量。
因此,不再需要这样做:
function foo(obj){ const a = obj.a const b = obj.b console.log(a, b) // 1, 2}
您还可以在解构语法中设置默认值:
function foo({ a = 0, b }){ console.log(a, b) // 0, 2}
foo({ b: 2 })
解构语法也适用于分配:
function foo(obj){ const { a, b } = obj console.log(a, b) // 1, 2}
从参数以外的地方获取对象时,这也很有用。
function getObj(){ return { a: 1, b: 2 }
}function foo(){ const { a, b } = getObj() console.log(a, b) // 1, 2}
这些解构技巧也适用于数组,而不仅仅是对象。
销毁参数:
function foo([ a, b ]){ console.log(a, b) // 1, 2}
foo([1, 2, 3])
销毁工作:
function foo(arr){ const [ a, b ] = arr console.log(a, b) // 1, 2}
Rest / Spread {#rest-spread}
销毁数组时,我们可以使用三点语法来获取数组中的所有其余项。
function foo([ a, b, ...c ]){ console.log(c) // [3, 4, 5]}
foo([1, 2, 3, 4, 5])
c
现在是一个自己的数组,其中包含其余项目:3, 4, 5
。
这种三点语法称为rest运算符。
这也适用于分配:
function foo(arr){ const [ a, b, ...c ] = arr console.log(c) // [3, 4, 5]}
foo([1, 2, 3, 4, 5])
rest运算符也可以在不破坏结构的情况下单独使用:
function foo(...nums){ console.log(nums) // [1, 2, 3, 4, 5]}
foo(1, 2, 3, 4, 5)
在这里,我们将数字作为独立参数传递,而不是作为单个数组传递。但是在函数内部,我们使用rest运算符将数字作为单个数组收集。当我们要遍历这些参数时,这很有用。
其余语法(由三点组成)看起来与另一个ES6功能运算符spread完全相同。
例如,如果我们要将两个数组合并为一个:
const a = [ 1, 2 ]const b = [ 3, 4 ]const c = [ ...a, ...b ]console.log(c) // [1, 2, 3, 4]
散布运算符用于散布所有项目并将它们放入另一个数组中。
传播也适用于对象:
const obj = { a: 1, b: 2 }const obj2 = { ...obj, c: 3 }console.log(obj2) // { a: 1, b: 2, c: 3 }
现在,第二个对象除了它自己的属性外,还应包含第一个对象的所有内容。
(从技术上讲,所有这些对象的传播和休息技巧都是ES9功能。ES6仅允许将传播和休息与数组一起使用。)
目前有的
我们讨论过:
-
const
尽可能使用 -
使用
for..of
具有迭代对象 -
使用生成器创建iterable和iterator
-
在参数上使用默认值
-
以各种方式使用解构语法。
-
并利用休息和传播
作为两个看似无关的概念,可迭代对象和解构语法实际上是相互兼容的:
function* twiceGen(){ let i = 0
while(i < 2){ yield i
i++
}
}const twice = twiceGen() // an iterableconst [ a, b ] = twice // destructuring
现在a
将会是0
,b
将来将会1
。
箭头功能 {#arrow-function}
ES6提供了创建函数,对象和类的更简单方法。
我们可以使用箭头语法来创建更简洁的函数:
const addOne = (num) => { return num + 1}
此箭头语法对于创建单行函数最有用:
const addOne = (num) => num + 1
此函数将自动返回表达式的求值num + 1
作为返回值。无需显式return
关键字。
如果函数仅接受一个参数,我们甚至可以省略括号:
const addOne = num => num + 1
如果没有任何参数,我们仍然需要一对空括号:
const getNum = () => 1
但是,此语法有一个警告。如果我们返回一个对象文字,这将不起作用:
const getObj = () => { a: 1, b: 2 } // error
这将产生语法错误,因为解析器将假定花括号用于功能块,而不是对象文字。
为了解决这个问题,我们必须将对象文字包装在一对括号中:
const getObj = () => ({ a: 1, b: 2 })
添加的括号基本上是解析器的显式符号,表示我们打算使用单行函数语法。
要记住的另一件事是,该this
关键字在箭头函数中不起作用。它不会给你一个错误。相反,它只会为您提供this
来自周围范围的相同参考。
function x() { const that = this
const y = () => { console.log(that === this) // true
}
y()
}
x()
所以this
这里每个都是相同的参考。
扩展 {#object-literal-extensions}
ES6还提供了一种更简单的方法来创建对象文字。
如果要将两个项目放入一个具有与变量相同的属性键的对象中,则可以使用传统的JavaScript执行以下操作:
const a = 1const b = 2const obj = { a: a, b: b,
}
但是在ES6中,语法可以更简单:
const a = 1const b = 2const obj = { a, b }
如果要将方法放在对象文字中,则可以执行以下操作:
const a = 1const b = 2const obj = { a, b,
getA() { return this.a
}, getB() { return this.b
}
}
(基本上,没有function
关键字和冒号。)
类-class {#class}
ES6提供了类似于其他面向对象语言的类构造。现在,我们不必依赖于构造函数和原型的混乱。
class Person { constructor(name, hobby){ this.name = name this.hobby = hobby
} introduce(){ console.log(`Hi, my name is ${this.name}, and I like ${this.hobby}.`)
}
}const andy = new Person('Andy', 'coding')
andy.introduce()
附带说明一下,该introduce
方法中的字符串称为模板字符串,它是使用反引号而不是引号创建的。如您所见,我们可以使用美元符号和大括号将表达式插入字符串。
与常规字符串相比,模板字符串的另一个好处是它可以跨越多行:
const str = `line 1
line 2
line 3
`
之所以称为模板字符串,是因为它对于实现模板很有用。
function p(text){ return `<p>${text}</p>`}
p("Hello world")
让我们回到讨论课堂上来。
一个类可以从另一个类继承(重用现有类的代码):
class Person {
...
}class ProfessionalPerson extends Person { constructor(name, hobby, profession){ super(name, hobby) // class parent's constructor()
this.profession = profession
} introduce(){ super.introduce() // call parent's introduce()
console.log(`And my profession is ${this.profession}.`)
}
}const andy = new ProfessionalPerson('Andy', 'coding', 'coding')
我们使用extends
关键字在两个类之间创建一个继承关系,并将其Person
作为父类。
我们在super
这里两次使用了关键字。第一次在constructor
中进行调用是为了调用父类的constructor
。第二次,我们像使用对象一样使用它来调用父类的introduce
方法。这是一个关键字,其行为根据您使用的位置而有所不同。
Map / Set / WeakMap / WeakSet {#map-set}
ES6带有两个新颖的数据结构:Map和Set。
Map是键-值对的集合:
const m = new Map()
m.set('first', 1)
m.set('second', 2)
m.get('first') // 1
地图对象可以使用任何对象类型作为键。
Set对象就像一个数组,但是仅包含唯一项:
const s = new Set()
s.add(1)
s.add(1)
尽管我们插入了两次,但该集合仍然只包含一项,因为我们两次插入了相同的东西。
让我们来谈谈更复杂的东西,WeakMap
和WeakSet
。他们是弱引用的版本Map
和Set
。我们只能将对象用作的键WeakMap
,并且只能将对象添加到Set
。
WeakMap
一旦不再引用A的项的键,就会对其进行垃圾回收(由JavaScript运行时从内存中删除)。
例如:
let key1 = {}let key2 = {}const m = new WeakMap()
m.set(key1, 1)
m.set(key2, 2)
key1 = null // de-referenced
之后key1
的解除引用,其对应的价值将定于垃圾收集,这意味着它将在未来的某个时候消失。
同样,如果我们将对象添加到中WeakSet
,然后取消引用,它也将被垃圾回收。
let item1 = {}let item2 = {}const s = new WeakSet()
s.add(item1)
s.add(item2)
item1 = null // de-referenced
尽管我们添加了两项,但该集合在垃圾回收后仅应包含一项,因为原始item1
对象不再被变量引用。
Promise {#promise}
最后但并非最不重要的一点是,Promise是ES6的另一个常用功能。它是对传统函数回调模式的改进。
例如,这是使用回调的传统方式:
setTimeout(function(){ const currentTime = new Date() console.log(currentTime)
}, 1000)
这是一个计时器,显示一秒钟后的时间。
这是一个使用相同setTimeout
逻辑的promise对象:
const afterOneSecond = new Promise(function(resolve, reject) { setTimeout(function(){ const currentTime = new Date()
resolve(currentTime)
}, 1000)
})
它接受带有两个参数的函数:resolve
和reject
。这两个都是当我们有返回值时可以调用的函数。我们调用该resolve
函数以返回值,并且可以调用该reject
函数以返回错误。
然后,我们可以afterOneSecond
使用以下then
语法将回调附加到此Promise对象:
afterOneSecond.then(t => console.log(t))
(单行箭头函数语法只是常规语法,不是必需的。)
与传统回调相比,promise的好处是可以传递promise对象。因此,在设置了承诺之后,我们可以自由地将其发送到其他地方,以处理计时器解决后的操作。
const afterOneSecond = new Promise(function(resolve, reject) { setTimeout(function(){ const currentTime = new Date()
resolve(currentTime)
}, 1000)
})
doSomethingAfterTheTimerResolved(afterOneSecond)
另一个很酷的事情是,promise可以与多个then
子句链接在一起:
afterOneSecond
.then(t => t.getTime())
.then(time => console.log(time))
每个then
子句将其值then
作为参数返回到下一个子句。
有用的方法 {#useful-methods}
这是添加到现有类型中的有用ES6方法的选定列表。
Object.assign(静态方法)
此方法提供了一种简单的方法来浅克隆现有对象:
const obj1 = { a: 1 }const obj2 = Object.assign({}, obj1)
String.prototype.repeat(实例方法)
返回一个重复的字符串:
'Hello'.repeat(3) // "HelloHelloHello"
String.prototype.startsWith(实例方法)
'Hello'.startsWith('H') // true
String.prototype.endsWith(实例方法)
'Hello'.endsWith('o') // true
String.prototype.includes(实例方法)
'Hello'.includes('e') // true
Array.prototype.find(实例方法)
返回回调函数返回的第一项 true
[1, 2, 3].find(x => { return x > 1})// 2
函数名称(属性)
这不是一种方法,而是一种属性。现在,每个函数都有一个name
属性,该属性为您提供字符串形式的函数名称。
setTimeout.name // "setTimeout"
包括您自己创建的功能:
function foo(){}
foo.name // "foo"
更多知识
我漏掉了一些ES6功能,或者是因为它们对于日常Vue开发不是必不可少的,或者是因为它们不能被Babel / core-js进行转译/填充。
-
Reflect and Proxy (Proxy can't be transpiled/polyfilled)
-
Subclassing of native types (can't be transpiled/polyfilled)
-
Tail call optimization (can't be transpiled/polyfilled)
-
The
y
andu
flags for RegExp -
Octal/binary literals
-
Typed array
-
Block-level function declarations
结论
ES6很酷并且已经准备就绪,因此您现在应该在代码中使用它。通过Vue CLI的Babel / core-js集成,即使您的应用程序必须支持IE 11,您也可以使用此处介绍的所有这些功能。