参考文章:
前言和ES6简介
ES6是JavaScript的统一规范。
node.js 是能让js代码配置服务器的技术,一般配置在3000端口。npm是管理js包的工具,其脚本命令npm script可以用来运行测试等功能。
Let和Const命令
-
let
var可以在声明前被调用,输出undefined,这种现象称为变量提升。 在代码块中,let声明前的变量都不能使用,这种现象称为暂时性死区 DTZ。
let作用的代码块,称为块级作用域。
所以let只能作用于对应代码块中。var是全局引用,let则在循环中表现比较正常。
for (var i = 0; i < 10; i++) { a[i]=i; } // 数组内元素均为10。复制代码
-
const
const表示常量,声明后不能改变。但其实是变量对应的地址不能改变,所以对于一个数组,仍然可以push/pop元素来改变它的值。(使用Object.freeze()来冻结对象)
顶层对象问题:顶层对象在不同js环境下,获取方式不同。ES6加入let/const后对顶层对象设计问题,有所帮助。
变量的解构赋值
类似python一样的多变量赋值
-
数组的解构赋值
let [a,b,c] = [1,2,3]
未被成功解构的为undefined;等号右边只要是Iterator就可以。let [a=1] = []
这种方式赋默认值。 -
对象的解构赋值
let { foo, bar } = { bar: "a", foo: "b" }
无需严格按照顺序,因为左侧其实是let { foo:foo, bar:bar }
的形式。解构赋值的特殊情况
let x; {x} = {x:1}; // 这里会报错,行首为大括号会被视为代码块 // 所以第二行需要整行用圆括号括起来复制代码
- 函数的解构赋值
function add([x,y]) { return x + y;} // 这里x,y都被解构了复制代码
-
圆括号问题
圆括号帮助解析赋值,使用一句话总结:声明不能用圆括号,赋值可以。
字符串的扩展
-
Unicode
"\u0061" === "a", 用"\u{xxxxxxxx}"可以表示UTF-16的字符。
-
codePointAt()
codePointAt()能够正确地返回UTF-16的码点,对应而言的charAt和charCodeAt都不行。但是codePointAt仍有bug,结合for of可以正确读取。
for (let ch of a){ console.log(ch.codePointAt(0).toString(16));}复制代码
-
fromCodePoint()
将Unicode转化为字符,与codePointAt方法相反。fromCodePoint可以接受多个参数,它会将这些参数先拼接为字符串。
-
字符串的遍历接口
for of
就是字符串的遍历接口,这个接口支持UTF-16格式。对应的传统遍历模式:for (let i=0;i<str.length;i++)
只能在UTF-8组成的字符串上正确运行。 -
includes(),startsWith(),endsWith()
indexOf()判断一个字符串是否包含在另一个字符串中。
- startsWith 判断参数字符串是否在原字符串的头部;
- endsWith 判断参数字符串是否在原字符串的尾部;
- includes 表示是否找到了参数字符串。
-
repeat()
repeat方法接受非负数为参数,小数参数向上取整。表示对字符串重复多少次,并返回结果。
-
padStart(), padEnd()
这两个函数接受两个参数,一个是填充后长度,另一个是填充字符串。padStart表示头部填充,padEnd表示尾部填充。
'x'.padStart(4,'ab')// 'abax'复制代码
-
模板字符串
非常重要的概念,反引号(`)内表示模板字符串。 一个例子:
name = 'rick';age = 22;s = `My name is ${name}, aged ${age}`;复制代码
当然也可以在${}内加入任何JS表达式,调用函数等等。
-
标签模板
在一个函数后面,可以直接跟一个模板字符串。
a = 5;b = 10;tag`Hello ${a+b} world ${a*b}`; //等价于tag(['Hello ',' world ',''],15,50)复制代码
使用标签模板有两个好处,一个是防止用户恶意输入</ script>脚本,因为这时候模板字符串的参数已经被抽取出来了;另外一个是用于国际化,你可以只国际化参数或非参数。
正则的扩展
- RegExp构造函数
ES6允许RegExp包含第二个参数作为正则表达式修饰符。多个修饰符可以叠加。
修饰符是影响整个正则规则的特殊符号。
- i: 大小写不敏感;
- g: 全局查找;
- m: 检测换行符。
- u: ES6新增,Unicode匹配。
var regex = new RegExp('xyz','i');var regex = new RegExp(/xyz/i);var regex = /xyz/i;复制代码
- 字符串的正则方法
字符串对象可以直接调用对应的正则对象的方法,包括match/replace/search/split。
- u修饰符
JS中,正则表达式的test()方法,表示是否匹配括号内参数是否匹配正则对象的模式。
ES6中加入了对Unicode的正则。关键语法就是u。
- u. 表示任意Unicode字符;
- \u{xx}表示模式中的具体unicode;
- 详情见原文...
- y修饰符
JS中,正则表达式的exec()方法,如果匹配到了模式,则返回匹配结果。
y修饰符表示粘连(sticky),加入了该修饰符进行匹配后,下一次会从上一次匹配的后一个字符开始匹配,这和g修饰符相同。不同的是,g只要剩下的字符串含有模式就会返回,但是y必须严格从下一个字符开始匹配。
var s = 'aaa_aa_a';var r1 = /a+/g;var r2 = /a+/y;r1.exec(s) // ["aaa"] 剩余“_aa_a”r2.exec(s) // ["aaa"] 剩余“_aa_a”r1.exec(s) // ["aa"]r2.exec(s) // null复制代码
y修饰符的设计本意,就是让头部匹配的标志^在全局匹配中都有效。
y修饰符的一个应用,是从字符串提取token(词元),y修饰符确保了匹配之间不会有漏掉的字符。
- s修饰符
s修饰符作用是让.可以匹配任意字符,包括换行符。
- 断言
/x(?=reg_pattern)/ 正前向断言,只有当字符串右侧出现匹配reg_pattern的字符时才匹配正则表达式。 /x(?!reg_pattern)/ 负前向断言。
JS原来只支持先行断言,而不支持后行断言。ES6加入了后行断言,一个例子是 /(?<=reg_pattern)x/
或 /(?<!reg_pattern)x/
.
- 具名组匹配和解构赋值
ES6支持给正则表达式中括号内的字表达式赋予具体的名称,有了这个名称过后就可以进行解构赋值。注意exec后的对象,在调用groups即可获得具名组的对象了。
// 旧定义const RE_DATE = /(\d{4})-(\d{2})-(\d{2})/;// 加入具名组const RE_DATE = /(?\d{4})-(? \d{2})-(? \d{2})/;// 调用const matchObj = RE_DATE.exec('1999-12-31');const year = matchObj.groups.year; // 1999const month = matchObj.groups.month; // 12const day = matchObj.groups.day; // 31// 例子:解构替换let re = /(? \d{4})-(? \d{2})-(? \d{2})/u;'2015-01-02'.replace(re, '$ /$ /$ ');复制代码
数值的扩展
Number 对象是原始数值的包装对象。
- 二进制和八进制
二进制前面带0b,八进制前面带0o。
- parseInt和parseFloat
现在这两个方法可以通过Number对象调用,而不只是全局函数。
- Number.isInteger()
这个方法可以判断一个Number是否为整数,但要注意JS采用IEEE 754标准,小数点后16个10进制位,对应超过了52个有效位,就会被丢弃。那么判断结果就会发生错误。
- Math对象扩展
- Math.trunc() 去除一个数的小数部分。
- Math.sign() 返回一个数的符号,可以是1,0,-1,-0,NaN
- Math.cbrt() 返回一个数的立方根。
- Math.clz32() 返回一个数的无符号32位表示,有多少个前置0.
- Math.log2() 返回2为底的对数。
- ... 其他函数不太常用
函数的扩展
- 函数的默认值
ES6之前不能指定函数的默认值,现在指定方式类似于python。参数默认值是惰性求值的,可以看做运行函数时,先触发计算默认值。
- 函数默认值结合解构赋值
难点:请问下面两种写法有什么差别?
// 写法一function m1({x = 0, y = 0} = {}) { return [x, y];}// 写法二function m2({x, y} = { x: 0, y: 0 }) { return [x, y];}复制代码
分析:写法一定义了函数的默认值为{},并且设置了解构赋值的默认值。但写法二定义了函数的默认值为一个对象,但没有设置解构赋值的默认值
- 参数默认值的位置
将含有默认值的参数放到前面(x=1,y),明显不能直接省略。但是显式地写(undefined,y)却可以触发默认值。当然这种写法很蠢...
- 作用域
函数默认值的作用域是单独的作用域,如f(x,y=x)。作用域就为x,y=x,赋值完了就销毁了。
- rest参数
...变量名
用于获取函数的多余参数,本质是一个数组。原来要获取多余参数,需要使用arguments变量。但是arguments是一个对象,实际使用时要转换成数组。rest参数就不需要这样。
- rest参数必须是最后一个参数。
- 函数的length属性不包括rest参数。
- name属性
foo.name返回函数名称(foo)。匿名函数在ES5返回"",但在ES6返回该匿名函数被赋值的变量名。
var f = function () {};// ES5f.name // ""// ES6f.name // "f"复制代码
- 箭头函数
箭头:=>可以用来定义函数。
var f = () => 5;// 等同于var f = function () { return 5 };var sum = (num1, num2) => num1 + num2;// 等同于var sum = function(num1, num2) { return num1 + num2;};复制代码
多于一条语句就要用大括号括起来,并且使用return返回。
箭头函数注意事项:
- 函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。
- 不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
- 不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
- 不可以使用yield命令,因此箭头函数不能用作 Generator 函数。
箭头函数体内的this和普通函数function的this不同,后者的this指的是全局对象window。
this在普通函数function中不是绑定的,也就是说this指定的对象是不确定的。如果this的函数被父对象调用了,那么this指代的就是父对象,否则就是window。
由于箭头函数内不存在this对象,那么this指代的就是父对象,而不会是window,从而实现了绑定。 除了this,以下三个变量在箭头函数之中也是不存在的,指向外层函数的对应变量:arguments、super、new.target。
- 尾调用和优化
尾调用指某个函数的最后一步是调用另一个函数。 其实道理是,如果函数体内有保存局部变量,那么即使最后一步调用另一个函数,也要保存这些局部变量和调用的位置(虽然是最后一行,也看成任意位置处理)。
function factorial(n) { if (n === 1) return 1; return n * factorial(n - 1);}factorial(5) // 120复制代码
这个递归函数就不是尾递归,因为保存了n作为局部变量。算法复杂度O(n)。
function factorial(n, total) { if (n === 1) return total; return factorial(n - 1, n * total);}factorial(5, 1) // 120复制代码
上面这个例子就是尾递归,不保存任何调用记录,算法复杂度为O(1)。
凭什么呢?联系本节第4点——作用域,函数的参数有单独作用域。如果把参数放到函数体中,那么这个参数是参数列表的变量的拷贝,成为了局部变量,占用调用栈。
- 尾递归函数的改写
上面尾递归函数明显很丑陋,有两种方法改写。一个是加入辅助函数,原函数就能使用正常的参数列表了。
function tailFactorial(n, total) { if (n === 1) return total; return tailFactorial(n - 1, n * total);}function factorial(n) { return tailFactorial(n, 1);}factorial(5) // 120复制代码
第二个方法是柯里化(Currying),简而言之就是把原函数从接受多个参数变成接受一个参数的过程,是函数式编程的一个重要思想。
// 自己的例子function foo(a1, a2, a3) { return a1*a2+a3;}function currying(fun,num1,num2) { return function (num3) { fun(num1,num2,num3); }}function foo2 = currying(fun,a1,a2);foo2(a3);复制代码
可惜尾递归优化只存在于严格模式下,正常函数的优化(因为自带两个局部变量arguments,caller)还是需要递归变循环...