JavaScript函数表达式和闭包

来源:互联网 发布:win10禁止卸载软件 编辑:程序博客网 时间:2024/05/29 08:07
函数表达式的特征:
定义函数有两种方式,一种是函数声明,一种是函数表达式。
函数声明 ,函数声明会提升,解释器会在代码执行之前先读取函数声明,所以函数的调用语句可以出现在函数声明语句之前
function name(){}

函数表达式,有点像变量赋值,创建这个函数叫匿名函数。

var name = function(){}


递归
函数通过名字调用自身
function factorial (num){     if(num <= 1){          return 1;     }else{          return num*factorial(num-1);     }}
但是通过函数名调用时有风险的,比如这样:
var anotherFunction = factorial;factorial = null;alert(anotherFunction (5)); //出错

原来的函数名已经不是函数了,这个解决方法可以用函数对象的属性 arguments.callee
arguments.callee是一个指向正在执行函数的指针,因此可以实现递归调用。
function factorial (num){     if(num <= 1){          return 1;     }else{          return num * arguments.callee(num-1);     }}
但是在严格模式下,是不允许脚本访问arguments.callee属性的,但是可以通过命名函数表达式来做到相同的结果.

var factorial = (function f(num){     if(num <= 1)return 1;     else return mun * f(num-1);});
创建了一个f()的命名函数表达式,这样在严格和非严格模式下都可以。

ECMAScript的函数名本身就是变量,所以函数可以作为值来使用,不仅可以像传递参数一样将一个函数传递给另一个函数, 而且可以将一个函数作为另一个函数的返回结果。


闭包(Closure)
闭包是什么?

JavaScript高级程序设计(第3版)定义: 闭包是 指有权访问另一个函数作用域中的变量的一个函数, 创建闭包常用方法就是在一个函数中创建另一个函数; 可以认为闭包就是将函数内部和外部链接起来的桥梁。

其他的定义或理解方式:
从特征上理解: 闭包就是函数的局部变量集合,只是这些局部变量在函数返回后会继续存在。
从语言特性上理解: 闭包就是就是函数的“堆栈”在函数返回后并不释放,我们也可以理解为这些函数堆栈并不在栈上分配而是在堆上分配。

从作用域链细节去理解闭包:

作用域链的出现是为了保证对执行环境有权访问的函数和变量的有序访问。作用域链的前端始终是当前执行代码所在环境的变量。如果这个环境是函数,则将其活动对象(函数的活动对象包含this, arguments, 和传入参数)作为变量对象,作用域链中的下一个变量对象来自外部(包含)环境,再下一个变量来自于下一个包含环境,这样一直延续到全局环境,全局执行环境的变量是作用域链的最后一个对象。
标识符的解析过程是始终寸作用域链的前端逐级向后回溯,直到找到标识符。
作用域链本质是指向变量的指针列表,它只引用但不包含实际的变量对象。

一般来讲函数执行结束后,局部的活动对象就会被销毁(垃圾回收),内存中仅保留全局执行环境的变量对象,但是闭包的情况并不是。

在一个函数内部定义的函数会将外部函数的活动对象添加到自己的作用域链中,匿名的函数在外部函数中被返回之后,它的作用域链包含了外部函数的活动对象和全局变量对象,这样内部的匿名函数可以访问外部函数定义的所有变量,更重要的是外部函数执行完毕之后,它的活动对象没有被销毁,而是继续保留在内存中,因为这些变量被返回的匿名函数引用。如果解除了返回匿名函数的引用(将保存返回值的变量设置为null就是解除引用),匿名函数被销毁,外部函数的活动对象才会被销毁。

下面的解释跟上面说的是同一个意思:
coolshell.cn  理解JavaScript闭包:解释了什么情况下函数内部的变量没有被垃圾回收机制回收。(js的垃圾回收算法,一种是”标记清除“,另一种是"引用计数")

在一个function对象内不光有function主体,还有当前作用域链的引用。
这种function和决定该function变量的作用域的组合在计算机科学中就称为闭包。

每当一个function被调用的时候,一个新的对象就会被自动的创造用来保存所涉及到的local变量,同时这个绑定对象会被添加到作用域链中,而当函数返回时,该保存的对象会从作用域链中移除。

function没有嵌套定义的时候,对应的绑定对象没有多余的引用,在函数返回之后会启动正常的垃圾回收机制。而当functioin被嵌套定义在其它的function内部时,每个function都有一个对作用域链的绑定对象引用。如果内部function留在外部function中,它自身以及绑定对象也会被垃圾回收。但要是外部function将内部定义的function在某处存为属性的话,就会产生对一个内部function的外部引用这种情况下内部function和它的绑定对象都不会被回收

一个值得注意的问题
作用域链这种机制在闭包中引入的副作用,闭包只能取得外部函数活动对象的最后一个值。

一个例子:function f(){     var result = new Array();     for(var i = 0; i< 10; i++){          result[i] = function(){          return i;          };     }     return result;}    var a = f();    for(var i = 0; i<10;i++){        console.log("a["+i+"]() = "+ a[i]());    }//打出来的值全是10;

因为返回的每个作用域链保存的是f()函数的活动对象,他们引用的都是头一个变量i,f()函数返回后,i的值是10,此时每个函数都引用保存着变量i的同一个变量对象,所以每个函数内部i的值都是10;
可以用一个匿名函数强制让闭包行为符合预期
function f(){     var result = new Array();     for(var i = 0; i< 10; i++){          result[i] = function(num){          //每个函数都有自己num变量的一个副本          return function(){              return num;           };          }(i);     }     return result;}    var a = f();    for(var i = 0; i<10;i++){        console.log("a["+i+"]() = "+ a[i]());    }
因此就能够返回各自不同的数值了。

闭包应用场景

  1. 保护函数内的变量安全。以最开始的例子为例,函数a中i只有函数b才能访问,而无法通过其他途径访问到,因此保护了i的安全性。
  2. 在内存中维持一个变量。依然如前例,由于闭包,函数a中i的一直存在于内存中,因此每次执行c(),都会给i自加1。
  3. 通过保护变量的安全实现JS私有属性和私有方法, 私有属性和方法在Constructor外是无法被访问的


this对象相关:
this基于运行时函数的执行环境绑定的,全局中this等于window,而函数作为某个对象的方法调用时,this等于那个对象。

var name = "window";var a = {     name : "a",     getName : function(){          return function(){              return this.name;           };     }};alert(a.getName()()); //window

之所以这样,是因为内部函数只能访问自己的活动对象this, arguments 她永远不可能直接访问外部函数的这两个变量。
不过可以将外部函数的this对象保存在闭包能够访问的变量里,闭包就能够访问这个对象了。
var name = "window";var a = {     name : "a",     getName : function(){          var that = this;          return function(){              return that.name;           };     }};alert(a.getName()()); //a

模仿块级作用域

js没有块级作用域的概念,块级语句定义的变量实际是在包含函数中而非语句中创建的。

块级作用域可以用匿名函数来模仿,语法如下[ JavaScript中处理全局变量]使用过该方法:
方式如下:
(function(){     // 这里是块级作用域})();
可以这样来理解,
var f = function(){};f();
把匿名函数赋给变量,然后调用它,可以用实际的函数值来取代变量f:
function(){}();
但是这样会导致语法错误, JavaScript将function关键字当成了函数声明的开始,而函数声明后面不能跟圆括号。但是函数表达式后面可以跟圆括号,将函数声明转化为函数表达式加上圆括号即可:
(function(){     // 这里是块级作用域})();
无论什么地方,需要一些临时变量就可以用这种私有的块级作用域,在改匿名函数中定义的变量都会在函数执行结束的时候被销毁。
这种技术经常在全局作用域的函数中被用在函数的外部,从而限制向全局作用域中添加过多的变量和函数,通过这种方式开发者可以使用自己的变量,不用担心搞乱全局作用域。










0 0
原创粉丝点击