📕 重学JavaScript: this中的隐式绑定场景
嗨,大家好!这里是道长王jj
~ 🎩🧙♂️
我相信大家了解了JavaScript的this关键字代表什么含义,这里我就不过多的阐述。
我这里就简要地概括一下:
- this是一个关键字,它表示当前执行上下文中的一个对象,也就是说,它指向谁取决于函数是怎么调用的。
- this有两种绑定方式,一种是显示绑定,一种是隐式绑定。
显示绑定就是用call/apply/bind这些方法来指定函数内部的this指向,这种方式比较直接,你可以自由地把this绑定到任何对象上。
隐式绑定就是根据函数调用的方式来确定函数内部的this指向,这种方式比较隐晦,你需要注意一些规则和特例。
今天我们就专门来聊聊关于隐式绑定的几个场景。
1️⃣ 全局上下文
当你在全局环境中使用this时,它指向全局对象(浏览器中是window,Node.js中是global)。
这是默认的绑定方式,也是最不安全的方式,因为你可能不小心修改了全局对象的属性或方法。
比如,你在全局环境中定义了一个变量a和一个函数fn,然后用this来访问它们,就像这样:
var a = 1;
function fn() {
console.log(this.a);
}
console.log(this.a); // 1
fn(); // 1
这里的this都指向全局对象,所以可以访问到a和fn。但是如果你不小心把this.a赋值为其他值,就会影响全局变量a,就像这样:
var a = 1;
function fn() {
this.a = 100;
console.log(this.a);
}
console.log(this.a); // 1
fn(); // 100
console.log(this.a); // 100
这里的fn函数内部的this也指向全局对象,所以它把this.a赋值为100,相当于把全局变量a赋值为100。这样就会导致全局变量被污染,可能会引发一些难以发现的bug。
2️⃣ 直接调用函数
当你直接调用一个函数时,它内部的this也指向全局对象。
这和全局上下文类似,但是有一个例外,就是当你在严格模式下调用函数时,它内部的this会指向undefined。
这是为了防止你误操作全局对象而设置的安全措施。
比如,你定义了一个函数fn,在非严格模式下直接调用它,就像这样:
function fn() {
console.log(this);
}
fn(); // window
这里的fn函数内部的this指向全局对象window。但是如果你在严格模式下直接调用它,就像这样:
"use strict";
function fn() {
console.log(this);
}
fn(); // undefined
这里的fn函数内部的this指向undefined。
这是因为在严格模式下,JS不允许你隐式地使用全局对象作为this,而是要求你显示地指定this的值。
如果你没有指定this的值,那么就会默认为undefined。这样可以避免你不小心修改了全局对象的属性或方法。
3️⃣ obj.fn()
的形式调用
当你用一个对象来调用它的方法时,它内部的this指向这个对象。
这是最常见的绑定方式,也是最符合直觉的方式,因为你可以把这个对象看作是方法的拥有者或接收者。
比如,你有一个对象叫obj,它里面有一个属性a和一个方法fn,然后用obj来调用它的方法fn,就像这样:
var obj = {
a: 1,
fn: function() {
console.log(this.a);
}
};
obj.fn(); // 1
这里的obj.fn()内部的this指向obj对象,所以可以访问到obj对象的属性a。但是如果你把obj.fn赋值给另一个变量,并用这个变量来调用函数,就像这样:
var obj = {
a: 1,
fn: function() {
console.log(this.a);
}
};
var anotherFn = obj.fn;
anotherFn(); // undefined
这里的anotherFn()内部的this不再指向obj对象,而是指向全局对象,所以无法访问到obj对象的属性a。
这是因为anotherFn只是一个普通的函数,它不是obj对象的方法,所以它不会继承obj对象的this。
4️⃣ DOM事件绑定(特殊)
当你给一个DOM元素绑定一个事件处理函数时,它内部的this指向这个DOM元素。
这是一个特殊的情况,因为这里涉及到了浏览器的事件机制,它会把事件处理函数作为DOM元素的方法来调用。
比如,你有一个按钮元素叫btn,它里面有一个文本内容叫click me,然后你给它绑定了一个点击事件处理函数fn,就像这样:
<button id="btn">click me</button>
<script>
var btn = document.getElementById("btn");
function fn() {
console.log(this); // <button id="btn">click me</button>
console.log(this.innerHTML); // click me
}
btn.addEventListener("click", fn);
</script>
这是因为浏览器会把fn函数作为btn元素的方法来调用,就像这样:
btn.fn();
所以fn函数内部的this就指向了btn元素。
5️⃣ new构造函数绑定
当你用new关键字来调用一个构造函数时,它内部的this指向一个新创建的对象。
这是一种特殊的绑定方式,因为这里涉及到了JS的原型链机制,它会把构造函数作为新对象的原型来调用。
比如,你定义了一个构造函数叫Person,它里面有一个属性name和一个方法sayName,然后用new关键字来创建一个实例叫p,就像这样:
function Person(name) {
this.name = name;
this.sayName = function() {
console.log(this.name); // Tom
};
}
var p = new Person("Tom");
这是因为JS会把Person函数作为p对象的原型来调用,就像这样:
p.__proto__ = Person.prototype;
Person.call(p, "Tom");
所以Person函数内部的this就指向了p对象。😁
6️⃣ 箭头函数
当你使用箭头函数时,它内部的this不会根据调用方式改变,而是继承自它所在的词法作用域(lexical scope)。
也就是说,箭头函数没有自己的this,它会沿着作用域链向上寻找最近的一个普通函数,然后把它内部的this作为自己的this。
比如,你定义了一个普通函数fn,在它里面定义了一个箭头函数arrowFn,并用this来访问a属性,就像这样:
var a = 1;
function fn() {
var a = 2;
var arrowFn = () => {
console.log(this.a);
};
arrowFn();
}
fn(); // 1
这里的arrowFn函数内部的this不是指向fn函数或者全局对象,而是指向fn函数外面的那个普通函数(在这里就是全局环境)。
这是因为箭头函数没有自己的this,它会沿着作用域链向上寻找最近的一个普通函数(在这里就是全局环境),
然后把它内部的this(在这里就是全局对象)作为自己的this。
也就是说,箭头函数内部的this和它外面的普通函数内部的this是一样的。
💥 出现多种隐式绑定时,优先级谁最高?
我先说答案:new > call、apply、bind > obj.fn() > 直接调用。
当一个函数有多种绑定方式时,它会按照这个优先级来确定内部的this指向。😁
比如,你有一个构造函数叫Person,它里面有一个属性name和一个方法sayName,然后你用new关键字来创建一个实例叫p,并用call方法来指定this指向obj对象,就像这样:
function Person(name) {
this.name = name;
this.sayName = function() {
console.log(this.name);
};
}
var obj = {
name: "Tom"
};
var p = new Person("Jerry").sayName.call(obj);
p.sayName() //Jerry
这里的p.sayName()内部的this指向谁呢?
根据优先级,我们可以知道,new绑定的优先级高于call绑定,所以p.sayName()内部的this指向p对象,而不是obj对象。
所以,当你调用p.sayName()时,就会打印出Jerry
那箭头函数呢?
其实箭头函数不属于这几个场景的优先级,因为箭头函数没有自己的this,它会继承自它所在的词法作用域(lexical scope)的this。
也就是说,箭头函数内部的this和它外面的普通函数内部的this是一样的。
所以,箭头函数内部的this的优先级取决于它外面的普通函数的优先级。
🎉 你觉得怎么样?这篇文章可以给你带来帮助吗?如果你有任何疑问或者想进一步讨论相关话题,请随时发表评论分享您的想法,让其他人从中受益。🚀✨