zoukankan      html  css  js  c++  java
  • ES6的函数

    1,带参数默认值的函数

    JS函数有个独特的行为:可以接受任意数量的参数,而无视函数声明的形参数量。未提供的参数会使用默认值来代替。实际传递的参数允许少于或多于正式指定的参数。

    在ES6中可以直接在形参里面进行默认值赋值:

     function makeRequest(url,timeout = 200, callback = function(){}){
         ...
     }
    

     在ES5中我们一般使用:

    function makeRequest(url,timeout,callback){
         timeout = timeout || 2000;
         callback = callback || function(){};
     }
    function makeRequest(url,timeout,callback){
         timeout = (typeof timeout !== 'undefined') ? timeout || 2000;
         callback = (typeof callback !== 'undefined') ? callback || function(){};
     }

    在形参未被提供情况下使用默认值。但是如果timeout的值为0,即假值,就会导致timeout被替换为默认值2000;

    所以应该更安全的选择typeof来检测参数的类型,很多流行的JS库都是使用上面第二种方法。

    那么,参数的默认值如何影响arguments对象?在ES5中

    //非严格模式下 
    function mixAgs(a,b){
         console.log(a === arguments[0]);
         console.log(b === arguments[1]);
         a = 3,b=4;
         console.log(a === arguments[0]);
         console.log(b === arguments[1]);
     }
     mixAgs(1,2); // true true true true
    
    //严格模式下
    function mixAgs(a,b){
         "use strict";
         console.log(a === arguments[0]);
         console.log(b === arguments[1]);
         a = 3,b=4;
         console.log(a === arguments[0]);
         console.log(b === arguments[1]);
     }
     mixAgs(1,2);  // true true false false

    function mixAgs(a,b = 5){
      console.log(a === arguments[0]);
      console.log(b === arguments[1]);
      a = 3,b=4;
      console.log(a === arguments[0]);
      console.log(b === arguments[1]);
    }
    mixAgs(1,2);  //true true false false

    可以看出,在非严格模式下,函数的arguments对象可以被更新,严格模式下arguments对象不能被更新。那么ES6的形参默认赋值的函数中,无论是处于严格或是非严格模式,arguments对象都是与ES5的严格模式一致,arguments对象始终能映射出初始调用状态。

    函数的参数默认表达式:

    参数默认并非只能是基本类型,也可以是一个函数,看下面的例子: 

    function getVal(){
        return 5;
    }
    
    function add(first,second = getVal()){
        return first + second;
    }
    
    console.log(add(1,1))  //2
    console.log(add(1))  //6

     ES6的参数默认值的暂时性死区(TDZ):

    与let声明相似,函数每个参数都会创建一个新的标识符绑定,在初始化之前不允许被访问,否则会抛出错误。参数初始化会在函数被调用时进行。

    函数参数拥有各自的作用域和暂时性死区,与函数体的作用于分离,送一参数的默认值不允许访问在函数内部声明的任意变量。 

    ES6的剩余参数:

    rest parameter由三个点(...)后面紧跟一个具名参数指定,包含传递给函数的其余参数的一个数组,由此得名“剩余”。

     function pick(object,...keys){
         let result = Object.create(null);
         for(let i = 0,len = keys.length;i<len;i++){
             result[keys[i]] = object[keys[i]];
         }
         return result
     }
    
     let book = {
         title:'Understanding ES6',
         author:'Miya',
         year:2016
     }
    
     let bookData = pick(book,'author','year');
     console.log(bookData);

    上面的keys是个剩余参数,包含object后面所有的参数,与arguments不同之处,剩余参数不包含第一个参数,一个函数必须只能有一个剩余参数,并且它必须放在最后。剩余参数后面不能再放其它形参,对象的setter被限定只能使用单个参数,剩余参数是不限制参数数量的,所以不能用在对象的setter里面。

    剩余参数如何影响arguments对象?

    设计剩余参数就是为了替代arguments对象。arguments对象在函数被调用时反映了传入的参数,与剩余参数能协同工作。

     函数构造器的增强:

    函数构造器在JS中并不常用。传给构造器的参数都是字符串形式,它们就是目标函数的参数和函数体,举个栗子:

    var add = new Function("first","second","return first + second");
    console.log(add(1,1))  //2
      var add = new Function("first","second = first","return first + second");
      console.log(add(1))  //2
    
    
      var data = new Function("...args","return args[0]");
      console.log(data(1,2))  //1

    而在ES6中呢,增强了Function的能力,允许使用默认参数以及剩余参数,确保它拥有与函数声明形式相同的所有能力。

    扩展运算符:

    它与剩余参数关联密切。剩余参数是允许将多个独立的参数合并到一个数组,而拓展运算符是允许将一个数组进行分割,并将各个项作为分离的参数传给函数。栗子如下:

     let values = [25,30,24,78,40];
     console.log(Math.max.apply(Math,values));  //78
    console.log(Math.max(...values)); //78

    虽然用apply是可行的,但是这样代码混淆真的好么?拓展运算符就让这种情况变的很简单。在数组前面加上(...)。大部分场景中,拓展运算符都是apply()方法的合适替代品。

    ES6的名称(name)属性:

    定义函数的方式各种各样,在JS中识别函数比价困难。而且匿名函数表达式的流行使得调试有点苦难,经常导致堆栈跟踪难以被阅读与理解。所以ES6中给所有函数添加了name属性。

     function doA(){
    
     }
     var doB = function(){
    
     }
     console.log(doA.name);   //doA
     console.log(doB.name);   //doB

    函数的双重用途:

    function Person(name){
         this.name = name;
     }
     var person = new Person("Miya"); //Person {name: "Miya"}name: "Miya"__proto__: Object
    var notAPerson = Person("John"); //undefined
     console.log(person,notAPerson);
    window.name //John

    在调用Person()来创建notAPerson时候输出undefined,在非严格模式下给window添加了name属性。在JS中惯用首字母大写的形式创建构造函数。JS为函数提供两个内部方法:Call和Construct,未使用new进行函数调用时,call方法执行,运行的是代码的函数体,当使用new进行函数调用,Construct方法执行,负责创建一个被称为新目标的新对象,将该新目标作为this去执行函数。拥有Construct方法的函数被称为构造器。

    并不是所有的函数都拥有Construct方法,因此并不是所有的函数都可以用new去调用。箭头函数就是个例外。

    判断函数如何被调用的? 

    function Person(name){
         if(this instanceof Person){
             this.name = name;
         }else{
             throw new Error("You must use new with Person.")
         }
     }

    在ES5中用instanceof进行判断其是否为构造器的一个实例,但是这种方法并不靠谱,看下面的例子(书籍P60代码有误)。

    使用new.target元属性进行判断函数被调用时是否使用了new,元属性指的是“非对象”,1,使用new.target判断是否使用特定构造器进行了调用。2,通过检查new.target这个元属性是否被定义。

     function Person(name){
         if(typeof new.target !== 'undefined'){
             this.name = name;
         }
         //或者使用new.target判断是否使用特定构造器进行调用
         if(new.target === Person){
             this.name = name;
         }
     }

       function AnotherPerson(name){
        Person.call(this,name);
       }

      var person = new Person("miya");

      var anotherPerson = new AnotherPerson('john');  //出错

    这两种方法都可以进行检测。ES6通过新增new.target消除函数调用方式的不确定性。还解决了一个不确定的部分——在代码块内部声明函数。

    块级函数:

    在ES3或者更早的版本中,块级函数(在代码块中声明函数)严格来说应当是一个语法错误,但是所有的浏览器却都支持该语法。但是浏览器有差异,最佳实践是切勿在代码块中声明函数,更好的选择是使用函数表达式。在ES6中将代码块中的函数视为块级声明,会被提升到所在代码块的顶部。外部不能被访问。与let的区别在于,let的函数表达式不能被提升到所在代码块的顶部。

    箭头函数:

    ES6中最为有趣的就是arrow function了,用箭头("=>”),箭头函数与传统函数的不同:

    1,没有this,super,arguments,new.target绑定

    2,不能使用new去调用,箭头函数没有Construct方法,因此不能被用为构造函数,使用new调用箭头函数会抛出错误。

    3,没有原型

    4,不能更改this,this的值在函数的整个生命周期内值不会改变。

    5,没有arguments对象,必须依赖具名参数或者剩余参数来访问函数的参数。

    6,不允许重复的具名参数,传统的函数只有在严格模式下才会禁止这种重复。

    在JS中,this的绑定是发生错误的常见根源之一,尤其嵌套函数,或者常规函数作为构造函数来使用,都是可以导致this发生变化而不利优化的原因。箭头函数也拥有name塑形,并且遵循与其他函数相同的规则。

    var reflect = value => value;
    var sum = (a,b) => a+b;
    var getName = () => "Miya";
    var doSomething = () => {};
    //花括号别用于 表示函数的主体,如果要返回一个对象字面量,必须将该字面量用圆括号包裹起来。
    var getTempItem = id => ({ id:id,name:'Temp' }) var getTempItem = function(id){ return { id:id,name:'Temp' } }

    创建立即调用函数表达式(immediately-invoked function expression,IIFE): 

    let person = function(name){
        return {
            getName : function(){
                return name;
            }
        }
    }("Miya");
    console.log(person.getName());  //Miya

    let person = (name => {
      return {
        getName: () => name
      }
    })("Miya");
    console.log(person.getName()); //Miya

     person对象包含getName()方法,该方法使用name参数作为返回值。箭头函数方法如上: 

     在JS中,this的绑定跟根据调用该函数时的上下文而改变,因此完全可能违背本意地影响预期外的对象。

    var myType = () => {};
    var obj = new myType();
    VM91:2 Uncaught TypeError: myType is not a constructor

    箭头函数prototype属性缺失,使用new方法会报错,因为箭头函数不存在construct方法,同理,箭头函数的this值由包含它的函数据决定,所以不能使用call(),apply()或者bind()方法来改变this的值。

    箭头函数与数组:

    能使用回调函数的数组方法有:sort(),map(),reduce()等方法,让其传参function更加简洁。

    没有arguments绑定:

    虽然arrow function没有自己的arguments对象,但是仍然能访问包含它的函数的arguments对象。看下面的栗子:

    function arrowArgs(){
        return () => arguments[0];
    }
    var a = arrowArgs(5,6,7);
    console.log(a()); //5

    识别箭头函数:

    var comparator = (a,b) => a-b;
    console.log(typeof comparator); //function
    console.log(comparator instanceof Function)  //true
      console.log(comparator.call(null,2,1)) //1
      console.log(comparator.apply(null,[2,1])) //1

      var bindC = comparator.bind(null,2,1);
      console.log(bindC()); //1

    箭头函数本质还是function,仍然可以对箭头函数使用call,apply,bind方法,只是箭头函数的this绑定不会受到影响。看上面的栗子:

    尾调用(tail call)优化:

    尾调用:指的是调用函数的语句是另一个函数的最后语句,ES6中对函数最为有趣的改动或许就是一项引擎优化,改变了尾部调用的系统。就像下面的tailFun方法那样。ES5引擎中实现的尾调用,其处理与其他函数调用一致:一个新的栈帧(stake frame)被创建被并被推到调用栈之上,用于表示该次函数调用,这意味着之前每个栈帧都被保留在内存中,当调用栈过大时会出问题。

    ES6在严格模式下力图为特定尾调用减少调用栈的大小,非严格模式的尾调用则保持不变。尾调用优化不会创建新的栈帧,而是清除当前栈帧并再次利用它:

    1,尾调用不能引用当前栈帧中的变量(意味着该函数不能是闭包);

    2,进行尾调用的函数用返回结果后不能作额外的操作;

    3,尾调用的结果作为当前函数的返回值。

    尾调用优化允许某些函数的调用被优化,以保持更小的调用栈,使用更少的内存,并防止堆栈溢出。当能进行安全优化时,它由引擎自动应用,例如递归调用factorial()方法的优化:

    function tailFun(){
        return doSomething(); //尾调用
      }
    function factorial(n){
        if(n <= 1){
            return 1;
        }else{
            //未被优化:在返回之后还要执行乘法
            return n*factorial(n-1);
        }
    }
    
    function factorial(n,p=1){
        if(n <= 1){
            return 1*p;
        }else{
            let result = n*p;
            //被优化
            return factorial(n-1,result);
        }
    }

    添加了默认值为1的第二个参数p,p保留着前一次乘法的结果,因此下一次的结果就能在进行函数调用之前被算出。尾调用优化是在书写任意递归函数时需要考虑的因素,因为它能显著提升性能,尤其在被应用到计算复杂度很高的函数时,有时候这就是中级程序员和高级程序员的差别所在了。 

     本篇的关键字:【函数的默认参数】【剩余参数】【拓展运算符】【name属性】【new.target】【箭头函数】【尾调用优化】

  • 相关阅读:
    Chrome cookies folder
    Fat URLs Client Identification
    User Login Client Identification
    Client IP Address Client Identification
    HTTP Headers Client Identification
    The Personal Touch Client Identification 个性化接触 客户识别
    购物车 cookie session
    购物车删除商品,总价变化 innerHTML = ''并没有删除节点,内容仍存在
    453
    购物车-删除单行商品-HTMLTableElement.deleteRow()
  • 原文地址:https://www.cnblogs.com/tangjiao/p/9244018.html
Copyright © 2011-2022 走看看