关于函数相关的一些概念,如执行环境、变量对象、作用域链、闭包,之前是详细查阅了资料研究了一番的,可如今时间久了,很多东西有生疏了。
记录下自己的大脑思考过程,是个很好的学习过程。
1.执行环境。(execution context)
由可执行的代码创建。分为3种。
1)全局执行环境。当程序开始执行时即进入全局执行环境。全局执行环境进栈。
2)函数执行环境(局部执行环境)。每当调用一个函数时,就进入一个局部执行环境,该函数执行环境进栈。在执行return之后,该函数执行环境出栈,把控制权返回给之前的执行环境。
3)eval()函数执行环境。使用js原生eval()函数时也会进入一个执行环境,执行环境进栈,在函数执行完毕后,执行环境出栈。
全局执行环境是最外层的一个执行环境。在web浏览器中,全局执行环境被认为是window对象,因为所有的全局变量和全局函数都是作为window对象 的属性和方法创建的。某个执行环境中的所有代码执行完毕后,该环境被销毁,保存在其中的所有变量和函数定义也随之销毁(全局执行环境直到应用程序退出—— 例如关闭网页或者浏览器——时才会被销毁),被垃圾回收机制回收。
2.变量对象(variable object)
每个执行环境都有一个与之对应的变量对象。作为执行环境的属性,规定了执行环境中可以操作的变量和函数。虽然我们的代码无法访问这个对象,但是解析器在处理数据时会在后台使用它。分为两种。
a. 全局执行环境的变量对象是全局变量,也就是全局对象自身--在程序开始时已经创建。这里说的即是window/global对象
b. 函数执行环境的变量对象成为活动对象AO,---在进入函数执行环境时创建
我们刚才说了变量对象决定了该作用域内可进行操作的函数和变量,那我们具体说下VO包含了哪些东西。
变量对象由3部分组成: 1,该执行环境内的所有函数声明。2.作为形参传递进来的arguments 3.该执行环境内的所有变量声明
3.作用域与作用域链
作用域即是函数和变量可被有效使用的范围。
作用域链是当代码在一个环境中执行时创建的,作用域链的用途就是要保证执行环境中能有效有序的访问所有变量和函数。作用域链的最前端始终都是当前执行的代码所在环境的变量对象,下一个变量对象是来自其父亲环境,再下一个变量对象是其父亲的父亲环境,直到全局执行环境。
标识符解析是沿着作用域链一级一级地搜索标识符的过程。搜索过程始终从作用域链的前端开始,然后逐级地向后回溯,直到找到标识符为止(如果找不到标 识符,通常会导致错误发生)。其实,通俗的理解就是:在本作用域内找不到变量或者函数,则在其父亲的作用域内寻找,再找不到则到父亲的父亲作用域内寻找, 直到在全局的作用域内寻找!
重点看下作用域链是什么:
scope chain:
一个函数执行环境的作用域链是在函数调用时开始创建的
由这个函数执行环境的变量对象AO+scope property组成
函数的scope property属性:
一个函数的scope property属性是在这个函数创建(定义或叫声明)的时候就存在了
不管它是否有被调用(里面放的是父级的变量对象)
所以,一个执行环境的作用域链包括的是自己的AO和所有父级环境的AO。理解了这些就清楚了为何内部函数可以使用外部函数中定义的变量和函数。
4.执行环境、变量对象、作用于链的关系图。
执行环境为一个对象,VO(AO),this、作用链作为对象的三个属性看待。所以在进入一个执行环境时,跟它相关的这三个属性也随之确定了。
activeExecutionContext = {
VO: {...}, // or AO
this: thisValue,
Scope: [ // Scope chain
// list of all variable objects
// for identifiers lookup
]
};
5.垃圾回收机制。
6.谈谈闭包。
6-1)什么是闭包。
说法一:闭包就是一个函数可以访问到另一个函数的变量---感觉等于没说。
说法二: mdn认为闭包是一个特殊的对象,包含了两个部分(闭包函数和函数创建时的环境---即变量)---感觉这个说法更贴近实际一些。
闭包,总体上是说依赖于作用域链和垃圾回收机制。要理解闭包最好从编译器的角度来理解。
6-2)闭包的应用场景
a.实现封装。
function Person(){ var name = "default"; return { getName : function(){ return name; }, setName : function(newName){ name = newName; } } } var john = Person(); console.log(john.getName()); john.setName("john"); console.log(john.getName()); var jack = Person(); console.log(jack.getName()); jack.setName("jack"); console.log(jack.getName());
6-3)闭包的特点
7)创建函数的方式(定义函数的方式)
由于这里主要讲解的是函数相关的重要概念,所以补充下可以通过哪些方式创建函数。
3种方法,
Function构造器
函数声明
函数表达式
8.this指向。
1)作为普通函数调用。
var name = "The Window"; var obj={ name: "My Object", getNameFunc: function () { return function(){ return this.name; } } }; console.log(obj.getNameFunc()());
obj.getNameFunc()等同于函数function(){ return this.name; }。
所以obj.getNameFunc()不再与obj有任何关系,所以它的调用仅仅是普通函数调用。在js的normal模式下,this指向window。在strict模式下,this为undefined。
参考:
这里的表达仅为个人理解,比较官方的更精确的表述请见参考。
《javascript高级程序设计第3版》
ECMAscript官方文档。
http://dmitrysoshnikov.com/ecmascript/chapter-6-closures/
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures