参考文献:ECMAScript 6 入门 — 阮一峰
引申阅读:Unicode编码方案概述
提醒自己:ES6中不但新增了许多新语法新概念,重要的是还新增了很多API,所以平时在开发中要多查多用!
2015年后的JS标准都可称为ES6
ES5中实行的是 提升 的原则,ES6中不允许 提升,所以在声明之前访问变量都是报错的(let、const等)
变量提升:
var tmp = new Date();
function f() {
console.log(tmp);
if (false) {
var tmp = 'hello world';
}
}
f(); // undefined
这段代码本质上是这样的:
var tmp = new Date();
function f() {
var tmp = undefined;
console.log(tmp);
if (false) {
tmp = 'hello world';
}
}
f(); // undefined
函数提升:
function f() { console.log('I am outside!'); }
(function () {
if (false) {
// 重复声明一次函数f
function f() { console.log('I am inside!'); }
}
f();
}());
这段代码运行在ES5环境中,本质上是这样的:
// ES5 环境
function f() { console.log('I am outside!'); }
(function () {
function f() { console.log('I am inside!'); } // 把整个函数声明给提升了
if (false) {
}
f(); // 'I am inside!'
}());
// 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
-
顶层对象:
① var
命令和function
命令声明的全局变量,依旧是顶层对象的属性;另一方面规定,let
命令、const
命令、class
命令声明的全局变量,不属于顶层对象的属性。也就是说,从 ES6 开始,全局变量将逐步与顶层对象的属性脱钩。
var a = 1;
// 如果在 Node 的 REPL 环境,可以写成 global.a
// 或者采用通用方法,写成 this.a
window.a // 1
let b = 1;
window.b // undefined
② 在浏览器中顶层对象是window对象,在Node中是global对象。在不同的环境中,顶层对象是不一样的,可不可以统一下?
垫片库system.global
模拟了这个提案,可以在所有环境拿到global
。
// CommonJS 的写法
require('system.global/shim')();
// ES6 模块的写法
import shim from 'system.global/shim'; shim();
上面代码可以保证各种环境里面,global
对象都是存在的。
// CommonJS 的写法
var global = require('system.global')();
// ES6 模块的写法
import getGlobal from 'system.global';
const global = getGlobal();
上面代码将顶层对象放入变量global
。
解构
可遍历的对象才可以解构,比如Object对象(内部属性可遍历)、数组等。
所谓的解构,就是按照对象的结构,将各个变量组合起来,ES6会根据对应关系,一一给这些变量赋值。数组是按照先后顺序依次解构,对象是根据属性名进行解构
对象解构的本质let { foo: foo, bar: bar } = { foo: "aaa", bar: "bbb" };
也就是说,对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。真正被赋值的是后者,而不是前者。所以,你可以简写成let { foo, bar } = { foo: "aaa", bar: "bbb" };
你如果想用其他的变量名,可以这样let { foo: f, bar: b } = { foo: "aaa", bar: "bbb" };
则f
的值就是aaa
,b
的值就是bbb
// 错误的写法
let x;
{x} = {x: 1};
// SyntaxError: syntax error
上面代码的写法会报错,因为 JavaScript 引擎会将{x}理解成一个代码块,从而发生语法错误。只有不将大括号写在行首,避免 JavaScript 将其解释为代码块,才能解决这个问题。
// 正确的写法
let x;
({x} = {x: 1});
知识点引申:最外层如果是{}
,代表一个代码块;最外层如果是()
,代表一个表达式,或语句。
-
字符串解构
const [a, b, c, d, e] = 'hello';
a // "h"
b // "e"
c // "l"
d // "l"
e // "o"
let {length : len} = 'hello'; //字符串对象有 length 属性
len // 5
-
数值和布尔值的解构赋值
let {toString: s} = 123;
s === Number.prototype.toString // true
let {toString: s} = true;
s === Boolean.prototype.toString // true
解构赋值的规则是,只要等号右边的值不是对象或数组,就先将其转为对象。
-
函数参数解构赋值
需要特别注意以下这种情况,易出错:
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
函数时,包含了两层意思:如果调用move
函数时传入参数,则参数按照{x = 0, y = 0}
解构;如果调用move
函数时未传入参数,则将{}
作为默认参数
变化一下:(换汤不换药)
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
函数时,包含了两层意思:如果调用move
函数时传入参数,则参数按照{x, y}
解构;如果调用move
函数时未传入参数,则将{ x: 0, y: 0 }
作为默认参数
-
圆括号的使用
可以使用圆括号的情况只有一种:赋值语句的非模式部分,可以使用圆括号。
啥是赋值语句,啥是声明语句?
答:let a = 1;
就是声明语句,a = 1;
就是赋值语句;函数声明中的参数声明也是声明语句
啥是模式,啥是非模式?
答:像{ o: { p: n } } = { o: { p: 2 } };
中,o
和 p
就是模式, n
就是非模式,也就是说,匹配对象中属性名的就是模式部分,和值相关的(本质上就是你声明的变量名)就是非模式部分。
字符串扩展
ES6中对ES5中不能正确处理码点大于0xFFFF
的字符进行了优化
没搞明白为啥"\uD842\uDFB7"==="\u{20BB7}"
为true
,而且他们都代表汉字 ""(这个汉字由两个字符组成),他们的二进制表示明明不同啊!有知道原因的吗?(后续:终于搞明白了,参考文献:Unicode编码方案概述,简单来讲就是在Unicode编码中,为全世界所有的字符都指定了唯一的编号,但是前65536
即2^16
即\uFFFF
个编号对应了世界上最常用的字符,同时在这前65536
个编号的0xD800-0xDFFF(十进制55296~57343)这个区间是特殊的,我们可以用这个区间的两个编号表示编号大于65536
的字符。很明显,"\uD842"
和"\uDFB7"
都是处于这个区间的,所以就可以表示大于65536
的编号为"\u{20BB7}"
的汉字""了)
因为有像 "" 这种文字的存在,所以遍历字符串的最好实现应该用for...of
循环,用for (let i = 0; i < text.length; i++)
这种循环碰到 "" 这种情况会有问题
标签模板
alert`123` // 这就是标签模板
// 等同于
alert(123)
标签模板其实不是模板,而是函数调用的一种特殊形式。“标签”指的就是函数,紧跟在后面的模板字符串就是它的参数。
如果模板字符里面有变量,会先将模板字符串先处理成多个参数,再调用函数。
let a = 5;
let b = 10;
tag`Hello ${ a + b } world ${ a * b }`;
// 等同于
tag(['Hello ', ' world ', ''], 15, 50);
研究上面代码会发现,如果有n
个变量(像${}
的这种)会将字符串模板分割成n+1
个
“标签模板”的一个重要应用,就是过滤 HTML 字符串,防止用户输入恶意内容。
let message = SaferHTML`${sender} has sent you a message.
`;
function SaferHTML(templateData) {
let s = templateData[0];
for (let i = 1; i < arguments.length; i++) {
let arg = String(arguments[i]);
// Escape special characters in the substitution.
s += arg.replace(/&/g, "&")
.replace(//g, ">");
// Don't escape special characters in the template.
s += templateData[i];
}
return s;
}
标签模板的另一个应用,就是多语言转换(国际化处理)
console.log`123`
// ["123", raw: Array[1]]
上面代码中,console.log接受的参数,实际上是一个数组。该数组有一个raw属性,保存的是转义后的原字符串。
进一步用代码说明:
tag`First line\nSecond line`
function tag(strings) {
console.log(strings.raw[0]);
// strings.raw[0] 为 "First line\\nSecond line"
// 打印输出 "First line\nSecond line"
}
raw
属性的这部分功能,String.raw()
也能实现,例子:
String.raw`Hi\n${2+3}!`;
// 返回 "Hi\\n5!"
String.raw`Hi\u000A!`;
// 返回 "Hi\\u000A!"
ES6会对模板字符串中的特殊符号进行转义,比如会转义\u{20bb7}
,但如果是遇到\unicode
这种转义失败的怎么办呢?
- 在ES6的最新标准中,对标签模板中的字符串如果转义失败,会返回
undefined
不报错 - 对模板字符串还是会报错
例如:
let bad = `bad escape sequence: \unicode`; // 报错
tag `bad escape sequence: \unicode`; // 不报错
正则扩展
/foo[^]bar/.test('foo\nbar')
// true
why? 正则表达式中^的两种意思,^
可以表示从开头匹配;表示排除,也就是说”[]”代表的是一个字符集,^
只有在字符集中才是反向字符集的意思。
这一部分好烦,不想看了!
数值扩展
浮点计算是不准确的,比如0.1 + 0.2 === 0.3 // false
怎么解决?ES6
在Number
对象上面,新增一个极小的常量Number.EPSILON
。根据规格,它表示 1 与大于 1 的最小浮点数之间的差。等于 2 的 -52 次方。Number.EPSILON === Math.pow(2, -52)
function withinErrorMargin (left, right) {
return Math.abs(left - right) < Number.EPSILON * Math.pow(2, 2);
}
0.1 + 0.2 === 0.3 // false
withinErrorMargin(0.1 + 0.2, 0.3) // true
1.1 + 1.3 === 2.4 // false
withinErrorMargin(1.1 + 1.3, 2.4) // true
上述代码表示,如果浮点计算误差在 2的-50次方 内,就认为是相等的。
在js中,无论对整数还是小数,都是有最大上限和下限的,都有对应的常量表示。超过最大值后,js就类似这样处理了9007199254740993 === 9007199254740992; // true
持续更新中。。。