es6看完才看得懂大神的源码,今天开始进入 es6 学习。
学习资料主要使用,阮一峰老师的《ECMAScript 6 入门》
ECMAScript
let 和 const
ES6 新增了 let 和 const 命令来声明变量。
let
let 声明的变量只在相应代码块里有效
let 与 var 类似,都用来声明变量,但 let 命令声明的变量只在 let 命令所在的代码块内有效。
1 | var a = []; |
这里 for 循环使用 var 和 let 会产生两种截然不同的结果,这是因为 js 中的 for 循环设置循环变量的那一部分是一个父作用域,而循环体内部是一个单独的自作用域,使用 let 声明时 i 变量属于循环体内部的作用域,所以外部访问会报错,而使用 var 时 i 变量属于外部作用域,a 数组填入的函数最后调用时,调用的其实是计算完毕的 i 变量,也就是10;
let 不会变量提升
变量提升:变量可以在声明之前使用,值为undefined;函数变量可以在声明之前直接使用,因此代码得以美化。
var 命令会发生变量提升现象,而 let 声明则不会,他声明的变量一定要在声明后使用,否则报错。
1 | console.log(foo); // undefined |
暂时性死区
只要块级作用域中存在 let 命令,他所声明的变量就绑定在这个区域,不受外部影响,在此区域此变量声明前不能进行操作,否则报错。
1 | var tmp = 12; |
const
const 用来声明一个只读的常量,一旦声明,就不可改变。
const 声明的变量一旦声明就必须赋值,不赋值就会报错。
const 和 let 一样只在声明所在的块级作用域中有效。
const 声明的常量也不提升,同样存在暂时性死区。
const 也不可以重复声明。
本质
const 声明的变量本质就是一个不可变的栈内存指针,
对于简单数据类型的数据(数值,字符串,布尔值),因为简单数据直接储存于栈内存,所以不可以操作,等同于常量;
但对于引用数值类型,因为变量只是在栈内存中保存的一个指针,指向相应堆内存中的数据,所以堆内存的数据操作不会引起报错。
1 | const foo = {} |
global 对象
ES5 的顶层对象在各个实现里不同一,在浏览器里是 window ,在 web worker 里是 self ,在 node 里是global。
为了同一段代码里能取到任意环境的顶层对象,一般采用 this 变量,但有局限性:
全局环境中,this 会返回顶层对象,但 node 和 es6 模块中,this 返回的是当前模块。
函数里面的 this, 如果函数不是作为对象的方法而是单纯作为函数运行,this 会指向顶层对象。
变量的解构赋值
Es6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这个被称为解构(Destructuring)。
数组的解构赋值
只要模式相同,左边的变量就会被赋予对应的值,在数组中,可以解构嵌套,单独取值,使用扩展符取多个值,取空值时则返回 undefined ,扩展符返回空数组。
1 | let [a, b, c] = [1, 2, 3] |
另一种情况是不完全解构,即等号左边的模式,只能匹配一部分等号右边的数组,这种情况下,解构依然成功。
1 | let [x, y] = [1, 2, 3, 4] |
数组中的解构似乎是遍历右侧数据来赋值,所以当右侧数据不可遍历时,将会报错。
Set 解构也可以使用数组的解构赋值
Set 对象,定义,属性,方法。
Set 对象暂时不被浏览器支持
Set 对象是值的集合,你可以按照插入的顺序迭代它的元素,set 中的元素只会出现一次,set中的元素是唯一的。
Set 对象通过 new Set([iterable]) 构造函数创建,构造函数可以传入字符串,数组,set对象等可迭代对象,也可以输入 null 和 undefined。
Set 对象有以下常用方法:
add(value) => 在 set 对象末尾添加一个值,可以输入任意类型包括 null 和 undefined
has(value) => 返回判断值是否在 set 对象中
delete(value) => 删除相应值,不传入参数时会删除 undefined;
forEach(callbackFn[,thisArg]) => 可以 forEach 递归值
set 对象在判断某个数组的值很有效果。
迭代器(Generator) 函数也可以解构。
1 | (function () { |
默认值
解构及函数参数可以传入默认值,默认值在严格使用 === 判断一个位置有值与否后才会赋值,只有一个数组成员严格等于undefined时才生效,unll不会生效。
1 | function fun(a = 1) { |
默认值还可以赋值为表达式的值:
1 | let f = 1; |
默认值还可以引用解构赋值的其他变量,但该变量必须已经声明。
1 | let [x = 1, y = x] = []; |
对象的解构赋值
对象解构与数组的解构有一个重要不同,数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到值。
1 | let obj = { foo: 1, bar: 22 } |
如果变量的属性名不一样,则必须如下形式:
1 | let {foo: baz} = obj |
实际上对象的解构赋值是下面形式的简写:
1 | let {foo: foo, bar: bar} = {foo: 'aaa', bar: 'bbb'} |
对比上面的数组解构,位置一对一的赋值,所以这里实际被赋值的是后面的 foo,对象同样是按模式匹配。
于数组一样,对象也可以嵌套解构:
1 | let obj = { foo: 1, bar: 22 } |
其中 p 的是解构的模式而不是变量,对象解构时,只要大括号内不是只有一个变量,就是模式,需要往下层大括号找。
对象的解构也可以指定默认值:
1 | let {x: y = 3} = {} |
对象的解构默认值生效的条件同样要属性严格等于 undefined,如果解构失败,变量的值等于 undefined。
如果解构模式是嵌套对象,而且子对象所在的父属性不存在会报错:
1 | // 报错 |
由于数组本身算是一种特殊的对象,因此可以对数组进行对象属性的解构。
1 | let arr = [1, 2, 3] |
字符串的解构赋值
字符串也可以解构赋值,这是因为此时,字符串被转换成了一个类似数组的对象。
1 | const [a, b, c, d, e] = 'hello'; |
数值和布尔值的解构赋值
解构赋值时,如果等号右边是数值和布尔值,则会先转为对象。
1 | let {toString: s} = 123; |
解构赋值的规则是,只要等号右边的值不是对象或数组,就先将其转为对象。由于undefined和null无法转为对象,所以对它们进行解构赋值,都会报错。
1 | let { prop: x } = undefined; // TypeError |
函数参数的解构赋值
函数参数也可以使用解构赋值。
1 | function add ([x, y]) { |
圆括号问题
解构赋值尽量不要使用圆括号,但凡影响模式匹配的都会报错。
用途
交换变量值
1 | let x = 1 |
从函数返回多个值
实际函数只能返回一个值,但解构可以让他们看起来像是穿出来多个值。
1 | function e () { |
函数参数的定义
1 | // 参数是一组有次序的值 |
提取 JSON 数据
1 | let jsonData = { |
函数参数的默认值
1 | jQuery.ajax = function (url, { |
字符串的扩展
ES6 加强了对 Unicode 的支持,并且扩展了字符串对象。
字符的 Unicode 表示法
JavaScript 允许采用 \uxxxx 的形式表示一个字符,其中四个 x 的部分就是字符的 Unicode 码点。
1 | "\u0061" // "a" |
但是,这种码点在 \u0000 ~ \uFFFF 之间的字符,超出这个范围的字符,必须用两个双字节的形式表示。
1 | "\uD842\uDfB7" // "𠮷" |
ES6 对这一点做出了改进,只要将码点放入大括号,就能正确解读字符。
1 | "\u{20BB7}" |
有了这种表示方法后,javaScript 共有 6 种方法可以表示一个字符。
1 | '\z' === 'z' // true => 普通转译自己 |
codePointAt()
JavaScript 内部,字符以 UTF-16 的格式储存,每个字符固定为 2 个字节,对于那些需要 4 个字节储存的字符(Unicode 码点大于0xFFFF的字符),JavaScript 会认为他们是两个字符。
1 | var s = "𠮷"; |
上面代码中,汉字“𠮷”(注意,这个字不是“吉祥”的“吉”)的码点是0x20BB7,UTF-16 编码为0xD842 0xDFB7(十进制为55362 57271),需要4个字节储存。对于这种4个字节的字符,JavaScript 不能正确处理,字符串长度会误判为2,而且charAt方法无法读取整个字符,charCodeAt方法只能分别返回前两个字节和后两个字节的值。
1 | let s = '𠮷a'; |
codePointAt方法的参数,是字符在字符串中的位置(从 0 开始)。上面代码中,JavaScript 将“𠮷a”视为三个字符,codePointAt 方法在第一个字符上,正确地识别了“𠮷”,返回了它的十进制码点 134071(即十六进制的20BB7)。在第二个字符(即“𠮷”的后两个字节)和第三个字符“a”上,codePointAt方法的结果与charCodeAt方法相同。
总之,codePointAt方法会正确返回 32 位的 UTF-16 字符的码点。对于那些两个字节储存的常规字符,它的返回结果与charCodeAt方法相同。
codePointAt方法返回的是码点的十进制值,如果想要十六进制的值,可以使用toString方法转换一下。
1 | let s = '𠮷a'; |
你可能注意到了,codePointAt方法的参数,仍然是不正确的。比如,上面代码中,字符a在字符串s的正确位置序号应该是 1,但是必须向codePointAt方法传入 2。解决这个问题的一个办法是使用for…of循环,因为它会正确识别 32 位的 UTF-16 字符。
1 | let s = '𠮷a'; |
codePointAt方法是测试一个字符由两个字节还是由四个字节组成的最简单方法。
1 | function is32Bit(c) { |
String.fromCodePoint
ES5 提供的 String.fromCodePoint() 方法,用于从码点返回对应字符,但这个方法不能识别 32 位的 UTF-16 字符(Unicode 编号大于 0xFFFF)。
1 | String.fromCharCode(0x20BB7) |
上面代码中,String.fromCharCode不能识别大于0xFFFF的码点,所以0x20BB7就发生了溢出,最高位2被舍弃了,最后返回码点U+0BB7对应的字符,而不是码点U+20BB7对应的字符。
ES6 提供了String.fromCodePoint方法,可以识别大于0xFFFF的字符,弥补了String.fromCharCode方法的不足。在作用上,正好与codePointAt方法相反。
如果 String.fromCodePoint 方法有多个参数,则他们会被合并成一个字符串返回。
注意,fromCodePoint 方法定义在 String 对象上,而 codePoitAt 方法定义在字符串的实例对象上。
字符串遍历接口
Es6 为字符串提供了遍历接口,使得字符串可以被 for ... of 循环遍历。
1 | for (let codePoint of 'foo') { |
除了遍历字符串,这个遍历器最大的优点是可以识别大于 oxFFFF 的码点,传统的 for 循环无法识别这样的码点。
1 | let text = String.fromCodePoint(0x20BB7); |
上面代码中,字符串的 test 只有一个字符,但是 for 循环会认为它包含两个字符,而 for of 循环则正确识别出一个字符。
at()
Es5 对字符串实例对象提供 chatAt 方法,返回字符串给定位置的字符。该方法不能识别码点大于 0xffff 的字符。
1 | 'abc'.charAt(0) // "a" |
normalize()
许多欧洲字符都带有语调,为了表示他们,Unicode 提供了两种方法,一种是直接设置码点表示,比如Ǒ(\u01D1),另一种是提供合成符号,即原字符与重音符号的合成,比如O(\u004F)和ˇ(\u030C)合成Ǒ(\u004F\u030C)。
这两种方法在视觉与语义上都是等价的,但 js 不能识别。
1 | '\u01D1'==='\u004F\u030C' //false |
ES6 提供了一个函数用来统一字符形式,称为 Unicode 的正规化。
1 | '\u01D1'.normalize() === '\u004F\u030C'.normalize() // true |
normalize 方法可以接受一个参数来制定 normalize 的方式,参数的四个可选值如下:
NFC ,标准等价合成,返回多个加单字符的合成字符,“标准等价”指的是视觉与语义上的等价。
NFD ,标准等价分解,在标准等价的前提下,返回字符分解的多个简单字符。
NFKC,兼容等价合成,返回合成字符,语义上等价,但视觉上不等价。
NFKD,兼容等价分解,兼容等价的前提下,返回多个分解的简单字符。
不过,normalize方法目前不能识别三个或三个以上字符的合成。这种情况下,还是只能使用正则表达式,通过 Unicode 编号区间判断。
includes(), startsWith(), endsWith()
过去js只提供了一种方法:indexOf(),来确定一个字符串是否在另一个字符串中。ES6 中又添加了三种新方法:
- includes():返回布尔值,表示是否找到了参数字符串。
- startsWith():返回布尔值,表示参数字符串是否在原字符串的头部。
- endsWith():返回布尔值,表示参数字符串是否在原字符串的尾部。
1 | let s = 'Hello world!'; |
这三个方法都支持第二个参数,表示开始搜索的位置。
1 | let s = 'Hello world!'; |
上面代码表示,使用第二个参数n时,endsWith的行为与其他两个方法有所不同。它针对前n个字符,而其他两个方法针对从第n个位置直到字符串结束。
repeat()
repeat方法返回一个新字符串,表示将原字符串重复n次。参数如果是小数,会被取整。如果repeat的参数是负数或者Infinity,会报错。但是,如果参数是 0 到-1 之间的小数,则等同于 0,这是因为会先进行取整运算。0 到-1 之间的小数,取整以后等于-0,repeat视同为 0。参数NaN等同于 0。如果repeat的参数是字符串,则会先转换成数字。
padStart() padEnd()
ES2017 引入了字符串补全长度的功能。如果某个字符串不够指定长度,会在头部或尾部补全。padStart()用于头部补全,padEnd()用于尾部补全。
padStart和padEnd一共接受两个参数,第一个参数用来指定字符串的最小长度,第二个参数是用来补全的字符串。
如果原字符串的长度,等于或大于指定的最小长度,则返回原字符串。
如果用来补全的字符串与原字符串,两者的长度之和超过了指定的最小长度,则会截去超出位数的补全字符串。
如果省略第二个参数,默认使用空格补全长度。
padStart的常见用途是为数值补全指定位数。下面代码生成 10 位的数值字符串。
1 | '1'.padStart(10, '0') // "0000000001" |
另一个用途是提示字符串格式。
1 | '12'.padStart(10, 'YYYY-MM-DD') // "YYYY-MM-12" |
matchAll()
matchAll方法返回一个正则表达式在当前字符串的所有匹配,详见《正则的扩展》的一章。
模板字符串
传统的 js 输出模板通常是这样的。
1 | $('#result').append( |
上面这种写法相当繁琐不方便,ES6 引入了模板字符串解决这个问题。
1 | $('#result').append(` |
模板字符串(template string)是增强版的字符串,用反引号(`)标识。它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量。
1 | // 普通字符串 |
上面代码中的模板字符串,都是用反引号表示。如果在模板字符串中需要使用反引号,则前面要用反斜杠转义。
1 | let greeting = `\`Yo\` World!`; |
如果使用模板字符串表示多行字符串,所有的空格和缩进都会被保留在输出之中。如果你不想要这个换行,可以使用trim方法消除它。
模板字符串中嵌入变量,需要将变量名写在${}之中。大括号内部可以放入任意的 JavaScript 表达式,可以进行运算,以及引用对象属性。模板字符串之中还能调用函数。如果模板字符串中的变量没有声明,将报错。
模板字符串甚至还能嵌套。
1 | const tmpl = addrs => ` |
如果需要引用模板字符串本身,在需要时执行,可以像下面这样写
1 | // 写法一 |
实例:模板编译
1 | (function () { |
标签模板
模板字符串的功能,不仅仅是上面这些。它可以紧跟在一个函数名后面,该函数将被调用来处理这个模板字符串。这被称为“标签模板”功能(tagged template)。
1 | alert`123` |
标签模板其实不是模板,而是函数调用的一种特殊形式。“标签”指的就是函数,紧跟在后面的模板字符串就是它的参数。
但是,如果模板字符里面有变量,就不是简单的调用了,而是会将模板字符串先处理成多个参数,再调用函数。
1 | let a = 5; |
上面代码中,模板字符串前面有一个标识名tag,它是一个函数。整个表达式的返回值,就是tag函数处理模板字符串后的返回值。
函数tag依次会接收到多个参数。
1 | function tag(stringArr, value1, value2){ |
我们可以按照需要编写tag函数的代码。下面是tag函数的一种写法,以及运行结果。
1 | let a = 5; |
下面是一个更复杂的例子。
1 | let total = 30; |
上面这个例子展示了,如何将各个参数按照原来的位置拼合回去。
passthru函数采用 rest 参数的写法如下。
1 | function passthru(literals, ...values) { |
“标签模板”的一个重要应用,就是过滤 HTML 字符串,防止用户输入恶意内容。
1 | let message = |
上面代码中,sender变量往往是用户提供的,经过SaferHTML函数处理,里面的特殊字符都会被转义。
1 | let sender = '<script>alert("abc")</script>'; // 恶意代码 |
标签模板的另一个应用,就是多语言转换(国际化处理)。
1 | i18n`Welcome to ${siteName}, you are visitor number ${visitorNumber}!` |
模板字符串本身并不能取代 Mustache 之类的模板库,因为没有条件判断和循环处理功能,但是通过标签函数,你可以自己添加这些功能。
String.raw()
ES6 还为原生的 String 对象,提供了一个raw方法。
String.raw方法,往往用来充当模板字符串的处理函数,返回一个斜杠都被转义(即斜杠前面再加一个斜杠)的字符串,对应于替换变量后的模板字符串。
String.raw方法可以作为处理模板字符串的基本方法,它会将所有变量替换,而且对斜杠进行转义,方便下一步作为字符串来使用。
String.raw方法也可以作为正常的函数使用。这时,它的第一个参数,应该是一个具有raw属性的对象,且raw属性的值应该是一个数组。
1 | String.raw({ raw: 'test' }, 0, 1, 2); |
模板字符串的限制
前面提到标签模板里面,可以内嵌其他语言。但是,模板字符串默认会将字符串转义,导致无法嵌入其他语言。
举例来说,标签模板里面可以嵌入 LaTEX 语言。
1 | function latex(strings) { |
上面代码中,变量document内嵌的模板字符串,对于 LaTEX 语言来说完全是合法的,但是 JavaScript 引擎会报错。原因就在于字符串的转义。
模板字符串会将\u00FF和\u{42}当作 Unicode 字符进行转义,所以\unicode解析时报错;而\x56会被当作十六进制字符串转义,所以\xerxes会报错。也就是说,\u和\x在 LaTEX 里面有特殊含义,但是 JavaScript 将它们转义了。
为了解决这个问题,ES2018 放松了对标签模板里面的字符串转义的限制。如果遇到不合法的字符串转义,就返回undefined,而不是报错,并且从raw属性上面可以得到原始字符串。
正则的扩展
// String
// .match()
// .replace()
// .search()
// .split()
// .exec()
// .test()
在原有的基础上第一参数为正则表达式时,第二个参数的修饰符将覆盖前一个;
添加 u 修饰符主要对付四字节的字符。
添加 y 修饰符,粘连修饰符,下一次匹配必须从这次匹配的末尾开始。
添加了 sticky 属性验证是否设置了 y 修饰符。
添加了 flags 属性返回正则表达式实例对象的修饰符。
添加了 s 修饰符,使得正则表达式的 (.)可以匹配任意字符,旧时代,dot 不能匹配 四字节字符和行终止符
行终止符表示一行的终结,比如:1. 换行符(\n) 2. 回车符(\r)3. 行分隔符 4. 段分隔符
数值的扩展
数值添加了对数运算符
二进制数和八进制数在严格模式下不能隐式转换
为 Math 对象添加了很多新方法
函数的扩展
一, 参数默认值
- 使用参数默认值时,不能有同名参数(默认每个参数声明使用let)
- 默认参数等于表达式时,实际使用的是表达式运算结果
- 默认参数后的参数不被函数 length 属性计数
- 默认参数会形成临时作用域,此作用域处于外层和函数内之间,函数初始化结束就消失
二, rest 参数
新增的 rest 参数就是一个数组,格式就是 ...items : 三个点加参数名,rest 参数和 arguments 的最大区别就是,arguments 仅仅是一个伪数组,没有很多数组的默认函数;
而 rest 就彻底就是一个数组,传入参数却可以任意个;
你同样可以同时使用普通参数和 rest 参数,但为了避免传参报错问题,一般推荐把 rest 参数放在最后一位;
三, 箭头函数
ES6 允许使用“箭头”(=>)定义函数。
1 | var f = v => v; |
基本用法
- 箭头函数不要参数或多个参数时,就用括号括起来;
- 多条语句时,使用大括号括起来,并且使用 return 语句;
- 直接返回对象时,要使用圆括号;
- 箭头函数同样可以解构赋值。
使用注意
- 箭头函数体内的 this 对象,就是定义时所在的对象,而不是使用时所在的对象;
- 不可以做构造函数,不能用 new 命令;
- 箭头函数中没有 arguments、super、new.target、this 对象,都是不可用的;
- 不可以使用 yield 命令,不能做 Generator 函数
reduce () 累加器
定义:是数组的一个方法,与 map 类似,对数组中每个元素执行一个方法,最后减少为单个值
语法:arr.reduce(callback([accumulator, currentValue, currentIndex, array]) [, initialValue])
参数:
callback 每个值要执行的方法,
- accumulator 累加器累加回掉的返回值,是上一次调用回掉时返回的累积值,或者初始值(initialValue)
- currentValue 本次处理的数组元素
- currentIndex 【可选】 当前元素在数组中的索引
- array 【可选】 调用 reduce 的数组
initialValue 【可选】 初始元素,用作 callback 的第一个参数的值
返回值:函数处理的结果
尾调用优化
- 尾调用:函数尾部调用回掉函数,并不进行其他操作称为尾调用。
- 尾调用优化:在函数尾部调用函数,尾函数不引用上层函数的变量,此时就可以删除上层作用域,节省内存,防止堆栈溢出,但仅在严格模式可用。
- 暂时可以通过将迭代改为循环来减少内存占用。
数组的扩展
- 扩展运算符 (…) 类似 rest 参数的逆运算,将一个数组转用逗号分隔的参数序列。
扩展运算符可代替函数的 apply() ,添加参数。只要有遍历接口(Interator)就可以使用。
- 可以便捷浅复制数组:a2 = […a1];
- 可以代替 concat 方法合并数组:[…a1, …a2];
- 可以与结构赋值结合:var [first, …rest] = [1,3,4,5,6];
- 可以字符串转数组,正确识别四字节 Unicode 字符长度:[…’hello’];
- 不止字符串,任何实现了 Iterator 接口的对象都可以用扩展运算符扩展为数组:
- […document.querySelectorAll(‘div’)],节点解析;
- Map 和 Set 结构,Generator 函数 都可以将结果转为数组。
- Array.from() 用于将类似对象的转为真正的数组,
只要对象含有 length 属性和实现了 Iterator 接口的对象都可使用。
- 将 NodeList 集合 和 函数内参数 arguments 转换为数组。
- 可以接受第二参数,类型为 function ,用来进行类似 map 对数组中的项操作后返回。
- 可以方便地获取 dom 文本内容。
Array.of() 用于将一组值转换为数组。
Array.copyWithin(target, start = 0, end = this.length) 用来将数组内部某一项覆盖另一个位置的项。
find() 和 findIndex() 参数为会掉函数,返回第一个符合条件的项位置。
fill(value, start = 0, end = length) 填充覆盖数组原项,可选位置数量。
entries(),keys()和values() 返回可遍历对象。