谈起js的原型链和作用域链,我觉得还是和图结合起来说比较明白,手绘了一些图片,
原型链
创建一个函数及调用构造函数创建实例的背后
当我们创建了一个新函数时,都会自动为该函数创建一个prototype属性,这个prototype属性是一个指针,指向这个函数的原型对象。而这个原型对象会自动创建一个constructor属性,constructor也是一个指针,指向prototype属性所在的那个函数,即我们创建的那个新函数。
当我们以new形式调用我们刚刚创建的那个函数时(即是以构造函数调用,可以把我们刚刚创建的那个函数理解为类,尽管js本身没有类的概念),我们创建了一个实例,这个实例内部有一个[[Prototype]]指针,指向构造函数的原型对象,即constructor所在的对象。(我们可以通过proto属性访问这个[[Prototype]],也可以通过Object.getPrototypeOf()来访问)
重写原型对象
重写函数的原型对象后,函数的prototype指向新的对象,而之前创建的实例的[[prototype]]指向的对象没有改变,尽管这个对象的constructor仍然指向函数,但是当前函数的原型对象和实例之间已经没有联系了。
继承中的原型链及默认的原型对象
直接上图就好了,,,
作用域链
每个执行环境都有一个与之关联的变量对象,环境中所有的变量和函数都保存在这个变量对象里面。这个变量对象虽然我们无法访问,但是解释器会在后台使用。
当代码在一个环境中执行时,会创建变量对象的一个作用域链,作用域链的前端始终是当前执行的代码所在环境的变量对象,下一个变量对象来自外部包含环境,一直延伸到全局执行环境。
每个函数都有自己的执行环境,当执行到一个函数时,该函数的环境被推入一个环境栈,函数执行后将其弹出。
作用域链与闭包有着密切的关系
作用域链实质上是一个指向变量对象的指针列表。
闭包,简单说就是有权访问另一个函数作用域中变量的函数(一整句话感觉还是觉得比较绕,分开说就是:闭包是一个函数,它不仅能够访问自己作用域中的变量,还能访问另一个函数作用域中的变量)。闭包常见的创建方式是在一个函数中返回另一个函数。
function Outer() {
var a = 5;
return function() {
console.log(a);
}
}
var fun = Outer();
fun();
如上,即使这个内部函数被返回了,而且是在全局调用的,但是它仍然可以访问变量a。这是因为内部函数的作用域链中包含了外部函数Outer()的作用域。
函数被调用时发生了什么?
1. 创建一个执行环境
2. 创建作用域链
3. 使用arguments及其他命名参数初始化活动对象
举个例子:
function add(a, b) {
return a + b;
}
var value = add(1, 2);
以这个简单的函数为例子,画出执行环境及作用域链图:
创建add函数时,会创建一个预先包含全局变量对象的作用域链,这个作用域链保存在函数内部的[[Scope]]属性中。
当调用add()时,会为函数创建一个执行环境,然后复制函数[[Scope]]属性中的对象构建执行环境的作用域链,之后函数的活动对象被创建并推入执行环境作用域的前端。
正常情况下,函数执行完毕后,局部活动对象被销毁,内存中仅保存全局执行环境的变量对象,但是闭包的情况有所不同。
在一个函数内部创建的另一个函数,会将外部函数的活动对象添加到它的作用域链中,以我们第一段代码为例子。Outer()中定义并返回了一个匿名函数,这个匿名函数的会将Outer()的活动对象添加到自己的作用域链中。当匿名函数被返回后,它的作用域链包含Outer()函数的活动对象和全局变量对象,所以这个匿名函数可以访问Outer()中的变量,即使他是在全局调用的。Outer()在执行完毕后,其执行环境的作用域链会被销毁,但是它的活动对象不会被销毁,因为匿名函数的作用域链仍在引用这个活动对象。