函数表达式-闭包,作用域链
2013-08-26 19:12:49
分类: JavaScript
函数声明与函数表达式是定义函数的两种方式。(其实 Function构造函数也算一种)
//函数声明
- function func(){}
//函数表达式
- var func1 = function f(){};
- var func2 = function(){};
- (function func3(){});
- ~function func4(){};
- !function func5(){};
- true,function func6(){};
两者主要的区别在于
1. 函数声明有函数声明提升的特征,与定义变量相似。
2. 函数表达式在某一些浏览器中有不兼容性的BUG。
1. 递归
递归函数是在一个函数调用自身的情况下构成的。
- function factorial( num ){
- if( num<=1 ){
- return 1;
- } else {
- return num * factorial( num-1 );
- }
- }
但是,代码容易导致一个问题
- var another = factorial;
- factorial = null;
- another( 4 );
那么以前的解决方法就是使用 arguments.callee 它会引用指向当前函数的地址,
- function factorial( num ){
- if( num<=1 ){
- return 1;
- } else {
- return num * argument.callee( num-1 );
- }
- }
那么上面代码就不会有以上问题。
- var another = factorial;
- factorial = null;
- another( 4 );
但是问题又来了 ES5 废弃了 argument.callee。
那么另外一种利用 函数表达式 解决方法就产生了:
- var factorial = (function f( num ){
- if( num<=1 ){
- return 1;
- } else {
- return num * f( num-1 );
- }
- });
感觉 这样不是很容易看懂,那么转化一下
- var factorial = (function (){
- function f( num ){
- if( num<=1 ){
- return 1;
- } else {
- return num * f( num-1 );
- }
- }
- return f;
- })();
2. 闭包
2.1 作用域链
当某个函数第一次被调用时会创建一个执行环境(EC)以及相应的作用域链,并把作用域链赋值给一个特殊的内部属性[[Scope]]。然后使用 this, arguments 和其他的命名参数值来初始化函数的活动对象(AO),但是在函数的作用域链中,外部函数的活动对象始终处于第二位,以此类推。作用域的终点是全局执行环境。
对于:
- function compare(v1, v2){
- if( v1 >v2 ){
- return v1
- }else{
- return v2
- }
- }
- var result = compare( 5,10 );
很简单的一个函数声明,然后调用 那么它的作用域链就如下所示:
后台的每个执行环境都有一个表示变量的对象-变量对象。全局的变量对象始终存在,而想compare函数这样的局部环境的变量对象只有当函数执行的时候才存在。
在创建compare函数时会创建一个预先包含全局变量对象的作用域链,保存在[[Scope]]中,当调用compare的时候回味函数创建一个执行环境,然后通过赋值函数的[[Scope]]属性中的对象构建起执行环境的作用域链。此后,又有一个活动对象被创建并被推入执行环境的作用域链前端。那么对于compare函数的执行环境而言,其作用域链中包含两个变量对象:本地活动对象和全局变量对象。显然作用域链本质上是一个指向变量对象的指针列表。
2.2 闭包与变量
开发人员很容易将将闭包可匿名函数混在一起。
闭包是指有权访问另一个函数作用于中变量的函数。创建闭包的常见方式就是在一个函数内部创建另外一个函数。
最简单的闭包
- var func = (function(){
- var i = 0;
- return function(){
- return i++
- }
- }());
然后为了方便理解 我们给内外函数加上函数名
- var func = (function a(){
- var i = 0;
- return function b(){
- return i++
- }
- }());
相当于
显然,func变量是指向内部的 b函数的,也很显然b是可以访问a函数中的变量数据 i,然而是没有显示的名称来让我们去访问a函数,于是就在某种意义上形成了闭包。 a对外不可见,对外可见的b可以访问a中的i。
从理论上来说就是具体的执行环境以及相应的作用域链的结果。
那么 之前的例子:
- function showAllNum( as ){
- for( var i =0,len = as.length ;i<len;i++ ){
- as[i].onclick = function(){
- alert( i );
- }
- // alert( i );
- }
- }
由于闭包的原因,会一直弹出同一个值 as.length 而不是 1234。。。,解决方法也是再次利用闭包
var a = function(){ alert(1); }
var b = function(){ alert(1); }
首先我们知道 a和b 除了看起来一样其他就没什么相同的,那么下面循环中,as[i]创建的是和ab一样的,仅仅是长得相同其实完全不同。
- function showAllNum( as ){
- for( var i =0,len = as.length ;i<len;i++ ){
- as[i].onclick = (function(a){ // 称为匿名X
- // 相当于 var a=a;
- return function(){ // 称为匿名Y
- alert( a );
- }
- }(i));
- // alert( i );
- }
- }
2.3 关于 this 对象
this 对象是在运行时基于函数的执行环境绑定的(匿名函数中具有全局性),有时候在一些闭包的情况下就有点不那么明显了。
- var name = "The Window";
- var obj = {
- name : "The object",
- getNameFunc : function(){
- return function(){
- return this.name;
- }
- }
- }
- alert( obj. getNameFunc()() )
这边会返回 "The Window",因为每个函数被调用的时候其活动对象都会自动获取2个特殊的变量:this 和 arguments。 内部函数搜索这两个变量只会在其活动对象内搜索为止,永远不可能直接访问外部函数中的这两个变量。当然可以使用闭包,将外部的this值保存在一个内部可以访问的变量中即可。
- var obj = {
- name : "The object",
-
- getNameFunc : function(){
- var that = this;
- return function(){
- return this.name;
- }
- }
- }
2.4 内存泄露
IE9之前,JScript对象和COM对象使用不同的垃圾收集例程,那么闭包会引起一些问题。
例如在闭包中保存一个HTML元素,那么该元素将无法销毁。
- function handler(){
- var ele = document.getElementById("ele");
- ele.onclick = function(){
- alert(ele.id);
- }
- }
创建一个闭包,而后闭包有创建一个循环引用。
最好的方式应该是这样
- function handler(){
- var ele = document.getElementById("ele");
- var id = ele.id;
- ele.onclick = function(){
- alert(id);
- }
- ele = null;
- }
闭包会引用包含函数的整个活动对象,即是闭包不直接引用ele,活动对象依然会对其保存一个引用,那么设置null就可以断开保存的引用,释放内存。
3. 模仿块级作用域
使用自执行的匿名函数来模拟块级作用域
(function(){
// 这里为块级作用域
})();
该方法经常在全局作用域中被用在函数外部,从而限制向全局作用域中添加过多的变量和函数影响全局作用域。也可以减少如闭包这样的对内存的占用,由于匿名函数没有变量指向,执行完毕就可以立即销毁其作用域链。
4. 私有变量
JS中没有私有成员的概念,所有的对象的属性都是公开的,不过倒是有私有变量。
- function(a, b){
- var c = a+b;
- rerturn c;
- }
a,b,c就是私有变量,并且我们将有权访问私有变量和私有函数的公有方法称为特权方法。
在构造函数中定义特权方法
- function MyObj(){
- var privateVar = 10;
- function privateFunc(){
- return false;
- }
-
- this.publicMethod = function(){
- privateVar ++;
- return privateFunc();
- }
- }
这里内部的 publicMethod 作为一个公有的方法,可以访问私有变量和私有函数,所以称为特权方法。但是这种方式会使我们在每一次创建新对象的时候都会创建一个这种方法。使用静态的私有变量来实现特权方法就可以避免这个问题。
4.1 静态私有变量
- (function(){
- var privateVar = 10;
- function privateFunc(){
- return false;
- }
- MyObj = function(){}
- MyObj.prototype.publicMethod = function(){
- privateVar ++;
- return privateFunc();
- }
- })();
这种方式使用原型来增强代码服用,避免每个实例都创建不同的方法。当然在查找数据的时候多寻找一层是他的明显不足之处。
感觉高级JS中这里的对比较为不合理,首先这2种方式完全没有关系,所实现的功能是不一样的没有哪种好哪种不好的问题。
前者只是为各个实例创建各自的数据信息,每一个实例的起始privateVar值均为10,而第二种方式只不过是让各个实例访问同一个privateVar而已,而各个实例的起始privateVar值就不确定了。
打比方说 我们有十个按钮,我要统计点击数,当我想统计每一个按钮单独的点击数时,那么必然使用前者构造函数,为各个按钮做统计。
而如果只需要统计总点击数,那么使用后者就可以很方便。当然各个统计比统计总点击数开销大事很正常的。
4.2 模块模式
模块模式是为单例创建私有变量和特权方法,单例就是只有一个实例的对象。如下:
- var single = function(){
- var privateVar = 10;
- function privateFunc(){
- return false;
- }
- return{
- publicMethod = function(){
- privateVar ++;
- return privateFunc();
- }
- }
- }()
4.3 增强的模块模式
简单来说是对其他的对象实例进行方法或者其他的扩展,下面是简单实例:
- var single = function(){
- var privateVar = 10;
- function privateFunc(){
- return false;
- }
- var obj = new otherFunc();
- obj.publicMethod = function(){
- privateVar ++;
- return privateFunc();
- }
- return obj;
- }();