挑战大厂系列文章,转载请注明来源
解析 new()、Object.create()、instanceof、call()、apply()、bind()等6个方法函数的原理和内部实现
new()
思路:把原型指向原型对象去获取方法,用 call/apply 去改 this 指向获取属性
function new(fn, ...args) {
/*
原型:__proto__
原型对象:prototype
可用此方法替代:let obj = Object.create(fn.prototype)
*/
// 1. 创建一个空对象,将这个对象的原型指向构造函数的原型对象
let obj = {
__proto__: fn.prototype
}
// 2. 执行构造函数,把里面的 this 指到这个对象
let res = fn.apply(obj, args)
// 3. 返回值为 object 类型则作为 new 方法的返回值返回,否则返回这个空对象
return res instanceof Object ? res : obj
}
Object.create()
思路:创建一个空函数 F 作为中介去承接原型对象的方法,obj.__proto__ = F.prototype = fn.prototype
/*
constructor 挂在原型对象 prototype 的属性上
F.prototype = {
constructor: F
}
把 fn.prototype 当做参数传进来
F.prototype = proto
F.prototype = fn.prototype = {
constructor: fn
}
F.prototype = {
constructor: fn
}
所以修改原型对象后需要重新把 constructor 指回去
*/
function create(proto) {
let F = function () {}
F.prototype = proto
F.prototype.constructor = F
return new F()
}
instanceof
思路:右边变量的原型对象存在于左边变量的原型链上
原型链:obj.__proto__.__proto__ 这样一直往上找直到 null 为止形成的链条
function myInstanceof(left, right) {
/*
let proto = left.__proto__
es6 新写法用 Object.getPrototypeOf(left) 替代 left.__proto__
*/
let proto = Object.getPrototypeOf(left)
while(true) {
if (proto === null) {
return false
}
// 此时左边变量的原型等于右边变量的原型对象
if (proto === right.prototype) {
return true
}
// 不等于就继续往原型链上找,left.__proto__ = left.__proto__.__proto__
proto = Object.getPrototypeof(proto)
}
}
call
思路:将要改变 this 指向的方法挂到目标 this 上执行并返回
Function.prototype.call = function (context = window, ...args) {
let fn = Symbol('fn')
context[fn] = this
let res = context[fn](...args)
delete context[fn]
return res
}
解析:
// 原理
let obj = {
name: 'obj',
fn: function() {
console.log(this.name)
}
}
obj.fn() // 此时里面的 this 指向 obj
Function.prototype.call = function (obj, ...args) {
obj = {
fn: f
}
let res = obj.fn(...args)
delete obj.fn
return res
}
f.call(obj)
apply
apply 和 call 是一样的,只是传的参数不一样,apply 传数组
Function.prototype.apply = function (context = window, arr) {
let fn = Symbol('fn')
context[fn] = this;
let res;
if (!arr) {
res = context[fn]();
} else {
res = context[fn](...arr);
}
delete context[fn]
return res;
}
bind
思路:bind 利用 apply 来改变指针,Object.create 来克隆原方法的原型对象,最后返回一个新的函数
/*
1、可以指定this
2、返回一个函数
3、可以传入参数
4、柯里化
5、一个绑定函数也能使用new操作符创建对象:这种行为就像把原函数当成构造器,提供的 this 值被忽略,同时调用时的参数被提供给模拟函数
*/
if (!Function.prototype.bind) {
Function.prototype.bind = function(context, ...args) {
let fn = this
let bound = function () {
// 指定this
return fn.apply(
this instanceof bound ? this : context, // new 忽略
args.concat(...arguments)
)
}
bound.prototype = Object.create(this.prototype)
return bound
}
}
往期系列
认识更多前端道友,一起进阶前端开发
微信公众号:前端一锅煮
QQ群:前端一锅煮
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。