call、apply和bind

定义

ECMAScript规范为所有函数都包含两个方法(这两个方法非继承而来), call 和 apply 。这两个函数都是在特定的作用域中调用函数,能改变函数的作用域,实际上是改变函数体内 this 的值 。


调用函数,传递参数

    //定义一个add 方法
    function add(x, y) {
        return x + y;
    }

    //用call 来调用 add 方法
    function myAddCall(x, y) {
        //调用 add 方法 的 call 方法
        return add.call(this, x, y);
    }

    //apply 来调用 add 方法
    function myAddApply(x, y) {
        //调用 add 方法 的 applly 方法
        return add.apply(this, [x, y]);
    }

    console.log(myAddCall(10, 20));    //输出结果30
  
    console.log(myAddApply(20, 20));  //输出结果40

改变函数作用域

    var name = '小白';

    var obj = {name:'小红'};

    function sayName() {
        return this.name;
    }

    console.log(sayName.call(this));    //输出小白

    console.log(sayName. call(obj));    //输入小红

高级用法,实现 js 继承

    //父类 Person
    function Person() {
        this.sayName = function() {
            return this.name;
        }
    }

    //子类 Chinese
    function Chinese(name) {
        //借助 call 实现继承
        Person.call(this);
        this.name = name;

        this.ch = function() {
            alert('我是中国人');
        }
    }

    //子类 America
    function America(name) {
        //借助 call 实现继承
        Person.call(this);
        this.name = name;

        this.am = function() {
            alert('我是美国人');
        }
    }


    //测试
    var chinese = new Chinese('成龙');
    //调用 父类方法
    console.log(chinese.sayName());   //输出 成龙

    var america = new America('America');
    //调用 父类方法
    console.log(america.sayName());   //输出 America

区别

call 和 apply 最大的好处:方便我们解耦,对象不需要和方法有任何的耦合性,能使我们写出更好的面相对象程序。
大家如果看一些 js 框架底层的话会看到好多地方都有大量用到。

call和apply都是改变上下文中的this并立即执行这个函数,bind方法可以让对应的函数想什么时候调就什么时候调用,并且可以将参数在执行的时候添加,这是它们的区别,根据自己的实际情况来选择使用。

参考链接

JavaScript中call,apply,bind方法的总结

手写call

//ES6写法
Function.prototype.myCall = function(obj,...arg){
    let val ;
    obj._fn_ = this;
    val = obj._fn_(...arg);  //不能直接return obj._fn_(...arg) 这样就不delete属性了
    delete obj._fn_;
    return val;
}
//ES5写法
Function.prototype.myCall = function(obj){
    let arg = [];
    let val ;
    for(let i = 1 ; i<arguments.length ; i++){ // 从1开始
        arg.push( 'arguments[' + i + ']' ) ;
    }
    obj._fn_ = this;
    val = eval( 'obj._fn_(' + arg + ')' ) // 字符串拼接,JS会调用arg数组的toString()方法,这样就传入了所有参数
    delete obj._fn_;
    return val;
}

手写apply

Function.prototype.myApply = function(obj,arr){
    if(obj === null || obj === undefined){
        obj = window;
    } else {
        obj = Object(obj);
    }
    let args = [];
    let val ;
    for(let i = 0 ; i<arr.length ; i++){
        args.push( 'arr[' + i + ']' ) ;
    }
    obj._fn_ = this;
    val = eval( 'obj._fn_(' + args + ')' ) 
    delete obj._fn_;
    return val
}

手写bind

if (!Function.prototype.bind) {
  Function.prototype.bind = function (oThis) {
      if (typeof this !== 'function') {
      return
    }
    
    let self = this
    let args = Array.prototype.slice.call(arguments, 1)
    let fBound = function() {
        let _this = this instanceof self ? this : oThis //检测是否使用new创建
        return self.apply(_this, args.concat(Array.prototype.slice.call(arguments)))
    }
    
    if (this.prototype) {
      fBound.prototype = this.prototype
    } 
    return fBound
  }
}
comments powered by Disqus