理解new、call、apply和bind

吐槽君 分类:javascript

一直以来对原型链、原型对象一知半解的,更不清楚 newcallapplybind 的内部实现原理,于是逼自己写个笔记加强记忆。

new

这个关键字主要实现了三个功能

  1. 让实例可以访问到私有属性
  2. 让实例可以访问到原型对象(prototype)上的属性
  3. 如果构造函数有返回值而且是 objectfunction ,则返回构造函数的返回值,否则返回新创建的对象

上代码:

function _new (fn, ...args) {
  // 如果 fn 不是函数,直接抛出异常
  if (typeof fn !== 'function') {
    throw new Error('fn must be a function')
  }
  var obj = new Object()
  // obj.__proto__ = Object.create(fn.prototype)
  // 有的实现方式是使用了 Object.create
  // 不知道有什么区别,若有大佬路过麻烦帮忙解答下
  // 这块代码的作用是让创建的新对象可以访问原型上面的属性
  // 实现了第2点
  obj.__proto__ = fn.prototype
  // 通过 apply 改变新创建对象的指向,实现 obj 指向 fn 的私有属性
  // 并接收 fn 的返回结果
  // 实现了第1点
  var res = fn.apply(obj, [...args])
  // 判断 fn 的返回结果
  // 实现了第3点
  return (typeof res === 'object' && res !== null) || typeof res === 'function' ? res : obj
}

// 测试
function Parent () {
  this.name = 'parent'
  console.log('----')
  // return {a: 1}
}
Parent.prototype.getName = function () {
  return this.name
}
function Child () {}
var c = _new(Parent)
 

同学们可以直接复制上面的代码去控制台调试体验。

callapply

callapply 就是我们常说的可以改变 this 指向的函数,返回调用这两个函数的执行结果

不同点是:

  • call 可以接收无限多个参数
  • apply 只能接收两个参数

上代码:

Function.prototype._call = function (context, ...args) {
  var context = context || window
  context.fn = this
  // 此处使用了 eval
  var res = eval('context.fn(...args)')
  delete context.fn
  return res
}
Function.prototype._apply = function (context, args) {
  var context = context || window
  context.fn = this
  // 此处没有使用 eval
  // 但结果是一样的
  // 有大佬还知道其他的区别吗
  var res = context.fn(...args)
  delete context.fn
  return res
}

// 测试
function A () {
  this.name = 'A'
  return this.name
}
function B () {
  this.name = 'B'
  return this.name
}
var res = A.apply(B)
console.log(res)

 

bind

bindcall 类似,都可以接收无限多个参数,只是 bind 返回的是一个函数。

实现思路:

  1. 创建一个新的函数
  2. 在新的函数体内使用 applycall 改变 this 指向
  3. 返回这个新函数

上代码:

Function.prototype._bind = function (context, ...args) {
  if (typeof this !== 'function') {
    throw 'this must be a function'
  }
  var self = this
  var res = function () {
    // 为什么不能使用 self.apply(context, [...args])
    // 如果使用,在 new 的时候会提前改变 this 指向
    self.apply(this instanceof self ? this : context, [...args])
  }
  if (this.prototype) {
    res.prototype = Object.create(this.prototype)
  }
  return res
}

// 测试
function Other() {
  this.a = 18
}
function Test() {
  console.log(this.a)
}
Test.prototype.t2 = function () {
  return 111
}
var o = new Other()
var c2 = Test._bind(o)
c2()  // 18
var c3 = new c2()
console.log(c3.t2())  // 111
 

Object.create

Object.create 创建了一个新的对象,其对象的__proto__ 指向了传入的对象,并且其第二个参数相当于Object.defineProperties()

  • 一般情况下我们创建的对象是这个样子的:

image.png

创建的对象都会携带一个 __proto__ 属性。

  • 使用 Object.create 创建的对象如下:

image.png

可以看出创建的新的对象的 __proto__ 指向了传入的对象。

  • 如果传入的是nullundefined 呢:

image.png

null 可以获取一个干净的不带 __proto__ 的对象。

  • 如果传入的是原型对象:

image.png

从上图可以得出结论,使用 Object.create 可以防止污染原来的原型对象上的属性。

总结:

  1. 新对象的 __proto__ 指向原对象
  2. 定义新对象的其他属性(Object.defineProperties()
  3. 防止污染原来的原型对象
  • 简单实现

上代码:

Object._create = function(obj, properties) {
  if (properties === null) throw new Error('properties must be null')
  if (typeof obj === 'undefined') throw new Error('proto is undefined')
  function F () {}
  F.prototype = obj
  var res = new F()
  if (properties !== undefined) Object.defineProperties(res, propertiesObject)
  if (obj === null) res.__proto__ = obj
  return res
}

var a = Object._create(null)
 

以上。

回复

我来回复
  • 暂无回复内容