一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第4天,点击查看活动详情。
六、上下文
6-1、认识函数中的上下文
函数中可以使用this关键字,它表示函数的上下文,函数中的this具体指代什么必须通过调用函数时的“前言后语”来判断。
来看一个小例子:
var obj = {
name: '张三',
age: 18,
sex: '男',
sayHello: function() {
console.log('你好,我是' + this.name + '我今年'+ this.age + '岁')
}
}
obj.sayHello() // 你好,我是张三我今年18岁
再来看一个例子:
var obj = {
name: '张三',
age: 18,
sex: '男',
sayHello: function() {
console.log('你好,我是' + this.name + '我今年'+ this.age + '岁')
}
}
var sayHello = obj.sayHello;
sayHello();
- 此处将函数单独“提”出来了,而不是通过对象+ 点的方式调用函数,而是直接通过圆括号调用此函数
- 此时会输出 你好,我是undefined我今年undefined岁
- 此处this则不会指代obj对象,而是指代window对象,由于window对象中没有name与age变量,所以为undefined
通过以上两个例子可以得出:
- 函数的上下文由调用方式决定,通过一个函数,用不同的形式调用,则函数的上下文不同
6-2、上下文绑定规则
函数的上下文(this关键字)由调用函数的方式决定,如果函数不被调用则不能确定函数的上下文。
6-2-1、规则1
对象打点调用它的方法函数( 对象.方法() ),则函数的上下文是这个打点的对象
6-2-1-1、案例1
function fn() {
console.log(this.a + this.b)
}
var obj = {
a: 1,
b: 2,
fn: fn
};
obj.fn(); // 3
函数上下文是在函数被调用时才能被决定的,fn是由obj打点调用的,所以this指向obj,那么this.a为1,this.b为2,结果为3
6-2-1-2、案例2
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
虽然obj2的fn被赋值为obj1的fn,但是函数的上下文是调用时才决定的,由于函数fn最终是由obj2打点调用的所以this指向obj2:this.a为3,this.b为4,结果为7
6-2-1-3、案例3
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方法返回一个对象,所以outer().fn()相当于对象打点调取fn,适用此规则,所以this.a为33,this.b为44,最终输出77
6-2-1-4、案例4
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
c方法的调用是由obj.c[0]打点调用的,而obj.c[0]为一个对象,随意this指向此对象:this.a为3,this.b为4,所以结果为7
6-2-2、规则2
使用圆括号直接调用函数,则函数的上下文是window对象
6-2-2-1、案例1
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
这里,fn变量保存的是obj1中fn函数的引用,最终fn是直接使用圆括号调用的,上下文是window对象,所以this.a为3,this.b为4,结果为7
6-2-2-1、案例2
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
obj中的属性b的值是通过直接调用fun()得到的,这里fun直接使用圆括号调用,上下文为window,所以this.a + this.b为1+2,b的值为3;而result的值是通过obj打点调用fun获取的,这里this指向obj对象,所以this.a + this.b 为3 + 3,最终结果为6
6-2-3、规则3
调用数组(类数组对象)枚举出的函数,那么上下文对象就是这个数组(类数组对象)。
6-2-3-1、案例1
var arr = ['A', 'B', 'C', function () {
console.log(this[0]);
}];
arr[3](); // A
此处调用的是arr[3]的函数,符合此规则,this指向该数组,所以this[0]为A
6-2-3-2、案例2
function fun() {
arguments[3]();
}
fun('A', 'B', 'C', function () {
console.log(this[1]) // B
});
由于调用的是arguments类数组对象中枚举出的函数,那么this指向该类数组对象,所以函数中输出的this[1]即类数组中的索引为1的内容,所以是B
6-2-4、规则4
IIFE中的函数,上下文是window对象
6-2-4-1、案例
var a = 1;
var obj = {
a: 2,
fun: (function () {
var a = this.a;
return function () {
console.log(a + this.a);
}
})()
};
obj.fun(); // 3
obj对象中的fun属性对应的是立即执行函数,会立即执行,由于IIFE中的函数上下文是window对象,所以其中的var a = this.a; 即:var a = 1;该函数执行后返回一个函数。所以obj.fun即返回的这个函数,又因为这个函数的执行是通过obj打点调用执行的(适用于规则1),所以这个函数的this.a即2,那么console.log中的内容即 1 + 2 ,所以最后输出3
6-2-5、规则5
定时器、延时器中的回调函数,上下文是window对象
6-2-5-1、案例1
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
setTimeout中的函数是obj中的fun属性对应的函数,2秒后会由延时器调用,由于延时器中的函数的上下文是window对象,所以this.a+ this.b即3+4,最后结果为7
6-2-5-2、案例2
var obj = {
a: 1,
b: 2,
fun: function () {
console.log(this.a + this.b);
}
};
var a = 3;
var b = 4;
setTimeout(function () {
obj.fun();
}, 2000); // 3
注意,这里与上面不同的是,setTimeout中传递的是一个匿名函数,该匿名函数中obj对象打点调用了fun对应的函数,所以fun函数中this.a+this.b即 1+2,只不过2秒后才会执行匿名函数。结果为3
6-2-6、规则6
事件处理函数的上下文是绑定事件的DOM元素
6-2-6-1、案例1
点击哪个盒子 ,哪个盒子变红
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<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() {
this.style.backgroundColor = 'red';
}
var box1 = document.getElementById('box1');
var box2 = document.getElementById('box2');
var box3 = document.getElementById('box3');
box1.onclick = setColorToRed;
box2.onclick = setColorToRed;
box3.onclick = setColorToRed;
</script>
</body>
</html>
6-2-6-2、案例2
点击哪个盒子,哪个盒子两秒后变红
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<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() {
// 备份上下文
var self = this;
setTimeout(function() {
self.style.backgroundColor = 'red';
}, 2000);
}
var box1 = document.getElementById('box1');
var box2 = document.getElementById('box2');
var box3 = document.getElementById('box3');
box1.onclick = setColorToRed;
box2.onclick = setColorToRed;
box3.onclick = setColorToRed;
</script>
</body>
</html>
要注意,由于有2秒的延迟,需要通过setTimeout调用该函数,但是setTimeout中函数的上下文是window,所以要备份一下上下文。(所以我们在书写this的时候要留意一下,this会不会被其他地方的逻辑所改变)
七、call与apply
使用call与apply可以指定函数的上下文:
function sum() {
console.log(this.c + this.m + this.e);
};
var zhangsan = {
c: 100,
m: 90,
e: 80
};
sum.call(zhangsan);
sum.apply(zhangsan);
以上案例,通过使用call/apply,将sum函数的上下文指定成为了zhangsan对象
7-1、call与apply的区别
call与apply的区别在接收参数的时候才能体现出来:
- call接收参数是以逗号分隔罗列开来
- apply接收参数是以数组接收
function sum(b1, b2) {
alert(this.c + this.m + this.e + b1 + b2);
};
var zhangsan = {
c: 100,
m: 90,
e: 80
};
sum.call(zhangsan, 66, 88);
sum.apply(zhangsan, [66, 88]);
7-2、上下文总结
规则 | 上下文 |
---|---|
对象.函数() | 对象 |
函数() | window |
数组下标 | 数组 |
IIFE | window |
定时器 | window |
DOM事件处理函数 | 绑定DOM的元素 |
call和apply | 任意指定 |
八、构造函数
8-1、用new操作符来调用函数
new 函数();
JS规定,使用new操作符调用函数会进行“四步走”:
1)函数体内会自动创建出一个空白对象
2)函数的上下文(this)会指向这个对象
3)执行函数体内的语句
4)自动返回上下文对象,即使函数没有return语句
比如,现有以下代码:
function fun() {
this.a = 3;
this.b = 6;
}
var obj = new fun();
console.log(obj);
第一步:函数体内创建空白对象
第二步:函数上下文this指向该对象
第三步:执行函数体内语句
则此时会为该空白对象添加上a与b属性
第四步:自动返回该对象
至此可见,上下文绑定规则便多了一条:
规则 | 上下文 |
---|---|
对象.函数() | 对象 |
函数() | window |
数组下标 | 数组 |
IIFE | window |
定时器 | window |
DOM事件处理函数 | 绑定DOM的元素 |
call和apply | 任意指定 |
new调用函数 | 创建出来的对象 |
8-2、什么是构造函数
我们先来看以下代码:
function People(name, age, sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
var xiaoming = new People('小明', 12, '男');
var xiaohong = new People('小红', 4, '女');
var xiaogang = new People('小刚', 17, '男');
console.log(xiaoming);
console.log(xiaohong);
console.log(xiaogang);
我们在通过new关键字来调取People函数时传了三个函数来构造出了三个不同的对象,为这三个对象都添加了name、age、sex属性。
- 用new调用一个函数,这个函数就被称为“构造函数”,任何函数都可以是构造函数,只需要用new调用它
- 构造函数用来“构造新对象”,它内部的语句将为新对象添加若干属性和方法,完成对象的初始化
- 构造函数必须用new关键字调用,否则不能正常工作(相当于独立调用函数,this指向window全局对象),开发者约定构造函数命名时首字母要大写
并不是一个函数名称首字母大写了它就是构造函数,而是要看其是否用new调用了
8-3、为对象添加方法
除此之外,我们还可以为构造出来的对象添加方法:
function People(name, age, sex) {
this.name = name;
this.age = age;
this.sex = sex;
this.sayHello = function () {
console.log('你好,我是' + this.name + ',我今年' + this.age + '岁了,我是个' + this.sex + '生');
};
this.sleep = function () {
console.log(this.name + '开始睡觉,zzzzz');
};
}
var xiaoming = new People('小明', 12, '男');
var xiaohong = new People('小红', 4, '女');
var xiaogang = new People('小刚', 17, '男');
console.log(xiaoming);
console.log(xiaohong);
console.log(xiaogang);
xiaohong.sayHello();
xiaogang.sleep();
这样,三个不同的对象都会拥有sayHello与sleep方法