文章仅作为总结记忆,如需理解具体产生原因,请点击相关文章阅读模块查看其中文章。
如果你试图理解this是什么?
需要了解的知识点有:
1.作用域
2.静态作用域
3.动态作用域
this
的作用域就是动态作用域,动态作用域在执行时确认,所以只能在执行阶段才能决定this
变量的作用域。
它是非常特殊的关键词标识符,在每个函数的作用域中被自动创建。
this的默认指向是全局对象
为了方便区分记忆,这里认为
this
的默认指向是非严格模式下的默认指向,严格模式下的指向作为非默认情况在下文提及。
以下列举了常见的环境下为全局对象的那些值,this在非严格模式下的默认指向是他们
浏览器环境:
window
、self
、frames
(他们的值都是全局对象,一般我们认为的this默认指向是window
)
Node环境:
global
worker环境:
self
通用:
globalThis
(IE不兼容)
this的非默认指向
1. 使用严格模式的情况
'use strict'
var name2 = 'name2'
console.log(this.name2)
// undefined
⭐ 严格模式下,全局环境下的this
指向默认为undefined
2. 使用了let、const声明顶层对象,class构造顶层函数的情况
顶层对象的属性与全局变量挂钩,被认为是JavaScript语言最大的设计败笔之一。😅
ES6为了改变这一点,一方面规定,为了保持兼容性,var命令和function命令声明的全局变量,依旧是顶层对象的属性;另一方面规定,let命令、const命令、class命令声明的全局变量,不属于顶层对象的属性。
也就是说,从ES6开始,全局变量将逐步与顶层对象的属性脱钩。
⭐ 使用let
、const
在全局声明变量,使用class
在全局构造函数时,生成的顶层对象不再绑定到全局对象上,此时用this
获取这些变量得到undefined
。
// 非严格模式
let name2 = 'window2';
const name3 = 'window3';
let doSth2 = function(){
console.log(this === window);
console.log(this.name2);
console.log(this.name3);
}
doSth2() // true, undefined, undefined
以上例子中的name2
、name3
和doSth2
不属于window
,所以this.name2
为undefined
。
function hh () {}
console.log('hh:', this.hh)
class hhh {}
console.log('hhh:', this.hhh)
// hh: ƒ hh () {}
// hhh: undefined
以上例子中hhh
用class
构造,不属于window
,所以this.hhh
为undefined
。
3. 对象的函数写法Object.keyName()的this指向
这种调用函数的写法!写法!写法!导致了
this
指向的变换。
箭头函数除外,因为箭头函数没有自己的this
。
let doSth = function(){
console.log(this.name);
}
var student = {
name: '若川',
doSth: doSth,
other: {
name: 'other',
doSth: doSth,
}
}
student.doSth(); // '若川'
student.other.doSth(); // 'other'
使用student.doSth()
的方式调用doSth
函数,this
指向student
var studentDoSth = student.doSth;
studentDoSth(); // 'window'
使用studentDoSth()
的方式调用doSth
函数,this
指向studentDoSth
所处的环境window
⭐ 根据调用函数时使用的对象中函数Object.keyName()
的这种写法,将keyName
函数中的this
指向了Object
对象
⭐ 或者通俗的说,让keyName
函数的this
成为Object
,变成了console.log(Object.name)
4. 使用了call,apply、bind修改函数中的this指向
// 'use strict';
function hh () {}
function h () {
console.log('hh:', this.hh)
}
h.call(undefined);
// hh: ƒ hh () {}
⭐ 非严格模式下,call()
、apply()
、bind()()
方法第一个参数是undefined
或者null
,this
是指向window
。
'use strict';
function hh () {}
function h () {
console.log('hh:', this.hh)
}
h.call(undefined);
// Uncaught TypeError: Cannot read properties of undefined (reading 'hh')
⭐ 严格模式下,call()
、apply()
、bind()()
方法第一个参数是undefined
或者null
,this
一定指向第一个参数,为undefined
。
var doSth = function(name){
console.log(this);
console.log(name);
}
doSth.call(2, '若川'); // Number{2}, '若川'
var doSth2 = function(name){
'use strict';
console.log(this);
console.log(name);
}
doSth2.call(2, '若川'); // 2, '若川'
⭐ 非严格模式下,call()
、apply()
、bind()()
方法第一个参数是原始值(数字,字符串,布尔值),this
会指向该原始值的自动包装对象。
⭐ 严格模式下,call()
、apply()
、bind()()
方法第一个参数是原始值(数字,字符串,布尔值),this
会指向该原始值。
4. 构造函数中的this
function Student(name){
this.name = name;
console.log(this); // {name: 'nname'}
// 相当于返回了
// return this;
}
var result = new Student('nname');
使用new操作符调用函数,会自动执行以下步骤。
- 创建了一个全新的对象。
- 这个对象会被执行
[[Prototype]]
(也就是__proto__
)链接。 - 生成的新对象会绑定到函数调用的
this
。 - 通过
new
创建的每个对象将最终被[[Prototype]]
链接到这个函数的prototype
对象上。 - 如果函数没有返回对象类型
Object
(包含Functoin
,Array
,Date
,RegExg
,Error
),那么new
表达式中的函数调用会自动返回这个新的对象。
利用上文的new Student()
翻译一下:
-
创建一个新
{}
; -
{}.__proto__ = Student.prototype
或者{}.[[Prototype]] = Student.prototype
-
function Student(){ this === {__proto__: Student.prototype}}
-
function Student(){ return this}
(*没有返回其他自定义对象类型Object的情况下) -
s1 = this
看文章面试官问:JS的this指向看到一个例子:
function Student(name){ this.name = name; // 第一个this } var s1 = new Student('若川'); Student.prototype.doSth = function(){ console.log(this.name); // 第二个this } s1.doSth(); // '若川'
文中
第一个this
和第二个this
的产生方式不同,第一个this
由构建函数这种构建方法产生,第二个this
由s1.doSth()
的对象中函数这种写法产生,可以理解为将s1
的this
给了doSth
。function Student(name){ console.log(this) // 第一个this this.name = name; } var s1 = new Student('若川'); Student.prototype.doSth = function(){ console.log(this) // 第二个this } const sdosth = s1.doSth sdosth() // Student {} // Window {window: Window, self: Window, document: document, name: '', location: Location, …}
换一种调用方法,
第二个this
就改变了
5. 箭头函数中的this
1、没有自己的
this、super、arguments
和new.target
绑定。
2、不能使用
new
来调用。
3、没有原型对象。
4、不可以改变this
的绑定。
5、形参名称不能重复。
⭐ 如果箭头函数被非箭头函数包含,则this
绑定的是最近一层非箭头函数的this
,否则this
的值则被设置为全局对象。
function foo() {
setTimeout(() => {
console.log('id:', this.id);
}, 100);
}
var id = 21;
foo.call({ id: 42 });
// id: 42
⭐ 箭头函数导致函数内部的this
指向是固定的,总是指向函数定义生效时所在的对象,相比之下,普通函数的this
指向是可变的。
var value = 1;
var result = (() => this.value).bind({value: 2})();
console.log(result); // 1
⭐ 试图使用call,apply、bind
修改箭头函数中的this
是无效的
6.DOM事件处理函数中的this
DOM
事件处理函数中的this
,默认指向绑定此事件的DOM
。
例如:addEventerListener
、attachEvent
、onclick
事件
一些浏览器,比如IE6~IE8下使用attachEvent
,this
指向是window
。
attachEvent
——兼容:IE7、IE8
;不兼容firefox、chrome、IE9、IE10、IE11、safari、opera
;
addEventListener
——兼容:firefox、chrome、IE、safari、opera
;不兼容IE7、IE8
。
<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>
需要注意下内联事件处理函数中的this
:
<button class="btn1" onclick="console.log(this === document.querySelector('.btn1'))">点我呀</button>
<button onclick="console.log((function(){return this})());">再点我呀</button>
第一个是button
本身,所以是true
,第二个是window
。
这里跟严格模式没有关系。 当然我们现在不会这样用了,但有时不小心写成了这样,也需要了解。
7.优先级
以上非默认情况同时出现时,对this
指向的影响是:
new
调用 > call、apply、bind
调用 > 对象上的函数调用 > 普通函数调用 。
总结记忆
如果要判断一个运行中函数的 this
绑定, 就需要找到这个函数的直接调用位置。 找到之后就可以顺序应用下面这四条规则来判断 this
的绑定对象。
new
调用:绑定到新创建的对象,注意:显示return
函数或对象,返回值不是新创建的对象,而是显式返回的函数或对象。call
或者apply
( 或者bind
) 调用:严格模式下,绑定到指定的第一个参数。非严格模式下,null
和undefined
,指向全局对象(浏览器中是window
),其余值指向被new Object()
包装的对象。- 对象上的函数写法调用:绑定到那个对象。
- 普通函数调用: 在严格模式下绑定到
undefined
,否则绑定到全局对象。
ES6 中的箭头函数:不会使用上文的四条标准的绑定规则, 而是根据当前的词法作用域来决定this
, 具体来说, 箭头函数会继承外层函数,调用的 this
绑定( 无论 this
绑定到什么),没有外层函数,则是绑定到全局对象(浏览器中是window
)。
DOM事件函数:一般指向绑定事件的DOM
元素,但有些情况绑定到全局对象(比如IE6~IE8
的attachEvent
)。
一定要注意,有些调用可能在无意中使用普通函数绑定规则。 如果想“ 更安全” 地忽略 this
绑 定, 你可以使用一个对象, 比如 ø = Object.create(null)
, 以保护全局对象。