美文网首页
ES6 函数的扩展

ES6 函数的扩展

作者: 貓咪是大王 | 来源:发表于2018-12-17 10:25 被阅读0次

(一)与结构赋值的默认值结合使用

//先来看一个例子
function foo({x,y=1}){
        console.log(x,y)
}
当函数参数为一个对象,变量才会通过解构赋值生成
function foo(url,{name="", age=88, other={} }){
        console.log(age);
}
不能省略第二个参数
function foo(url,{name='猫咪是大王'}={}){
        console.log(name);
    }
第二个参数可以省略

对函数的参数设置默认值的两种写法区别

function methods1({x=0,y=0}={}){//设置对象解构赋值,函数参数的默认值为空对象
        return [x,y];
    }
    function methods2({x,y}={x:0,y:0}){//没有设置对象解构赋值,函数参数的默认值是一个有具体属性的函数
        return [x,y];
    }
函数无参数
变量都传值
x传值y不传
x、y都无值

(二)参数默认值的位置

一般定义默认值的参数应该是函数的尾参数,这样比较容易看出省略哪些参数,如果是非尾部的参数设置默认值,则这个参数无法省略

function foo(x=0,y){
        return [x,y];
    }
function foo(x=1,y=66,z){
        return [x,y,z]
    }
undefined触发该参数等于默认值,而null没有

(三)函数的length属性

指定了默认值后,函数的length属性将返回没有指定默认值属性参数个数,即length属性将失真

var f=function (a){console.log(a)}
    console.log(f.length)
    var ff=function (a=0){console.log(a)}
    console.log(ff.length)
    var fff=function (a,b=0,c=0){console.log(a)}
    console.log(fff.length)

特殊情况:如果设置了默认值的参数不是为参数,那么length属性也不再计入后面的参数

var f=function (a=1,b,c){console.log(a)}
    console.log(f.length)

(四)作用域

一旦设置参数的默认值,函数进行声明初始化时,参数会形成一个单独的作用域,初始化结束后,这个作用域就会消失,但是这种语法在不设置参数默认值值时是不会出现的

var x=1;
    function foo(x,y=x){
        console.log(y);
    }
//调用函数foo时,参数常量形成一个单独的作用域,在这个作用域里面,默认值变量x指向第一个参数x,而不是全局变量x
let x=1;
 function foo(y=x){
        let x=88;
        console.log(y)
    }
//函数foo调用时,参数y=x形成一个单独的作用域,在这个作用域里面,变量x本身没有定义,
//所以指向外层的全局变量x,因而函数调用时,函数体内部的局部变量x影响不到默认值变量x


如果此时全局变量x不存在则报错

如果参数默认值为一个函数,该函数的作用域也遵循这个规则

    let foo="out";
    function oFoo(func=x => foo){
        let foo="in";
        console.log(func())
    }
    var x=1;
    function foo(x,y = function(){x=2}){
        var x=3;
        y();
        console.log(x)
    }
//foo的参数形成一个单独的作用域,这个作用域轴线声明了变量x,然后声明了变量y,
//y的默认值是一个匿名函数,这个匿名函数内部的变量x指向同一个作用域的第一个参数写x,
//而foo内部又声明了一个内部变量x,该变量与第一个参数x由于不是同一个作用域,所以不是
//同一个变量。执行y后,内部变量x和外部全局变量x的值都没变

(五)rest参数

rest参数用于获取函数的多与参数,它搭配的变量是一个数组,该变量将多余的参数放入其中

function add(...values){
        let sum=0;
        for(var val of values){
            sum +=val;
        }
        return sum;
    }

注意:
1.函数的length属性不包括rest参数
2.rest参数只能是最后一个参数,它之后不能再有其他参数,否则会报错

(六)name属性

函数的name属性返回该函数的函数名

function foo(){}

构造函数返回的函数实例的name属性
bind返回的函数,name属性值会加上bound前缀

(七)箭头函数

ES6允许使用 箭头 (=>)来定义函数

var foo = a => a
//上面代码等同于
var foo=function(a){
  return a;
}

//如果箭头函数不需要参数或者需要多个参数,可以使用圆括号来代表参数部分
var foo= () => 666
//等同于
var foo = function(){
  return 66;
}

var sum = (a1,a2) => a1+a2;
//等同于
var sum=function(a1,a2){
  return a1+a2;
}

//如果箭头函数的代码部分多于一条语句,则要使用大括号将其括起来,并使用return语句返回
var sum = (a1,a2) => { return a1+a2;}

//由于大括号被解释为代码块,所以如果箭头函数直接返回一个对象,必须在对象外面加上括号
var getArticles = id => ({ id:id, name:"猫咪是大王"});

//箭头函数也可以与变量的解构赋值结合使用
const full = ({ first, last}) => first + ' ' +last;
//等同于
function full(person) {
  return person.first+ ' '+person.last;
}

箭头函数的一个用处就是简化回调函数

[1,2,3].map(x => x * x);
//等同于
[1,2,3].map(function(){
  return x * x;
})

var result = values.sort((a,b) => a-b)
//等同于
var result=values.sort(function(a,b){
  return a - b;
})

rest 参数与箭头函数结合

const num=(...nums) => nums
const headAndfoot=(head,...foot) =>[head,foot]

注意:
1.函数体内的this对象就是定义时所在的对象,而不是使用时所在对象
this对象的指向是可变的,但是在箭头函数中是固定的

    function foo(){
        setTimeout(() => {
            console.log('id', this.id);
        },1000)
    }
    var id=66;
//如果是普通函数,执行this应该指向全局对象window,输出66,但是箭头函数导致this总是指向函数定义生效时所在的对象,所以输出88
//箭头函数可以让setTimeout里面的this绑定定义时所在的作用域而不是指向运行时所在的作用域
    function Timer(){
        this.a1=0;
        this.a2=0;
        // 箭头函数
        setInterval(() => this.a1++, 1000);
        // 普通函数
        setInterval(function(){
            this.a2++;
        },1000);
    }
    var timer = new Timer();
    setInterval(() => console.log('a1:',timer.a1),3100);
    setInterval(() => console.log('a2:',timer.a2),3100);

2.箭头函数不可以当构造函数,不能使用new命令,否则报错

var foo=() => 88;

3.不可以用arguments对象,该对象在函数体不存在,如果要用,可以用rest参数代替

var foo=() => {
        console.log(arguments);
        return 1;
}

4.不可以用yield命令,因此箭头函数不能用作Generator函数

一个Generator函数与普通function的区别就是函数名前面多了一个星号 * 但是执行时有很大不同,
与yield命令配合,可以实现暂停执行的功能

以下代码中有几个this

    function foo(){
        return () =>{
            return () =>{
                return () =>{
                    console.log('id',this.id);
                };
            };
        };
    }
    var f =foo.call({id:1});

    var f1=f.call({id:2})()();
    var f2=f().call({id:3})();
    var f3=f()().call({id:4});
上面的代码只有一个this,就是foo上午this,所以f1,f2,f3都输出同样的结果。因为所有的内层函
数都是箭头函数,都没有自己的this,他们的this都是最外层foo函数的this

除了this外,arguments,super,new.target在箭头函数中也是不存在的
箭头函数没有自己的this,当然也不能用call()、apply()、bind()这些方法去改变this的指向

嵌套的箭头函数

//es5语法
    function insert(value){
        return {into:function (array){
            return {after:function(afterValue){
                array.splice(array.indexOf(afterValue) +1,0,value);
            }};
        }};
    }
    insert(2).into([1,3]).after(1);

//es6语法
    let insert=(value) => ({into:(array) => ({after:(afterValue)=>{
        array.splice(array.indexOf(afterValue) + 1, 0, value);
        return array;
    }})})

(八)绑定this

箭头函数可以绑定this对象,大大减少了显式this对象的写法(call、apply、bind),但是并非适用于所有场合
函数绑定运算符是并排的双冒号(::),双冒号左边是一个对象,右边是一个函数。该运算符会自动将左边的对象作为上下文环境(即this对象)绑定到右边的函数上。

foo::bar;
//等同于
bar.bind(foo);

foo::bar(...arguments);
//等同于
bar.apply(foo,arguments);

如果双冒号左边为空,右边是一个对象的方法,则等于将该方法绑定在该对象上

var method=obj::obj.foo;
//等同于
var method = ::obj.foo;

let log=::console.log;
//等同于
var log=console.log.bind(console)

由于双冒号运算符返回的还是原对象,因此可以采用链式写法

    import {map, takeWhile,forEach} from "iterlib";
    getPlayers()
    ::map(x=> x.character())
    ::takeWhile(x=> x.strength>100)
    ::forEach(x => console.log(x))
    
    let {find,html}=jake;
    docment.querySelectorAll("div.myClass")
    ::find("p")
    ::html("lalala")

(九)尾调用优化

尾调用是函数式编程的一个重要概念,是指某个函数的最后一步调用另一个函数

function f(x){
  return g(x)
}

一些错误的实例

//1.
function f(x){
  let y=g(x);
  return y;
}  //调用函数g之后还有操作

//2.
function f(x){
        return g(x)+1;
    } //调用函数g之后还有操作

//3.
function f(x){
        g(x);
    }
//等同于
function f(x){
        g(x);
        return undefined;
    }

尾调用不一定出现在尾部,只要是最后一步操作即可

function f(x){
        if(x>0){
            return m(x)
        }
        return n(x);
    }//函数m和n都属于尾调用,因为他们都是函数f的最后一步操作

尾调用优化

  • 尾调用由于是函数的最后一步操作,所以不需要保留外层函数的调用帧,因为调用位置、内部变量等信息都不会再用到了,直接用内层函数的调用帧,取代外层函数的调用帧就可以了。
    function f(){
        let m=1;
        let n=2;
        return g(m+n);
    }
    f();

    //等同于
    function f(){
        return g(3);
    }
    f();

    //等同于
    g(3)

尾递归
函数自己调用自己称为递归,尾调用自身就称为尾递归
递归非常耗内存,我在菜鸟题中有提过,因为需要同时保存成百上千个调用帧,所以很容易发生栈溢出,但是对于尾递归来说,只存在一个调用帧,所以永远不会发生栈溢出错误

    function f(n,f1=1,f2=1){
        if(n<=1){
            return f2;
        }
        return f(n-1,f2,f1+f2)
    }

(十)严格模式

ES6的尾调用只在严格模式下开启,正常模式下无效,因为在正常模式下函数内部会有两个变量,可以跟踪函数的调用栈

  • func.arguments:返回调用时函数的参数
  • fun.caller:返回调用当前函数的那个函数
    尾调用优化发生时,函数的调用栈会改写,因此上面两个变量就会失真。严格模式禁用这两个变量,所以尾调用仅在严格模式下生效

相关文章

网友评论

      本文标题:ES6 函数的扩展

      本文链接:https://www.haomeiwen.com/subject/jlbacqtx.html