js函数详解(一)

1.声明函数有两种方式
①函数声明例如:
function xx(){}
②函数表达式例如
var xx=function(){}
这两种方式是不一样的,解析器解析时会优先解析function即函数声明,然后再解析其他语句,但是函数表达式用的匿名函数赋值给变量的形式,因此它与普通的赋值语句没有什么差别,所以是顺序解析的。他们的区别可以用例子来表示:

if(condition)
{
	function say(){alert("hi")}
}
else
{
	function say(){alert("hello")}
}

这段代码会带来意想不到的错误,因为解析器优先解析函数声明所以第二个函数会覆盖第一个函数,况且不同浏览器修复这种错误的行为是不同的,所以不要这么写,但是如果换成函数表达式则一点问题都没有,如下例:

if(condition)
{
	var say =  function(){alert("hi")}
}
else
{
	var say =  function(){alert("hello")}
}

这样就不会导致错误,因为解析器会顺序解析。
2. 函数递归
看一个经典的例子,阶乘函数:

function factorial(num){
    if (num <= 1){
        return 1;
    } else {
        return num * factorial(num-1);
    }
}

这是一个经典的阶乘函数,但是 如果我把它改成下面的形式就可能引发错误

var anotherFactorial = factorial;
factorial = null;
alert(anotherFactorial(4));  //error!

这是因为虽然把factorial赋值给了另外一个变量,另外一个变量也指向这个函数,但是anotherfactorial内部需要调用factorial,而此时factorial已经被清空了,所以会导致错误。
我们知道arguments.callee是一个指向正在执行函数的指针,因此可以用它来实现函数的递归调用而不发生上面那样的错误,修改如下:

function factorial(num){
  if (num <= 1){
        return 1;
    } else {
        return num * arguments.callee(num-1);
    }
}

var anotherFactorial = factorial;
factorial = null;
alert(anotherFactorial(4));  //24

因为arguments.callee始终指向的是正在执行的函数,所以即使factorial被清空,但是正在执行的函数变成了anotherfactorial,因此可以调用新的函数,从而保证递归的正确进行
但是这种方法在严格模式下可能会报错,所以有一种更稳妥的方式实现递归,即在利用函数声明把函数赋值给一个变量,无论外面的变量怎么换,内部的变量始终不变,例如:

var factorial2=(function factorial(num){
   if (num <= 1){
        return 1;
    } else {
        return num * factorial(num-1);
    }
});

var anotherFactorial = factorial2;
factorial2 = null;
alert(anotherFactorial(4));  //24

其实就是一个简单的赋值,我们把factorial赋值给了另外一个变量factorial2,然后又把factorial2赋值给了anotherfactorial,相当于把factorial赋值给了anotherfactorial,那么我再清空factorial2就不会对anotherfactorial造成什么影响,这种方式是比较稳妥的。

3.闭包
闭包指的是函数有权访问另一个函数作用域中的变量。
最简单的创建闭包是在函数中包含函数,例如:

function createCompareFunction(propertyName)
{
	return function(object1,object2)
	       {
			   var value1 = object1[propertyName];
			   var value2 = object2[propertyName];
			   if(value1 < value2)
			   {
				   return -1;
			   }
			   else if (value1 > value2)
			   {
				   return 1;
			   }
			   else
			   {
				   return 0;
			   }
		   };
}

内部函数是有权访问外部函数的propertyName属性的,其实这个过程是内部函数首先获得执行环境,然后生成相应的作用域链,然后其他命名参数或arguments来初始化活动对象,但是内层的活动对象始终处于第一位,外层函数的活动对象处于第二位,外层函数的外层函数处于第三位,以此类推,直到顶层执行环境即全局环境。

4.闭包带来的问题
由于闭包函数会扩展自己的作用域链,因此拥有闭包的函数在执行闭包函数时,因为闭包要访问外层函数的变量,因此当调用闭包函数时,解析器只会销毁外层函数的执行环境,但并不销毁外层函数的活动对象,因为外层函数的活动对象还需要被内层闭包函数引用,所以外层函数的活动对象仍然留在内存中,这样就不可避免的造成了内存的占用和浪费,我们可以在执行完闭包函数后,清除对闭包函数的引用来释放内存。例如:
//创建内部闭包函数的引用
var comparename = createCompareFunction(“name”);
//调用闭包函数
var result = comparename({name:“nic”},{name:“bic”});
//销毁闭包函数的引用
comparename = null;

5.闭包与变量
闭包只能访问外部函数的最后一次变量值,因此他会导致一些问题,例如下面的函数就不能返回预期的结果:

function createFunctions(){
    var result = new Array();
    
    for (var i=0; i < 10; i++){
        result[i] = function(){
            return i;
        };
    }
    
    return result;
}

var funcs = createFunctions();

//every function outputs 10
for (var i=0; i < funcs.length; i++){
    document.write(funcs[i]() + "<br />");
}

结果是每个闭包函数都会返回10,而不是他自己的下标,因为外部函数的变量i最后一次的值是10,所以每个函数引用的都是i,i的值是10,但是我们可以利用下面的手段来强制闭包函数返回正确的结果:

function createFunctions(){
   var result = new Array();
    
    for (var i=0; i < 10; i++){
        result[i] = function(num){
            return function(){
				return num;
			}
        }(i);
     }  
          return result;
}
      
var funcs = createFunctions();
  
//返回0到9
 for (var i=0; i < funcs.length; i++){
     document.write(funcs[i]() + "<br />");
 }

这简单说来就相当于在闭包函数中又包含了闭包函数,而且(i)是立即执行函数的意思,i是要向立即执行的函数中传递的参数,那么外层闭包函数执行后会把i赋值给num,然后返回num,既然每次i都不同,那么自然num不同,他们是按值传递,所以是可以返回正确结果的。

6.关于this对象
闭包中的this对象跟非闭包中的this对象稍有不同,看下面的例子:

var name = "The Window";
        
var object = {
    name : "My Object",

    getNameFunc : function(){
        return function(){
            return this.name;
        };
    }
};

alert(object.getNameFunc()());  //"The Window"

为什么调用内部闭包函数后返回的是全局变量window的name值呢?简单说来内部闭包函数只能向上查找一级活动对象,而在该例子中,实际上相当于三层函数嵌套,最外层的object也是一层,不可以忽略这一层,对象字面量形式和直接命名函数本质上是一样的,因此最内层的函数只会搜索到getNameFunc这一层,而这一层里根本没有活动对象,最后内部函数只好去全局环境中寻找活动对象,于是找到了window,将window的name属性值返回。

我们可以把object的this对象赋值给最内层函数的外层函数,这样最内层就可以直接访问了,最内层只需向上查找一级就可以立即找到该对象,然后正确的返回该对象,修改如下:

var name = "The Window";
            
var object = {
     name : "My Object",
 
     getNameFunc : function(){
         var that = this;
         return function(){
             return that.name;
         };
     }
 };
 
 alert(object.getNameFunc()());  //"MyObject"

在这里我们把object的this对象赋值给了that,该that对象存在于最内层函数的外层函数,因此闭包函数只需查找到这一级就可以正确返回结果了。
或者更简单的更改是利用参数传递的思想,例如:

var name = "The Window";          
var object = {
    name : "My Object",

    getNameFunc : function(that){
         that = this;
        return function(){
            return that.name;
        };
    }
};
alert(object.getNameFunc()());  //"MyObject"

首先我们可以确定一点最内层函数可以访问外层函数的that属性,然后外层函数又可以访问他的外层函数的this属性,所以that中保存的就是最外层object函数的this对象,这样通过传递,最内层函数就可以访问最外层的this对象,只不过需要中间变量传递一下,这样是不是好理解多了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值