🍎 JavaScript this 指向问题
相关问题:
- JavaScript 中对函数
this的理解?
# 概念
this 关键字时函数运行时自动生成的一个 内部对象。只能在函数内部使用,总是指向 调用它 的对象。 this 可以更加使用便捷的方式来引用对象,进行一些 API 设计时,代码更加简洁和易于复用。
绝大多数情况下,函数的调用方式决定了 this 的值(调用时绑定)。同时 this 在函数执行的过程中,一旦被确定了就不能再更改。
this是什么:
任何函数本质上都是通过某个对象来调用的,⭐️ 如果没有制定就是全局作用域下的
window。所有函数内部都有一个变量
this;它的值是调用函数的当前对象;
# this 指向什么?
在全局作用域下:
- 浏览器中
this没有指定时,为window; - 所以全局作用域下,一般
this指向的是window;
但是开发中很少在全局作用域下使用 this ,一般在函数中使用 this 。
在函数被调用时,都会创建一个 执行上下文。这个执行上下文中记录函数的调用栈、函数的调用方法、传入的参数信息等; this 是函数内部其中的一个属性。
函数在定义时,默认会给
this绑定一个值;this的绑定和定义的位置(编写的位置)没有关系;this的绑定与调用方式和调用的位置有关系。this是在运行时被绑定的。
# 绑定规则
根据不同的使用场景, this 会有不同的值。
# 默认绑定
在独立的函数中调用,意味着函数没有绑定到每个对象上进行调用。可以分为两种情况:
普通函数调用。函数直接被调用,并没有任何的对象关联。这种独立的函数调用会使用默认绑定,通常默认绑定时,函数中的
this指向全局对象window。此时全局环境中定义的变量,在函数中可以通过
this获取。(针对var生命的变量)🌰 例子:
var name = "Simon" function person() { return this.name } console.log(person()) // Simon1
2
3
4
5调用
this的对象是函数,而函数本身没有name这个变量,所以往外找,最终找到指向window。严格模式下,不能讲全局对象用于默认绑定,
this会绑定到undefined。函数调用链:所有的函数调用都没有被绑定到某个对象上。
🌰 例子:
function test1() { console.log(this); // window test2(); } function test2() { console.log(this); // window test3() } function test3() { console.log(this); // window } test1();1
2
3
4
5
6
7
8
9
10
11
12
13
14将函数作为参数,传入到另一个函数中:
🌰 例子:
function foo(func) { func() } function bar() { console.log(this) // window } foo(bar)1
2
3
4
5
6
7
8
9🌰 🌟 例子 :
function foo(func) { func() } var obj = { name: "why", bar: function() { console.log(this); // window } } foo(obj.bar);1
2
3
4
5
6
7
8
9
10
11
12此时的函数虽然在对象之中,但是真正调用函数的位置,并没有进行任何的对象绑定,只是一个独立函数的调用,所以这里的
this仍然为window。
# 隐式绑定
函数还可以作为某个对象的方法调用,此时 this 指向的就是这个上级对象。可以分为以下的情况:
通过对象调用函数:
🌰 例子:
function foo() { console.log(this) // obj } var obj = { name: "Simon", foo: foo } obj.foo()1
2
3
4
5
6
7
8
9
10🌰 例子:
function foo() { console.log(this) // obj1 } var obj1 = { name: "Simon", foo: foo } var obj2 = { name: "Simon2", obj1: obj1 } obj2.obj1.foo()1
2
3
4
5
6
7
8
9
10
11
12
13
14
15此时,
foo调用的位置仍然是obj1,所以这里this绑定的对象仍然是obj1。隐式丢失:
function foo() { console.log(this) // window } var obj = { name: "Simon", foo: foo } var bar = obj1.foo bar()1
2
3
4
5
6
7
8
9
10
11因为这里
foo被调用的位置时bar,而bar在进行调用的时候没有绑定任何的对象,也就是没有形成隐形绑定。相当于默认绑定。
隐式绑定有一个前提条件:必须在调用的对象内部有一个对函数的引用(例如一个属性)。如果没有这样的引用,在进行调用的时候,会报找不到该函数的错误。正是通过这个引用,间接地将 this 绑定到这个对象上。
# 显式修改绑定
如果不希望在对象内部包含这个函数的引用,又希望在这个对象上强制调用。使用 apply / call / bind 方法。改变函数调用的对象,它们的第一个参数表示改变后调用这个函数的对象,因此 this 指向这个对象,这个过程明确的指定了 this 指向的对象所以称为显式绑定。
JavaScript 的所有函数都可以使用
call/apply方法。
call/apply:两者的区别知识参数上的传递有区别:🚃 JavaScript 装饰器模式和转发 | notebook (simon1uo.github.io) (opens new window),功能类似。
🌰 例子:
function foo() { console.log(this) } foo.call(window) // window foo.call({name: "simon"}) // {name: "simon"} foo.call(123) // Number 对象存放1231
2
3
4
5
6
7bind:如果希望函数总是显式绑定到一个对象上,这样每次调用函数时就不用每次都绑定this到一个特定的对象上。🌰 例子:
function foo() { console.log(this) // window } var obj = { name: "simon" } var bar = foo.bind(obj) bar() // obj bar() // obj1
2
3
4
5
6
7
8
9
10
11
12
# 内置函数
在 JavaScript 的内置函数(或者第三方库中的内置函数),这些内置函数允许传入另一个函数(回调函数),此时我们不会显式调用这些函数,而是 JavaScript 内部帮助我们执行。
setTimeout():🌰 例子:
setTimeout(function() { console.log(this) // window }, 1000)1
2
3与
setTimeout的内部调用有关。setTimeout内部通过apply绑定this对象,并且绑定的是window对象。forEach():🌰 例子:
var names = ['abc', 'def', 'ghi'] names.forEach(function(name) { console.log(this) // window*3 })1
2
3
4默认情况下传入的函数是自动调用函数(默认绑定);
可以根据
forEach(value, thisArg)的方法传入第二个参数,改变传入的对象:var obj = {name: "why"}; names.forEach(function(name) { console.log(this) // window*3 }, obj)1
2
3
4HTML 元素的点击:
🌰 例子 /
div:<div class="box"></div>1var box = document.querySelector(".box"); box.onclick = function() { console.log(this); // box对象 }1
2
3
4因为发生点击时,执行传入的回调函数被调用时,会将
box对象绑定到该函数中。
传入到内置函数的回调函数 this :
- 某些内置函数很难判断。一方面可以分析源码,另一方面通过经验判断。无论如何最终都是根据之前的 默认绑定或者隐式 / 显式绑定。
# new 绑定
JavaScript 通过构造器 new 关键字生成一个实例对象,此时 this 指向这个实例对象。
步骤:
- 创建一个全新的对象;
- 这个新对象会被执行
Prototype连接;- 这个新对象会绑定到函数调用的
this上(this的绑定在这个步骤完成);- 如果函数没有返回其他对象,表达式会返回这个新对象;
🌰 例子:
function test() {
this.x = 1
}
var obj = new test()
obj.x // 1
2
3
4
5
6
之所以
obj.x为1,是因为new改变了this的指向。
🌰 例子 / 特殊情况:
function fn() {
this.user = 'xxx'
return {}
}
var a = new fn()
console.log(a.user) // undefined
2
3
4
5
6
此时
new的过程中遇到了一个return,此时this指向的是返回的对象。当返回的是一个简单类型时,
this指向实例对象:function fn() { this.user = 'xxx' return 1 } var a = new fn() console.log(a.user) // xxx1
2
3
4
5
6当返回的是
null时,虽然也是对象,但是此时this指向实例对象。
# 绑定的优先级
显示绑定 > new 绑定 > 隐式绑定 > 默认绑定
- 因为存在其他规则是,就会使用其他规则绑定
this。而不是默认绑定,所以默认绑定的优先级最低。
🌰 例子 / 隐式绑定与显式绑定比较:
function foo() {
console.log(this.a)
}
var obj1 = {
a: 2,
foo: foo
}
var obj2 = {
a: 3,
foo: foo
}
obj1.foo() // 2
obj2.foo() // 3
obj1.foo.call(obj2) // 3
obj2,foo.call(obj1) // 2
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
🌰 例子 / new 绑定与隐式绑定比较:
function foo(something) {
this.a = something
}
var obj1 = {
foo: foo
}
var obj2 = {}
obj1.foo(2)
console.log(obj1.a) // 2
obj1.foo.call(obj2, 3) // 3
console.log(obj2.a) // 3
var bar = new obj1.foo(4)
console.log(obj1.a) // 2
console.log(bar.a) // 4
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
🌰 例子 / new 绑定与显示绑定比较:
function foo(something) {
this.a = something
}
var obj1 = {}
var bar = foo.bind(obj1)
bar(2)
console.log(obj1.a) // 2
var baz = new bar(3);
console.log(obj1.a) // 2
console.log(baz.a) // 3
2
3
4
5
6
7
8
9
10
11
12
bar被绑定到 obj1 上,但是new bar(3)并没有像我们预计的那样把obj1.a修改为 3。但是,new修改了绑定调用bar()中的this
# 其他情况绑定规则
# 忽略规则绑定
如果在显式绑定中,绑定对象时传入的参数为 undefined 或者 null ,那么这个显式绑定就会被忽略,然后使用默认规则。
🌰 例子:
function foo() {
console.log(this)
}
var obj = {
name = "simon"
}
foo.call(obj) // obj
foo.call(undefined) // window
foo.call(null) // window
var bar = foo.bind(null)
bar() // window
2
3
4
5
6
7
8
9
10
11
12
13
14
# 间接函数引用
创建函数的间接引用,这时使用默认绑定规则。
🌰 例子:
var num1 = 100
var num2 = 0
var result = (num2 = num1)
console.log(result) // 100
2
3
4
引申到:
function foo() {
console.log(this)
}
var obj1 = {
name = "obj1",
foo: foo
}
var obj2 = {
name = "obj2"
}
obj1.foo() // obj1
(obj2.foo = obj1.foo)() // window
2
3
4
5
6
7
8
9
10
11
12
13
14
15
因为赋值
(obj2.foo = obj1.foo)的结果是foo函数。所以foo函数被直接调用,使用默认绑定规则。
# 箭头函数的 this 指向
ES6 中箭头函数的语法,不使用四种绑定规则(不绑定 this ),而是由外层作用域决定 this 。
🌰 例子:
var obj = {
data: [],
getData: function() {
var _this = this;
setTimeout(function() {
// 模拟获取到的数据
var res = ["abc", "cba", "nba"];
_this.data.push(...res);
}, 1000);
}
}
obj.getData();
2
3
4
5
6
7
8
9
10
11
12
13
使用
setTimeout来模拟网络请求,需要通过this获取obj对象存放到data。但是setTimeout中的函数直接的this绑定的是window,所以需要在外层定义var _this = this。
如果使用 箭头函数,可以直接使用 this 。因为箭头函数不会绑定 this 对象,那么 this 引用就会从上层作用域找到对应的 this :
var obj = {
data: [],
getData: function() {
setTimeout(() => {
// 模拟获取到的数据
var res = ["abc", "cba", "nba"];
this.data.push(...res);
}, 1000);
}
}
obj.getData();
2
3
4
5
6
7
8
9
10
11
12
如果 getData 也是箭头函数:
var obj = {
data: [],
getData: () => {
setTimeout(() => {
console.log(this)
}, 1000);
}
}
obj.getData();
2
3
4
5
6
7
8
9
10
那么
this会从上层作用域找,找到全局作用域。所以为window。
