closure主要涉及到js的其他几个特性:scopechain,垃圾(内存)回收机制,函数嵌套等。1.Scopechain:函数在定义的时候创建为所使用的变量的值找到一个索引,其内部规则是将函数本身的局部变量放在最前面,将其父函数中下一层的变量放在其次,变量在更高级别的函数中放置稍后,依此类推,直到全局对象。当函数中需要查询某个变量的值时,js解释器会去作用域链中查找,先从最前面的局部变量开始查找,如果没有找到对应的变量,则转到下一层链式查找,一旦找到变量,就不再继续。如果找到***并且没有找到所需的变量,则解释器返回undefined。2、内存回收机制:函数开始执行时,会为其中定义的变量划分内存空间,以备后面的语句使用。当函数执行完后返回时,这些变量就被认为是无用的。相应的内存空间也被回收了。下次执行这个函数时,所有的变量都会回到原来的状态,重新赋值使用。但是如果这个函数内部嵌套了另一个函数,而这个函数有可能被外部调用。而如果这个内部函数使用了外部函数的一些变量,这个内存回收机制就会出问题。如果外部函数返回后直接调用内部函数,那么内部函数是不可能读取到他需要的外部函数中变量的值的。因此,当js解释器遇到函数定义时,会自动将函数和它可能用到的变量(包括局部变量和父、祖先函数变量(自由变量))保存在一起。即建立一个闭包,这些变量不会被内存收集器回收,只有当内部函数不能被调用时(比如被删除,或者没有指针),闭包才会被销毁,没有变量任何闭包引用的对象都会在下一次内存回收开始时被回收。三、局部变量&全局变量1、全局变量的作用域是全局的,在Javascript中到处都有定义;在函数内部声明的变量是局部变量,它们的作用域是局部的。函数体内有一个定义,每次执行函数时都会创建和销毁变量。2、全局变量作用域内的变量可以不用var语句使用,但声明局部变量时必须使用var语句,否则会被视为对全局变量的引用。3.varscope="local"声明的变量;在整个checkScope函数范围内有效,所以第一个document.write(scope);当作用域引用局部变量时执行,并且局部变量作用域还没有被执行。已定义,所以输出“undefined”。一个好的编程习惯是在函数的开头收集所有变量声明。document.write(window.scope)//输出global全局变量总是存在于运行时上下文作用域链的末尾,所以在解析标识符时查找全局变量最慢。所以在写代码的时候,尽量少用全局变量,尽量用局部变量。一个好的经验法则是:如果一个跨范围对象被多次引用,在使用它之前将它存储在一个局部变量中(文档、窗口等)。JavaScript代码在执行过程中,遇到标识符时,会根据标识符的名称在执行上下文(ExecutionContext)的作用域链中查找。从作用域链的第一个对象(函数的ActivationObject对象)开始,如果没有找到,则在作用域链中查找下一个对象,依此类推,直到找到标识符的定义。如果搜索后没有找到范围内的最后一个对象,即全局对象(GlobalObject),则会抛出错误提示用户该变量未定义(undefined)。这就是ECMA-262标准中描述的函数执行模型和标识符解析(IdentifierResolution)的过程。由ECMA-262标准第三版定义,此内部属性包含函数创建范围内的对象集合。这个集合称为函数的作用域链,它决定了函数可以访问哪些数据。作用域的第一个对象永远是当前执行代码所在环境的变量对象functiona(x,y){varb=x+y;returnb;}当函数a被创建时,它的作用域链被填充在全局对象中,全局对象中有所有的全局变量vartatal=a(5,10);在执行该函数时,会创建一个名为“executioncontext”的内部对象,runtimecontext定义了函数执行时的环境。值按照它们在函数中出现的顺序被复制到运行时上下文的作用域链中。它们共同构成了一个新的对象,叫做“激活对象”,这个对象包含了函数的所有局部变量、命名参数、参数集合和this,然后这个对象会被推入作用域链的前端,当运行时上下文被destroyed,活着的对象也被销毁。ECMAScript变量可能包含两种不同数据类型的值:原始类型值和引用类型值。原始值是那些保存在栈内存中的简单数据段,即值完全保存在内存中的一个位置。引用类型值指的是那些保存在堆内存中的对象,也就是说变量中保存的其实只是一个指针,而这个指针指向内存中的另一个位置,也就是保存该对象的位置。5种基本数据类型:Undefined、Null、Boolean、Number和String。这五种基本数据类型的值分别在内存中占据固定大小的空间,所以它们的值都可以保存在栈内存中。如果分配给变量的值是引用类型,则必须在堆内存中为该值分配空间。由于此类值的大小不固定,因此无法将它们存储在堆栈内存中。但是内存地址的大小是固定的,所以内存地址可以保存在栈内存中。这样,在查询引用类型的变量时,可以先从栈中读取内存地址,然后“顺藤摸瓜”找到堆中存放的值。栈内存中存储的每个值都占用固定大小的空间,可以按顺序访问它们。如果一块内存的地址存放在栈内存中,这个值就像是一个指针,指向对象在堆内存中的位置。存储在堆内存中的数据不是按顺序访问的,因为每个对象需要的空间是不相等的。将引用类型的值从一个变量复制到另一个变量时,存储在堆栈上的值的副本也被复制到为新变量分配的空间中。不同的是,这个值的副本实际上是一个指针,这个指针指向一个存储在堆上的对象。复制操作之后,两个变量实际上将引用同一个对象。因此,改变一个变量会影响另一个变量。typeof运算符是确定变量是字符串、数字、布尔值还是未定义原始数据类型的完美工具。在检查引用类型的值时,ECMAScript提供了instanceof运算符。4.闭包只要有调用内部函数的可能,JavaScript就需要保留被引用的函数。而JavaScript运行时需要跟踪所有引用这个内部函数的变量,直到最后一个变量被丢弃,JavaScript垃圾回收器才能释放相应的内存空间(红色部分是理解闭包的关键)。闭包最大的用处有两个,一是读取函数内部的变量,二是将这些变量的值时刻保存在内存中。使用闭包的注意事项1)闭包会导致函数中的所有变量都存放在内存中,消耗大量内存,所以不能滥用闭包,否则会导致网页出现性能问题,并可能造成内存泄漏在IE中。解决方法是在退出函数之前删除所有未使用的局部变量。2)闭包会在父函数外改变父函数内变量的值。所以,如果你把父函数当作一个对象(object),把闭包当作它的公共方法(PublicMethod),把内部变量当作它的私有属性(privatevalue),那么你一定要注意不要随意更改父函数内部变量的值。闭包的一些例子:
