call、apply、bind、new、instanceof 实现原理
call 模拟实现
更改函数的 this 指向并执行函数。
Function.prototype.myCall = function(context) {
var context = context || window
context.fn = this
var args = []
for(var i = 1; i < arguments.length; i++) {
args.push('arguments[' + i + ']')
}
var result = eval('context.fn(' + args + ')')
delete context.fn
return result
}
分析:
- 首先
context
作为可选参数,如果不传或传null
,默认上下文为window
- 然后给上下文
context
添加方法fn
,fn
就是要执行的函数,谁调用了 call() ,fn
就是谁。 所以context.fn = this
- call 还可以传入多个参数作为调用的函数的参数,可以通过 arguments 对象获取函数的参数。
- 最后调用函数保存返回值,删除给对象添加的
fn
,将结果返回。
核心就是,通过将函数添加到对象上,然后通过对象调用这个函数,从而使函数中的 this 指向这个对象。
更简洁的写法:
Function.prototype.myCall = function(context, ...rest) {
var context = context || window
context.fn = this
var result = context.fn(...rest)
delete context.fn
return result
}
apply 模拟实现
apply 实现与 call 类似,区别在于对参数的处理
Function.prototype.myApply = function(context, arr) {
var context = context || window
context.fn = this
var result
if(!arr) {
result = context.fn()
} else {
var args = []
for(var i = 0; i < arr.length; i++) {
args.push('arr[' + i + ']')
}
result = eval('context.fn(' + args + ')')
}
delete context.fn
return result
}
简洁:
Function.prototype.myApply = function(context, arr) {
var context = context || window
context.fn = this
var result
if(arr) {
result = context.fn(...arr)
}else {
result = context.fn()
}
delete context.fn
return result
}
bind 模拟实现
bind 方法返回一个改变 this 指向的函数,bind 第一个参数作为这个函数的 this,后续参数会在函数调用时,作为函数的参数传入,然后才会传入函数的实参。
举个例子:
function foo(name,age) {
console.log('name:' + name + 'age:' + age)
}
var f = foo.bind(null,'张三')
f(20) // name:张三age:20
实现:
Function.prototype.myBind = function(context) {
var self = this
var args = [].slice.call(arguments, 1)
function F() {
var args2 = [].slice.call(arguments)
var arglist = args.concat(args2)
return self.apply(this instanceof F ? this: context, arglist)
}
F.prototype = Object.create(self.prototype)
return F
}
简洁版:
Function.prototype.myBind = function(context) {
var self = this
var args = [...arguments].slice(1)
function F() {
return self.apply(this instanceof F ? this : context,args.concat(...arguments))
}
F.prototype = Object.create(self.prototype)
return F
}
- bind 返回的是一个函数,
并在这个函数里面返回
原函数.apply()
实现改变 this 。 - 需注意的是:如果 bind 返回的函数作为构造函数执行时(new 函数),this 不能改成传入的
context
,this 应该是声明的变量。 - bind() 传入的除
context
的后续参数,先存起来,在调用由 bind 返回的新函数时 ,向 apply() 传入由原来存的参数和新传入的参数组成的数组(注意顺序)。
new 模拟实现
new 运算符创建了一个用户自定义类型的实例
在执行 new 构造函数()
时,发生了:
- 生成一个新对象
- 将对象的原型引用(
__proto__
)指向构造函数的原型 - 绑定 this 为该对象,执行构造函数中的内容
- 如果函数执行的返回值是对象,就返回这个对象,否则返回之前创建的新对象
function myNew() {
var constructor = [].shift.call(arguments)
var obj = Object.create(constructor.prototype)
var res = constructor.apply(obj, arguments)
return res instanceof Object ? res: obj
}
ES6 简洁版:
function myNew(constructor, ...rest) {
let obj = Object.create(constructor.prototype)
let res = constructor.apply(obj, rest)
return res instanceof Object ? res : obj
}
instanceof
能正确判断对象的类型,原理是判断构造函数的原型对象是否能在对象的原型链上
function myInstanceof(obj, fn) {
let prototype = fn.prototype
let objProto = obj.__proto__
while(true) {
if(objProto == null)
return false
if(objProto === prototype)
return true
objProto = objProto.__proto__
}
}
- 获得构造函数的原型
- 获得实例对象的原型
- 判断构造函数的原型是否就是实例对象的原型,如果是就返回 true ,不是就让实例对象的原型指向自身的原型继续判断,直到实例对象的原型为 null,返回 false
参考资料: