前端面试经常会被问到的this指向相关问题,比如
- js怎么改变this指向?
- call/apply/bind的区别?
- 手写call/apply/bind方法
要回答上面的这些问题,只需要搞懂其方法内部实现的原理
call
我们先来看看call方法相关的使用例子
var person = {
fullName: function() {
return this.firstName + " " + this.lastName;
},
describe: function(city, country) {
return this.firstName + " " + this.lastName + "," + city + "," + country;
}
}
var person1 = {
firstName:"Bill",
lastName: "Gates",
}
var person2 = {
firstName:"Steve",
lastName: "Jobs",
}
person.fullName.call(person1); // 将返回 "Bill Gates"
person.fullName.call(person2); // 将返回 "Steve Jobs"
person.describe.call(person1, "Seattle", "USA"); // 将返回 "Bill Gates,Seattle,USA"
使用call
方法后,person1
和person2
可以调用person
的 fullName 方法,可想而知,其作用就是改变this指向
call
方法接收的参数分为两个部分,第一个参数为this的指向
,后续则是参数列表,当没有参数的时候this
指向window
思路
-
- 先获取call前面的对象方法
-
- 将该对象方法塞给需要改变this的对象里
-
- 调用方法并删除
通俗点讲就是将方法给指向的对象,让指向对象自己调用,其this自然指向本身
Function.prototype.myCall = function (target, ...args) {
target = target || window
const funKey = Symbol()
target[funKey] = this
const res = target[funKey](...args)
delete target[funKey]
return res
}
person.fullName.myCall(person1); // 将返回 "Bill Gates"
person.fullName.myCall(person2); // 将返回 "Steve Jobs"
person.describe.myCall(person1, "Seattle", "USA"); // 将返回 "Bill Gates,Seattle,USA"
用Symbol
防止覆盖掉target里同名方法
apply
func.apply(thisArg, [argsArray])
apply
和call
没有什么很大的差异就是在传参上不一样,call
接收的是参数列表,apply
接收的是参数数组
// 例子
var person = {
fullName: function() {
return this.firstName + " " + this.lastName;
},
describe: function(city, country) {
return this.firstName + " " + this.lastName + "," + city + "," + country;
}
}
var person1 = {
firstName:"Bill",
lastName: "Gates",
}
var person2 = {
firstName:"Steve",
lastName: "Jobs",
}
person.fullName.apply(person1); // 将返回 "Bill Gates"
person.fullName.apply(person2); // 将返回 "Steve Jobs"
person.describe.apply(person1, ["Seattle", "USA"]); // 将返回 "Bill Gates,Seattle,USA"
根据传参的不同,我们调整一下上面call
的方法
Function.prototype.myApply = function (target, args=[]) {
target = target || window
const funKey = Symbol()
target[funKey] = this
const res = target[funKey](...args)
delete target[funKey]
return res
}
person.fullName.apply(person1); // 将返回 "Bill Gates"
person.fullName.apply(person2); // 将返回 "Steve Jobs"
person.describe.apply(person1, ["Seattle", "USA"]); // 将返回 "Bill Gates,Seattle,USA"
bind
我们直接来看例子,并分析一下bind的用法和特点
var person = {
fullName: function() {
return this.firstName + " " + this.lastName;
},
describe: function(city, country) {
return this.firstName + " " + this.lastName + "," + city + "," + country;
}
}
var person1 = {
firstName:"Bill",
lastName: "Gates",
}
var person2 = {
firstName:"Steve",
lastName: "Jobs",
}
const person1Name = person.fullName.bind(person1)
const person2Name = person.fullName.bind(person2)
const person1Desc = person.describe.bind(person1,"Seattle")
person1Name() // 将返回 "Bill Gates"
person2Name() // 将返回 "Steve Jobs"
person1Desc("USA") // 将返回 "Bill Gates,Seattle,USA"
person1Desc.call(person2,"USA") // 返回 "Bill Gates,Seattle,USA"
const newDesc = new person1Desc("USA") // 返回 "undefined undefined,Seattle,USA"
bind
函数并不是立即执行,而是返回一个新函数,当我们想通过call
来改变bind
的this指向时,发现无效。根据输出结果。
MDN有相关描述
绑定函数也可以使用
new
运算符构造,它会表现为目标函数已经被构建完毕了似的。提供的 this 值会被忽略,但前置参数仍会提供给模拟函数。
通俗点就是:在new运算符构造过程中,已经确定的this会被忽略,且返回的实例还是会继承构造函数的构造器属性与原型属性,并且能正常接收参数。
可以总结bind
函数的一下特点
- 可以修改this指向
- 返回一个绑定了this的新函数
- 绑定后的this指向无法被修改
- 使用new运算符构造,this会被忽略,实例继承属性
根据以上特点我们来手写一下
Function.prototype.myBind = function (target, ...args) {
const _fn = this
let bound = function(...remainArg) {
// 使用 instanceof 判断 this 是否为当前函数的实例,true则表示在通过 new 构建实例
let newTarget = this instanceof bound ? this : target
const funKey = Symbol()
newTarget[funKey] = _fn
const res = newTarget[funKey](...args,...remainArg)
delete newTarget[funKey]
return res
}
bound.prototype = _fn.prototype; // 原型链继承
return bound
}
const person1Name = person.fullName.myBind(person1)
const person2Name = person.fullName.myBind(person2)
const person1Desc = person.describe.myBind(person1,"Seattle")
person1Name() // 将返回 "Bill Gates"
person2Name() // 将返回 "Steve Jobs"
person1Desc("USA") // 将返回 "Bill Gates,Seattle,USA"
person1Desc.call(person2,"USA") // 返回 "Bill Gates,Seattle,USA"
const newDesc = new person1Desc("USA") // 返回 "undefined undefined,Seattle,USA"