ES6
参考文献:https://www.bookstack.cn/read/es6-3rd/spilt.8.docs-string.md
1 ES6简介
ECMAScript 6.0(以下简称 ES6)是 JavaScript 语言的下一代标准,已经在 2015 年 6 月正式发布了。它的目标,是使得 JavaScript 语言可以用来编写复杂的大型应用程序,成为企业级开发语言。
Babel 是一个广泛使用的 ES6 转码器,可以将 ES6 代码转为 ES5 代码,从而在老版本的浏览器执行。
2 let和const命令
let命令
-
声明的变量仅在块级作用域内有效
-
不存在变量提升,所声明的变量一定要在声明后使用,否则报错
-
暂时性死区:使用let命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称 TDZ)。
在没有
let
之前,typeof
运算符是百分之百安全的,永远不会报错。使用let之后:变量声明之前都是死区,只要使用该变量就会报错。在变量声明之前,使用typeof 运行时会抛出ReferenceError
- let不允许在相同作用域内,重复声明同一个变量
块级作用域
没有块级作用域的不合理场景:
- 内层变量可能会覆盖外层变量
- 用来计数的循环变量泄露为全局变量
块级作用域的出现,实际上使得获得广泛应用的匿名立即执行函数表达式(匿名 IIFE)不再必要了。
ES6 引入了块级作用域,明确允许在块级作用域之中声明函数。
ES6 规定,块级作用域之中,函数声明语句的行为类似于
let
,在块级作用域之外不可引用。ES6浏览器中,行为方式
- 允许在块级作用域内声明函数。
- 函数声明类似于
var
,即会提升到全局作用域或函数作用域的头部。- 同时,函数声明还会提升到所在的块级作用域的头部。 undefined
根据这三条规则,浏览器的 ES6 环境中,块级作用域内声明的函数,行为类似于
var
声明的变量。考虑到环境导致的行为差异太大,应该避免在块级作用域内声明函数。如果确实需要,也应该写成函数表达式,而不是函数声明语句。
const命令
const
声明一个只读的常量。一旦声明,常量的值就不能改变。
const
声明的变量不得改变值,这意味着,const
一旦声明变量,就必须立即初始化,不能留到以后赋值。
const
的作用域与let
命令相同:只在声明所在的块级作用域内有效。
const
命令声明的常量也是不提升,同样存在暂时性死区,只能在声明的位置后面使用。
const
声明的常量,也与let
一样不可重复声明。
const
实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。例子:常量
a
是一个数组,这个数组本身是可写的,但是如果将另一个数组赋值给a
,就会报错。如果真的想将对象冻结,应该使用
Object.freeze
方法。添加新属性不起作用,严格模式时还会报错。
ES5 只有两种声明变量的方法:var
命令和function
命令。ES6 除了添加let
和const
命令,后面章节还会提到,另外两种声明变量的方法:import
命令和class
命令。所以,ES6 一共有 6 种声明变量的方法。
顶层对象属性
顶层对象,在浏览器环境指的是window
对象,在 Node 指的是global
对象。ES5 之中,顶层对象的属性与全局变量是等价的。
顶层对象的属性与全局变量挂钩,败笔:
- 没法在编译时就报出变量未声明的错误,只有运行时才能知道(因为全局变量可能是顶层对象的属性创造的,而属性的创造是动态的)
- 程序员很容易不知不觉地就创建了全局变量(比如打字出错)
- 顶层对象的属性是到处可以读写的,这非常不利于模块化编程。
window
对象有实体含义,指的是浏览器的窗口对象,顶层对象是一个有实体含义的对象,也是不合适的。
ES6改变:为了保持兼容性,var
命令和function
命令声明的全局变量,依旧是顶层对象的属性;另一方面规定,let
命令、const
命令、class
命令声明的全局变量,不属于顶层对象的属性。也就是说,从 ES6 开始,全局变量将逐步与顶层对象的属性脱钩。
globeThis对象
JavaScript 语言存在一个顶层对象,它提供全局环境(即全局作用域),所有代码都是在这个环境中运行。但是,顶层对象在各种实现里面是不统一的。
- 浏览器里面,顶层对象是
window
,但 Node 和 Web Worker 没有window
。 - 浏览器和 Web Worker 里面,
self
也指向顶层对象,但是 Node 没有self
。 - Node 里面,顶层对象是
global
,但其他环境都不支持。
同一段代码为了能够在各种环境,都能取到顶层对象,现在一般是使用this
变量,但是有局限性。
- 全局环境中,
this
会返回顶层对象。但是,Node 模块和 ES6 模块中,this
返回的是当前模块。 - 函数里面的
this
,如果函数不是作为对象的方法运行,而是单纯作为函数运行,this
会指向顶层对象。但是,严格模式下,这时this
会返回undefined
。 - 不管是严格模式,还是普通模式,
new Function('return this')()
,总是会返回全局对象。但是,如果浏览器用了 CSP(Content Security Policy,内容安全策略),那么eval
、new Function
这些方法都可能无法使用。
ES2020 在语言标准的层面,引入globalThis作为顶层对象。也就是说,任何环境下,globalThis都是存在的,都可以从它拿到顶层对象,指向全局环境下的this。
3 变量的解构赋值
数组的解构赋值
ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)。
ES6 允许写成下面这样。
let [a, b, c] = [1, 2, 3];
上面代码表示,可以从数组中提取值,按照对应位置,对变量赋值。
(1)如果解构不成功,变量的值就等于undefined
。
(2)不完全解构,即等号左边的模式,只匹配一部分的等号右边的数组。这种情况下,解构依然可以成功。
(3)如果等号的右边不是数组、Set【具备 Iterator 接口】,那么将会报错。
(4)解构赋值允许指定默认值。
let [x = 1] = [undefined];
x // 1
let [x = 1] = [null];
x // null
当一个数组成员严格等于undefined
,默认值生效。
果一个数组成员是null
,默认值就不会生效。
对象的解构赋值
对象的解构与数组有一个重要的不同。数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。
(1)如果解构失败,变量的值等于undefined
。
(2)对象的解构赋值,可以很方便地将现有对象的方法,赋值到某个变量。
let { log, sin, cos } = Math;
const { log } = console;
log('hello') // hello
上面代码的例一将Math
对象的对数、正弦、余弦三个方法,赋值到对应的变量上,使用起来就会方便很多。例二将console.log
赋值到log
变量。
(3)如果变量名与属性名不一致,必须写成下面这样。
let { foo: baz } = { foo: 'aaa', bar: 'bbb' };
baz // "aaa"
对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。真正被赋值的是后者,而不是前者。foo
是匹配的模式,baz
才是变量。真正被赋值的是变量baz
,而不是模式foo
。
字符串的解构赋值
(1)字符串各个单词解构赋值【类似数组】
(2)类似数组的对象都有一个length
属性,因此还可以对这个属性解构赋值。
数值、布尔值的解构赋值
解构赋值时,如果等号右边是数值和布尔值,则会先转为对象。
函数的解构赋值
函数参数的解构也可以使用默认值。
function move({x = 0, y = 0} = {}) {
return [x, y];
}
move({x: 3, y: 8}); // [3, 8]
move({x: 3}); // [3, 0]
move({}); // [0, 0]
move(); // [0, 0]
上面代码中,函数move
的参数是一个对象,通过对这个对象进行解构,得到变量x
和y
的值。如果解构失败,x
和y
等于默认值。
注意,下面的写法会得到不一样的结果。
function move({x, y} = { x: 0, y: 0 }) {
return [x, y];
}
move({x: 3, y: 8}); // [3, 8]
move({x: 3}); // [3, undefined]
move({}); // [undefined, undefined]
move(); // [0, 0]
上面代码是为函数move
的参数指定默认值,而不是为变量x
和y
指定默认值,所以会得到与前一种写法不同的结果。
undefined
就会触发函数参数的默认值。
圆括号问题
ES6 的规则是,只要有可能导致解构的歧义,就不得使用圆括号。
以下三种解构赋值不得使用圆括号。
(1)变量声明语句(2)函数参数(3)赋值语句的模式
可以使用圆括号的情况
可以使用圆括号的情况只有一种:赋值语句的非模式部分,可以使用圆括号。
[(b)] = [3]; // 正确
({ p: (d) } = {}); // 正确
[(parseInt.prop)] = [3]; // 正确
上面三行语句都可以正确执行,因为首先它们都是赋值语句,而不是声明语句;其次它们的圆括号都不属于模式的一部分。第一行语句中,模式是取数组的第一个成员,跟圆括号无关;第二行语句中,模式是p
,而不是d
;第三行语句与第一行语句的性质一致。
解构赋值用途
(1)交换变量的值
(2)从函数返回多个值
(3)函数参数的定义
解构赋值可以方便地将一组参数与变量名对应起来。
(4)提取 JSON 数据
解构赋值对提取 JSON 对象中的数据,尤其有用。
let jsonData = {
id: 42,
status: "OK",
data: [867, 5309]
};
let { id, status, data: number } = jsonData;
console.log(id, status, number);// 42, "OK", [867, 5309]
上面代码可以快速提取 JSON 数据的值。
(5)遍历 Map 结构
任何部署了 Iterator 接口的对象,都可以用for...of
循环遍历。Map 结构原生支持 Iterator 接口,配合变量的解构赋值,获取键名和键值就非常方便。
const map = new Map();
map.set('first', 'hello');
map.set('second', 'world');
for (let [key, value] of map) {
console.log(key + " is " + value);
}
// first is hello
// second is world
如果只想获取键名,或者只想获取键值,可以写成下面这样。
4 字符串的扩展
字符的Unicode表示法
ES6 加强了对 Unicode 的支持,允许采用\uxxxx
形式表示一个字符,其中xxxx
表示字符的 Unicode 码点。但是,这种表示法只限于码点在\u0000
~\uFFFF
之间的字符。超出这个范围的字符,必须用两个双字节的形式表示。
只要将码点放入大括号,就能正确解读该字符。
"\u{20BB7}"
// "𠮷""
\u{41}\u{42}\u{43}"
// "ABC"
有了这种表示法之后,JavaScript 共有 6 种方法可以表示一个字符。
'\z' === 'z' // true
'\172' === 'z' // true
'\x7A' === 'z' // true
'\u007A' === 'z' // true
'\u{7A}' === 'z' // true
字符串的遍历接口
ES6 为字符串添加了遍历器接口,使得字符串可以被for...of
循环遍历。
这个遍历器最大的优点是可以识别大于0xFFFF
的码点,传统的for
循环无法识别这样的码点。
let text = String.fromCodePoint(0x20BB7);
for (let i = 0; i < text.length; i++) {
console.log(text[i]);
}
// " "
// " "
for (let i of text) {
console.log(i);
}// "𠮷"
上面代码中,字符串text
只有一个字符,但是for
循环会认为它包含两个字符(都不可打印),而for...of
循环会正确识别出这一个字符。
模板字符串
传统的 JavaScript 语言,输出模板通常是这样写的(下面使用了 jQuery 的方法)。
$('#result').append(
'There are <b>' + basket.count + '</b> ' +
'items in your basket, ' +
'<em>' + basket.onSale +
'</em> are on sale!'
);
上面这种写法相当繁琐不方便,ES6 引入了模板字符串解决这个问题。
$('#result').append(`
There are <b>${basket.count}</b> items
in your basket, <em>${basket.onSale}</em>
are on sale!`
);
(1)模板字符串(template string)是增强版的字符串,用反引号(`)标识。它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量。
(2)如果使用模板字符串表示多行字符串,所有的空格和缩进都会被保留在输出之中。
如果你不想要这个换行,可以使用trim
方法消除它。
(3)模板字符串中嵌入变量,需要将变量名写在${}
之中。大括号内部可以放入任意的 JavaScript 表达式,可以进行运算,以及引用对象属性。
(4)模板字符串之中还能调用函数。如果大括号中的值不是字符串,将按照一般的规则转为字符串。比如,大括号中是一个对象,将默认调用对象的toString
方法。
字符串新加方法
传统上,JavaScript 只有indexOf
方法,可以用来确定一个字符串是否包含在另一个字符串中。ES6 又提供了三种新方法。
- includes():返回布尔值,表示是否找到了参数字符串。
- startsWith():返回布尔值,表示参数字符串是否在原字符串的头部。
- endsWith():返回布尔值,表示参数字符串是否在原字符串的尾部。
repeat
方法返回一个新字符串,表示将原字符串重复n
次。
参数如果是小数,会被取整。
如果
repeat
的参数是负数或者Infinity
,会报错。参数
NaN
等同于 0。如果
repeat
的参数是字符串,则会先转换成数字。
ES2017 引入了字符串补全长度的功能。如果某个字符串不够指定长度,会在头部或尾部补全。padStart()
用于头部补全,padEnd()
用于尾部补全。
padStart()
和padEnd()
一共接受两个参数,第一个参数是字符串补全生效的最大长度,第二个参数是用来补全的字符串。如果原字符串的长度,等于或大于最大长度,则字符串补全不生效,返回原字符串。
如果用来补全的字符串与原字符串,两者的长度之和超过了最大长度,则会截去超出位数的补全字符串。
如果省略第二个参数,默认使用空格补全长度。
用途:
padStart()
的常见用途是为数值补全指定位数。 另一个用途是提示字符串格式。
ES2019 对字符串实例新增了trimStart()
和trimEnd()
这两个方法。它们的行为与trim()
一致,trimStart()
消除字符串头部的空格,trimEnd()
消除尾部的空格。它们返回的都是新字符串,不会修改原始字符串。
除了空格键,这两个方法对字符串头部(或尾部)的 tab 键、换行符等不可见的空白符号也有效。