简介
JavaScript
它是世界上发展最快的编程语言之一,不仅可以用来编写在浏览器中运行的客户端程序,还可以用来编写在浏览器中运行的客户端程序Node.js
的发展,JavaScript
它也广泛应用于编写服务端程序。JavaScript
这种语言的不断发展和完善正在进行中2015
年正式发布ECMAScript6
已经成为了JavaScript
这门语言的下一代标准使JavaScript
用于编写复杂的大型应用程序更方便。近年来,几乎所有的应用都被使用JavaScript
语言开发项目正在使用中ES
开发新特性。
随着ES2015
标准委员会决定每年发布一份ES
新版本。但是很多开发者并没有真正理解ES2015
每个版本的新特征是什么,这些新特征和ES5
语法差异,更不用说这些新特性在实际项目中的应用场景了。
由于篇幅原因,笔者将ES6~ES12
分成了ES进阶之路一
和ES进阶之路二
如果是的话,两篇文章ES6
很清楚你可以直接看ES7、ES8、ES9、ES10、ES11、ES12(ES高级之路二)
我相信只要你仔细阅读作者,ES
你一定会成为一系列文章ES
大神。
先来看看ES6
吧,篇幅有点长,建议收藏后再看,后期也可以当做字典查阅。
块级作用域
我们知道在js
有全局作用域和函数作用域ES6
又增加了一个块级作用域。块级作用域是什么?
关于什么是块,只要认识 {}
例如if
后面的{}
,for
循环后面的{}
等等。
if (true) {
var a = 5; console.log(a); // 5 } console.log(a); // 5
if (true) {
let a = 5; console.log(a); // 5 } console.log(a);// Uncaught ReferenceError: a is not defined
在上面的例子中,我们可以看到它在使用var
在块{}
中定义的变量是全局变量,里外都能访问。但是在使用let
在块{}
定义的变量是块级作用域变量,只能在块中访问,外部不能访问。
const
定义的变量也是如此,只能在块中访问,外部不能访问。
if (true) {
const a = 5; console.log(a); // 5 } console.log(a);// Uncaught ReferenceError: a is not defined
所以ES6
事实上,块级作用域的概念仍然需要和谐let、const
配合使用。什么是?let、const
各有什么特点?
let
ES6
新增了let
命令,用来声明变量。
1. let 声明的全局变化不是全局对象window的属性
这就意味着,你不可以通过 window.变量名
的方式访问这些变量,而 var
声明的全局变量是 window
的属性,是可以通过 window.变量名
的方式访问的。
var a = 5
console.log(window.a) // 5
let a = 5
console.log(window.a) // undefined
2. 用let定义变量不允许重复声明
这个很容易理解,使用 var
可以重复定义,使用 let
却不可以。
var a = 5
var a = 6
console.log(a) // 6
如果是 let
,则会报错
let a = 5
let a = 6
// Uncaught SyntaxError: Identifier 'a' has already been declared
// at <anonymous>:1:1
3. let声明的变量不存在变量提升
function foo() {
console.log(a)
var a = 5
}
foo() //undefined
上述代码中, a
的调用在声明之前,所以它的值是 undefined
,而不是 Uncaught ReferenceError
。实际上因为 var
会导致变量提升,上述代码和下面的代码等同:
function foo() {
var a
console.log(a)
a = 5
}
foo() //undefined
而对于 let
而言,变量的调用是不能先于声明的,看如下代码:
function foo() {
console.log(a)
let a = 5
}
foo()
// Uncaught ReferenceError: Cannot access 'a' before initialization
在这个代码中, a
的调用是在声明之前,因为 let
没有发生变量提升,所有读取 a
的时候,并没有找到,而在调用之后才找到 let
对 a
的定义,所以会报错。
4. let声明的变量具有暂时性死区
只要块级作用域内存在 let
命令,它所声明的变量就绑定在了这个区域,不再受外部的影响。
var a = 5
if (true) {
a = 6
let a
}
// Uncaught ReferenceError: Cannot access 'a' before initialization
上面代码中,存在全局变量 a
,但是块级作用域内 let
又声明了一个局部变量 a
,导致后者绑定这个块级作用域,所以在let
声明变量前,对 a
赋值会报错。
ES6
明确规定,如果区块中存在 let
和 const
命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。
总之,在代码块内,使用 let
命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”
有时“暂时性死区”比较隐蔽,比如:
function foo(b = a, a = 2) {
console.log(a, b)
}
foo()
// Uncaught ReferenceError: Cannot access 'a' before initialization
5. let 声明的变量拥有块级作用域
let
实际上为 JavaScript
新增了块级作用域
{
let a = 5
}
console.log(a) // undefined
a
变量是在代码块 {}
中使用 let
定义的,它的作用域是这个代码块内部,外部无法访问。
我们再看一个项目中很常见的 for
循环:
for (var i = 0; i < 3; i++) {
console.log('循环内:' + i) // 0、1、2
}
console.log('循环外:' + i) // 3
如果改为 let
会怎么样呢?
for (let i = 0; i < 3; i++) {
console.log('循环内:' + i) // 0、1、2
}
console.log('循环外:' + i) // ReferenceError: i is not defined
继续看下面两个例子的对比,这时 a
的值又是多少呢?
if (true) {
var a = 5
}
console.log(a) // 5
if (true) {
let a = 5
}
console.log(a)// Uncaught ReferenceError: a is not defined
const
const
和let
类似,但是使用const
定义的变量是常量。如果变量是基本类型就不能改变值,如果是引用类型就不能改变引用地址。
1. const定义的变量是常量
基本数据类型
const a = 1
console.log(a) // 1
a = 2
console.log(a)// Uncaught TypeError: Assignment to constant variable.
引用数据类型
const user = {
name: "randy" };
user.name = "demi"; // 不改变引用地址的修改是可以的
console.log(user); //{ name: "demi" }
user = {
name: "jack" };
console.log(user); // Uncaught TypeError: Assignment to constant variable.
2. const 声明的全局变量不是全局对象window的属性
这就意味着,你不可以通过 window.变量名
的方式访问这些变量,而 var
声明的全局变量是 window
的属性,是可以通过 window.变量名
的方式访问的。
var a = 5
console.log(window.a) // 5
const a = 5
console.log(window.a) // undefined
3. 用const定义变量不允许重复声明
这个很容易理解,使用 var
可以重复定义,使用 const
却不可以。
var a = 5
var a = 6
console.log(a) // 6
如果是 const
,则会报错
const a = 5
const a = 6
// Uncaught SyntaxError: Identifier 'a' has already been declared
// at <anonymous>:1:1
4. const声明的变量不存在变量提升
function foo() {
console.log(a)
var a = 5
}
foo() //undefined
上述代码中, a
的调用在声明之前,所以它的值是 undefined
,而不是 Uncaught ReferenceError
。实际上因为 var
会导致变量提升,上述代码和下面的代码等同:
function foo() {
var a
console.log(a)
a = 5
}
foo() //undefined
而对于 const
而言,变量的调用是不能先于声明的,看如下代码:
function foo() {
console.log(a)
const a = 5
}
foo()
// Uncaught ReferenceError: Cannot access 'a' before initialization
在这个代码中, a
的调用是在声明之前,因为 const
没有发生变量提升,所有读取 a
的时候,并没有找到,而在调用之后才找到 const
对 a
的定义,所以会报错。
5. const声明的变量具有暂时性死区
只要块级作用域内存在 const
命令,它所声明的变量就绑定在了这个区域,不再受外部的影响。
var a = 5
if (true) {
a = 6
const a
}
// Uncaught ReferenceError: Cannot access 'a' before initialization
上面代码中,存在全局变量 a
,但是块级作用域内 const
又声明了一个局部变量 a
,导致后者绑定这个块级作用域,所以在const
声明变量前,对 a
赋值会报错。
ES6
明确规定,如果区块中存在 let
和 const
命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。
总之,在代码块内,使用 const
命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”
6. const 声明的变量拥有块级作用域
const
实际上为 JavaScript
新增了块级作用域
{
const a = 5
}
console.log(a) // undefined
a
变量是在代码块 {}
中使用 const
定义的,它的作用域是这个代码块内部,外部无法访问。
解构赋值
在 ES6
中新增了变量赋值的方式:解构赋值。允许按照一定模式,从数组、对象、字符串中提取值,对变量进行赋值。
数组解构赋值
数组的解构是严格按照顺序来的,一一对应。
let [a, b, c] = ["a", "b", "c"]
console.log(a, b, c) // a b c
let [one, two, three] = new Set([1, 2, 3])
console.log(one, two, three) // 1 2 3
// 被赋值的变量还可以是对象的属性,不局限于单纯的变量。
let user = {
}
[user.firstName, user.secondName] = 'Kobe Bryant'.split(' ')
console.log(user.firstName, user.secondName) // Kobe Bryant
// 可以跳过赋值元素
let [name, , title] = ['John', 'Jim', 'Sun', 'Moon']
console.log( title ) // Sun
// rest 参数,相当于把剩下的放在一个数组里
// 我们可以使用 rest 来接受赋值数组的剩余元素,不过要确保这个 rest 参数是放在被赋值变量的最后一个位置上。
let [name1, ...rest] = ["jack", "randy", "demi"];
console.log(name1); // jack
console.log(rest[0]); // randy
console.log(rest[1]); // demi
console.log(rest.length); // 2
// 默认值,取不到值得时候就会使用默认值
let [name = "randy", surname = "demi"] = ["jack"]
console.log(name) // jack
console.log(surname) // demi
对象解构赋值
对象的解构是按照属性名来的,跟顺序无关。
const user = {
name: "randy", age: 24 };
const {
age, name } = user;
console.log(name, age); // randy 24
// 取别名
const {
name: n, age: a } = user;
console.log(n, a); // randy 24
// 默认值
const {
name, age, sex = "male" } = user;
console.log(name, age, sex); // randy 24 male
// rest参数,相当于把剩下的放在一个对象里
const {
name, ...rest } = user;
console.log(name); // randy
console.log(rest); // {age: 24}
// 嵌套对象,对于嵌套我们保证层级一样即可
const user2 = {
name: "demi",
age: 24,
address: {
province: "湖南", city: "岳阳", area: "汨罗" },
likes: ["orange", "apple"],
};
const {
name,
age,
address: {
province, city, area },
likes: [fruits1, fruits2],
} = user2;
console.log(name, age, province, city, area, fruits1, fruits2); // demi 24 湖南 岳阳 汨罗 orange apple
字符串解构赋值
字符串的解构跟数组有点类似,也是可以设置默认值、rest
参数等这里笔者就不再细说了。
let str = 'randy'
let [a, b, c, d, e] = str
console.log(a, b, c, d, e) // r a n d y
String扩展
Unicode表示法
ES6
加强了对 Unicode
的支持,允许采用\uxxxx
形式表示一个字符,其中xxxx
表示字符的 Unicode
码点。
"\u0061"
// "a"
但是,这种表示法只限于码点在\u0000~\uFFFF
之间的字符。超出这个范围的字符,必须用两个双字节的形式表示。
"\uD842\uDFB7"
// "𠮷"
"\u20BB7"
// " 7"
上面代码表示,如果直接在\u
后面跟上超过0xFFFF
的数值(比如\u20BB7
),JavaScript
会理解成\u20BB+7
。由于\u20BB
是一个不可打印字符,所以只会显示一个空格,后面跟着一个7。
ES6
对这一点做出了改进,只要将码点放入大括号,就能正确解读该字符。
"\u{20BB7}"
// "𠮷"
有了这种表示法之后,JavaScript
共有 6 种方法可以表示一个字符。
'\z' === 'z' // true
'\172' === 'z' // true
'\x7A' === 'z' // true
'\u007A' === 'z' // true
'\u{7A}' === 'z' // true
遍历器接口
ES6
为字符串添加了遍历器接口,详见Iterator
一节,使得字符串可以被for...of
循环遍历。
for (let item of 'randy') {
console.log(item) // r a n d y
}
模板字符串
在 ES6
之前对字符串的处理是相当的麻烦,看如下场景:
字符串很长包括几种情形一个是开发时输入的文本内容,一个是接口数据返回的文本内容。如果对换行符处理不当,就会带来异常。
如果字符串不是静态内容,往往是需要加载变量或者表达式,这个也是很常见的需求。之前的做法是字符串拼接:
var a = 20
var b = 10
var c = 'JavaScript'
var str = 'My age is ' + (a + b) + ' and I love ' + c
console.log(str)
如果字符串有大量的变量和表达式,这个拼接简直是噩梦。
我们通常写代码都是有逻辑运算的,对于字符串也是一样,它包含的内容不是静态的,通常是根据一定的规则在动态变化。
var retailPrice = 20
var wholesalePrice = 16
var type = 'retail'
var showTxt = ''
if (type === 'retail') {
showTxt += '您此次的购买单价是:' + retailPrice
} else {
showTxt += '您此次的批发价是:' + wholesalePrice
}
看到这样的代码一定会感到很熟悉,通常大家的做法是使用上述的字符串拼接+逻辑判断,或者采用字符串模板类库来操作。
看了上述的应用场景,就要引入 String Literals
话题,这个是用来解决字符串拼接问题,从 ES6
开始可以这样定义字符串了。
`string text`
`string text line 1 string text line 2`
`string text ${
expression} string text`
在这里你可以任意插入变量或者表达式,只要用 ${}
包起来就好。
注意
这里的符号是反引号,也就是数字键 1 左边的键,不是单引号或者双引号
这样就可以轻松解决字符串包含变量或者表达式的问题了,对于多行的字符串,之前是这样处理
console.log('string text line 1\n' +
'string text line 2')
// "string text line 1
// string text line 2"
现在可以这样做了
console.log(`string text line 1 string text line 2`)
// "string text line 1
// string text line 2"
完全不需要 \n
来参与。
前面的字符串字面量解决了字符串拼接的问题,对于包含复杂逻辑的字符串并不是简单的表达式能搞定的。所以需要另一种解决方案:Tag Literals
,还是看上述那个例子:
var retailPrice = 20
var wholesalePrice = 16
var type = 'retail'
var showTxt = ''
if (type === 'retail') {
showTxt += '您此次的购买单价是:' + retailPrice
} else {
showTxt += '您此次的批发价是:' + wholesalePrice
}
现在可以定义一个 Tag
函数,然后用这个 Tag
函数来充当一个模板引擎:
function Price(strings, type) {
let s1 = strings[0]
const retailPrice = 20
const wholesalePrice = 16
let txt = ''
if (type === 'retail') {
txt = `购买单价是:${
retailPrice}`
} else {
txt = `批发价是:${
wholesalePrice}`
}
return `${
s1}${
txt}`
}
let showTxt = Price `您此次的${
'retail'}`
console.log(showTxt) //您此次的购买单价是:20
strings
参数指的是 Tag
函数后面被变量分割开的字符串集合,type
参数是对应第一个变量,Tag
函数可以有多个 type
类似的参数。
新增方法
String.prototype.fromCodePoint()
用于从 Unicode
码点返回对应字符,并且可以识别大于0xFFFF
的字符。
// ES5
console.log(String.fromCharCode(65)) // A
// ES6
console.log(String.fromCodePoint(65)) // A
String.prototype.includes()
ES5
中可以使用indexOf
方法来判断一个字符串是否包含在另一个字符串中,indexOf
返回出现的下标位置,如果不存在则返回-1。
const str = 'randy'
console.log(str.indexOf('dy'))
ES6
提供了includes
方法来判断一个字符串是否包含在另一个字符串中,返回boolean
类型的值。
const str = 'randy'
console.log(str.includes('dy'))
String.prototype.startsWith()
判断参数字符串是否在原字符串的头部, 返回boolean
类型的值。
const str = 'randy'
console.log(str.startsWith('ra'))
String.prototype.endsWith()
判断参数字符串是否在原字符串的尾部, 返回boolean类型的值。
const str = 'randy' console.log(str.endsWith('dy'