JavaScript作用域链和原型链
- 作用域链
-
作用域
作用域,是指变量的生命周期(一个变量在哪个范围内保持一定的值),在JavaScript中,作用域分为以下几种:
全局作用域
、函数作用域
、块级作用域
、动态作用域
。-
全局作用域
全局变量存在于整个程序中,程序中的任何函数、任何方法均可访问全局变量,但全局变量不能滥用,所以操作或使用未生命的变量,JS会默认生命此变量为全局变量。
-
函数作用域
函数作用域内,对外是封闭的,外层无法直接访问内部变量,内部可访问外层的变量
-
块级作用域(ES6)
使用功能
let
关键字声明,可形成块级作用域 -
动态作用域
this引用会存在动态作用域
-
执行上下文
执行上下文即当前代码的执行环境,可以描述成是一个包含变量对象、活动对象、作用域链和this的数据结构。
-
作用域链
JS所有函数都有它的执行环境(执行上下文),当它执行的时候会被压入环境栈,如果执行完毕,会将当前环境弹出,执行环境中有函数作用域中定义的变量和其他信息,作用域链最顶端是当前执行环境,下一个是上层环境,一直到Windows对象。举个例子:
function Outer(){ var out = 123; Inner(); function Inner(){ var inner = 456; console.log(out,inner); //123,456 } } Outer();
Inner
内部既可以访问Inner
的作用域,也可以访问Outer
的作用域。因此,当多个作用域互相嵌套时,就形成了作用域。词法作用域在查找标识符的时候会就近查找,从当前作用域向外逐层查找,当抵达最外层作用域依然没有找到,则停止查找。如果是RHS
则会抛出错误,如果是LHS
则会在全局范围内声明该变量。 -
闭包
定义:函数在定义的作用域以外的地方被调用,闭包使得函数可以继续访问定义时的作用域。举个例子:
function fn(){ var temp = 123; function func(){ console.log(temp); } return func; } var func = fn(); func(); //123
func
则是有一个闭包函数,使得全局范围可以访问fn
函数作用域内部的变量temp,且函数执行完后,fn
对象的活动变量不会被销毁,因为fn
返回的函数func
的作用域链还保持着fn
的活动变量,因此JavaScript的垃圾回收机制不会回收fn
活动变量。综上所述,闭包会引起内存泄漏,尽量不要使用闭包。 -
垃圾回收机制
JS的垃圾回收机制分为两种,分别为标记清除和引用计数
- 标记清除:垃圾收集器在运行的时候会给存储在内存中的所有变量都加上标记,然后它会去掉环境中的变量以及环境中的变量引用的变量的标记,如果此时还存在有标记的变量就视为准备删除的变量,因为环境中的变量无法访问到这些变量,最后垃圾收集器完成内存清理,销毁这些变量并回收内存。
- 引用计数:记录每一个值被引用的次数,当声明一个变量并将一个引用类型值赋给该变量时,该变量的引用次数加一,如果一个值又被赋给另外一个变量,则该值的引用次数减一,当这个值的引用次数为0时,就说明没有办法再访问这个值了,就可以回收该变量的空间,当垃圾收集器下次运行时,它会释放这个值占用的内存。
-
- 原型链
-
原型
所有的函数都有一个特殊的属性:
prototype
,prototype属性是一个指针,指向原型对象,原型对象中的方法和属性都可以被函数的实例所共享。通过构造函数创建的对象实例都可以共享构造函数的原型的方法。举个例子:var Person = function(name){ this.name = name; } Person.prototype.sayName = function(){ console.log(name); } var person = new Person('aa'); person.sayName(); //aa
上述例子中对象
person
是Person
构造函数创建的实例,通过new
操作符调用构造函数时主要执行以下几个步骤:- 创建新的对象,并将函数的this指向新创建的对象
- 执行函数
- 返回新创建的对象
通过构造函数创建的对象,内部有一个
_proto_
指针指向构造函数的原型对象构造函数的原型对象有一个
constructor
属性,指向构造函数-
原型链
当执行以下代码时:
person.toString();
首先会在
person
实例中查找toString
属性,发现不存在该属性;会转到Person
原型对象去找toString
属性,仍然不存在;接着到Person
原型对象的_proto_
指向的Object
原型对象中找,终于找到,然后执行toString
方法。这样整个串起来就是一条原型链,原型链的顶端为null
prototype
:构造函数具有的属性,指向调用构造函数而创建的实例的原型_proto_
:指向对象的原型constructor
:原型对象的属性,指向原型对象对应的构造函数
- 作用域链和原型链的比较
- 作用域链是主要的作用是查找标识符,从当前作用域沿着作用域链依次查找,如找到会停止查找,否则会继续查找,直到尾部;尾部依然没有找到的情况分为两种,如果
RHS
,会抛出未找到变量的错误,如果是LHS
,会在查找的最外层声明变量。 - 原型链是用于查找引用类型的属性,沿着作用域链依次向上查找,如果找到该属性会停止查找并作出相应的操作,否则会沿着原型链依次查找到结尾,尾部依然没有找到的情况分为两种,如果是
RHS
,会返回undefined
,如果是LHS
,会在该对象实例中声明变量,不会去查找原型链。