call, apply, bind 讲解 (含自己实现)

this指向基本知识

例 1
var name = '伞兵一号', age = 18;
var obj = {
	name: '伞兵二号',
    objAge: this.age,
    myFun: function() {
		console.log( this.name + '年龄' + this.age)
    }
}
obj.objAge;  // 19
obj.myFun()  // 伞兵二号年龄 undefined (obj中没有age这个属性)
例 2
var name = '伞兵一号';
function shows() {
	console.log(this.name)
}
shows()  // 伞兵一号

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

三者的作用

1,call()、apply()、bind() 都是用来重定义 this 这个对象的!

如:

var name = '小王', age = 12;
var obj = {
  name: '小张',
  age: 13,
  myFun: function() {
    console.log(this.name + '年龄' + this.age);
  }
}
var test = {
  name: '小红',
  age: 14
}
obj.myFun.call(test); // 小红年龄14
obj.myFun.apply(test); // 小红年龄14
obj.myFun.bind(test)(); // 小红年龄14

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

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

2,对比call 、bind 、 apply 传参情况下

const name = '小王'; const
  age = 12;
const obj = {
  name: '小张',
  age: 13,
  myFun(sex, high) {
    console.log(`${this.name} 年龄 ${this.age} 性别 ${sex} 身高 ${high}`);
  },
};

const test = {
  name: '小红',
  age: 14,
};
obj.myFun.call(db,'成都','上海');    // 小红 年龄 14 性别 男 身高 188
obj.myFun.apply(db,['成都','上海']);     // 小红 年龄 14 性别 男 身高 188
obj.myFun.bind(db,'成都','上海')();     // 小红 年龄 14 性别 男 身高 188
obj.myFun.bind(db,['成都','上海'])();  // 小红 年龄 14 性别 男 身高 188 undefined

三者的区别

从上面四个结果不难看出:

call 、bind 、 apply 这三个函数的第一个参数都是 this 的指向对象,第二个参数差别就来了:

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

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

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

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

手写call

Function.prototype.myCall = function(context) {
    if (typeof this !== "function") {
        throw new Error("Function.prototype.myCall - what is trying to be bound is not callable");
    }
    // context代表传进来的第一个参数,也就是我们要把函数的this绑定到context上
    let that = context || window;
    // 获取剩余参数
    let args = Array.prototype.slice.call(arguments, 1)
    that.func = this; // 这个this就是调用call的这个对象,也就是我们需要改变this指向的函数f,因为是f调用了myCall,根据this规则,this指向调用者也就是myCall
    that.func(...args); // 现在this就绑定到context上了,我们传入参数执行就好
    delete that.func; // 回收func,不占用内存
}

// 测试
function getUser(age){
    console.log(this.name);
    console.log(age)
}
let user = {    
    name: 'Jason'
}
getUser.myCall(user,24) // Jason   24

补充:argument类数组转换为数组的方法

//方法一 
let arg = Array.prototype.slice.call(argment,1)
//方法二 
let arg = Array.prototype.concat.apply([],arguments)
//方法三 
let arg = Array.from(arguments)
// 方法四
let arg = [...arguments]

手写aplly

实现和call差不多,只不过传入的只有两个参数,一个是需要绑定this的对象,一个存放参数的数组

Function.prototype.myApply = function(context, arg) {
    if (typeof this !== "function") {
        throw new Error("Function.prototype.myApply - what is trying to be bound is not callable");
    }
    let that = context || window;
    if (!Array.isArray(arg)) {
        console.log("参数必须是一个数组");
        return;
    }
    that.func = this;
    that.func(...arg);
    delete that.fun;
}

// 测试
function getUser(age){
    console.log(this.name);
    console.log(age)
}
let user = {    
    name: 'Jason'
}

getUser.myApply(user,[24]) // Jason   24

手写bind

通过上述讲解可以看出 bind 有如下特性:

  • 1、指定 this
  • 2、传入参数
  • 3、返回一个函数
  • 4、柯里化
Function.prototype.myBind = function (context) {
    // 调用 bind 的不是函数,需要抛出异常
    if (typeof this !== "function") {
        throw new Error("Function.prototype.myBind - what is trying to be bound is not callable");
    }
    const that = context || window;
    // 保留需要改变this的函数
    const selfThis = this;
    // 实现第2点,因为第1个参数是指定的this,所以只截取第1个之后的参数
    let args = Array.prototype.slice.call(arguments, 1);
    // 实现第3点,返回一个函数
    return function() {
        // 实现第4点,这时的arguments是指bind返回的函数传入的参数
        // 即 return function 的参数
        args = [...args, ...arguments];
        // 实现第1点
        that.func = selfThis;
        that.func(...args);
        delete that.func;
    }
}
Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐