执行环境(executioncontext,为简单起见,有时也称为“环境”)是JavaScript中最为重要的一个概念。执行环境定义了变量或函数有权访问的其余数据,决定了它们各自的行为。每一个执行环境都有一个与之关联的 变量对象(variableobject),环境中定义的全部变量和函数都保存在这个对象中。虽然咱们编写的代码没法访问这个对象,但解析器在处理数据时会在后台使用它。全局执行环境是最外围的一个执行环境。根据ECMAScript实现所在的宿主环境不一样,表示执行环境的对象也不同。在Web浏览器中,全局执行环境被认为是window对象,所以全部全局变量和函数都是做为window对象的属性和方法建立的。某个执行环境中的全部代码执行完毕后,该环境被销毁,保存在其中的全部变量和函数定义也随之销毁(全局执行环境直到应用程序退出——例如关闭网页或浏览器——时才会被销毁)。html
每一个函数都有本身的执行环境。当执行流进入一个函数时,函数的环境就会被推入一个环境栈中。而在函数执行以后,栈将其环境弹出,把控制权返回返回给以前的执行环境。ECMAScript程序中的执行流正是由这个方便的机制控制着。当代码在一个环境中执行时,会建立变量对象的一个做用域链(scopechain)。前端
做用域链的用途,是保证对执行环境有权访问的全部变量和函数的有序访问。做用域链的前端,始终都是当前执行的代码所在环境的变量对象。若是这个环境是函数,则将其活动对象(activationobject)做为变量对象。面试
活动对象在最开始时只包含一个变量,即arguments对象(这个对象在全局环境中是不存在的)。做用域链中的下一个变量对象来自包含(外部)环境,而再下一个变量对象则来自下一个包含环境。这样,一直延续到全局执行环境;全局执行环境的变量对象始终都是做用域链中的最后一个对象。算法
标识符解析是沿着做用域链一级一级地搜索标识符的过程。搜索过程始终从做用域链的前端开始,而后逐级地向后回溯,直至找到标识符为止(若是找不到标识符,一般会致使错误发生)。编程
---- 摘自 JavaScript高级程序设计浏览器
注意: 除了全局做用域以外,每一个函数都会建立本身的做用域,做用域在函数定义时就已经肯定了。而不是在函数调用时肯定。
做用域只是一个“地盘”,一个抽象的概念,其中没有变量。要经过做用域对应的执行上下文环境来获取变量的值。同一个做用域下,不一样的调用会产生不一样的执行上下文环境,继而产生不一样的变量的值。因此,做用域中变量的值是在执行过程当中产生的肯定的,而做用域倒是在函数建立时就肯定了。闭包---- 摘自 https://www.cnblogs.com/wangf...模块化
理论说完,直接上代码。函数
function Fn() { var count = 0 function innerFn() { count ++ console.log('inner', count) } return innerFn } var fn = Fn() document.querySelector('#btn').addEventListener('click', ()=> { fn() Fn()() })
一、 浏览器打开,进入全局执行环境,也就是window对象,对应的变量对象就是全局变量对象。oop
二、当代码执行到fn的赋值时,执行流进入Fn函数,Fn的执行环境被建立并推入环境栈,与之对应的变量对象也被建立,当Fn的代码在执行环境中执行时,会建立变量对象的一个做用域链,这个做用域链首先能够访问本地的变量对象(当前执行的代码所在环境的变量对象),往上能够访问来自包含环境的变量对象,如此一层层往上直到全局环境。
三、手动执行点击事件。
点击了3次的结果,接下来进入闭包环节。
先介绍下垃圾回收机制。
离开做用域的值将被自动标记为能够回收,所以将在垃圾收集期间被删除。“标记清除”是目前主流的垃圾收集算法,这种算法的思想是给当前不使用的值加上标记,而后再回收其内存。
---- 摘自 JavaScript高级程序设计
通俗点说就是:
一、函数执行完了,其执行环境会出栈,其变量对象天然就离开了做用域,面临着被销毁的命运。可是若是其中的某个变量被其余做用域引用着,那么这个变量将继续保持在内存当中。
二、全局变量对象在浏览器关闭时才会被销毁。
接下来看看上面的代码。
对了先画张图。
如今就解释下为何会有不一样的结果。
一、经过做用域访问外层函数的私有变量/方法,而且使这些私有变量/方法保留再内存中
function add(num) { var count = num function addTemp(otherNum) { if (!otherNum) return count count += otherNum return addTemp } return addTemp }
二、避免全局变量的污染
三、代码模块化 / 面向对象编程oop
function Animal() { var hobbies = [] return { addHobby: name => {hobbies.push(name)}, showHobbies: () => {console.log(hobbies)} } } var dog = Animal() dog.addHobby('eat') dog.addHobby('sleep') dog.showHobbies()
定义了一个Animal的方法,里面有一个私有变量hobbies,这个私有变量外部没法访问。全局定义了dog的变量,而且把Animal执行后的对象赋值给了dog(其实dog就是Animal的实例化对象),经过dog对象里的方法就能够访问Animal中的私有属性hobbies。这么作能够保证私有属性只能被其实例化对象访问,而且一直保留在内存中。固然还能够实例化多个对象,每一个实例对象所引用的私有属性也互不相干。
固然还能够写成构造函数(类)的方式
function Animal() { var hobbies = [] this.addHobby = name => {hobbies.push(name)}, this.showHobbies = () => {console.log(hobbies)} } var dog = new Animal()