JavaScript中call,apply,bind方法的使用及原理

在JavaScript里,call(),apply(),bind()都是Function内置的三个方法, 它们的作用都是显示的绑定this的指向,三个方法的第一个参数都是this指向的对象,也就是函数在运行时执行的上下文。

当我们定义一个新的对象,需要使用其他对象的方法的时候,我们不需要重新开发重复的方法逻辑,借助apply,apply,bind三个方法可以实现对这些的方法的调用。

我们定义三者的概念:

apply:调用一个对象(obj)的方法(func),并使用新的对象(thisArg)代替该对象,参数是数组

obj.func.apply(thisArg, [argsArray])

call:调用一个对象(obj)的方法(func),并使用新的对象(thisArg)代替该对象,参数是列表

obj.func.call(thisArg, arg1, arg2, ...)

bind:bind()方法创建一个新的函数,在bind()被调用时,这个新函数的this被bind的第一个参数指定,其余的参数将作为新函数的参数供调用时使用,第一个thisArg在setTimeout中创建一个函数时传递的原始值都会转化成object,如果bind参数列表为空,thisArg赋值当前当前运行环境this

function.bind(thisArg[,arg1[,arg2[, ...]]])

特点:

  • apply,call,bind三个方法第一个参数都是函数在调用时this指向的对象,也就是运行时的上下文(this显示绑定的原理)
  • apply,call第一个参数为空,null,undefined,this指向的是window
  • apply,call两个方法只是参数形式有所不同,apply参数是一个数组,call则是参数列表版本
  • apply,call 则是立即调用,bind 是则返回对应函数

常见的一些应用:

数组合并

我们创建arr和other两个数组,当我们需要合并两个数组的时候,可以使用concat方法进行操作,但是concat需要创建新的数组对象,我们可以借助apply方法不需要创建新的对象,不需要遍历数组,也可以实现需求:

let arr = [2,3,4,5,6];
let other = [10,89];
arr.push.apply(arr, other); // or Array.prototype.apply(arr, other)

console.log(arr); // [2, 3, 4, 5, 6, 10, 89]

我们借助了apply方法,push方法在调用的时候,this指向的是arr对象,这里的参数列表other,我们将other数组合并到arr数组对象上。

下面的是两种比较常见的写法,其实是一样的

继承

function Animal(name) {
    this.name = name;
    this.run = function() {
        console.log(`${this.name} is running!`)
    }
}

function Cat(name) {
    Animal.call(this, name);
}

let tom = new Cat("TOM");
tom.run(); // TOM is running!

创建绑定函数

this.name = 'global';
let cacheFun = {
    name : 'cache',
    getCacheName : function() {
        console.log("Name: ",this.name)
    }
}
cacheFun.getCacheName(); // Name is cache

let cacheName = cacheFun.getCacheName;
cacheName(); // Name:  global

在创建绑定函数的实例中,我们分析一下一部分调用过程:

  • 我们cacheFun对象作为缓存name字段的对象
  • 我们声明变量cacheName存储getCacheName方法的引用
  • 当我们调用cacheName方法时this绑定的是window对象,所以this.name获取的是global

我们需要借助bind方法,在函数调用的时候,绑定this的执行到cacheFun对象上:

let cacheNameByBind = cacheName.bind(cacheFun);
cacheNameByBind();  // Name is cache

实现原理

  • apply
Function.prototype.applyFun = function(content) {
    content = content || window;
    content.fun = this;
    var argsArr = arguments[1];

    if(!argsArr) {
       return content.fun();
    }
    if(!Array.isArray(argsArr)) {
        throw Error("参数列表必须是数组的格式!")
    }else {
        var result ;
        var len = argsArr.length;
        for(var i=0;i < len; i++) {
            result = content.fun(argsArr[i]);
        }
        delete content.fun;
        return result;
    }
}

// 测试
var arr = [1,5];
var other = [34,5,5];
Array.prototype.push.applyFun(arr, other);
console.log(arr) //  [1, 5, 34, 5, 5]
  • call
Function.prototype.callFun = function(content) {
    content = content || window;
    content.fun = this;

    var args = [];

   for(var i=1;i<arguments.length;i++) {
        args.push(arguments[i]);
    }
    eval(`content.fun(${args})`); //字符串拼接
    delete content.fun;
}

let arr = [1,2,3]
Array.prototype.push.callFun(arr, 4,5,6,6);
console.log(arr); // [1,2,3,4,5,6,6]
  • bind

基础版

Function.prototype.bindFun = function(content) {
  let that = this;
  return function() {
    return that.apply(content)
  }
}

let Person = {
  getName : function() {
    console.log(`Name is ${this.name}`)
  }
}
let Liming = {
  name: 'ming'
}


let getName = Person.getName;
let getNmaeByming = getName.bindFun(Liming);

getNmaeByming(); // Name is ming

最终版本,处理通过new创建绑定是的this绑定,将that对象传入闭包缓存

Function.prototype.bindFun = function(content) {
  if(typeof this !== 'function') {
    return;
  }
  var that = this;
  var args = Array.prototype.slice.call(arguments, 1);
  var _bindFun = function() {
    var _content = this instanceof that ? this: content;
    var _args = [].slice.call(arguments, 0);
    that.apply(_content , args.concat(_args));
  }
  var BindFun = function() {};

  if(this.prototype) {
    BindFun.prototype = this.prototype;  
    _bindFun.prototype = new BindFun();
  }
  return _bindFun;
}

// 测试
function Point(x, y) {
  this.x = x;
  this.y = y;
}

Point.prototype.toString = function() { 
  return this.x + ',' + this.y; 
};

var emptyObj = {};
var YAxisPoint = Point.bindFun(emptyObj, 0);

var axisPoint = new YAxisPoint(5);
var str = axisPoint.toString(); 

console.log(str) // '0,5'

参考

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

编辑于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券