理解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)

以上。

回复

我来回复
  • 暂无回复内容