前言
😈大家好,我是一溪风月
一名前端工程师,在开发的过程中,总觉得很多小伙伴对this
的使用方式不太了解,或者说有时候摸不着头脑this
是JavaScript中非常重要的概念,如果this
不会使用会在开发中遇到问题不能够解决,所以今天我们就来学习下this
的常见使用方式,在这里我们不会考虑箭头函数的情况,因为它本就没有this
我们只会考虑有this
的情况,那么让我们一次性解决这些问题吧。
一.直接调用(独立函数调用/全局调用)
😂直接调用顾名思义就是直接进行函数的调用,怎么直接调用哪? 我们常见的直接调用分为两种情况,如下:
js
function foo () {
console.log(this)
}
//进行函数调用
foo()
这是最常见的一种情况,这种情况this
在非严格模式的情况下指向的是全局对象,这种调用方式,我们称之为独立函数调用,或者称之为全局调用。
还有另外一种情况是在对象中定义,然后赋值给一个变量,然后单独对这个赋值后的函数进行调用,依然是独立函数调用或者称之为全局调用,非严格模式下指向的是window。
js
let obj = {
name: "zpj",
foo: function () {
console.log(this)
}
}
let bar = obj.foo
bar()
这种写法,依然属于独立函数调用,指向的依然是window或者说全局对象。
🚨注意:独立函数调用(全局调用)this的指向在严格模式下并不指向window而是指向undefined
所以我们在使用的时候千万不要使用this
来代替window,而是直接使用window。
二.对象绑定
😈通过对象绑定比较容易理解,因为我们在平时会经常用到,当我们在如下的这种方式进行的时候会指向调用它的对象obj
;
js
let obj={
name:"aaa",
foo:function(){
console.log(this);
}
}
obj.foo()
// { name: 'aaa', foo: [Function: foo] }
三.new绑定
🦊在讲解new绑定之前,我们先来看下当我们在new
一个对象的时候到底做了什么事情。
- 创建新的空对象
- 将
this
指向这个空对象。 - 指向函数体中的代码。
- 没有显示返回空对象的时候默认返回这个对象。
😶🌫️从第二步我们知道,当我们进行new
操作的时候会将this
绑定到实例化的这个对象上面。
js
function foo (name) {
this.name = name
console.log(this.name)
}
let bar = new foo("zzz")
console.log(bar)
// zzz
// foo { name: 'zzz' }
通过上述的代码我们可以看到当我们进行对象实例化的时候函数内部的this
指向的是实例化的这个对象。
四.this指向绑定事件的元素
js
<ul id="color-list">
<li>red</li>
<li>yellow</li>
<li>blue</li>
<li>green</li>
<li>black</li>
<li>white</li>
</ul>
js
// this 是绑定事件的元素
// target 是触发事件的元素 和 srcElememnt 等价
let colorList = document.getElementById("color-list");
colorList.addEventListener("click", function (event) {
console.log('this:', this);
console.log('target:', event.target);
console.log('srcElement:', event.srcElement);
})
🤡有些时候我们会遇到一些困扰,比如在div
节点的事件函数内部,有一个局部的 callback
方法,该方法被作为普通函数调用时,callback
内部的this
是指向全局对象 window
的.
js
<div id="div1">我是一个div</div>
js
window.id = 'window';
document.getElementById('div1').onclick = function(){
console.log(this.id); // div1
const callback = function(){
console.log(this.id); // 因为是普通函数调用,所以 this 指向 window
}
callback();
}
此时有一种简单的解决方案,可以用一个变量保存 div节点的引用,如下:
js
window.id = 'window';
document.getElementById('div1').onclick = function(){
console.log(this.id); // div1
const that = this; // 保存当前 this 的指向
const callback = function(){
console.log(that.id); // div1
}
callback();
}
五.显式绑定(call/apply)
🥴有的时候this
的指向并不能如我们所愿,这个时候我们需要手动去更改this
的指向,来满足我们的需求,其实在JavaScript中给我们提供了能够更改this
的绑定,首先我们看下call和apply。
js
function foo(name,age){
console.log(this);
console.log(name,age);
}
const obj = {
name:"zs",
age:12,
}
foo.call(obj,'ls',30)
// { name: 'zs', age: 12 }
// ls 30
通过call
函数我们可以看到我们可以手动的将this
绑定到我们新定义的对象上面来,并且通过call
单个传参的方式将参数传递给了这个函数,实现了函数的调用和this
的绑定。
js
function foo (name, age) {
console.log(this)
console.log(name, age)
}
const obj = {
name: "zs",
age: 12,
}
foo.apply(obj, ['nnn', 45])
// { name: 'zs', age: 12 }
// ls 30
我们会发现我们使用apply
的方式进行绑定的修改结果依然如此,差别在于他们的传参方式不同,call是单个的方式进行传参的,而apply是通过数组的方式传参的。
六.bind函数的显式绑定
🐻bind的绑定和call
和apply
的使用有些差别,使用bind会生成一个新的函数,这个新函数我们称之为BF绑定函数,我们需要手动对这个函数进行调用。
js
function foo(){
console.log("foo",this)
}
let obj = {
name:"why"
}
let bar = foo.bind(obj)
bar()
七.内置函数的调用绑定思考
😂我们在开发中会用到很多内置的函数,比如定时器setTimeout
这个时候我们需要靠经验来判断当前的this指向因为有些东西根本不是我们来调用的,而是函数内部调用的,我们根本不知道他们做了什么,这些内容需要我们自己总结一下,内容如下。
- 定时器内部的函数
this
指向window
js
setTimeout(function () {
console.log(this, "1")
}, 500)
setTimeout(() => {
console.log(this, "2")
}, 500)
- 按钮的点击事件,指向事件发起的对象,也就是绑定事件的元素。
js
let box = document.querySelector(".test")
box.onclick = function () {
console.log(this)
}
forEach
中的this
也是指向window
js
const array = [1, 2, 3, 4, 5, 6]
array.forEach(item => {
console.log(this)
})
八.绑定优先级的比较
🥴我们前面了解的都是一些独立的规则,但是实际的情况往往是比较复杂的,可能涉及到多个绑定一起使用的情况,这个时候我们就需要研究一下不同函数之间调用的优先级。
- 直接调用(默认绑定)的优先级是最低的。
- 显式绑定优先级高于对象绑定(隐式绑定)。
js
function foo () {
console.log(this.name)
}
let obj = {
name: "zzz",
bar: foo
}
obj.bar.call({
name: "aaa"
})
// aaa
- new绑定优先级高于对象绑定(隐式绑定)的优先级
js
function foo (name) {
this.name = name
console.log(this.name)
}
let obj = {
name: "zzz",
bar: foo
}
new obj.bar("ccc")
// ccc
- new绑定不能和
call
与apply
一起使用,new
绑定的优先级比bind
高。
js
function foo (name) {
this.name = name
console.log(this.name)
}
let bar = foo.bind("zzz")
new bar("ccc")
// ccc
bind
和apply
的优先级,bind
的优先级更高,也高于call
因为call
和apply
使用方法一样。
js
function foo () {
console.log(this)
}
let bar = foo.bind("zzz")
bar.call("aaa")
// zzz
九.绑定规则之外
🥴其实在上述的绑定规则之外还有许多我们有时候按照规则难以理解的情况,我们来总结下有哪些情况。
- 在显式绑定当中如果传入
null
undefined
这个绑定会被忽略,使用默认规则,严格模式能够绑定。
js
function foo () {
console.log(this)
}
foo.apply(null)
foo.apply(undefined)
// window
// window
- 创建一个函数的间接引用,使用函数的默认绑定规则,指向
window
js
var obj1 = {
name:"obj1",
foo:function(){
console.log("foo",this)
}
}
var obj2={
name:"obj2"
};
(obj2.foo = obj1.foo)()
// window
十 .面试题尝试
js
const o1 = {
text: 'o1',
fn: function () {
return this.text;
}
}
const o2 = {
text: 'o2',
fn: function () {
return o1.fn();
}
}
const o3 = {
text: 'o3',
fn: function () {
var fn = o1.fn;
return fn();
}
}
console.log(o1.fn()); // o1
console.log(o2.fn()); // o1
console.log(o3.fn()); // undefined