JavaScript训练合集——手写call & apply & bind

117 阅读3分钟

原生call、apply、bind的使用

这三者出现的原因都是与JavaScript中非常非常重要的this有关。

例1

var name = '小王', age = 17;
var obj = {
    name: '小张',
    objAge: this.age,
    myFun: function() {
        console.log( this.name + "年龄" + this.age );
    }
}
obj.objAge;   // 17
obj.myFun();  // 小张年龄 undefined 

例2

var fav = '剑圣';
function shows() {
    console.log(this.fav);
}
shows();   // 剑圣

比较一下这两者的this指向的差别, 第一个打印里面的this指向obj, 第二个全局声明的shows()函数thiswindow;

call、apply、bind都是用来重新定义this这个对象的

如:

var name = '小王', age = 17;
var obj = {
    name: '小张',
    objAge: this.age,
    myFun: function() {
        console.log( this.name + '年龄' + this.age );
    }
}
var db = {
    name: '盲僧',
    age: 99
}
obj.myFun.call(db);  // 盲僧年龄99
obj.myFun.apply(db); // 盲僧年龄99
obj.myFun.bind(db)();  // 盲僧年龄99

以上除了bind方法后面多了个()外, 结果返回都一致!

由此得出结论, bind返回的是一个新的函数, 你必须调用它才会被执行.

对比call、bind、apply传参情况

var name = '小王', age = 17;
var obj = {
    name: '小张',
    objAge: this.age,
    myFun: function(fm, t) {
        console.log( this.name + '年龄' + this.age, '来自' + fm + '来自' + t );
    }
}
var db = {
    name: '锐雯',
    age: 99
}

obj.myFun.call(db,'成都','上海');     // 德玛 年龄 99  来自 成都去往上海
obj.myFun.apply(db,['成都','上海']);      // 德玛 年龄 99  来自 成都去往上海  
obj.myFun.bind(db,'成都','上海')();       // 德玛 年龄 99  来自 成都去往上海
obj.myFun.bind(db,['成都','上海'])();   // 德玛 年龄 99  来自 成都, 上海去往 undefined

微妙的差距!

从上面四个结果不难看出: call 、bind 、 apply 这三个函数的第一个参数都是 this 的指向对象,第二个参数差别就来了:

call 的参数是直接放进去的,第二第三第 n 个参数全都用逗号分隔,直接放到后面obj.myFun.call(db,'成都', ... ,'string' )

apply 的所有参数都必须放在一个数组里面传进去obj.myFun.apply(db,['成都', ..., 'string' ])

bind 除了返回是函数以外,它 的参数和 call 一样。

当然,三者的参数不限定是 string 类型,允许是各种类型,包括函数 、 object 等等!

手写call、apply、bind

call

改变this指向用的,可以接受多个参数

 Function.prototype.myCall = function(ctx) {
    ctx = ctx || window; // ctx 就是 obj
    let fn = Symbol();
    ctx[fn] = this; // this就是foo
    let result = ctx[fn](...arguments);
    delete ctx[fn];
    return result;
}
let obj = { name: 'CreatorRay' };
function foo() { return this.name };
// 就是把foo函数里的this指向, 指向obj
console.log(foo.myCall());

/**
 * 用Symbol是因为他是独一无二的, 避免和obj里的属性重名
 * 原理就是把foo添加到obj里, 执行foo拿到返回值, 再从obj里把foo删掉
 * 
 */

apply

原理同上,只不过apply接受第二个参数是数组,不支持第三个参数

Function.prototype.myApply = function(ctx) {
    ctx = ctx || window;
    let fn = Symbol();
    ctx[fn] = this;
    let result;
    if(arguments[1]) {
        result = ctx[fn](...arguments[1]);
    }else {
        result = ctx[fn]();
    }
    delete ctx[fn];
    return result;
}

bind

Function.prototype.myBind = function(ctx) {
    const self = this;
    const fn = function() {};
    const bind = function() {
        const _this = this instanceof fn ? this : ctx;
        return self.apply(_this, [...args, ...arguments]);
    }
    fn.prototype = this.prototype;
    bind.prototype = new fn();
    return bind;
}

/**
 * bind 不会立即执行, 会返回一个函数
 * >>> 函数可以直接执行并且传参, 如foo.myBind(obj, 1)(2, 3), 所以需要
 * [...args, ...arguments] 合并参数
 * >>> 函数也可以new, 所以要判断原型 this instanceof fn
 * 
 */

call、apply、bind的区别

  • 都可以改变this指向
  • call和apply会立即执行,bind不会,而是返回一个函数
  • call和bind可以接收多个参数,apply只能接收两个,第二个是数组
  • bind参数可以分多次传入