this指向
this是执行上下文中的一个属性,它指向最后一次调用这个方法的对象,也就是说只有在函数被调用时,this才会被绑定。在实际开发中,this指向可以通过四种模式来判断:
- 函数调用模式,函数作为一个普通函数调用时,this指向全局对象window;
function testDefault() {
console.log(this);
}
// this指向window对象
testDefault() // window
- 构造器模式调用,函数作为构造函数,使用new调用时,this指向这个构造函数的实例;
function Person(niName, age) {
this.niName = niName
this.age = age
// this指向构造函数的实例person
console.log(this); // Person { niName: 'limi2020', age: 25 }
}
let person = new Person('limi2020', 25)
- 方法模式调用,函数作为一个对象的方法调用时,this指向这个对象;
let obj = {
niName: 'limi2020',
say() {
console.log(this);
}
}
// this指向obj对象
obj.say() // { niName: 'limi2020', say: [Function: say] }
- call、apply、bind模式调用,这三个方法可以显式地指定函数的this。
let object = { niName: 'limi2020'}
function testCall() {
return this
}
// this被显示指向object,以call()方法为例
console.log(testCall.call(object)); //{ niName: 'limi2020' }
四种模式的优先级:构造器调用模式 > call、apply、bind调用模式 > 方法调用模式 > 函数调用模式
call()、apply()、bind()
这三个方法都可以显式地指定函数的this,三个方法的第一个参数指定了函数体内this对象的指向。
1.call()、apply()、bind()区别
- call()和apply()会立即执行,bind()不会,而是返回一个函数;
- call()和bind()可以接收多个参数,apply()只能接收两个参数,且第二参数是一个数组或类数组;
- bind()参数可以分多次传入。
2.call()的实现
- 判断调用的对象是否为函数,因为即使定义在函数原型上,也有可能出现call等方式调用的情况;
- 判断传入的上下文对象是否存在,不存在,则将其设置为window对象;
- 将这个函数作为上下文对象的一个属性;
- 使用这个上下文对象调用这个函数,并将结果返回;
- 删除这个属性
- 返回结果
Function.prototype.myCall = function(context) {
// 判断调用对象是否为函数,因为即使定义在函数原型上,也有可能出现使用call调用的情况
if(typeof this !== 'function') {
console.error('type error!')
}
// 判断传入的上下文对象是否存在,不存在,则设置为window
context = context || window
// 定义一个属性,避免与对象的属性发生命名冲突
let fn = Symbol()
// 将这个函数作为上下文对象的一个属性
context.fn = this
// 使用上下文对象来调用这个方法,并将结果保存
let result = context.fn(...arguments)
// 删除属性
delete context.fn
// 返回结果
return result
}
// 测试myCall和call是否相同
function test() {
return this
}
let obj = { niName: 'limi2020'}
console.log(test.myCall(obj)); // { niName: 'limi2020' }
console.log(test.call(obj)); // { niName: 'limi2020' }
3.apply()的实现
apply()和call()的实现相同,只是在参数处理上有些差别,apply()只有两个参数,且第二个参数为数组或类数组。
Function.prototype.myApply = function(context) {
// 判断调用的对象是否为函数,因为即使定义在函数原型上,也有可能出现call等方式调用的情况
if(typeof this !== 'function') {
console.error('type errror!');
}
// 判断传入的上下文对象是否存在,不存在,则将其设置为window对象
context = context || window
// 定义一个属性,避免与对象的属性发生命名冲突
let fn = Symbol()
// 将这个函数作为上下文对象的一个属性
context.fn = this
let result = null
// 使用上下文对象调用这个方法,并将结果返回
if(arguments[1]) {
// 参数处理:apply()只有两个参数,第二个参数为数组或类数组
result = context.fn(...arguments[1])
} else {
result = context.fn()
}
// 删除属性
delete context.fn
// 返回结果
return result
}
// 测试myApply和apply是否哦相同
function test() {
return this
}
let obj = { niName: 'limi2020' }
console.log(test.myApply(obj)); // { niName: 'limi2020' }
console.log(test.apply(obj)); // { niName: 'limi2020' }
4.bind()的实现
- 判断调用对象是否为函数,因为即使定义在函数原型上,也有可能出现call等方式调用的情况;
- 保存当前函数的引用
- 创建一个函数, 并返回
- 函数内部使用apply来绑定函数调用,判断函数是否为构造函数,是就传入当前函数的this,其余都传入指定的上下文对象。
Function.prototype.myBind = function (context) {
// 判断调用对象是否为函数,因为即使定义在函数原型上,也有可能出现call等方式调用的情况
if (typeof this !== 'function') {
console.error('type error!');
}
// 保存当前函数的引用
let fn = this
// 获取其余参数
let args = [...arguments].slice(1)
// 创建一个函数返回
return function Fn() {
// 函数内部调用apply来绑定函数调用,需要判断作为构造函数的情况,如果是构造函数就传入当前函数的this, 其余情况传入指定的上下文对象
return fn.apply(this instanceof Fn ? this : context, args.concat(...arguments))
}
}
// 测试myBind和bind是否相同
function test() {
return this
}
let obj = { niName: 'limi' }
console.log(test.myBind(obj)()); // { niName: 'limi' }
console.log(test.bind(obj)()); // { niName: 'limi' }