this指向
![在这里插入图片描述](https://img-blog.csdnimg.cn/9958e56de5d0437fa7f8309da8b656f0.jpg?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA55Cq5YWu5aWI6Iul5L2V,size_20,color_FFFFFF,t_100,g_se,x_16#pic_center)
1. 调用位置
在理解this的绑定过程之前,首先是要理解调用位置:调用位置就是函数在代码中被调用的位置(不是声明的位置),只有明白调用位置,才能弄懂this到底引用的是声明。
function baz() {
// 当前调用栈
// 当前调用位置是全局作用域
console.log('baz')
bar() // bar的调用位置
}
function bar() {
// 当前调用栈是baz -> bar
// 当前调用位置在baz中
console.log('bar')
foo() // foo的调用位置
}
function foo() {
// 当前调用栈是baz -> bar -> foo
// 当前调用位置在bar中
console.log('foo')
}
baz() // baz的调用位置
2. 绑定规则
1. 默认绑定
- 最常用的函数调用类型:独立函数调用,也可以看作无法应用其他规则时的默认规则
function foo() {
console.log(this.a)
}
var a = 2
foo() // 2
当调用foo()时,this.a被解析为全局变量a,因为函数调用时应用了默认绑定,因此this指向全局变量。
严格模式
如果使用严格模式,则不能将全局对象用于默认绑定,因此this为undefined:
"use strict"
function foo() {
console.log(this.a)
}
var a = 2
foo() // TypeError: this is undefined;
注意:虽然this的绑定取决于调用位置,但只有foo()运行在非严格模式下,默认绑定才能绑定到全局对象;但在严格模式下调用则不影响默认绑定
function foo() {
// 非严格模式
console.log(this.a)
}
var a = 2
(function() {
"use strict"
foo()
})() // 2, 不影响默认绑定
、
2. 隐式绑定
第二种要考虑调用位置是否又上下文对象,或者说是否被某个对象拥有或包含
function foo() {
console.log(this.a)
}
var obj = {
a: 2,
foo: foo
}
obj.foo() // 2
调用位置使用obj上下文来引用函数,可以说函数被调用时obj对象拥有或包含对象引用。当foo()被调用时,它的前面加上了对obj的引用。当函数引用又上下文对象时,隐式绑定规则会把函数调用中的this绑定到这个上下文对象,因为调用foo()时this被绑定到obj,因此this.a和obj.a一样。
对象属性引用链中只有上一层或最后一层在调用位置起作用
function foo() {
console.log(this.a)
}
var obj2 = {
a: 10,
foo: foo
}
var obj1 = {
a: 2,
obj1: obj2
}
obj1.obj1.foo()
隐式丢失
最常见的问题是被隐式绑定的函数会丢失绑定对象,也就是说它会应用默认绑定,从而把this绑定到全局对象或undefined上。
function foo() {
console.log(this.a)
}
var obj = {
a: 2,
foo : foo
}
var bar = obj.foo()
var a = 'global'
bar() // global
虽然bar是obj.foo的一个引用,但它引用的是foo函数本身,因此此时的bar()其实是一个不带任何修饰的函数调用,应用了默认绑定。
还有一种就说传入回调函数时:
function foo() {
console.log(this.a)
}
function foo1(fn) {
fn()
}
var obj = {
a: 2,
foo: foo
}
var a = 'golbal'
foo(obj.foo) // folbal
参数传递就说一种隐式赋值,因此传入函数时也会被隐式赋值
3. 显示绑定
function foo() {
console.log(this.a)
}
var obj = {
a: 2
}
foo.call(obj) // 2
通过call,可以在调用foo时强制把它的this绑定到obj上,如果传入一个原始值来当作this绑定的对象,这个原始值会被转化成它的对象形式(呢哇String()等等)。通常被称为“装箱”,但显示绑定并不能解决丢失绑定的问题。
硬绑定
function foo() {
console.log(this.a)
}
var obj = {
a: 2
}
var bar = functon() {
foo.call(obj)
}
bar() // 2
setTimeout(bar, 1000) // 2
// 硬绑定的bar不能再修改它的this
bar.call(window) // 2
创建了函数bar(),并在它的内部手动调用了foo.call(obj),因此强制把foo的this绑定到了obj。无论之后如何调用函数bar,它总会手动在obj上调用foo。这种绑定是一种显式的强制绑定,称为硬绑定。
硬绑定的典型应用场景就说创建一个包裹函数,负责接收参数返回值:
function foo(something) {
console.log(this.a, something)
return this.a + something
}
var obj = {
a: 2
}
var bar = function() {
return foo.apply(obj, arguments)
}
var b = bar(3) // 2, 3
console.log(b) // 5
另一种使用方法就是创建一个可以复用的赋值函数:
function foo(something) {
console.log(this.a, something)
return this.a + something
}
// 简单的辅助绑定函数
function bind(fn, obj) {
return function() {
return fn.apply(obj, arguments)
}
}
var obj = {
a: 2
}
var bar = bind(foo.obj)
var b = bar(3) // 2, 3
console.log(b) // 5
API调用的“上下文”
第三方库的许多函数,以及js语言和宿主环境中许多新的内置函数,都提供了一个可选的参数,通常被称为“上下文”,其作用和bind(…)一样,确保回调函数指向的this。
function() {
console.log(el, this.id)
}
var obj = {
id: 'aaa'
}
// 调用foo时把this绑定到obj
[1, 2, 3].forEach(foo, obj) // 1 aaa 2 aaa 3 aaa
4. new绑定
使用new来调用函数,或者说发生构造函数调用时,会自动执行下面操作:
- 创建(构造)一个全新的对象
- 新对象会被执行[[Prototype]]连接
- 新对象会绑定到函数调用的this
- 如果函数没有返回其他对象,那么new表达式中的寒素调用会自动返回这个新对象‘
function foo(a) {
this.a = a
}
var bar = new foo(2)
console.log(bar.a) // 2
使用new来调用foo时,会构造一个新对象把它绑定到foo(…)调用中的this上,new最后一种可以影响函数调用时this绑定行为的方法,称之为new绑定。
5. 箭头函数中的this指向
箭头函数不适用this的四种规则,而是根据外层(函数或全局)作用域来决定this的。
function foo() {
// 返回一个箭头函数
return (a) => {
// this继承自foo()
console.log(this.a)
]
}
var obj1 = {
a: 2
}
var obj2 = {
a: 3
}
var bar = foo.call(obj1)
bar.call(obj2) // 2
foo内部创建的箭头函数会捕获调用foo()的this,由于foo()的this绑定到obj1,bar(引用箭头函数)的this也会绑定到obj1,箭头函数的绑定无法被修改。(new也不可以)
箭头函数最常用于回调函数中,例如事件处理器或定时器
function foo() {
setTimeout(() => {
console.log(this.a)
}, 1000)
}
var obj = {
a: 2
}
foo.call(obj) // 2
3. 判断this的优先级
- 函数是否在new中调用,如果是的话this绑定的是新创建的对象
- 函数通过call,apply或硬绑定调用,this绑定的是指定的对象
- 函数在上下文对象中调用,绑定的是上下文对象
- 最后就是默认绑定,如果在严格模式下,绑定到undefined,否则绑定到全局对象