1.暂时性死区(temporal dead zone,TDZ)
es6明确规定,若在区块中有let和const命令,则这个区块对这些命令声明的变量从一开始就形成了封闭的区域,只要在声明前使用这些变量就会报错。
2.箭头函数注意事项
a.箭头函数体内的this对象就是定义时所在的对象,而不是使用时所在的对象;
b.箭头函数不能做构造函数;
c.箭头函数内不存在arguments对象,如果要用可以用rest参数代替;也没有指向外层函数的对应变量super,new.target
d.不可以使用yield命令,因此箭头函数不能用作Generator函数;
普通函数中的this指向时可变的,但箭头函数中它是固定的,这种特性有利于封装回调函数,this指向的固定化并不是因为箭头函数内部有绑定this的机制,实际原因是箭头函数根本没有自己的this,导致内部的this就是外层代码块的this。也因为它没有this,所以不能用作构造函数。
由于箭头函数没有自己的this,当然就不能用cal()、apply()、bind()这些方法去改变this指向
3.尾调用优化
a.尾调用(Tail Call)是函数式编程的一个重要概念,指函数的最后一步是调用别的函数,尾调用不一定在函数的尾部,只要是函数但最后一步操作
1 function f(x){ 2 if(x>0){ 3 return m(x) 4 } 5 return n(x) 6 }
以上函数中,m(x),n(x)都是尾调用
b.尾调用优化,尾调用的特殊调用位置,使其和别的函数调用不同
函数调用会在内存形成一个“调用帧(call frame)”,保存调用位置和内部变量等信息;若函数A的内部调用了函数B,在A的调用帧上方还会形成一个B的调用帧,等B运行结束,将结果返回到A,B的调用帧才会消失;若B内还调用函数C,
那就还有一个C的调用帧,以此类推,所有调用帧就形成了“调用栈(call stack)”;
尾调用由于是函数的最后一步操作,不需要保留外层函数的调用帧,也因为调用位置,内部变量等信息也不会再用到,直接用内层函数的调用帧取代外层函数的即可;
1 function f(){ 2 let m =1; 3 let n = 2; 4 return g(m+n) 5 } 6 f(); 7 //等同于 8 function(){ 9 return g(3); 10 } 11 //等同于 12 g(3);
上面函数,若g不是尾调用,函数f需要保存内部变量m和n的值,g的调用位置等信息,但因为调用g之后,函数f就结束了,所以执行最后一步,完全可以删除f(x)的调用帧,只保留g(3)的调用帧。
以上行为叫“尾调用优化(Tail Call Optimization)”,即只保留内层函数的调用帧。若所有的函数都是尾调用,则可以做到每次执行时调用帧只有一项,这将大大节省内存,此为“尾调用优化”的意义;
只有不再用到外层函数的内部变量,内层函数的调用帧才会取代外层函数的调用帧;
4.尾递归
a.函数尾调用自身称为尾递归,尾递归优化对递归操作意义重大
递归因为需要同时保存成百上千个调用帧,很耗费内存,且容易发生“栈溢出(stack overflow)”;
尾递归因为只有一个调用帧,所以永远不会发生“栈溢出”错误;
以计算Fibonacci数列为例:
非尾递归的Fibonacci数列实现
1 //非尾递归的Fibonacci数列实现 2 function Fibonacci(n){ 3 if(n<=1){return 1}; 4 return Fibonacci(n-1)+Fibonacci(n-2); 5 } 6 //尾递归优化的Fibonacci数列实现 7 function Fibonacci(n, ac1=1, ac2=1){ 8 if(n<=1){return ac2}; 9 return Fibonacci(n-1, ac2, ac1+ac2) 10 }
b.递归函数如何改写称尾递归
修改递归函数,确保最后一步帧调用自身;
方法就是:把所有用到的内部变量改写称函数的参数;但是传多个参数不太直观,有两种方法,
方法1,在尾递归函数之外再提供一个正常形式的函数;或者使用柯里化(currying),将多参数函数转换成单参数函数的形式;
1 function currying(fn, n,l){ 2 return function(m){ 3 return fn.call(this, m, n,l); 4 } 5 } 6 7 8 //调用 9 const fibonacci = currying(Fibonacci, 1, 1); 10 11 fibonacci(5);
方法2:采用ES6的函数默认值
function Fibonacci(n, ac1=1, ac2=1){ if(n<=1){return ac2}; return Fibonacci(n-1, ac2, ac1+ac2) } Fibonacci(5)
以上因为有默认值所有不用提供此参数值;
注:尾调用优化只在严格模式下生效,因为严格模式禁用func.arguments,func.caller两个变量,因为尾调用优化发生时,函数的调用栈会改写,这两个变量会失真(两个变量是用来跟踪正常模式下函数的调用栈的);
5.扩展运算符...
合并数组/ 与解构赋值结合,生成数组 / 识别32位Unicode字符,正确返回字符串长度[...str].length;
可以将任何Iterator接口的对象转换成真正的数组:扩展运算符内部调用的是数据解构的Iterator接口,只要具有Iterator接口的对象,都可使用扩展运算符,Map结构,Set结构,Generator函数;
1 let map = new Map([ 2 [1, 'one'], 3 [2, 'two'], 4 [3, 'three'], 5 ]); 6 let arr = [...map.keys()];//[1,2,3]
6.Array.from(obj, func, 参数)
用于将两类对象转为真正的数组,a:类似数组的对象(array-like object),即必须有length属性的对象;b:可遍历(iterable)对象
1 let arraylike = { 2 '0': 'a', 3 '1': 'b', 4 '2': 'c', 5 length:3 6 }; 7 //ES5写法 8 var arr1 = [].slice.call(arraylike);//['a', 'b', 'c'] 9 //ES6写法 10 let arr2 = Array.from(arraylike)
第二个参数作用类似map方法,用来对每个元素进行处理,将处理后的值放入返回的数组
1 Array.from(arraylike, x=> x*x); 2 //等同于 3 Array.from(arraylike).map(x=> x*x)
若map函数中用到了this,可传入Array.from第三个参数,用来绑定this;
Array.from可将字符串转为数组,返回字符串的长度,因为能正确处理Unicode字符
1 function countSymbols(string){ 2 return Array.from(string).length; 3 }