本文共 1100 字,读完只需 4 分钟

概述

前一篇文章我们尝试模拟实现了 call 和 apply 方法,其实 bind 函数也可以用来改变 this 的指向。bind 和 call和 apply 两者的区别在于,bind 会返回一个被改变了 this 指向的函数。

本文介绍如何模拟实现 bind 函数:

首先观察 bind 函数有什么特点:

var person = {
    name: 'jayChou'
}

function say(age, sex) {
    console.log(this.name, age, sex);
}

var foo = say.bind(person, '男', 39);

foo();  // jayChou 男 39
  1. 返回一个函数
  2. 函数参数以逗号的形式传入
  3. 改变了 this 的指向

一、call 简单实现

既然 bind 内部也要用改变 this 指向,我们可以用现成的 call 函数来实现这一功能。

Function.prototype.newBind = function(context) {
    if(typeof this !== 'function') {
        throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
    }
    var self = this;
    return function () {
        return self.call(context)
      }
}

说明一下:
if 判断是为了校验,只能让函数来调用此方法,并抛出错误。
第二个 return 是为了让返回的函数有返回值的功能。

测试一下:

var person = {
    name: 'jayChou'
};

var say = function() {
    console.log(this.name);
}

var foo = say.newBind(person);
foo();  // jayChou

成功啦。

二、传递参数

刚才的函数是没有传递参数,当然不行,所以我们想办法把函数的参数也传递进去。

bind 函数有个特点,就是在绑定的时候可以传参,返回的函数还可以继续传参。

var person = {
    name: 'jayChou'
};

var say = function(p1, p2) {
    console.log(this.name, p1, p2);
}

var foo = say.bind(person, 18);
foo(20);  // jayChou 18 20

还可以写成:

say.bind(person, 18)(20); // jayChou 18 20

好,进入正题,考虑传参的事。在前面的文章,我们讲过 arguments 类数组对象的一些特性,不能直接调用数组的方法,但是可以用原型方法间接来调用,我们采用 apply 的方式来传递参数。

Function.prototype.newBind = function(context) {
    if(typeof this !== 'function') {
        throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
    }
    var self = this;
    var args = Array.prototype.slice.call(arguments, 1);  // 间接调用数组方法,获取第一次传的参数
    
    return function () {
        var innerArgs = Array.prototype.slice.call(arguments);
        return self.apply(context, args.concat(innerArgs))
      }
}


var person = {
    name: 'jayChou'
};

var say = function(p1, p2) {
    console.log(this.name, p1, p2);
}

var foo = say.newBind(person, 18);  // 第一次传参
foo(20);  // 第二次传参

最后输出:

jayChou 18 20

结果正确,以上就完成了 bind 函数基本功能的实现。

但是!

三、作为构造函数时?

bind 函数其实还有一个非常重要的特点:

bind返回的函数如果作为构造函数,搭配new关键字出现的话,我们的绑定this就需要“被忽略”。

意思就是指:当使用 nuw 关键字把 bind 返回的函数作为构造函数,之前改变了指向的 this 就失效了。返回的函数的 this 就关联到了构造函数的实例对象上。

针对这个特点,需要再做一些修改:

Function.prototype.newBind = function(context) {
    if(typeof this !== 'function') {
        throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
    }
    var self = this;
    var args = Array.prototype.slice.call(arguments, 1);  // 间接调用数组方法,获取第一次传的参数
    
    let tempFn = function {};  // 利用一个空函数作为中转
    
    tempFn.prototype = this.prototype;  // 修改返回函数的 prototype 为绑定函数的 prototype,实例就可以继承绑定函数的原型中的值
    
    var resultFn = function () {
        var innerArgs = Array.prototype.slice.call(arguments);
        if (this instanceof tempFn) {  // 如果 返回函数被当做构造函数后,生成的对象是 tempFn 的实例,此时应该将 this 的指向指向创建的实例。
            return self.apply(this, args.concat(innerArgs));
        } else {
            return self.apply(context, args.concat(innerArgs))
        }
      }
      
    resultFn = new tempFn();
    return resultFn;
}

总结

本文尝试模拟实现了 bind 函数,bind 函数与 call,apply 函数的区别在于,bind 函数返回一个指定了 this 的函数,函数并未执行。其次,当返回的函数作为构造函数时,之前绑定的 this 会失效。

欢迎关注我的个人公众号“谢南波”,专注分享原创文章。


种瓜南山下
305 声望18 粉丝

南瓜