JavaScript 中“this”可以指向的 8 个不同的地方
函数中的 this 在调用时是绑定的,完全取决于函数的调用位置(即函数的调用方式)。要知道 this 指向什么,你必须知道相关函数是如何被调用的。
Global context
在非严格模式和严格模式下,this 指的是顶级对象(浏览器中的窗口)。
this === window // true
'use strict'
this === window;
this.name = 'dog';
console.log(this.name); // dog
函数上下文
var name = 'window';
var doSth = function(){
console.log(this.name);
}
doSth(); // 'window'
您可能会错误地认为 window.doSth() 被调用并因此指向 window.doSth()。尽管在这种情况下 window.doSth 确实等于 doSth。名称等于 window.name。这是因为在 ES5 中,全局变量挂载在顶层对象(浏览器为 Window)中。
事实上,事实并非如此。
let name2 = 'window2';
let doSth2 = function(){
console.log(this === window);
console.log(this.name2);
}
doSth2() // true, undefined
本例中,let 不给顶层对象添加属性(浏览器为Window);window.name2 和 window.doSth 都是未定义的。
在严格模式下,正常函数中的 this 行为不同,如未定义。
'use strict'
var name = 'window';
var doSth = function(){
console.log(typeof this === 'undefined');
console.log(this.name);
}
doSth(); // true,// Cannot read properties of undefined (reading 'name')
这称为默认绑定。熟悉 call 和 apply 的读者会使用这个类比:
doSth.call(undefined);
doSth.apply(undefined);
效果是一样的。调用 apply 所做的一件事是更改 this 所指的函数中的第一个参数。第一个参数是 undefined 或 null,在非严格模式下指向 window。
在严格模式下,它指的是第一个参数。
经常有这样的代码(回调函数),其实是正常的函数调用模式。
var name = 'dog';
setTimeout(function(){
console.log(this.name);
}, 0);
setTimeout(fn | code, 0, arg1, arg2, ...)
fn.call(undefined, arg1, arg2, ...);
对象函数(方法)调用模式
var name = 'window';
var doSth = function(){
console.log(this.name);
}
var student = {
name: 'dog',
doSth: doSth,
other: {
name: 'other',
doSth: doSth,
}
}
student.doSth(); // 'dog'
// call like this
student.doSth.call(student);
student.other.doSth(); // 'other'
// call like this
student.other.doSth.call(student.other);
但是,经常会出现将对象中的函数分配给变量的情况。这实际上又是一个普通函数,所以使用普通函数的规则(默认绑定)。
var studentDoSth = student.doSth;
studentDoSth(); // 'window'
// call like this :
studentDoSth.call(undefined);
call(), apply(), bind()
上面提到了call、apply,这里有一个详细的解释。Function.prototype.call()。
fun.call(thisArg, arg1, arg2, ...)
根据参数thisArg的描述可知,call就是将函数中的this改为指向thisArg并执行该函数,这让JS灵活了很多。
在严格模式下,thisArg 是一个原始值,它是一个值类型,即原始值。不会被包裹到一个对象中。例如:
var doSth = function(name){
console.log(this);
console.log(name);
}
doSth.call(2, 'dog'); // Number{2}, 'dog'
var doSth2 = function(name){
'use strict';
console.log(this);
console.log(name);
}
doSth2.call(2, 'dog'); // 2, 'dog'
构造函数调用模式
function Student(name){
this.name = name;
console.log(this); // {name: 'dog'}
// return this;
}
var result = new Student('dog');
由此可知,调用new操作符时,this指向的是新生成的对象。
原型链中的调用模式
function Student(name){
this.name = name;
}
var s1 = new Student('dog');
Student.prototype.doSth = function(){
console.log(this.name);
}
s1.doSth(); // 'dog'
这是对象的方法调用模式。自然,它指向生成的新对象。如果对象继承自其他对象。它还将通过原型链进行查找。
箭头函数调用模式
我们先来看看箭头函数和普通函数的重要区别:
它没有自己的 this、super、arguments 和 new.target 绑定。
你不能使用 new 来调用。
没有原型对象。
this 的绑定不能更改。
形参名称不能重复。
箭头函数中没有 this 绑定,它的值必须通过查找作用域链来确定。如果箭头函数包含在非箭头函数中,则 this 绑定到最近的非箭头函数的 this 上,否则 this 的值设置为全局对象。例如:
var name = 'window';
var student = {
name: 'dog',
doSth: function(){
// var self = this;
var arrowDoSth = () => {
// console.log(self.name);
console.log(this.name);
}
arrowDoSth();
},
arrowDoSth2: () => {
console.log(this.name);
}
}
student.doSth(); // 'dog'
student.arrowDoSth2(); // 'window'
其实,等价于箭头函数外的this,也就是普通函数在箭头函数之上缓存的this。如果没有正常功能,则为全局对象(浏览器中的窗口)。
也就是说,不可能通过call、apply、bind来绑定箭头函数的this(它本身没有this)。而call、apply、bind可以绑定缓存箭头函数上面的普通函数的this。
var student = {
name: 'dog',
doSth: function(){
console.log(this.name);
return () => {
console.log('arrowFn:', this.name);
}
}
}
var person = {
name: 'person',
}
student.doSth().call(person); // 'dog' 'arrowFn:' 'dog'
student.doSth.call(person)(); // 'person' 'arrowFn:' 'person'
DOM事件处理函数调用
<button class="button">onclick</button>
<ul class="list">
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
<script>
var button = document.querySelector('button');
button.onclick = function(ev){
console.log(this);
console.log(this === ev.currentTarget); // true
}
var list = document.querySelector('.list');
list.addEventListener('click', function(ev){
console.log(this === list); // true
console.log(this === ev.currentTarget); // true
console.log(this);
console.log(ev.target);
}, false);
</script>
onclick 和 addEventListener 是指向绑定事件的元素。
总结
如果要确定一个正在运行的函数的 this 绑定,则需要找到该函数的直接调用位置。找到它之后,我们可以应用以下四个规则来确定 this 的绑定对象。
new call:绑定新创建的对象,注意:显示返回函数或对象,返回值不是新创建的对象,而是显式返回的函数或对象。
call或apply(或bind)call:在严格模式下,绑定到指定的第一个参数。在非严格模式下,null 和 undefined 指向全局对象(浏览器中的窗口),其余值指向由 new Object() 包裹的对象。
对对象的函数调用:绑定到该对象。
普通函数调用:在严格模式下绑定到 undefined,否则绑定到全局对象。
ES6 中的箭头函数:以上四个标准绑定规则将不会用到,但这会根据当前词法范围来确定。
具体来说,箭头函数会继承外层函数并调用 this 绑定(不管 this 绑定什么),如果没有外层函数,则绑定到全局对象(浏览器中的窗口)。
DOM事件函数:一般指向事件绑定的DOM元素,但在某些情况下是绑定到全局对象。
今天的文章内容就到这里节省了,感谢您的阅读,希望你能学到一些新东西,祝编程愉快!