- 说明:该文属于 大前端全栈架构白宝书专栏,目前阶段免费,如需要项目实战或者是体系化资源,文末名片加V!
- 作者:哈哥撩编程,十余年工作经验, 从事过全栈研发、产品经理等工作,目前在公司担任研发部门CTO。
- 荣誉:2022年度博客之星Top4、2023年度超级个体得主、谷歌与亚马逊开发者大会特约speaker、全栈领域优质创作者。
- 🏆 白宝书系列
- 🏅 启示录 - 攻城狮的自我修养
- 🏅 Python全栈白宝书
- 🏅 ChatGPT实践指南白宝书
- 🏅 产品思维训练白宝书
- 🏅 全域运营实战白宝书
- 🏅 大前端全栈架构白宝书
文章目录
- ⭐认识上下文
- ⭐上下文规则
- 🌟上下文规则1
- 🌟上下文规则2
- 🌟上下文规则3
- 🌟上下文规则4
- 🌟上下文规则5
- 🌟上下文规则6
⭐认识上下文
小时候学习做阅读理解时,老师经常会强调,注意上下文。比如有一个句子:这是一个好习惯,我们应该坚持。如果不结合上文的意思,根本不知道“这”指的是什么。如果结合上文,比如,随手关灯,这是一个好习惯,我们应该坚持。我们就知道此时的“这”指的是“随手关灯”,那么整个句子的语义就好理解了。
在函数中,也经常需要结合上下文来编写和理解代码。
函数中可以使用
this
,表示函数的上下文
与中文中的“这”类似,函数中的this具体指代什么必须
通过调用函数时的“前言后语”来判断
下面看一个例子:
- 第一种调用方式:对象打点调用自己的方法:
var xiaomumu = {
name: '小沐沐',
age: 2,
sex: '男',
hobbies: ['秋千', '滑梯'],
sayHello: function () {
console.log('Hello, 我是' + this.name + ',我今年' + this.age + '岁了');
}
};
xiaomumu.sayHello(); // Hello, 我是小沐沐,我今年2岁了
- 第二种调用方式:对象的方法被赋值给一个全局变量,全局变量加圆括号调用:
var xiaomumu = {
name: '小沐沐',
age: 2,
sex: '男',
hobbies: ['秋千', '滑梯'],
sayHello: function () {
console.log('Hello, 我是' + this.name + '我今年' + this.age + '岁了');
}
};
var sayHello = xiaomumu.sayHello; // 将这个属性存入了一个全局变量(变量的名称可以和属性名相同,方便理解)
sayHello(); //Hello, 我是undefined我今年undefined岁了
这两种调用方式,this指代的对象分别是:
- 第一种,对象打点调用方式,函数中的this指代这个打点的对象
- 第二种,圆括号直接调用函数,函数中的this指代window对象
其实可以这么理解,第二种中,因为全局变量都是window的属性,相当于用window.sayHello()的方式调用了这个函数。所以this指代的就是window对象。
所以我们一定要记住:只有函数被调用时,我们才能知道this指代的是什么函数的上下文由函数的调用方式
决定,同一个函数,用不同的形式调用它,则函数的上下文不同
⭐上下文规则
我们知道,不同的调用方式,函数的上下文就不同,那么我们怎么去判断函数的上下文呢?这就要学习各种不同的函数调用规则。
🌟上下文规则1
规则1:对象打点调用它的方法函数,则函数的上下文是这个打点的对象
下面看几个案例,练习一下:
题目一: 下面代码的运行结果是什么?
function fn() {
console.log(this.a + this.b);
}
var obj = {
a: 11,
b: 22,
fn: fn
};
obj.fn();
运行结果:33
题目二: 下面代码的运行结果是什么?
var obj1 = {
a: 1,
b: 2,
fn: function () {
console.log(this.a + this.b);
}
};
var obj2 = {
a: 3,
b: 4,
fn: obj1.fn
};
obj2.fn();
运行结果: 7
一定要记住,谁打点调用函数,函数中的this指代的就是谁
题目三: 下面代码的运行结果是什么?
function outer() {
var a = 11;
var b = 22;
return {
a: 33,
b: 44,
fn: function () {
console.log(this.a + this.b);
}
};
}
outer().fn();
运行结果:77
上面的代码中,outer()返回了一个对象,相当于还是一个对象打点调用了函数,所以适用于对象打点调用函数,函数的上下文就是这个对象的规则。
题目四: 下面代码的运行结果是什么?
function fun() {
console.log(this.a + this.b);
}
var obj = {
a: 1,
b: 2,
c: [{
a: 3,
b: 4,
c: fun
}]
};
var a = 5;
obj.c[0].c();
运行结果:7
上面的代码中,最终调用函数的是obj.c[0]
这个对象,所以函数中的this指代的就是obj.c[0]
🌟上下文规则2
规则2:圆括号直接调用函数,则函数的上下文是window对象
题目一: 下面代码的运行结果是什么?
var obj1 = {
a: 1,
b: 2,
fn: function () {
console.log(this.a + this.b);
}
};
var a = 3;
var b = 4;
var fn = obj1.fn;
fn();
运行结果:7
题目二: 下面代码的运行结果是什么?
function fun() {
return this.a + this.b;
}
var a = 1;
var b = 2;
var obj = {
a: 3,
b: fun(),
fun: fun
};
var result = obj.fun();
console.log(result);
运行结果:6,题目分析如下:
这是一道非常正规的面试题,大家一定要学会分析其中的代码逻辑
🌟上下文规则3
规则3:数组(类数组对象)枚举出函数进行调用,上下文是这个数组(类数组对象)
数组[下标]()
类数组对象:所有键名为自然数序列(从0开始),且有length属性的对象
arguments对象是最常见的类数组对象,它是函数的实参列表
题目一: 下面代码的运行结果是什么?
var arr = ['A', 'B', 'C', function () {
console.log(this[0]);
}];
arr[3]();
运行结果: “A”
上面的代码适用规则3,this指代的就是arr这个数组
题目二: 下面代码的运行结果是什么?
function fun() {
arguments[3]();
}
fun('A', 'B', 'C', function () {
console.log(this[1]);
});
运行结果:“B”。我们可以打印一下arguments,可以看到arguments是一个类数组对象,里面其实就是fun()函数被调用时,传入的实参:
🌟上下文规则4
规则4:IIFE中的函数,上下文是window对象。(IIFE在以前的文中提到过,是立即可执行函数。)
(function (){
//立即可执行函数
})()
题目一: 下面代码的运行结果是什么?
var a = 1;
var obj = {
a: 2,
fun: (function () {
var a = this.a;
return function () {
console.log(a + this.a);
}
})()
};
obj.fun();
运行结果:3。这是一个大厂的面试题,题目分析如下:
🌟上下文规则5
规则5:定时器、延时器调用函数,上下文是window对象
setInterval(函数,时间);
setTimeout(函数,时间);
题目一: 下面代码的运行结果是什么?
var obj = {
a: 1,
b: 2,
fun: function () {
console.log(this.a + this.b);
}
};
var a = 3;
var b = 4;
setTimeout(obj.fun, 2000);
运行结果:7。因为用延时器调用的函数,适用规则5,this指代的就是window对象,即this.a
和this.b
的值分别是3,4
题目二: 下面代码的运行结果是什么?
var obj = {
a: 1,
b: 2,
fun: function () {
console.log(this.a + this.b);
}
};
var a = 3;
var b = 4;
setTimeout(function () {
obj.fun(); //适用规则1
}, 2000);
运行结果:3。这里要注意,延时器不是直接调用obj里的fun函数,而是一个匿名函数,这个匿名函数里又调用了obj.fun(),所以此时的this应指代的是obj这个对象,即this.a
和this.b
的值分别是1,2
🌟上下文规则6
规则6:事件处理函数的上下文是绑定事件的DOM元素
DOM元素.onclick = function() {
};
题目一: 点击哪个盒子,哪个盒子就变红,要求使用同一个事件处理函数实现(不能用事件委托)
<html lang="en">
<head>
<style>
div {
width: 200px;
height: 200px;
float: left;
border: 1px solid #000;
margin-right: 10px;
}
</style>
</head>
<body>
<div id="box1"></div>
<div id="box2"></div>
<div id="box3"></div>
<script>
function setColorToRed(o) {
this.style.backgroundColor = 'red';
}
var box1 = document.getElementById('box1');
var box2 = document.getElementById('box2');
var box3 = document.getElementById('box3');
box1.onclick = function () {
setColorToRed(box1);
}
box1.onclick = setColorToRed;
box2.onclick = setColorToRed;
box3.onclick = setColorToRed;
</script>
</body>
</html>
运行结果:点击哪个盒子,哪个盒子就变红。这里的this指代的就是绑定事件的DOM元素;注意区分this和e.target的不同
题目二: 点击哪个盒子,哪个盒子在2000毫秒后就变红,要求使用同一个事件处理函数实现(不能用事件委托)
题目分析:
这个题目其实就是上个题目+一个延时器,但是需要注意的是加上延时器后,this指代的可能会不一样,所以需要使用一个程序猿非常常用的技术:备份上下文
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
div {
width: 200px;
height: 200px;
float: left;
border: 1px solid #000;
margin-right: 10px;
}
</style>
</head>
<body>
<div id="box1"></div>
<div id="box2"></div>
<div id="box3"></div>
<script>
function setColorToRed(o) {
//备份上下文
var self = this; // 通常使用self来备份上下文,也有使用that或_this的,这个技术非常的常用!
setTimeout(() => {
self.style.backgroundColor = 'red';
}, 2000);
}
var box1 = document.getElementById('box1');
var box2 = document.getElementById('box2');
var box3 = document.getElementById('box3');
box1.onclick = function () {
setColorToRed(box1);
}
box1.onclick = setColorToRed;
box2.onclick = setColorToRed;
box3.onclick = setColorToRed;
</script>
</body>
</html>
注意:程序员在书写this的时候通常会看一下这个this到底指代什么,需不需要备份