Skip to content

手写 call、apply 及 bind 函数

javascript
Function.prototype.Dcall = function (context) {
  if (typeof this !== 'function') {
    throw new TypeError('Error')
  }
  var context = context || window
  context.say = this
  var args = []
  for (let i = 1; i < arguments.length; i++) {
    args.push('arguments[' + i + ']')
  }
  var result = eval('context.say(' + args + ')')
  delete context.say
  return result
}
javascript
Function.prototype.Dapply = function (context, arr) {
  if (typeof this !== 'function') {
    throw new TypeError('Error')
  }
  var context = context || window
  var result
  context.fn = this
  if (!arr) {
    result = context.fn()
  } else {
    var args = []
    for (let i = 0; i < arr.length; i++) {
      args.push('arr[' + i + ']')
    }
    result = eval('context.fn(' + args + ')')
  }
  return result
}
javascript
Function.prototype.Dbind = function (context) {
  if (typeof this !== 'function') {
    throw new TypeError('Error')
  }
  var self = this
  var args = Array.prototype.slice.call(arguments, 1)
  return function () {
    var bindArgs = Array.prototype.slice.call(arguments)
    return self.apply(context, args.concat(bindArgs))
  }
}
javascript
// 示例函数
function greet() {
  console.log(`Hello, ${this.name}!`)
}

// 1. 使用 Dcall 方法调用函数,并指定上下文对象和参数
const person1 = { name: 'Alice' }
greet.Dcall(person1) // 输出:Hello, Alice!

// 2. 使用 Dapply 方法调用函数,并指定上下文对象和参数数组
const person2 = { name: 'Bob' }
const args = ['Nice to meet you!']
greet.Dapply(person2, args) // 输出:Hello, Bob!

// 3. 使用 Dbind 方法创建新函数,预先绑定上下文和参数
const person3 = { name: 'Charlie' }
const greetPerson3 = greet.Dbind(person3)
greetPerson3() // 输出:Hello, Charlie!

new

在调用 new 的过程中会发生以上四件事情:

  1. 新生成了一个对象
  2. 链接到原型
  3. 绑定 this
  4. 返回新对象

实现分析:

  1. 创建一个空对象
  2. 获取构造函数
  3. 设置空对象的原型
  4. 绑定 this 并执行构造函数
  5. 确保返回值为对象
javascript
Function.prototype.Dnew = function (fn, ...args) {
  var obj = Object.create(fn.prototype)
  var ret = fn.apply(obj, args)
  return ret instanceof Object ? ret : obj
}

// 或
Function.prototype.DDnew = function () {
  var obj = {}
  var Constructor = Array.prototype.shift.call(arguments)
  obj.__proto__ = Constructor.prototype
  var result = Constructor.call(obj, arguments)
  return result instanceof Object ? result : obj
}

instanceof

实现原理

就是只要右边变量的 prototype 在左边变量的原型链上即可。因此,instanceof 在查找的过程中会遍历左边变量的原型链,直到找到右边变量的 prototype,如果查找失败,则会返回 false,告诉我们左边变量并非是右边变量的实例。

javascript
{
  function new_instance_of(leftVaule, rightVaule) {
    let rightProto = rightVaule.prototype
    leftVaule = leftVaule.__proto__
    while (true) {
      if (leftVaule === null) return false
      if (leftVaule === rightProto) return true
      leftVaule = leftVaule.__proto__
    }
  }
}
{
  function new_instance_of(leftVaule, rightVaule) {
    let proto = Object.getPrototypeOf(leftVaule)
    while (true) {
      if (proto == null) return false
      if (proto === rightVaule.prototype) return true
      proto = Object.getPrototypeOf(proto)
    }
  }
}

总结:

使用 typeof 来判断基本数据类型是 ok 的,不过需要注意当用 typeof 来判断 null 类型时的问题,如果想要判断一个对象的具体类型可以考虑用 instanceof,但是 instanceof 也可能判断不准确,比如一个数组,他可以被 instanceof 判断为 Object。所以我们要想比较准确的判断对象实例的类型时,可以采取 Object.prototype.toString.call() 方法