手动实现 new、call、apply 以及 bind

124 阅读3分钟

我正在参加「掘金·启航计划」

第二天

一、new

1. new 做了什么

  1. 创建一个新的空对象 obj{}。
  2. 将这个对象的原型设为构造函数的原型链。
  3. 将构造函数内部的 this 指向 new 创建的新对象,并执行构造函数。
  4. 判断构造函数内部有无返回值,没有返回值则返回 new 创建的对象。如果有返回值则判断返回值是否是普通类型和 null,如果是则直接将 new 创建的对象返回出去,可如果返回值是引用类型数据,则会将这个返回值当做对象返回出去。

2. 手动实现一个 new

// 实现 new
function myNew(Fn, ...arg) {
  // 创建一个空对象,并将构造函数的原型赋予到这个空对象上
  let obj = Object.create(Fn.prototype);
  // 将构造函数的 this 指向这个空对象,并执行构造函数
  let fun = Fn.call(obj, arg);
  // 最后将这个对象返回出去
  return fun instanceof Object ? fun : obj;
}

// 测试
function myFunction(name, sex){
  this.name = name
  this.sex = sex
}

// 对构造函数的原型添加方法
myFunction.prototype.run = function(){
    console.log(this.name, this.sex)
}

// 创建实例对象
let zs = myNew(myFunction, '张三', '男')

// 调用实例原型上的方法
zs.run()

// 输出这个对象
console.log(zs)

这里我们可以发现 zs 这个对象的原型已然指向了 myFunction 原型链,constructor 构造器指向了 myFunction。

二、call、apply、bind

1. call、apply、bind 的作用

改变函数调用时的作用域。

1.1. 三者区别

  1. call:除了参数集不是数组,其他与 apply 无异。

function.call(this, arg1, arg2, ....)

  1. apply:与 call 差不多,只是参数集是数组。

function.apply(this, [arg1, arg2, ...])

  1. bind:与 call 无异,只是返回结果改为了函数。

let fun = function(this, arg1, arg2, ...)

1.2 使用

// 在 window 全局添加一个 name 属性
window.name = 'window';

// 声明一个对象
const obj = {
  // 对象中也声明一个 name 属性
  name: "obj",
};

// 声明一个函数
function say(arg1, arg2) {
  console.log(this.name, arg1, arg2);
}

// 正常输出:window 你好 世界
say("你好", "世界");

// call 输出:obj 你好 世界
say.call(obj, "你好", "世界");

// apply 输出:obj 你好 世界
say.apply(obj, ["你好", "世界"]);

// bind 输出:obj 你好 世界
let msg = say.bind(obj, "你好", "世界")();

2. 手动实现 call、apply、bind

下面例子中使用的代码是上面例子的代码,下面为了省略没有摘抄。

2.1 实现 call

Function.prototype.myCall = function(context, ...args){
  // 判断有无传入作用域,如果没有传入,则直接使用 windows 全局作用域
  var context = context || window
  // 将当前调用的函数暂时赋给 fn 属性,待会好执行
  context.fn = this
  // 使用 eval 执行当前函数,并将 args 参入
  var res = eval('context.fn(...args)')
  // 最后删除这个属性
  delete context.fn
  // 返回执行结果
  return res
}

// 测试,输出:obj 1 2
say.myCall(obj, '1', '2')

2.2 实现 apply

// 实现 apply
Function.prototype.myApply = function(context, args){
  // 获取作用域
  var context = context || window
  // 拿到当前要执行的函数
  context.fn = this
  // 放在 eval 中执行
  let res = eval('context.fn(...args)')
  // 然后删除这个用来存放当前要执行函数的属性
  delete context.fn
  // 将执行结果返回
  return res
}
// 测试,输出:obj 3 4
say.myApply(obj, ['3', '4'])

2.3 实现 bind

Function.prototype.myBind = function(context, ...args){
  // 获取目标执行作用域
  var context = context || window
  // 拿到当前要执行的函数
  let fn = this
  // 使用 call 执行,并返回一个函数
  return function(){
    return fn.call(context, ...args)
  }
}
// 测试,输出:obj 5 6
let my = say.myBind(obj, '5', '6')()