手写 call , apply, bind 方法

手写call方法
手写call思路:对象通过 Object.attributes 来访问它的属性.这时给对象添加一个属性,该属性指向需要绑定对象的函数。
通过Object.attributes去调用函数,函数中的this 指向了调用它的对象Object. 最后将属性从对象中移除。
    1. object.f = this
    2. object.f()
    3. delete object.f;
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)
  • argument类数组转换为数组的方法
//方法一 
let arg = Array.prototype.slice.call(argment,1)
//方法二 
let arg = Array.prototype.concat.apply([],arguments)
//方法三 
let arg = Array.from(arguments)
// 方法四
let arg = [...arguments]
  • ...展开语法传递参数
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对象

手写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)

*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)
  • ...展开语法实现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)
手写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
阅读 123

推荐阅读