简述作用域链、闭包、原型链

本文只是先让自己对这3个东西有个概念上的理解而已。深入理解还要再看书

reference - 彻底理解js的作用域链

reference - 彻底理解js闭包

reference - 彻底理解js的原型链

作用域链(scope chain)

  • 作用域链与变量对象有着密不可分的关系,因为作用域链就是变量对象的数组。其中第一个是当前函数的活动对象,第二个是当前活动函数的父亲的上下文的活动对象,第三个是当前活动函数的爷爷的上下文的活动对象。。。最后一个是全局执行环境变量对象(window)。
  • 作用域链的用途,是保证对执行环境有权访问的所有变量和函数的有序访问。作用域链的前端,始终是当前执行的代码所在的环境的变量对象。如果这个环境是函数,则将其活动对象作为变量对象。作用域链中的下一个变量对象来自包含(外部)环境,而再下一个变量对象则来自下一个包含环境。这样,一直延续到全局执行环境;全局执行环境的变量对象始终都是作用域链的最后一个对象。
  • 举个例子:
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
var temp1;
function fun1() {
var temp2;
function fun2() {
var temp3
}
}
var func = fun1();
func();

//内部环境可以通过作用域连访问所有外部环境。
var temp1 = 1;
function fun1() {
console.log(temp1);
var temp2 = 2;
function fun2() {
var temp3 = temp2;
console.log(temp3);
}
return fun2;
}
var func = fun1();
func();
//打印结果
//1
//3
//在fun2中找不到temp2和temp1,这时候就会往上查找,直至找到对应的变量。相反如果执行向下查找的操作,则会报错。

image.png

  • 由例子可知,内部环境可以通过作用域链访问所有外部环境,但外部环境不能访问内部环境中的任何变量和函数。这些环境之间的联系是线性的、有次序的。每个环境可以向上搜索作用域链,以查询变量和函数名;但任何环境都不能通过向下搜索作用域链而进入另一个执行环境。

闭包

闭包是指那些能访问外部作用域中声明变量的函数。

闭包的应用

柯里化

利用闭包保存和收集所有参数。

1
2
3
function add(){

}
模块化

封装私有变量,向外暴露接口。

一道关于闭包的经典面试题

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
36
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<ol>
<li>第一项</li>
<li>第二项</li>
<li>第三项</li>
<li>第四项</li>
</ol>
</body>
<script>
// window.onload = function() { // 函数1
// var lis = document.getElementsByTagName('li');
// for (var i = 0; i < lis.length; i++) {
// lis[i].onclick = function() { // 函数2
// alert(i);
// }
// }
// }
// 每次输出都是3
window.onload = function() { // 函数1
var lis = document.getElementsByTagName('li');
for (var i = 0; i < lis.length; i++) {
lis[i].onclick = (function(private_i) { // 函数2
return function() { // 函数3
alert(private_i);
}
})(i); // 这里将i作为参数,调用函数2
}
}
</script>
</html>

原型与原型链

原型

原型:每一个Javascript对象(null除外),在创建的时候就会与之关联另一个对象,这个对象就是我们所说的原型,每一个对象都会从原型“继承”属性。

prototype每个函数都有一个prototype属性。函数的prototype指向了一个对象,这个对象正是调用该构造函数而创建的实例的原型。

prototype3.png

1
2
3
4
5
6
7
8
function Person() {
}
// prototype是函数才会有的属性
Person.prototype.name = 'wzb';
var person1 = new Person();
var person2 = new Person();
console.log(person1.name) // wzb
console.log(person2.name) // Kevin

__proto__这是每个javascript对象(除了null)都具有的一个属性,这个属性会指向对象的原型。

1
console.log(person.__proto__ === Person.prototype); // true

constructor每个原型都有一个constructor属性,这个属性只想与之关联的构造函数。

1
console.log(Person === Person.prototype.constructor); // true

实例与原型:当读取实例的属性时,如果找不到,就会查找与对象相关联的原型中的属性,如果还查不到,就去找原型的原型,一直找到顶层为止。其实原型对象就是通过 Object 构造函数生成的,结合之前所讲,实例的 __proto__ 指向构造函数的 prototype 。

1
2
3
4
5
6
7
8
function Person() {
}
Person.prototype.name = 'wzb';
var person = new Person();
person.name = 'husbin';
console.log(person.name) // husbin
delete person.name;
console.log(person.name) // wzb

原型链:

原型链的顶层是null。

1
console.log(Object.prototype.__proto__ === null) // true

图中由相互关联的原型组成的链状结构就是原型链,也就是蓝色的这条线。

prototype5.png