专栏首页前端小菜鸟JavaScript中call,apply,bind方法的使用及原理
原创

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 条评论
登录 后参与评论

相关文章

  • 前端路由实现方式

    路由:根据不同的url地址,显示不同的页面或者更新局部视图,呈现出来不同的内容。前端路由的实现方式分为服务端,Hash,History三种常见的路由实现方式。

    伯爵
  • 前端开发涉及的Web安全

    前端开发涉及常见的Web安全漏洞有:浏览器Web安全,跨站脚本攻击(XSS),跨站请求伪装(CSRF),点击劫持,HTTP劫持,DNS劫持,文件上传漏洞等,以跨...

    伯爵
  • JavaScript的构造函数

    在Java语言中,我们使用构造函数是实例化对象的过程,在JavaScript语言中我们可以使用构造函数的方式创建对象,如:

    伯爵
  • 前端路由相关实现

    版权声明:本文为吴孔云博客原创文章,转载请注明出处并带上链接,谢谢。 https://blog.csdn.net/wkyseo/articl...

    空空云
  • 悟透JavaScript

    这本书分为了三个部分,第一部分“JavaScript真经”主要讲解JavaScript的一些核心概念,主要是数据类型、函数、原型、对象。并通过在JavaScri...

    张子阳
  • Javascript/ES6语法快速查询

    这是一个 ES2015(ES6) 的Cheatsheet,其中包括提示、小技巧、最佳实践和一些代码片段,帮助您 完成日复一日的开发工作。

    mojocn
  • 理想的继承范式——寄生组合式继承

    就只是小茗
  • JavaScript设计模式--简单工厂模式

    工厂模式定义一个用于创建对象的接口,这个接口由子类决定实例化哪一个类。该模式使一个类的实例化延迟到了子类。而子类可以重写接口方法以便创建的时候指定自己的对象类型...

    wfaceboss
  • 星际巡航—玩转javascript中this!

    在javascript异步编程、函数式编程中,有两个至关重要的技术callback与this变量,又称之为回调与当前对象上下文。

    张晓衡
  • 等了那么久,终于等到新游戏啦!大炮英雄Cocos Creator实现,关注获取代码!

    摆放一个背景图,在背景图上添加背景地面、开始按钮、4个角色选择按钮、游戏logo。

    张晓衡

扫码关注云+社区

领取腾讯云代金券