前言
大家好,之前作者写过一篇文章:深入理解JavaScript的this关键字,分析了this的基本概念,以及绑定规则,但this是面试中的一个重难点,所以本文通过两道分析this指向的复杂题来巩固我们的this知识,特别是第二道题,还涉及到行为委托的知识,大家可以往下看,看看是否能做出来!
题目一
一个能够将背景颜色变绿的按钮。
html
html
<button class="button">点击变色</button>
<script src="./button.js">
new Button('button');
</script>
button.js
js
function Button(id){ //1.请分析下面两个this是指什么,为什么?
this.element = document.querySelector(`#${id}`);
this.bindEvent();
}
Button.prototype.bindEvent = function() { //2.请分析下面的this是指什么,为什么?
this.element.addEventListener('click', this.setBgColor.bind(this))
}
Button.prototype.setBgColor = function(){ //3.请分析下面的this是指什么,为什么?
this.element.
}
让我们进行仔细分析:
1. Button 构造函数中的 this
js
function Button(id) {
this.element = document.querySelector(`#${id}`);
this.bindEvent();
}
this 指向 :新创建的 Button 实例对象
原因 :当使用 new Button('button')
调用时,new
操作符会:
- 创建一个新的空对象
- 将这个新对象的原型指向 Button.prototype
- 将这个新对象绑定到构造函数中的 this
- 执行构造函数中的代码
- 返回这个新对象(除非构造函数返回另一个对象)
2. bindEvent 方法中的 this
js
Button.prototype.bindEvent = function() {
this.element.addEventListener('click', this.setBgColor.bind(this))
}
this 指向 :Button 实例对象
原因 :当通过实例调用 bindEvent()
方法时(如 this.bindEvent()
),方法内部的 this 会隐式绑定到调用它的对象(即 Button 实例)。这是 JavaScript 的方法调用规则。
3. setBgColor 方法中的 this
js
Button.prototype.setBgColor = function() {
this.element.
}
this 指向 :Button 实例对象
原因:这里有两个关键点:
-
在
bindEvent()
中使用了.bind(this)
将setBgColor
的 this 显式绑定到了当前的 Button 实例 -
通过
.bind(this)
我们确保了无论setBgColor
如何被调用,this 都指向 Button 实例
注意:如果没有bind(this)
会怎么样?
如果去掉 .bind(this)
,代码会变成这样:
js
Button.prototype.bindEvent = function() {
this.element.addEventListener('click', this.setBgColor) // 去掉了 .bind(this)
}
setBgColor
中的this
将不再指向 Button 实例
当点击事件触发时,this.setBgColor
作为事件处理函数被调用,由于在addEventListener
内,所以此时的this
会默认指向触发事件 的 DOM 元素(即<button>
元素),而不是 Button 实例。- 代码会报错
在setBgColor
方法中尝试访问this.element
时,因为this
现在是<button>
元素,而<button>
元素没有element
属性,所以会抛出类似这样的错误:
Uncaught TypeError: Cannot read property 'style' of undefined
好了,题目1结束了,看看你的分析是否是正确的吧!这题里的this绑定规则用到了隐式绑定,显式绑定,new绑定以及事件触发事件时的this绑定等,是一道复杂的题目。
题目二
这道题涉及到JS中的行为委托的委托理论,是来自于《你不知道的JavaScript》里的例题,当时作者第一次分析时,会把this和原型链给搞混,大家可以来做一下
js
Foo = {
init: function(who) { //1.分析init里面的this
this.me = who;
},
identify: function(){ //2.分析identify里面的this
return "Im" + this.me;
}
};
Bar = Object.create(Foo);
Bar.speak = function() { //3.分析这里面的this
console.log('hello',this.identify() + ".");
}
var b1 = Object.create(Bar);
b1.init("zhangsan");
b1.speak();
让我们逐步分析这段代码中的 this
绑定情况:
首先注意,这里b1 = Object.create(Bar)
是创建 b1
对象,并为b1
对象设置原型链,这里注意区分new的方式,他不会调用构造函数。它不像new方式,刚开始就会指定this,它刚开始不会指定任何this,只有在后面方法调用时才会绑定this。
1. init
方法中的 this
js
init: function(who) {
this.me = who; // 这里的 this 指向b1
}
分析:
- 当通过
b1.init("zhangsan")
调用时 init
是作为b1
对象的方法被调用的- 根据隐式绑定规则,方法中的
this
指向调用它的对象 this
指向 :b1
对象
2. identify
方法中的 this
js
identify: function() {
return "Im" + this.me; // 这里的 this 也指向b1
}
分析:
- 当通过
this.identify()
在speak
方法中调用时 identify
是作为this
(即b1
对象)的方法被调用的- 根据隐式绑定规则,方法中的
this
指向调用它的对象 this
指向 :b1
对象
3. speak
方法中的 this
js
speak: function() {
console.log('hello', this.identify() + "."); // 这里的 this 指向
}
分析:
- 当通过
b1.speak()
调用时 speak
是作为b1
对象的方法被调用的- 根据隐式绑定规则,方法中的
this
指向调用它的对象 this
指向 :b1
对象
所以你会发现这三个this都是指向b1,原型链只是帮我们分析要调用哪个函数,分析this只用关注是谁调用的就行!请看下面的完整执行流程
完整执行流程
-
b1 = Object.create(Bar)
创建b1
对象,继承Bar
的原型链 -
b1.init("zhangsan")
调用:init
中的this
指向b1
- 设置
b1.me = "zhangsan"
-
b1.speak()
调用:speak
中的this
指向b1
- 调用
this.identify()
(即b1.identify()
) identify
中的this
也指向b1
- 返回
"Imzhangsan"
- 最终输出:
hello Imzhangsan.
原型链关系
js
b1 -> Bar -> Foo
好了,题目二结束了,我们需要注意这道题中,Object.create()的作用,分析this时,只要看最后的调用位置即可!
总结
JavaScript 中的 this 机制是语言的核心特性之一,也是开发者必须掌握的难点。通过本文两道经典题目的分析, 我们可以理解 this 的关键在于:它不是在定义时确定的,而是在调用时根据执行上下文动态绑定的。掌握这一核心原则,配合本文的分析方法,就能在各种复杂场景中准确判断 this 指向。通过不断练习和思考,this 将不再是令人头疼的问题,而会成为你灵活运用 JavaScript 的强大工具