手写call,apply,bind方法

2,148 阅读2分钟

1. 手写call方法

手写call思路: 对象通过 Object.attributes 来访问自身的属性,这是给对象添加一个属性,该属性指向需绑定对象的函数上。通过 Object.attributes 去调用函数,函数中的 this 指向调用它的对象,最后将属性从对象中移除。

Function.prototype.myCall = function(context) {
   const self = context || window;
   let arg = Array.prototype.slice.call(arguments,1)
   self.f = this;
   self.f(...arg);
   delete self.f;
}

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

1.1  arguments 类似数组转换为数组的方法

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

1.2 ....  展开语法传递参数

Function.prototype.myCall = function(context, ...item) {
   const self = context || window;
   self.f = this;
   self.f(...item);
   delete self.f;
}

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

如上代码中的 const self = this || window 是用指定传入的对象为null时,默认为 window对象

2. 手写apply方法

apply 与 call 的实现方式相同,apply 只能出入两个参数, 第一个参数为绑定的 this 对象,第二个参数为一个数组,作为函数的参数。

Function.prototype.myApply = function(context, arr) {
    const self = context || window;
    if(!Array.isArray(arr)){
      throw new TypeError("传入的参数类型不正确,传入一个数组");
    }
     self.f = this;
     self.f(...arr);
     delete self.f;
}

// 测试
function getAge(age){
    console.log(age)
}
getAge.myApply(null, 24)

2.1  arguments 实现 apply 可传递多个参数(其实的是一个伪数组对象)

Function.prototype.myApply = function(context) {
    const self = context || window;
    let arg = Array.prototype.slice.call(arguments,1)
    if(!Array.isArray(arg)){
      throw new TypeError("传入的参数类型不正确,传入一个数组");
    }
     self.f = this;
     self.f(...arg);
     delete self.f;
}

// 测试
function getAge(age){
    console.log(age)
}
getAge.myApply(null, 24)

2.2 ... 展开语法实现 apply 传递多个参数(其实传入了一个数组)

Function.prototype.myApply = function(context,...item) {
    const self = context || window;
    if(!Array.isArray(item)){
      throw new TypeError("传入的参数类型不正确,传入一个数组");
    }
     self.f = this;
     self.f(...item);
     delete self.f;
}

// 测试
function getAge(age){
    console.log(age)
}
getAge.myApply(null, 24)

3. 手写bind方法

bind 与call,apply  不同在于函数执行后将创建一个新函数返回。bind 函数可传入两个参数,第一个参数是 this 绑定的对象, 第二个参数是一个数组或者类数组对象,第二个参数将作为实参在新函数执行时被传入。

Function.prototype.myBind = function(context){
    const obj = context || window;
    const self = this;
    let arg = Array.prototype.slice.call(arguments, 1);
    return function(){
        let arg2 = Array.prototype.slice.call(arguments, 1);
        self.apply(obj, arg.concat(arg2));
    }
}

如上 mybind 创建的新函数作为普通函数调用时 this 指向你所绑定的对象。当做为构造函数通过 new 去创建实例对象时 this 绑定的对象就 会被忽略了。因为构造函数中的会隐式创建一个实例对象,this 指向这个实例对象。而不再是指向你所指定的对象。

Function.prototype.myBind = function(context){
    const obj = context || window;
    const self = this;
    let arg = Array.prototype.slice.call(arguments, 1);
    const newFun = function(){
        let arg2 = Array.prototype.slice.call(arguments, 1);
        if(this instanceof newFun) {
            self.apply(this, arg.concat(arg2));
        } else {
            self.apply(obj, arg~~~~.concat(arg2));
        }
    }

    //支持 new 创建对象时,实例__proto__指向绑定对象函数的prototype
    newFun.prototype = this.prototype
    return newFun;
}

// 测试
let user = {
        name: 'Jason',
        age: 23,
}
function Fun(school) {
    this.school = school;
    console.log(this.name);
    console.log(this.age);
    console.log(this.school);
}
let fun = Fun.myBind(user, '江南大学');
//  this -> user
fun()

// this -> 对象实例
let obj = new  fun();
console.log(obj.__proto__ === Fun.prototype) // ture