前言:this的迷惑行为大赏
大家好,我是你们的老朋友FogLetter,今天我们来聊聊JavaScript中最让人迷惑又最重要的概念之一——this。相信不少同学在初学JavaScript时,都曾被this的"善变"折磨得死去活来。它时而指向window,时而指向某个对象,有时候干脆undefined给你看。
但别担心,今天我将用最生动的方式,带大家彻底搞懂this的指向规则,以及如何用call、apply、bind来驯服这个"善变"的家伙。最后我们还会聊聊如何用这些知识来写出更优雅的封装代码。
一、this的四种基本指向规则
1. 默认绑定:普通函数调用
var name = 'a';
function fn(){
var name = 'b';
console.log(this.name);
}
fn(); // 输出什么?
这里fn()是直接调用的,没有绑定任何对象,此时this指向全局对象(浏览器中是window)。所以输出'a'。
注意:在严格模式下("use strict"),这种情况this会是undefined,可以避免意外的全局污染。
2. 隐式绑定:方法调用
let obj = {
name: 'fog',
fn: function() {
console.log(this.name);
}
}
obj.fn(); // 输出 'fog'
当函数作为对象的方法调用时,this指向调用它的对象。这里obj.fn()中的this就是obj。
但有个坑:
const fn2 = obj.fn;
fn2(); // 输出 'a'
为什么?因为赋值后fn2是直接调用的,没有通过obj,所以this又回到了默认绑定规则。
3. 显式绑定:call/apply/bind
var a = {
name: 'fog',
fn: function(a,b) {
console.log(this.name);
console.log(a,b);
}
}
const b = a.fn;
b.call(a,1,2); // fog 1 2
b.apply(a,[1,2]); // fog 1 2
b.bind(a,1,2)(); // fog 1 2
这三种方法都可以显式指定this的值:
call:立即执行,参数逐个传递apply:立即执行,参数以数组形式传递bind:返回一个新函数,需要再调用一次
4. new绑定:构造函数调用
function Person(name,age) {
this.name = name;
this.age = age;
}
const haha = new Person('haha',18);
console.log(haha.name); // 'haha'
使用new调用函数时:
- 创建一个新对象
- 这个新对象会绑定到函数的
this - 如果函数没有返回其他对象,就自动返回这个新对象
二、箭头函数:this的例外
箭头函数没有自己的this,它会捕获所在上下文的this值,作为自己的this值。
var a = {
func2: function() {
setTimeout(() => {
this.func1(); // this正确指向a
}, 1000)
}
}
对比普通函数:
var a = {
func2: function() {
setTimeout(function() {
this.func1(); // 报错,this指向window
}, 1000)
}
}
三、实战:封装一个按钮组件
让我们用this和原型链的知识,封装一个按钮组件:
// button.js
function Button(id) {
this.element = document.querySelector(`#${id}`);
this.bindEvent();
}
Button.prototype.bindEvent = function() {
this.element.addEventListener('click', this.setBgColor.bind(this));
}
Button.prototype.setBgColor = function() {
this.element.style.backgroundColor = '#1abc9c';
}
// 使用
new Button('button');
为什么需要bind(this)?
如果不绑定,事件处理函数中的this会指向触发事件的DOM元素。通过bind(this),我们确保在setBgColor方法中,this仍然指向Button实例。
四、this指向的优先级
当多种规则同时适用时,优先级如下:
- new绑定:
var obj = new Foo() - 显式绑定:
call/apply/bind - 隐式绑定:
obj.foo() - 默认绑定:
foo()
五、常见陷阱与最佳实践
1. 回调函数中的this丢失
// 错误示范
var obj = {
data: 'important',
fetchData: function() {
ajaxCall(function(response) {
console.log(this.data); // undefined
});
}
}
// 正确做法1:使用箭头函数
var obj = {
data: 'important',
fetchData: function() {
ajaxCall((response) => {
console.log(this.data); // 'important'
});
}
}
// 正确做法2:使用bind
var obj = {
data: 'important',
fetchData: function() {
ajaxCall(function(response) {
console.log(this.data); // 'important'
}.bind(this));
}
}
2. 多层嵌套中的this
var obj = {
name: 'outer',
inner: {
name: 'inner',
logName: function() {
console.log(this.name);
}
}
}
obj.inner.logName(); // 'inner'
记住:this指向最后调用它的对象,与定义位置无关。
结语
this的指向看似复杂,但只要掌握了四种基本规则,配合箭头函数和call/apply/bind的使用,就能轻松驾驭。在封装组件或类时,合理使用这些知识可以让代码更加健壮和优雅。
记住,理解this的关键在于看函数是如何被调用的,而不是在哪里定义的。多练习,多思考,很快你就能对this的指向了如指掌了!
希望这篇文章对你有帮助,如果觉得不错,别忘了点赞收藏哦~我们下期再见!