ES6补充

更多请参考

阮一峰老师 ES6 入门教程https://es6.ruanyifeng.com/

MDN 官方文档https://developer.mozilla.org/zh-CN/

一、作用域

1、作用域链

作用域链本质上是底层的变量查找机制。在函数被执行时,会优先查找当前函数作用域中查找变量,如果当前作用域查找不到则会依次逐级查找父级作用域直到全局作用域。这种嵌套关系的作用域串联起来就形成了作用域链

2、JS 垃圾回收机制

垃圾回收机制(Garbage Collection) 简称 GC

JS 中内存的分配和回收都是自动完成的,内存在不使用的时候会被垃圾回收器自动回收

虽然垃圾回收器会帮我们自动回收内存,但我们仍有必要了解 JS 的内存管理机制。它可以帮助我们理解内存泄漏(内存无法被回收)

不再用到的内存,没有及时释放,就叫做内存泄漏

内存的生命周期

JS 环境中分配的内存, 一般有如下生命周期:

  1. 内存分配:当我们声明变量、函数、对象的时候,系统会自动为他们分配内存

  2. 内存使用:即读写内存,也就是使用变量、函数等

  3. 内存回收:使用完毕,由垃圾回收自动回收不再使用的内存

说明:

全局变量一般不会回收(关闭页面回收);

一般情况下局部变量的值, 不用了, 会被自动回收掉

两种常见的浏览器垃圾回收算法

引用计数法

IE 采用的引用计数算法, 定义“内存不再使用”,就是看一个对象是否有指向它的引用,没有引用了就回收对象

具体步骤:

  1. 跟踪记录被引用的次数

  2. 如果被引用了一次,那么就记录次数 1,多次引用会累加

  3. 如果减少一个引用就减 1

  4. 当引用次数是 0 时 ,则释放内存

它存在一个致命的问题:嵌套引用(循环引用)

如果两个对象相互引用,尽管他们已不再使用,垃圾回收器不会进行回收,导致内存泄露。

1
2
3
4
5
6
7
8
function fn() {
let o1 = {};
let o2 = {};
o1.a = o2;
o2.a = o1;
return "引用计数无法回收";
}
fn();

正常情况下,上述代码中的变量 o1、o2 在函数 fn 执行完毕之后就应该被回收掉,但是根据引用计数法,虽然函数 fn 已经执行完成,但是由于 o1、o2 中存在着相互引用的关系,因此实际上并不会被回收,这就造成了内存泄漏。于是就有了标记清除法

标记清除法

现代的浏览器已经不再使用引用计数算法了

现代浏览器通用的大多是基于标记清除算法的某些改进算法,总体思想都是一致的。

核心:

  1. 标记清除算法将“不再使用的对象”定义为“无法达到的对象”。

  2. 就是从根部(在 JS 中就是全局对象)出发定时扫描内存中的对象。 凡是能从根部到达的对象,都是还需要使用的。

  3. 那些无法由根部出发触及到的对象被标记为不再使用,稍后进行回收。

image-20230510142831343

同样上述的代码,如果用标记清除法,在函数 fn 执行完毕之后,函数作用域里面的变量,从全局对象开始已经访问不到了,因此会被回收掉

3、闭包

MDN 官方解释:一个函数对周围状态的引用捆绑在一起,内层函数中访问到其外层函数的作用域

从代码结构上看,闭包 = 内层函数 + 外层函数的变量。

1
2
3
4
5
6
7
8
function outer() {
const a = 100;
function inner() {
console.log(a);
}
inner();
}
outer();

闭包作用:封闭数据,提供操作,外部也可以访问函数内部的变量

闭包的基本形式

1
2
3
4
5
6
7
8
function outer() {
const a = 100;
return function inner() {
console.log(a);
};
}
const fun = outer();
fun();

闭包应用:实现数据的私有

比如,我们要做个统计函数调用次数,函数调用一次,就++

1
2
3
4
5
6
7
let i = 1;
function count() {
i++;
console.log(`函数被调用了${i}次`);
}
count(); // 2
count(); // 3

但是这样定义的全局变量 i 很容易被修改,一旦修改,统计的函数调用次数就不准确了。可以通过闭包解决这个问题

1
2
3
4
5
6
7
8
9
10
function count() {
let i = 1;
return function fn() {
i++;
console.log(`函数被调用了${i}次`);
};
}
const fun = count();
fun(); // 2
fun(); // 3

这样实现了数据私有,无法直接修改 count

二、深入对象

创建对象的三种方式

  • 利用对象字面量创建对象

  • 利用 new Object 创建对象

  • 利用构造函数创建对象

1、构造函数

构造函数 :是一种特殊的函数,主要用来初始化对象

使用场景:常规的 {…} 语法允许创建一个对象。比如我们创建了佩奇的对象,继续创建乔治的对象还需要重新写一 遍。此时可以通过构造函数,它就像一个模子一样,能帮助我们快速创建多个类似的对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const xiaoming = {
name: '小明',
age: 18,
gender: '男'
}
const xiaohong = {
name: '小红',
age: 17,
gender: '女'
}
// 这种类似的结构,我们可以通过构造函数来创建
// 声明一个"人类"的构造函数
function Person(name,age,gender) {
this.name = name
this.age = age
this.gender = gender
}
// 通过实例化来批量生产“人类”
const xiaoming = new Person('小明'18'男')
const xiaohong = new Person('小红'18'女')

构造函数本质上也函数,不过一般情况下:

  • 它们的命名以大写字母开头,以表明这是一个构造函数

  • 它们只能由 “new” 操作符来执行

new Object()、new Date()这些都是内置的构造函数

构造函数的实例化执行过程

  1. 创建新对象

    2.构造函数 this 指向新对象

    3.执行构造函数代码,修改 this,添加新的属性

    4.返回新对象

实例成员&静态成员

实例成员:通过构造函数创建的对象称为实例对象,实例对象中的属性和方法称为实例成员。

1
2
3
4
5
6
7
8
9
10
11
function Person(name, age, gender) {
// 构造函数中直接声明的这些方法和属性就是实例成员
this.name = name;
this.age = age;
this.gender = gender;
}
// 直接为构造函数添加的这些方法和属性就是静态成员
Person.eyes = 2;
Person.say = function () {
console.log("会说话");
};

请注意:

  • 构造函数创建的实例对象彼此独立互不影响
  • 一般公共特征的属性或方法静态成员设置为静态成员
  • 静态成员方法中的 this 指向构造函数本身

2、内置构造函数

引用类型:Object,Array,RegExp,Date 等

包装类型:String,Number,Boolean 等

数组常见实例方法-核心方法

image-20230511084102099

reduce 语法

image-20230511084301258

求和

1
2
const arr = [1, 2, 3, 4, 5];
const result = arr.reduce((prev, current) => prev + current);

数组常见实例方法-其他方法

  1. 实例方法 join 数组元素拼接为字符串,返回字符串(重点)
  2. 实例方法 find 查找元素, 返回符合测试条件的第一个数组元素值,如果没有符合条件的则返回 undefined(重点)
  3. 实例方法every 检测数组所有元素是否都符合指定条件,如果所有元素都通过检测返回 true,否则返回 false(重点)
  4. 实例方法some 检测数组中的元素是否满足指定条件 如果数组中有元素满足条件返回 true,否则返回 false
  5. 实例方法 concat 合并两个数组,返回生成新数组
  6. 实例方法 sort 对原数组单元值排序
  7. 实例方法 splice 删除或替换原数组单元
  8. 实例方法slice返回一个新的数组对象,这一对象是一个由 begin 和 end 决定的原数组的浅拷贝(包括 begin,不包括 end)(此截取方法字符串也有)
  9. 实例方法 reverse 反转数组
  10. 实例方法 findIndex 返回数组中满足提供的测试函数的第一个元素的索引。若没有找到对应元素则返回 -1
  11. 实例方法includes用来判断一个数组是否包含一个指定的值,根据情况,如果包含则返回 true,否则返回 false
  12. 实例方法push添加元素到数组末尾
  13. 实例方法pop(弹出)删除数组末尾的元素
  14. 实例方法shift删除数组头部元素
  15. 实例方法unshift添加元素到数组的头部
  16. 实例方法indexOf返回数组中第一次出现给定元素的下标,如果不存在则返回 -1
  17. 实例方法fill用一个固定值填充一个数组中从起始索引到终止索引内的全部元素。不包括终止索引

数组常见静态方法

  1. Array.from():对一个伪数组或可迭代对象创建一个新的,浅拷贝的数组实例
  2. Array.isArray():用于确定传递的值是否是一个 Array

String 常见实例方法

在 JavaScript 中的字符串、数值、布尔具有对象的使用特征,如具有属性和方法。这是因为它们是 JavaScript 底层使用 Object 构造函数“包装”来的

String 常见实例方法

  1. 实例属性 length 用来获取字符串的度长(重点)
  2. 实例方法 split('分隔符') 用来将字符串拆分成数组(重点)
  3. 实例方法 substring(需要截取的第一个字符的索引[,结束的索引号]) 用于字符串截取(重点)
  4. 实例方法 startsWith(检测字符串[, 检测位置索引号]) 检测是否以某字符开头(重点)
  5. 实例方法 includes(搜索的字符串[, 检测位置索引号]) 判断一个字符串是否包含在另一个字符串中,根据情况返回 true 或 false(重点)
  6. 实例方法 toUpperCase 用于将字母转换成大写
  7. 实例方法 toLowerCase 用于将就转换成小写
  8. 实例方法 indexOf 检测是否包含某字符
  9. 实例方法 endsWith 检测是否以某字符结尾
  10. 实例方法 replace 用于替换字符串,支持正则匹配
  11. 实例方法 match 用于查找字符串,支持正则匹配
  12. 实例方法trim从字符串的两端清除空格,返回一个新的字符串,而不修改原始字符串
  13. 实例方法slice 方法提取某个字符串的一部分,并返回一个新的字符串,且不会改动原字符串

Number 常见方法

  1. 实例方法toFixed设置保留小数位的长度
  2. 静态方法 Number.parseInt() 方法依据指定基数,解析字符串并返回一个整数。
  3. 静态方法Number.parseFloat() 方法可以把一个字符串解析成浮点数

Date 常见方法

  1. 实例方法toLocaleString方法返回该日期对象的字符串
  2. 实例方法toLocaleDateString 方法返回指定日期对象日期部分的字符串
  3. 实例方法getDate根据本地时间,返回一个指定的 Date 对象为一个月中的哪一日(1-31
  4. 实例方法getDay根据本地时间,返回一个指定的 Date 对象是在一周中的第几天(0-6),0 表示星期天
  5. 实例方法getFullYear根据本地时间,返回一个指定的 Date 对象的完整年份(四位数年份)
  6. 实例方法getHours根据本地时间,返回一个指定的 Date 对象的小时(023
  7. 实例方法getMinutes根据本地时间,返回一个指定的 Date 对象的分钟数(059
  8. 实例方法getMonth根据本地时间,返回一个指定的 Date 对象的月份(011),0 表示一年中的第一月
  9. 实例方法getSeconds根据本地时间,返回一个指定的 Date 对象的秒数(059)
  10. 实例方法getTime返回一个数值,表示从 1970 年 1 月 1 日 0 时 0 分 0 秒(UTC,即协调世界时)距离该 Date 对象所代表时间的毫秒数(时间戳)
  11. 静态方法Date.now()返回自 1970-1-1 00:00:00 UTC(世界标准时间)至今所经过的毫秒数(时间戳)

Math 常见方法

  1. 静态方法Math.floor(x)返回小于一个数的最大整数,即一个数向下取整后的值

  2. 静态方法Math.ceil(x)返回大于一个数的最大整数,即一个数向上取整后的值

  3. 静态方法Math.max(x,y,...)返回零到多个数值中最大值

  4. 静态方法Math.min(x,y,...)返回零到多个数值中最小值

  5. 静态方法Math.pow(x,y)返回一个数的 y 次幂

  6. 静态方法Math.random()返回一个 0 到 1 之间的伪随机数

  7. 静态方法Math.round(x)返回四舍五入后的整数

三、面向对象

1、面向对象的编程思想

面向过程编程

面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候再一个一个的依次调用

优点:性能比面向对象高,适合跟硬件联系很紧密的东西,例如单片机就采用的面向过程编程

缺点:没有面向对象易维护、易复用、易扩展

面向对象编程 (oop)

面向对象是把事务分解成为一个个对象,然后由对象之间分工与合作

在面向对象程序开发思想中,每一个对象都是功能中心,具有明确分工。

面向对象编程具有灵活、代码可复用、容易维护和开发的优点,更适合多人合作的大型软件项目

面向对象的特性:

  • 封装性
  • 继承性
  • 多态性

优点:易维护、易复用、易扩展,由于面向对象有封装 、继承、多态性的特性,可以设计出低耦合的系统,使系统更加灵活、更加易于维护

缺点:性能比面向过程低

2、封装

JavaScript 中可以通过构造函数实现面向对象的封装

将相同的结构封装在构造函数中,通过 this 指向实现数据的共享。并且通过构造函数创造出来的实例对象之间彼此独立、互不影响

1
2
3
4
5
6
7
8
9
10
11
function Person(name, age, gender) {
this.name = name;
this.age = age;
this.gender = gender;
this.sing = function () {
console.log("说话");
};
}
const ldh = new Person("刘德华", 18, "男");
const zxy = new Person("张学友", 20, "男");
console.log(ldh.sing === zxy.sing); // false

然而,由于构造函数创造出来的实例对象之间彼此独立、互不影响。我们会发现ldh.singzxy.sing并不相等,这不是我们想要的,因为实际上每个实例对象(比如ldhzxy)之间的sing方法应该是相同的,他们实现的是相同的功能,因此没必要单独给它们各自分配内存。

image-20230511101913432

原型

使用原型对象就可以实现方法的共享

  • 构造函数通过原型分配的函数是所有实例对象所共享的
  • JavaScript 规定,每一个构造函数都有一个 prototype 属性,指向另一个对象,所以我们也称为原型对象
  • 这个对象可以挂载函数,对象实例化不会多次创建原型上的函数,节约内存
  • 我们可以把那些不变的方法,直接定义在 prototype 对象上,这样所有对象的实例就可以共享这些方法
  • 构造函数和原型对象中的 this 都指向实例化的对象
1
2
3
4
5
6
7
8
9
10
11
function Person(name, age, gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
Person.prototype.sing = function () {
console.log("说话");
};
const ldh = new Person("刘德华", 18, "男");
const zxy = new Person("张学友", 20, "男");
console.log(ldh.sing === zxy.sing); // true

constructor 属性

每个原型对象里面都有个 constructor 属性(constructor 构造函数)

该属性指向该原型对象的构造函数

使用场景:

如果有多个对象的方法,我们可以给原型对象采取对象形式赋值. 但是这样就会覆盖构造函数原型对象原来的内容,这样修改后的原型对象 constructor 就不再指向当前构造函数了 。也就是说正常情况下,原型对象上是存在着 constructor 属性的,现在我们想往原型对象上挂载多个方法,我们不想一个一个的添加,于是我们采用直接赋值的方式。但是我们通过赋值的形式往原型对象上挂载方法后,把原来的原型对象上的 constructor 属性覆盖掉了。此时,我们可以在修改后的原型对象中,添加一个 constructor 指向原来的构造函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
function Person(name, age, gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
// 多个方法挂载到原型对象上,为了方便不想一个一个的挂载
Person.prototype.sing = function () {
console.log("说话");
};
Person.prototype.dance = function () {
console.log("跳舞");
};
// 于是直接给prototype属性赋值
Person.prototype = {
sing: function () {
console.log("说话");
},
dance: function () {
console.log("跳舞");
},
};
// 但是这样,原本原型对象prototype上的constructor 属性就被赋值给覆盖掉了
Person.prototype = {
// 因此利用constructor,手动指回Person构造函数
constructor: Person,
sing: function () {
console.log("说话");
},
dance: function () {
console.log("跳舞");
},
};
const ldh = new Person("刘德华", 18, "男");
const zxy = new Person("张学友", 20, "男");
console.log(ldh.sing === zxy.sing); // true

但是当我们把原本放在构造函数上的一些公共属性或方法放到原型对象上后,实例对象如何访问到原型对象上的公共属性或方法呢?

proto

对象都会有一个属性 proto 指向构造函数的 prototype 原型对象,之所以我们对象可以使用构造函数 prototype 原型对象的属性和方法,就是因为对象有 proto 原型的存在

image-20230511105014332

3、原型继承

继承是面向对象编程的另一个特征,通过继承进一步提升代码封装的程度,JavaScript 中大多是借助原型对象实现继承的特性

1
2
3
4
5
6
7
8
9
10
11
12
// 定义一个中国人的构造函数
function Chinese() {
this.header = 1;
this.eyes = 2;
this.language = "chinese";
}
// 定义一个日本人的构造函数
function Japanese() {
this.header = 1;
this.eyes = 2;
this.language = "japanese";
}

我们发现,不管是中国人还是日本人,他们都属于人类。因此可以将人类的一些公共特征抽取出来,单独封装成一个人类的构造函数,让中国人和日本人共享这个构造函数的属性和方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 封装一个人类的构造函数
function Human() {
this.header = 1;
this.eyes = 2;
}
// 中国人的构造函数
function Chinese() {
this.language = "chinese";
}
// 日本人的构造函数
function Japanese() {
this.language = "japanese";
}
// 通过改变中国人和日本人的原型对象指向到Human,以此继承Human上的方法和属性
Chinese.prototype = Human;
// 不要忘了constructor 指回Chinese
Chinese.prototype.constructor = Chinese;
// 同理日本人
Japanese.prototype = Human;
Japanese.prototype.constructor = Japanese;

但是此时会有一个问题,当我们向单独给 Chinese 添加一个属性或者方法时,我们会发现 Japanese 也被自动添加了这个属性或方法。并且由于Japanese.prototype.constructor是后来定义的,它将Chinese.prototype.constructor = Chinese覆盖掉了,因此Chinese.prototype.constructor竟然也指向了Japanese

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 封装一个人类的构造函数
function Human() {
this.header = 1;
this.eyes = 2;
}
// 中国人的构造函数
function Chinese() {
this.language = "chinese";
}
// 日本人的构造函数
function Japanese() {
this.language = "japanese";
}
// 通过改变中国人和日本人的原型对象指向到Human,以此继承Human上的方法和属性
Chinese.prototype = Human;
// 不要忘了constructor 指回Chinese
Chinese.prototype.constructor = Chinese;
// 同理日本人
Japanese.prototype = Human;
Japanese.prototype.constructor = Japanese;
Chinese.prototype.smoking = function () {
console.log("抽烟");
};
const xiaoming = new Chinese();
const guitian = new Japanese();
console.log(xiaoming, guitian);

image-20230511111944145

这是因为Chinese.prototypeJapanese.prototype都是一个指向了构造函数Human的地址,通过Chinese.prototype.smoking修改了Human的值,Japanese.prototype也会受到影响。因此我们可以将Chinese.prototypeJapanese.prototype各自指向一个 Human 的实例对象,这样它们就互不影响了!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 封装一个人类的构造函数
function Human() {
this.header = 1;
this.eyes = 2;
}
// 中国人的构造函数
function Chinese() {
this.language = "chinese";
}
// 日本人的构造函数
function Japanese() {
this.language = "japanese";
}
// 通过改变中国人和日本人的原型对象指向到Human,以此继承Human上的方法和属性
Chinese.prototype = new Human();
// 不要忘了constructor 指回Chinese
Chinese.prototype.constructor = Chinese;
// 同理日本人
Japanese.prototype = new Human();
Japanese.prototype.constructor = Japanese;
Chinese.prototype.smoking = function () {
console.log("抽烟");
};
const xiaoming = new Chinese();
const guitian = new Japanese();
console.log(xiaoming, guitian);

image-20230511115553081

原型链

基于原型对象的继承使得不同构造函数的原型对象关联在一起,并且这种关联的关系是一种链状的结构,我们将原型对 象的链状结构关系称为原型链

image-20230511115754927

原型链-查找规则

当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性。

如果没有就查找它的原型(也就是 proto指向的 prototype 原型对象)

如果还没有就查找原型对象的原型(Object 的原型对象)

依此类推一直找到 Object 为止(null)

proto对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路线

可以使用 instanceof 运算符检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上

4、深浅拷贝

开发中我们经常需要复制一个引用类型。如果直接赋值,当我们修改一个值时,另一个值也变了

浅拷贝和深拷贝只针对引用类型

浅拷贝常见方法:

  • 拷贝对象:Object.assgin() / 展开运算符
  • 拷贝数组:Array.prototype.concat() 或者展开运算符

但是如果引用数据类型里面出现了嵌套引用数据类型的情况,深拷贝就又会出现上述问题,这时需要深拷贝

深拷贝常见方法:

  • 通过递归实现深拷贝
  • lodash 库 cloneDeep 函数
  • 通过 JSON.stringify()实现

递归

如果一个函数在内部调用其本身,那么这个函数就是递归函数

由于递归很容易发生“栈溢出”错误(stack overflow),所以必须要加退出条件 return

利用递归函数实现 setTimeout 模拟 setInterval 效果

1
2
3
4
5
function getTime() {
console.log(new Date().toLocaleString());
setTimeout(getTime, 1000);
}
getTime();

利用递归函数实现基本的深拷贝

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function deepCopy(newData, oldData) {
// 遍历原对象
for (let k in oldData) {
// 如果是数组
if (oldData[k] instanceof Array) {
newData[k] = [];
deepCopy(newData[k], oldData[k]);
} else if (oldData[k] instanceof Object) {
// 如果是对象
newData[k] = {};
deepCopy(newData[k], oldData[k]);
} else {
// 基本数据类型直接赋值
newData[k] = oldData[k];
}
}
}

通过 JSON.stringify()实现深拷贝

1
2
3
4
5
6
7
8
9
10
11
const obj = {
name: "佩奇",
age: 18,
hobby: ["抽烟", "喝酒", "烫头"],
family: {
sister: "乔治",
father: "猪爸爸",
mother: "猪妈妈",
},
};
const obj2 = JSON.parse(JSON.stringify(obj));

JSON.stringify()可以将 JS 对象转化为 JSON 字符串,JSON 字符串就是一个字符串,就不存在所谓的地址引用了

5、异常处理

throw 抛异常

异常处理是指预估代码执行过程中可能发生的错误,然后最大程度的避免错误的发生导致整个程序无法继续运行

1
2
3
4
5
6
7
8
9
10
11
function counter(x, y) {
if (!x || !y) {
// throw '参数不能为空!'
throw new Error("参数不能为空!");
console.log(111);
}
console.log(222);
return x + y;
}
counter();
console.log(333);

image-20230511144023536

  • throw 抛出异常信息,整个程序也会终止执行
  • throw 后面跟的是错误提示信息,可以直接跟字符串
  • Error 对象配合 throw 使用,能够设置更详细的错误信息

try /catch 捕获异常

可以通过 try / catch 捕获错误信息(浏览器提供的错误信息)

1
2
3
4
5
6
7
8
9
10
11
12
13
function fn() {
try {
const p = document.querySelector(".p");
p.style.color = "red";
} catch (error) {
console.log(error.message);
} finally {
console.log("finally执行了!");
}
console.log("我是函数fn中的最后打印");
}
fn();
console.log("我是函数fn外的打印");
  • 将预估可能发生错误的代码写在 try 代码段中
  • 如果 try 代码段中出现错误后,会执行 catch 代码段,并截获到错误信息
  • finally 不管是否有错误,都会执行
  • 使用 try / catch 捕获错误信息,发生异常后程序不会终止执行

image-20230511145301739

debugger

可以在代码中用 debugger 打断点调试程序,它和我们在控制台打断点的效果是一样的

6、this

this 指向

  • 普通函数中的 this:指向 window
  • 构造函数、原型对象中的 this:指向实例化对象
  • 对象中的方法中的 this:指向该对象
  • 定时器、延时器中的 this:指向 window
  • 事件处理函数中的 this:指向事件源
  • 箭头函数中的 this:实际上箭头函数中并不存在 this,箭头函数中的 this 指向上层作用域的 this,如果上层作用域也没有 this,则一级一级向上查找

简单来说,this 总是指向调用者。我们所说的 this 指向是按作用域划分的

改变 this 指向

JavaScript 中还允许指定函数中 this 的指向,有 3 个方法可以动态指定普通函数中 this 的指向

call()

语法:

image-20230511152012252

  • thisArg:在 fun 函数运行时指定的 this 值
  • arg1,arg2:函数 fun 正常的参数
  • 返回值就是函数的返回值,因为它就是调用函数(当一个函数调用了 call 方法时,这个函数也被调用执行了)

apply()

语法:

image-20230511152310061

  • thisArg:在 fun 函数运行时指定的 this 值
  • argsArray:函数 fun 正常的参数,但是在 apply 中必须包含在数组里面
  • 因为 apply 的参数主要跟数组有关系,因此在涉及到数组处理时,应该想到 apply 比如使用 Math.max() 求数组的最大值
  • 返回值就是函数的返回值,因为它就是调用函数当一个函数调用了 apply 方法时,这个函数也被调用执行了)
1
2
3
4
// 求数组最大值
const arr = [1, 3, 5, 7, 9];
// console.log(Math.max(...arr))
console.log(Math.max.apply(null, arr));

bind()

语法:

image-20230511153919522

  • thisArg:在 fun 函数运行时指定的 this 值
  • arg1,arg2:函数 fun 具体的参数
  • 返回由指定的 this 值和初始化参数改造的原函数拷贝 (新函数),并不调用原函数
  • 因此当我们只是想改变 this 指向,并且不想调用这个函数的时候,可以使用 bind,比如改变定时器内部的 this 指向
  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2023-2025 congtianfeng
  • 访问人数: | 浏览次数: