关于闭包、原型、原型链 你需要知道的都在这了
一、闭包
在 JavaScript 中,我们可以随时创建函数,可以将函数作为参数传递给另一个函数,并在完全不同的代码位置进行调用。我们已经知道函数可以访问其外部的变量。
但如果在函数被创建之后,外部变量发生了变化会怎样?函数会获得新值还是旧值?如果将函数作为参数传递并在代码中的另一个位置调用它,该函数将访问的是新位置的外部变量吗?
通俗的讲:就是函数a的内部函数b,被函数a外部的一个变量引用的时候,就创建了一个闭包。JS中,每当创建一个函数,闭包就会在函数创建的同时被创建出来。
作用:能够在函数定义的作用域外,使用函数定义作用域内的局部变量,并且不会污染全局。
原理:基于词法作用域链和垃圾回收机制,通过维持函数作用域的引用,让函数作用域可以在当前作用域外被访问到。
一个例子:
1 | function a(){ |
这个例子中i是函数a中的一个变量,它的值在函数b中被改变,函数b每执行一次,i的值就在原来的基础上加 1 。
因此,函数a中的i变量会一直保存在内存中。
当我们需要在模块中定义一些变量,并希望这些变量一直保存在内存中但又不会 “污染” 全局的变量时,就可以用闭包来定义这个模块。
用处:它的最大用处有两个,一个是它可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。
应用
- 定时器
- 事件监听器
- ajax 请求
- 跨窗口通信
- web workers
- 任何其他的异步/同步任务中
- 只要使用了回调函数,实际上就是使用闭包。
- 实现节流防抖函数
另一个例子:
1 | let num = new Array(); |
优点
① 减少全局变量;
② 减少传递函数的参数量;
③ 封装;
缺点
① 使用闭包会占有内存资源,过多的使用闭包会导致内存溢出等
(解决:把那些不需要的变量,但是垃圾回收又收不走的的那些赋值为null,然后让垃圾回收走)
二、原型与原型链
JavaScript
常被描述为一种基于原型的语言——每个对象拥有一个原型对象
当试图访问一个对象的属性时,它不仅仅在该对象上搜寻,还会搜寻该对象的原型,以及该对象的原型的原型,依次层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾。
准确地说,这些属性和方法定义在Object的构造器函数(constructor functions)之上的prototype
属性上,而非实例对象本身
原型:每个对象都会在其内部初始化一个属性,就是prototype(原型)。通俗的说,原型就是一个模板,更准确的说是一个对象模板。
原型链:当我们访问一个对象的属性时,如果这个对象内部不存在这个属性,那么他就会去prototype里找这个属性,这个prototype又会有自己的prototype,于是就这样一直找下去,也就是我们平时所说的原型链的概念。
通俗的说,就是利用原型让一个引用类型继承另一个引用类型的属性和方法;比如, Student → Person → Object ,学生继承人类,人类继承对象类。
原型链继承和构造函数继承的例子:
1 | function Animal(name) { |
由这里引申出关于继承的例子(几种常见的):
- 原型链继承
1 | function SuperType() { |
- 构造函数继承
1 | function SuperType(name) { |
- ES6 中 class 的继承
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24class Pet {
constructor(name, age) {
this.name = name;
this.age = age;
}
showName() {
console.log("调用父类的方法");
console.log(this.name, this.age);
}
}
// 定义一个子类
class Dog extends Pet {
constructor(name, age, color) {
super(name, age); // 通过 super 调用父类的构造方法
this.color = color;
}
showName() {
console.log("调用子类的方法");
console.log(this.name, this.age, this.color);
}
}
还有组合继承(伪经典继承)、寄生组合式继承 不是很常见,可以自行了解。
做点题练手看看学没学会:
1 | var A=function(){} |
答案:1,undefined,2,3
原因是b继承A,所以b.n就为1,而m在A中找不到,所以为undefined
以此类推,c继承的时候A添加了n和m,所以c.n和c.m分别是2和3
其中,undefined是一个表示”无”的原始值,null用来表示尚未存在的对象。
1 | var F=function(){}; |
答案:a()、报错找不到b这个函数、a()、b()。
F 是个构造函数,而 F 是构造函数 Function 的一个实例。因为 F instanceof Object === true 、F instanceof Function === true,由此我们可以得出结论:F 是 Object 和 Function 两个的实例,即 F 能访问到 a, 也能访问到 b。
对于 f ,f 并不是 Function 的实例,因为它本来就不是构造函数,调用的是 Function 原型链上的相关属性和方法了,只能访问到 Object 原型链。所以 f.a() 输出正常,而 f.b() 就报错
F.a 的查找路径:F 自身:没有 —> F.__ proto __ (Function.prototype):没有—> F.__ proto __ . __ proto __(Object.prototype):找到了输出 a()
F.b 的查找路径:F 自身:没有 —> F.prototype(Function.prototype):b()
f.a 的查找路径:f 自身:没有 —> f. __ proto __ (Object.prototype):输出 a()
f.b 的查找路径:f 自身:没有 —> f. __ proto__ (Object.prototype):没有 —> f. __ proto __ . __ proto__ (Object.prototype.__ proto__:null):找不到,报错
参考
【1】https://zhuanlan.zhihu.com/p/129022735
【3】https://febook.hzfe.org/awesome-interview/book2/js-inherite