理解new、call、apply和bind
分类:javascript
一直以来对原型链、原型对象一知半解的,更不清楚 new
、 call
、 apply
和 bind
的内部实现原理,于是逼自己写个笔记加强记忆。
new
这个关键字主要实现了三个功能
- 让实例可以访问到私有属性
- 让实例可以访问到原型对象(
prototype
)上的属性 - 如果构造函数有返回值而且是
object
或function
,则返回构造函数的返回值,否则返回新创建的对象
上代码:
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)
同学们可以直接复制上面的代码去控制台调试体验。
call
和 apply
call
和 apply
就是我们常说的可以改变 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
bind
和 call
类似,都可以接收无限多个参数,只是 bind
返回的是一个函数。
实现思路:
- 创建一个新的函数
- 在新的函数体内使用
apply
或call
改变this
指向 - 返回这个新函数
上代码:
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()
。
- 一般情况下我们创建的对象是这个样子的:
创建的对象都会携带一个 __proto__
属性。
- 使用
Object.create
创建的对象如下:
可以看出创建的新的对象的 __proto__
指向了传入的对象。
- 如果传入的是
null
或undefined
呢:
传 null
可以获取一个干净的不带 __proto__
的对象。
- 如果传入的是原型对象:
从上图可以得出结论,使用 Object.create
可以防止污染原来的原型对象上的属性。
总结:
- 新对象的
__proto__
指向原对象 - 定义新对象的其他属性(
Object.defineProperties()
) - 防止污染原来的原型对象
- 简单实现
上代码:
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)
以上。