一、作用
call、apply、bind用于改变函数执行时的this指向
二、用法
fn.call(fn,...params)
fn.apply(fn,[...params])
fn.bind(fn,..params)
注:call和apply执行fn返回结果并改变this指向 bind函数只改变this指向不执行返回一个新的函数,具体用法本文不做多于介绍
三、手写函数
通过对象调用属性方法的方式来修改this指向
1.手写$call
let obj = {
name: '哈哈',
age: 12
}
Function.prototype.$call = function() {
let fn = this
let o = arguments[0]
let params = [...arguments].splice(1)
o.fn = fn
o.fn(...params)
}
function see(pa) {
this.age = pa
console.log(this.name, this.age)
}
see.$call(obj, 13)
2.手写$apply
let obj = {
name: '123'
}
function fn(str) {
console.log(this.name,str)
}
Function.prototype.$apply = function() {
let con = [...arguments][0]
con.fn = this
let params = [...arguments][1]
return con.fn(...params)
}
fn.$apply(obj,['haha'])
call与apply实现思路基本相同只是对参数传递形式做了不同处理
3.手写$bind
let obj = {
name: '123',
age: 12
}
Function.prototype.$bind = function() {
let fn = this
let o = [...arguments][0]
let args = [...arguments].splice(1)
console.log(args)
return function() {
fn.call(args === [] ? o : o,...args)
}
}
function see(pa) {
this.age = pa
console.log(this.name, this.age)
}
see.$bind(obj)()
bind方法返回一个函数
以上代码只展现了方法实现的具体思路,在源码中还会对继承问题兼容问题进行处理下面以bind为例研究一下
var ArrayPrototypeSlice = Array.prototype.slice;
Function.prototype.bind = function(otherThis) {
var baseArgs= ArrayPrototypeSlice.call(arguments, 1),
baseArgsLength = baseArgs.length,
fToBind = this,
fNOP = function() {},
fBound = function() {
baseArgs.length = baseArgsLength; // reset to default base arguments
baseArgs.push.apply(baseArgs, arguments);
return fToBind.apply(
fNOP.prototype.isPrototypeOf(this) ? this : otherThis, baseArgs
);
};
if (this.prototype) {
fNOP.prototype = this.prototype;
}
fBound.prototype = new fNOP();
return fBound;
};
首先来看继承,源码中的继承关系通过一个空函数fNOP来完成,此过程相当于在原函数的prototype后方加上一条新的分支返回的新函数可以通过这条分支访问原函数原型链上的所有属性和方法
–新的原型链关系图–
疑问1 为什么不将原函数的prototype 直接赋值给fBound的prototype,而通过空函数FNOP来实现原型关系
解答: 首先我们要明白实现继承的目的是为了bind返回的新函数可以使用所有原函数原型上的方法和属性,如果直接将原函数的prototype 直接赋值给fBound的prototype,由于引用类型的特点指向同一个内存地址,当返回的新函数的原型链发生改变时会同时影响原函数的原型链关系,这是我们不希望看到的
疑问2 fNOP.prototype.isPrototypeOf(this)的作用
解答: bind()返回的新函数在执行时会去判断fnop的prototype是否在当前this的原型链上,那么什么情况下的this的原型链存在fnop的prototype呢?
这时候我们就需要搞清楚bind返回的新函数fBound的this指向问题, 由于bind的作用是修改this指向,我们很容易想当然的认为fBound中的this 指向bind函数中传入的obj对象。这是不对的,我们始终要去关注fBound的执行环境。
var ArrayPrototypeSlice = Array.prototype.slice;
let obj = {
name: '张三'
}
Function.prototype.$bind = function(otherThis) {
var baseArgs= ArrayPrototypeSlice.call(arguments, 1),
baseArgsLength = baseArgs.length,
fToBind = this,
fNOP = function() {},
fBound = function() {
this.haha = 1
baseArgs.length = baseArgsLength; // reset to default base arguments
baseArgs.push.apply(baseArgs, arguments);
console.log(fNOP.prototype.isPrototypeOf(this), this, '12312312')
return fToBind.apply(
// {↓此行标记↓}
fNOP.prototype.isPrototypeOf(this) ? this : otherThis, baseArgs
);
};
if (this.prototype) {
// Function.prototype doesn't have a prototype property
fNOP.prototype = this.prototype;
}
fBound.prototype = new fNOP();
return fBound;
};
function see(a){
console.log(a)
console.log(this, this.name)
}
see.$bind(obj,1)()
let xxx = see.$bind(obj,1)
let o = new xxx()
执行see.$bind(obj,1)()此时see中this指向obj 打印this.name 输出张三 同时bind返回的函数fBound中this的打印结果为window对象 显然window对象的原型链上不存在 fNOP.prototype fNOP.prototype.isPrototypeOf(this)为false
执行new xxx() fBound打印this结果为fBound对象
fNOP.prototype.isPrototypeOf(this)为true
由此可见fNOP.prototype.isPrototypeOf(this) 的目的是 当fBound作为构造函数出现时我们希望原函数的this同样指向实例对象 避免fBound作为构造函数去new时产生意料之外的结果