es6小记

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 }

 

posted @ 2020-05-19 22:27  姜子牙的姜  阅读(185)  评论(0编辑  收藏  举报