目录2:let 和 const 命令
- var 存在变量提升。为了纠正这个现象,let,const 声明的变量会产生“暂时性死区”
暂时性死区的本质:只要进入当前作用域,所使用的变量就已经存在,但是不可获取。只有等到声明变量的那一行代码出现,才可以获取和使用该变量。 - let const 不允许在相同作用域内重复声明一个变量。var 可以!
- ES5规定,函数只能在顶层作用域和函数作用域内声明,不能在块级作用域声明。 但为了兼容以前的旧代码,浏览器并没有遵循这个规定,还是支持在块级作用域内声明函数,函数会被提升到顶部;
- ES6引入了块级作用域,明确可以在块级作用域中声明函数。ES6规定,块级作用域之中,函数声明语句的行为类似于let,在块级作用域之外不可引用。但是,实际上,为了减轻因此产生的不兼容问题,浏览器可以不遵守上面的规定,有自己的行为方式(只对ES6浏览器有效):
- 允许在块级作用域内声明函数。
- 函数声明类似于var,即会提升到全局作用域或函数作用域的头部。
- 同时,函数声明还会提升到所在的块级作用域的头部。
实际运行如下:// 浏览器的 ES6 环境 function f() { console.log('I am outside!'); } (function () { if (false) { // 重复声明一次函数f function f() { console.log('I am inside!'); } } f(); }()); // Uncaught TypeError: f is not a function
注意:// 浏览器的 ES6 环境 function f() { console.log('I am outside!'); } (function () { var f = undefined; if (false) { function f() { console.log('I am inside!'); } } f(); }()); // Uncaught TypeError: f is not a function
-
考虑到环境导致的行为差异太大,应该避免在块级作用域内声明函数。如果确实需要,应写成函数表达式。
-
ES6的块级作用域必须有大括号
// 第一种写法,报错 没有大括号,所以不存在块级作用域,而let只能出现在当前作用域的顶层 if (true) let x = 1; // 第二种写法,不报错 if (true) { let x = 1; }
-
函数声明也是如此,严格模式下,函数只能声明在当前作用域的顶层。
- const 实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。如果想将对象冻结,应该使用
Object.freeze()
方法。
除了将对象本身冻结,对象的属性也应该冻结:const foo = Object.freeze({}); // 常规模式时,下面一行不起作用; // 严格模式时,该行会报错 foo.prop = 123;
var constantize = (obj) => { Object.freeze(obj); Object.keys(obj).forEach( (key, i) => { if ( typeof obj[key] === 'object' ) { constantize( obj[key] ); } }); };
- ES5有两种声明变量的方法:
var
和function
。ES6除了let
和const
,还有import
和class
。 - 顶层对象:在浏览器中指
window
对象,在Node中指global
对象。ES5中,顶层对象的属性和全局变量是等价的。但是在ES6中,var
function
声明的全局变量依旧是顶层对象的属性,而let
const
class
声明的全局变量不属于顶层对象的属性。
var a = 1;
// 如果在 Node 的 REPL 环境,可以写成 global.a
// 或者采用通用方法,写成 this.a
window.a // 1
let b = 1;
window.b // undefined
- 顶层对象在各种实现里面是不统一的:
- 浏览器,顶层对象获取:
window
,self
,frames
, 全局环境中的this
, 函数不作为对象的方法运行,而是单纯作为函数运行,this
会指向顶层对象,但是严格模式下,返回undefined
- Web Worker,顶层对象获取:
self
- Node,顶层对象获取:
global
- ES2020引入了
globalThis
作为顶层对象,任何环境下都可以通过它拿到顶层对象;
- 浏览器,顶层对象获取:
目录3:变量的解构赋值
- ES6内部使用严格相等运算符
===
,来判断一个位置是否有值。只有严格等于undefined
,默认值才会生效。 - 对象的解构与数组的解构有一个重要的不同。数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性名同名,才能取到正确的值;
- 解构失败等于
undefined
- 对象解构,如果变量名与属性名不一致,必须写成下面这样:
let { foo: baz } = { foo: 'aaa', bar: 'bbb' };
baz // "aaa"
let obj = { first: 'hello', last: 'world' };
let { first: f, last: l } = obj;
f // 'hello'
l // 'world'
- 对象的解构赋值可以取到继承的属性
- 注意点:
- 将一个已经声明的变量用于解构赋值,需要非常小心:
// 错误的写法 let x; {x} = {x: 1}; // SyntaxError: syntax error // 正确的写法 let x; ({x} = {x: 1});
- 解构赋值允许等号左边的模式之中,不放置任何变量名:
({} = [true, false]); ({} = 'abc'); ({} = []);
- 数组本质是特殊的对象,因此可以对数组进行对象属性的解构:
let arr = [1, 2, 3]; let {0 : first, [arr.length - 1] : last} = arr; first // 1 last // 3
- 字符串也可以解构赋值,如下:字符串会被转换为一个类似数组的对象:
类似数组的对象都有一个length
属性,因此还可以对这个属性解构赋值const [a, b, c, d, e] = 'hello'; a // "h" b // "e" c // "l" d // "l" e // "o" let {length : len} = 'hello'; len // 5
- 解构赋值的规则是:只要等号右边的值不是对象或数组,就先将其转为对象。由于,
undefined
和null
无法转为对象,所以对它们进行解构赋值,都会报错。 - 解构赋值中,不能使用圆括号:变量声明语句,函数参数,赋值语句的模式; 可以使用圆括号:赋值语句的非模式部分;
10.变量解构赋值的用途:- 交换变量的值
- 从函数返回多个值
- 函数参数的定义
- 提取JSON数据
- 函数参数的默认值
- 遍历Map结构
- 输入模块的指定方法
目录四:字符串的扩展
- 字符的Unicode表示法;ES6加强了对
Unicode
的支持,允许采用\uxxxx
形式表示一个字符,其中xxxx
表示字符的Unicode码点;'\z' === 'z' // true '\172' === 'z' // true '\x7A' === 'z' // true '\u007A' === 'z' // true '\u{7A}' === 'z' // true
- ES6为字符串添加了遍历器接口
iterator
,使得字符串可以被for ... of
循环遍历
传统的for
循环无法识别这样的码点let text = String.fromCodePoint(0x20BB7); //for循环会认为它包含两个字符(都不可打印) for (let i = 0; i < text.length; i++) { console.log(text[i]); } // " " // " " for (let i of text) { console.log(i); } // "𠮷"
- 根据标准,JSON数据必须是UTF-8编码。但是
JSON.stringify()
方法有可能返回不符合UTF-8标准的字符串。UTF-8标准规定,0xD800
到0xDFFF
之间的码点,不能单独使用,必须配对使用;比如,\uD834\uDF06
是两个码点,但是必须放在一起配对使用,代表字符𝌆
。这是为了表示码点大于0xFFFF
的字符的一种变通方法。
JSON.stringify()
的问题在于,它可能返回0xD800
到0xDFFF
之间的单个码点。为了确保返回的是合法的UTF-8字符,ES2019改变了JSON.stringify()
的行为。如果遇到0xD800
到0xDFFF
之间的单个码点,或者不存在的配对形式,它会返回转义字符串,留给应用自己决定下一步的处理。·
JSON.stringify('\u{D834}') // ""\\uD834""
JSON.stringify('\uDF06\uD834') // ""\\udf06\\ud834""
目录5:字符串的新增方法
- ES5提供
String.fromCharCode()
方法,用于从Unicode
码点返回对应字符,但是这个方法不能识别码点大于0xFFFF
的字符; - ES6提供了
String.fromCodePoint()
方法,可识别大于0xFFFF
的字符; - 传统上,JS只有
indexOf
方法,用来确定一个字符串是否包含在另一个字符串中。ES6又提供了三种新的方法:- includes():返回布尔值,表示是否找到了参数字符串。
- startsWith():返回布尔值,表示参数字符串是否在原字符串的头部。
- endsWith():返回布尔值,表示参数字符串是否在原字符串的尾部。
- 实例方法:
normalize()
includes()
startsWith()
endsWith()
repeat()
:返回一个新的字符串,将原字符串重复n次- ES2017引入了字符串补全长度的功能;如果某个字符串不够指定长度,会在头部或尾部补全;
padStart()
用于头部补全,padEnd()
用于尾部补全;
第一个参数是字符串补全生效的最大长度,第二个参数是用来补全的字符串。'x'.padStart(5, 'ab') // 'ababx' 'x'.padStart(4, 'ab') // 'abax' 'x'.padEnd(5, 'ab') // 'xabab' 'x'.padEnd(4, 'ab') // 'xaba'
- ES2019对字符串实例新增了
trimStart()
和trimEnd()
方法;它们的行为与trim()
一致,trimStart()
消除字符串头部的空格,trimEnd()
消除尾部的空格。它们返回的都是新字符串,不会修改原始字符串。
浏览器还部署了额外的两个方法,trimLeft()
是trimStart()
的别名,trimRight()
是trimEnd()
的别名。 matchAll()
方法返回一个正则表达式在当前字符串的所有匹配;