本篇整理了《深入理解ES6》书中的块级作用域和字符串章节中的重要知识点。
块级作用域绑定
var声明及变量提升(Hoisting)机制
通过关键字var声明的变量,无论是在哪声明的,都会被提升到当前作用域的顶部,而初始化操作依旧留在原处执行。
块级声明
块级声明用于声明在指定块的作用域之外无法访问的变量。块级作用域(词法作用域)存在于:
- 函数内部
- 块中({}之间的区域)
let声明
声明变量
变量作用域限定在当前代码块中
声明不会被提升
同一作用域中不能重复声明
const声明
声明常量,一旦设定变不可更改(意味着不能重新赋值,如果声明的是对象,对象属性值是可以修改的)
所声明的常量必须进行初始化
声明不会被提升
同一作用域中不能重复声明
临时死区(Temporal Dead Zone)
Javascript 引擎在扫描代码发现变量声明时,要么将它们提升至作用域顶部(遇到 var 声明),要么将声明放到TDZ中(遇到 let 和 const 声明)。访问TDZ中的变量会触发运行时错误。只有执行过变量声明语句后,变量才会从TDZ中移出,然后方可正常访问。
|
|
是因为value在TDZ中,即使不易出错的typeof也无法阻挡引擎抛出错误。
|
|
是因为typeof value是在TDZ外执行的。
循环中的块作用域绑定
循环中的let声明
每次迭代循环都创建一个新变量,并以之前迭代中同名变量的值将其初始化。而 var 每次迭代同时共享着变量。还是把经典的例子拿出来吧
|
|
上面例子只需把var 修改为 let 就可以像期望的那样运行,输出 0,1,2直到9
循环中const声明
普通for循环中,可以在初始化变量时使用const,但是更改这个变量的值就会抛出错误。
在for-in 或 for-of 中使用const的行为与使用let一致,是因为每次迭代不会修改已有绑定,而是创建一个新绑定。
全局作用域绑定
var被用于全局作用域时,它会创建一个新的全局变量作为全局对象(浏览器环境中的window对象)的属性。因此它会覆盖全局变量。
如果在全局作用域中使用let或者const,会在全局作用域下创建一个新的绑定,但该绑定不会添加为全局对象的属性。因此不会覆盖全局变量。
|
|
|
|
块级绑定最佳实践的进化
默认使用const,只有确实需要改变变量的值时使用let。因为大部分变量的值在初始化不应再改变,而预料外的变量值的改变是很多bug的源头。
字符串和正则表达式
更好的Unicode支持
Javascript字符串一直基于16位字符编码(UTF-16)进行构建。每16位的序列是一个编码单元(code unit),代表一个字符。length、chartAt()等字符串属性和方法都是基于这种编码单元构造的。在过去16位足以包含任何字符,知道Unicode引入扩展字符集,编码规则才不得不进行变更。
UTF-16码位
在UTF-16中,前2^16个码位均以16位的编码单元表示,这个范围被称作基本多文种平面(BMP)。超出这个 范围的码位则要归属于某个辅助平面,其中的码位仅用16位就无法表示了。为此,UTF-16引入了代理对,其规定用两个16位编码单元表示一个码位。这也就是说字符串里的字符有两种,一种是由一个编码单元16位表示的BMP字符,另一种是由两个编码单元32位表示的辅助平面字符。
在ES5中,所有字符串的操作都是机缘16位编码单元。如果采用同样的方式处理包含代理对的UTF-16位编码字符,得到的结果可能与预期不符
|
|
Unicode字符”𠮷”是通过代理对来表示的,因此,这个示例中的JavaScript字符串操作将其视为两个16位字符。这就意味着:
- 变量text的长度事实上是1,但它的length属性值却为2。
- 变量text被判定为两个字符,因此匹配单一字符的正则表达式会失效。
- 前后两个16位的编码单元都不表示任何可打印的字符,因此charAt()方法不会返回合法的字符串。
- chartCodeAt()方法同样不能正确地识别字符。它会返回每个16位编码单元对应的数值,在ES5中,这是你最接近真实值得结果了。
ES6中增加了专门针对代理对的功能。
codePointAt()方法
参数为编码单元位置,而非字符位置作为参数,返回与字符串中给定位置对应的码位,即一个整数值。
|
|
上面代码中,JavaScript将”𠮷a”视为三个字符,codePointAt方法在第一个字符上,正确地识别了“𠮷”,返回了它的十进制码点134071(即十六进制的20BB7)。在第二个字符(即“𠮷”的后两个字节)和第三个字符“a”上,codePointAt方法的结果与charCodeAt方法相同。
检测一个字符占用的编码单元数量,最简单的方法是调用codePointAt方法
|
|
String.fromCodePoint()方法
ES5提供String.fromCharCode方法,用于从码点返回对应字符,但是这个方法不能识别32位的UTF-16字符(Unicode编号大于0xFFFF)。
ES6提供了String.fromCodePoint方法,可以识别0xFFFF的字符,弥补了String.fromCharCode方法的不足。在作用上,正好与codePointAt方法相反。
normalize()方法
许多欧洲语言有语调符号和重音符号。为了表示它们,Unicode提供了两种方法。一种是直接提供带重音符号的字符,比如Ǒ(\u01D1)。另一种是提供合成符号(combining character),即原字符与重音符号的合成,两个字符合成一个字符,比如O(\u004F)和ˇ(\u030C)合成Ǒ(\u004F\u030C)。
这两种表示方法,在视觉和语义上都等价,但是JavaScript不能识别。
|
|
上面代码表示,JavaScript将合成字符视为两个字符,导致两种表示方法不相等。
ES6提供字符串实例的normalize()方法,用来将字符的不同表示方法统一为同样的形式,这称为Unicode正规化。
|
|
normalize方法可以接受一个参数来指定normalize的方式,参数的四个可选值如下。
- NFC,默认参数,表示“标准等价合成”(Normalization Form Canonical Composition),返回多个简单字符的合成字符。所谓“标准等价”指的是视觉和语义上的等价。
- NFD,表示“标准等价分解”(Normalization Form Canonical Decomposition),即在标准等价的前提下,返回合成字符分解的多个简单字符。
- NFKC,表示“兼容等价合成”(Normalization Form Compatibility Composition),返回合成字符。所谓“兼容等价”指的是语义上存在等价,但视觉上不等价,比如“囍”和“喜喜”。(这只是用来举例,normalize方法不能识别中文。)
- NFKD,表示“兼容等价分解”(Normalization Form Compatibility Decomposition),即在兼容等价的前提下,返回合成字符分解的多个简单字符。
不过,normalize方法目前不能识别三个或三个以上字符的合成。这种情况下,还是只能使用正则表达式,通过Unicode编号区间判断。
正则表达式 u 修饰符
当一个正则表达式添加了u修饰符,它就从编码单元操作模式切换为字符模式,如此一来正则表达式就不会视代理对为两个字符,从而完全按照预期正常运行。
|
|
其它字符串变更
字符串中的子串识别
- includes()方法
- startsWith()方法
- endsWith()方法
第一个参数指定要搜索的文本;第二个参数是可选的,指定一个开始搜索的位置的索引值。
repeat()方法
其它正则表达式语法变更
y 修饰符
它会影响正则表达式搜索过程中的sticky属性,当在字符串中开始字符串匹配时,它会通知搜索从正则表达式的lastIndex属性开始进行,如果在指定位置没能成功匹配,则停止继续匹配。
正则表达式的复制
ES5中,可以通过给RegExp构造函数传递正则表达式作为参数来复制这个正则表达式,就像这样:
|
|
此处的变量re2只是re1的一份拷贝,如果给RegExp构造函数提供第二个参数,为正则表达式指定一个修饰符,则代码无法运行。
ES6中修改了这个行为,可以通过第二个参数来修改其修饰符了。
flags属性
ES5获取flag的方式
|
|
为了使获取修饰符更加简单,ES6新增了一个flags属性;
模板字面量
基础语法
使用反撇号(`)来定义字符串
多行字符串
|
|
字符串占位符
占位符由一个左侧的${和右侧的}符号组成,中间可以包含任意的Javascript表达式。
|
|
标签模板
标签指的是在模板字面量第一个反撇号前方标注的字符串,就像这样:
|
|
定义标签
标签可以是一个函数,调用时传入加工过的模板字面量各部分数据,但必须结合每个部分来创建结果。第一个参数是一个数组,包含JavaScript解释过后的字面量字符串,它之后的所有参数都是每一个占位符的解释值。
|
|
可以通过arr和values来重新组合回结果。