前端必备——js中原型链、闭包全解(下)

js的文章我们已经接近尾声,前面的文章我们系统介绍了js中的数组、字符串、函数、数据结构、bom、dom、事件、表单、前后台数据交互、客户端存储、canvas绘图。上一篇文章我们了解了js中的原型、原型链。今天我们来顺势了解一下js中的作用域和作用域链以及闭包。需要代码的朋友可以百度搜索“栾鹏全栈”到我csdn博客下载和复制代码。
博主就要开课了,会用30多堂课的内容带大家学习python、数据爬虫,数据分析,数据挖掘,数据可视化。想转行大数据、人工智能的朋友可以加入,欢迎支持。
上一篇我们了解了什么是派生?什么是实例化?什么是原型,原型链,原型链的向上搜索,以及实例化对象产生新变量的三种方式。今天我们来了解作用域链和闭包。
作用域
在js中没有块级作用域。js中有全局作用域,函数作用域。就是说只有在函数内存在局部变量。在for或if语句内定义的变量,在for或if外也是可以访问的。
作用域是在定义时确定的而不是运行时确定的。
函数内不使用var声明的变量是全局变量 。
作用域链
下图就是作用域链的全部内容。
每个函数都有一个作用域(可以把window想象成全局函数),每个作用域会对应有一个活动对象、函数作用域链、函数上下文对象的作用域链。他们并不是一直存在,而是动态创建的。
1、在定义函数时,就会为这个函数生成一个作用域链(一个指针列表),它每一项都指向一个外部作用域的活动对象。作用域链的地址存储在函数的[[scope]]属性中。作用域链是栈结构,它由上至下,依次排列着由内到外的作用域对应的活动对象。函数的作用域链的第一项是定义函数时所处的作用域对应的活动对象,而不是函数自己的活动对象。全局活动对象始终都是作用域链中的最后一个对象。
2、在执行到函数时,会创建函数的活动对象和函数的上下文对象。活动对象中存放了当前作用域中定义的所有属性和方法。这也就是为什么定义函数时创建的函数作用域链不包含函数自己的活动对象,因为定义函数时,函数的活动对象还没有创建。
在刚进入该函数执行环境时,活动对象的属性和方法还未赋值,所以值为undefined,这个是js的提前声明机制。函数一边执行一边对活动对象中的属性进行赋值。
在创建了上下文对象后,会为这个上下文对象也创建一个作用域链。上下文对象的作用域链是通过复制函数的作用域链再添加上函数的活动对象地址而形成。
在执行函数函数中查找变量,就是在这个函数的上下文对象的作用域链中查找的。
所以在执行过程中我们只是根据上下文对象来确定变量的使用。
3、在执行完函数后,如果没有变量再指向这个函数的上下文对象,则这个上下文对象就会销毁。而对象的销毁是由垃圾回收机制决定的,没有指向的对象都会被销毁。
代码运行执行过程
从上面关于作用域链的内容,我们知道函数的定义与函数的作用域链有关,函数的执行与函数上下文对象的作用域链有关,变量的查找,是在执行时沿着函数上下文对象的作用域链进行的。
代码的执行,是在一个执行栈中进行的。执行到哪个作用域就将该作用域的上下文对象压入栈顶。作用域执行完毕,若没有其他作用域中的变量指向该上下文对象中的属性,就将这个上下文对象弹出执行栈。否则将该上下文对象保存在执行栈中。
栈中有一个指针负责指向代码执行处当前作用域的上下文对象。
看下面的代码,最后输出10。
我们来看一下过程。
函数执行中变量的查找,是在执行时沿着函数上下文对象的作用域链进行的。而函数上下文对象是在函数运行时创建的。所以为了简化理解,我们只关心函数的运行(不关心函数的定义)。
在整个代码的运行中,经历的是下方的过程:
先进入全局函数运行,然后运行a函数,然后返回全局函数,然后运行b函数,然后返回全局函数,代码运行结束。
所以创建上下文对象的过程也是先创建全局上下文对象,然后创建函数a上下文对象,由于运行完函数a后,函数a的上下文对象中的属性被函数b的作用域链引用(因为函数的作用域链是在函数定义时创建的)。所以函数a的上下文对象没有被弹出销毁。但是当前环境已经进入全局上下文对象。然后调用b函数,又在栈顶创建了函数b的上下文对象。函数b执行完后,函数b的上下文对象弹出销毁。但是函数b的作用域链并没有销毁,所以此时仍然有变量指向函数a的上下文对象。但是当前环境已经进入全局上下文对象,继续运行。
闭包
闭包是指有权访问另一个函数作用域中的变量的函数,创建闭包的常见方式就是在一个函数内部创建另一个函数。闭包保存的是整个变量的对象。上述代码运行执行过程中的内部函数b就是一个闭包。
闭包的作用
闭包可以用在许多地方。它的最大用处有两个,一个是可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。
理解闭包就要记住:
当函数被创建,就有了作用域,当被调用时,就有了作用域链,当被继承时就有了原型链,当需要获取作用域链或原型链上的变量或值时,就有了闭包。