「这是我参与2022首次更文挑战的第31天,活动详情查看:2022首次更文挑战」。
在上两篇文章中,我们介绍了this指向的四个规则。除了这四种规则,还有一些javascript的内置函数或者第三方库中的内置函数,它们的this指向是什么样的呢?以及一个函数调用位置如果应用了多条规则,谁的优先级更高呢?这篇文章我们具体总结下。
内置函数的this绑定分析
有时候,我们会调用一些javascript的内置函数或者一些第三方库中的内置函数,例如setTimeout、数组的forEach、元素div的点击事件等。这些内置函数会要求我们传入另外一个函数。我们并不会显示得调用这些函数,而是javascript内部或者第三方库内部帮助我们执行。
那么这些函数中的this是如何绑定的呢?
setTimeout
setTimeout(function () {
console.log(this);
}, 2000)
我们可以看到setTimeout中的this指向的是window。
我们可以猜测setTimeout内部进行了默认绑定。
function mySetTimeout(fn, duration) {
fn()
}
mySetTimeout(function () {
console.log(this);
}, 2000)
如果想要修改this指向,可以使用call方法。
function mySetTimeout(fn, duration) {
// fn()
fn.call('abc')
}
此时,this就指向了abc。
监听点击
我们创建一个盒子,并给这个盒子绑定点击事件。
const boxBiv = document.querySelector('.box')
boxBiv.onclick = function() {
console.log(this);
}
点击这个盒子,我们看到this指向的是这个盒子。
我们也可以通过addEventListener来给盒子注册点击事件,与上面不同的是,addEventListener对同一个事件可以注册多次。
boxBiv.addEventListener('click', function() {
console.log(this);
})
boxBiv.addEventListener('click', function() {
console.log(this);
})
boxBiv.addEventListener('click', function() {
console.log(this);
})
点击这个盒子,我们可以看到this也是指向的是这个盒子。
我们可以猜测addEventListener内部实现了fn.call(boxBiv)使得this指向的是这个盒子。
数组.forEach/map/filter/find
var names = ['abc', 'cba', 'nba']
names.forEach(function(item) {
console.log(item,this);
})
names.map(function(item) {
console.log(item, this);
})
我们可以看到这些数组方法中的this指向的是window。
我们可以在这些方法中传入第二个参数修改this指向。
names.forEach(function(item) {
console.log(item,this);
}, 'abc')
names.map(function(item) {
console.log(item, this);
}, 'cba')
规则优先级
默认绑定的优先级最低
默认绑定的优先级是最低的,存在其他规则时就会通过其他规则的方式来绑定this。
显示绑定高于隐式绑定
call/apply的显示绑定高于隐式绑定
var obj = {
name: 'obj',
foo: function() {
console.log(this);
}
}
// obj.foo()
// 1. call和apply的显示绑定高于隐式绑定
obj.foo.call('abc')
obj.foo.apply('abc')
bind的优先级高于隐式绑定
var bar = obj.foo.bind('aaa')
bar()
更明显的比较
function foo() {
console.log(this);
}
var obj = {
name: 'obj',
foo: foo.bind('aaa')
}
obj.foo()
new高于隐式绑定
var obj = {
name: 'obj',
foo: function() {
console.log(this);
}
}
// new的优先级高于隐式绑定
var r = new obj.foo()
new优先级高于显示绑定
new关键字不能和call/apply一起来使用,所以不存在谁的优先级更高。new绑定可以和bind一起使用,new绑定优先级更高。
// new的优先级高于bind
function foo() {
console.log(this);
}
var bar = foo.bind('aaa')
var obj = new bar()
总结下this绑定规则的优先级:
new绑定 > 显示绑定(call/apply/bind)> 隐式绑定(obj.foo())> 默认绑定(独立函数调用)
特殊绑定
忽略显示绑定
call/apply/bind: 当传入null/undefined时,这个显示绑定会被忽略,会使用默认绑定,自动将this绑定为全局对象。
function foo () {
console.log(this);
}
foo.call('abc')
foo.call({})
// call/apply/bind:当传入null/undefined时,自动将this绑定成全局对象
foo.apply(null)
foo.apply(undefined)
var bar = foo.bind(null)
bar()
间接函数引用
var obj1 = {
name: 'obj1',
foo: function() {
console.log(this);
}
}
var obj2 = {
name: 'obj2'
}
obj2.bar = obj1.foo
obj2.bar()
判断以上代码,我们知道obj2调用了foo函数,进行了隐式绑定,所以this指向的是obj2。
那么,下面的代码this指向什么呢?
var obj1 = {
name: 'obj1',
foo: function() {
console.log(this);
}
}
var obj2 = {
name: 'obj2'
};
/* obj2.bar = obj1.foo
obj2.bar() */
// {}后面不能省略;因为下面的代码会把上面的代码看成一个整体
(obj2.bar = obj1.foo)()
创建一个函数的间接引用,这种情况使用默认绑定规则。
赋值(obj2.bar = obj1.foo)的结果是foo函数。foo函数被直接调用,那么是默认绑定。
箭头函数中的this指向
箭头函数是ES6之后增加的一种编写函数的方法,它比函数表达式更加简洁。
箭头函数不会绑定this、arguments属性
箭头函数不能作为构造函数来使用(不能和new一起来使用,会抛出错误)
箭头函数不使用this的四种标准规则,也就是箭头函数不绑定this,而是根据外层作用域来决定this。
var name = 'haha'
var foo = () => {
console.log(this);
}
foo()
foo是个箭头函数,调用foo时,箭头函数不绑定this,根据外层作用域来决定this,而它的外层作用域是全局,所以this指向window。
我们更改下foo的调用方式。
var obj = {foo: foo}
obj.foo()
foo.call('abc')
foo无论怎么调用(通过obj调用、call、apply调用),都是箭头函数调用。箭头函数不绑定this,根据外层作用域来决定this。
应用场景
我们使用setTimeout模拟网络请求,将结果放到data属性中。
var obj = {
data: [],
getData: function () {
setTimeout(function () {
var result = ['abc', 'cba', 'nba']
console.log(this);
this.data = result
}, 2000)
}
}
obj.getData()
上述代码中setTimeout中的this指向是window,并不是obj。
在箭头函数之前,我们使用_this来获取obj。
var obj = {
data: [],
getData: function () {
+ var _this = this
setTimeout(function () {
var result = ['abc', 'cba', 'nba']
// this.data = result
+ _this.data = result
}, 2000)
}
}
obj.getData()
因为obj调用了getData函数,进行了隐式绑定,所以_this指向的是obj。
有了箭头函数,箭头函数里面的this由外层作用域决定。此时箭头函数的外层作用域是getData,而obj调用了getData,是隐式绑定,所以this引用就会从上层作用域中找到对应的this。
var obj = {
data: [],
getData: function () {
// var _this = this
/* setTimeout(function () {
var result = ['abc', 'cba', 'nba']
this.data = result
// _this.data = result
}, 2000) */
setTimeout(() => {
var result = ['abc', 'cba', 'nba']
this.data = result
}, 2000)
}
}