模拟实现 new call apply bind函数

文章详细解释了JavaScript中new操作符创建对象的过程,包括设置_proto_属性和执行构造函数。接着介绍了call、apply和bind方法的使用和模拟实现,特别是如何处理this的指向和参数传递。最后讨论了bind在构造函数中的特殊情况及其模拟实现时的注意事项,包括处理new操作的情况和保持原型链的完整。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

使用new操作符,发生的过程

  1. 创建一个新的对象
  2. 把该对象的_proto_属性设置为构造函数的prototype属性,即完成原型链
  3. 执行构造函数中的代码,构造函数中的this指向该对象(创建的新对象)
  4. 返回对象 (注意函数没有返回对象类型,才会自动返回这个新对象)
function _new() {
    let obj = new Object() //创建一个对象
    constructor = [].shift.call(arguments) //获取构造函数
    obj.__proto__ = constructor.prototype //将对象的_proto_属性设置为构造函数的prototype属性
    let ret = constructor.apply(obj,arguments) //执行构造函数
    return ret instanceof Object ? ret : obj //返回对象
  }

es6写法

function _new(fn,...arg) {
    let obj = Object.create(fn.prototype)
    let ret = fn.apply(obj,arg)
    return ret instanceof Object ? ret : obj
  }

例子:

function Person (name,age) {
    this.name = name
    this.age = age
  }
  Person.prototype.getName = function() {
    return this.name
  }
  let p1 = new Person('张三',18)
  p1.getName()// 张三
 let p2 = _new(Person,'李四',20)
 p2.getName()// 李四

call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。
call 方法第一个参数是要绑定给this的值,后面传入的是一个参数列表。当第一个参数为null、undefined的时候,默认指向window

模拟的步骤可以分为:

  1. 将函数设为对象的属性
  2. 执行该函数
  3. 删除该函数
Function.prototype.myCall = function(context) {
     context = Object(context) || window;
    let arg = [...arguments].slice(1)
    context.fn = this
    let ret = context.fn(...arg)
    delete context.fn
    return ret
  }
  
let dog = {
    name: '狗',
    eat(food) {
      console.log(this.name + '爱吃' + food )
    },
  }
  let cat = {
    name: '猫'
  }
dog.eat.call(cat,'鱼') // 猫爱吃鱼
dog.eat.myCall(cat,'鱼') // 猫爱吃鱼

apply接受两个参数,第一个参数是要绑定给this的值,第二个参数是一个参数数组
apply和call用法相似,唯一的区别是apply传参是数组

Function.prototype.myApply = function(context,arg) {
    context = Object(context) || window;
    context.fn = this
    let ret = arg ? context.fn(...arg) : context.fn()
    delete context.fn
    return ret
  }

bind和call很相似,第一个参数是this的指向,从第二个参数开始是接收的参数列表。区别在于bind方法返回值是函数以及bind接收的参数列表的使用

模拟步骤

  1. 可传入一个对象,作为函数的上下文对象
  2. 可传入多个参数
  3. 返回一个新的函数

先实现指定函数上下文对象的功能

Function.prototype.myBind = function (context) {
    //获取到call函数
    let self = this;
    //需要返回的函数
    let fBound = function() {
      return self.apply(context);
    }
    return fBound;
}
let foo = {
    value: 1
};
function bar() {
  console.log(this.value);
}
let myBind1 = bar.myBind(foo)
myBind1() // 1

实现传参部分

Function.prototype.myBind = function (context) {
    //获取到call函数
    let self = this;
    let bindArg =  [...arguments].slice(1);
    //需要返回的函数
    let fBound = function() {
    let arg =  [...arguments];
      return self.apply(context,bindArg.concat(arg));
    }
    return fBound;
}
let foo = {
    value: 1
};
function bar(name,age) {
  console.log(this.value);
  console.log(name)
  console.log(age)
}
let myBind1 = bar.myBind(foo,'lisi')
myBind1(18)
// 1 lisi 18

构造函数模拟实现
bind函数还有个特点:

一个绑定函数也能使用new操作符创建对象:这种行为就像把原函数当成构造器。提供的 this 值被忽略,同时调用时的参数被提供给模拟函数。

let myBind2 = bar.bind(foo,'lis')
new myBind2(18)
// undefined lisi 18

因为使用了new关键字,使用new调用构造函数,会创建一个新的对象,this会指向这个新的对象,而新的对象上并没有value属性,所以会打印undefined。

继续在myBind上改造,判断如果返回函数被当做构造函数使用,那么上下文对象则指定为this

Function.prototype.myBind = function (context) {
    //获取到call函数
    let self = this;
    //需要返回的函数
    let bindArg =  [...arguments].slice(1);
    let fBound = function() {
      let arg =  [...arguments];
      return self.apply(this instanceof fBound ? this : context, bindArg.concat(arg));
    }
    return fBound;
}
let foo = {
    value: 1
};
function bar(name,age) {
  console.log(this.value);
  console.log(name)
  console.log(age)
}
let myBind1 = bar.myBind(foo,'lisi')
new myBind1(18)
// undefined lisi 18

注意需要关联调用myBind方法的函数的原型对象

Function.prototype.myBind = function (context) {
    //获取到call函数
    let self = this;
    //需要返回的函数
    let bindArg =  [...arguments].slice(1);
    let fBound = function() {
      let arg =  [...arguments];
      return self.apply(this instanceof fBound ? this : context, bindArg.concat(arg));
    }
    return fBound;
}
let foo = {
    value: 1
};
function bar(name,age) {
  console.log(this.value);
  console.log(name)
  console.log(age)
}
bar.prototype.skill = 'eat'
let myBind1 = bar.myBind(foo,'lisi')
let obj = new myBind1(18)
consloe.log(obj.skill) // undefined

可以看到bar函数原型对象上的属性并没有找到,因为我们返回了一个新的函数,这个新的函数被当做构造函数调用,并将新的创建的对象指定为bar函数的上下文对象,所以无法找到skill属性。

所以我们需要将myBind返回的函数与bar函数的原型对象进行关联

Function.prototype.myBind = function (context) {
    //获取到call函数
    let self = this;
    //需要返回的函数
    let bindArg =  [...arguments].slice(1);
    let fBound = function() {
      let arg =  [...arguments];
      return self.apply(this instanceof fBound ? this : context, bindArg.concat(arg));
    }
    fBound.prototype = Object.create(self.prototype);
    return fBound;
}
let foo = {
    value: 1
};
function bar(name,age) {
  console.log(this.value);
  console.log(name)
  console.log(age)
}
bar.prototype.skill = 'eat'
let myBind1 = bar.myBind(foo,'lisi')
 let o = new myBind1(18)
console.log(o.skill) // eat
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值