JavaScript 函数以及闭包

本文详细介绍了JavaScript中函数的创建方法,包括函数声明、函数表达式及new函数对象,并探讨了匿名函数的不同应用场景,如作为值使用、构建闭包等,以及如何利用闭包实现块级作用域和特权函数。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

JavaScript中函数、闭包可能是用到很频繁的了,并且jQuery框架也是利用函数以及闭包的很多特性,所以掌握函数和闭包的概念对于我们掌握原生javaScript和jQuery源码来说都是非常有帮助的。


那么首先从JS函数的创建开始说起。函数的创建又这么三种方式:

1. 函数声明,直接上代码:

//声明函数
function FunctionDeclare(){
    alert("函数声明方式创建函数");
}
//调用函数
FunctionDeclare();

 

上述代码以下面这种方式写也是可以的。

FunctionDeclare();
function FunctionDeclare(){
    alert("函数声明方式创建函数");
}

因为在JavaScript中,有一个函数提升的概念,也就是浏览器首先在解析时,先解析函数声明,所以在执行的过程中不会发生错误。

2. 函数表达式


var Func = function (){
    alert("函数表达式创建函数");
}
Func();

首先,将一个匿名函数赋值给一个变量,那么此匿名函数的名字变为了Func,之后调用函数时,直接利用函数名调就可以了。

看似这两种方法都差不多,其实两者还是有比较大的区别。

如果利用函数表达式实现下面的代码是不可行的

Func();
var Func = function(){
    alert("函数表达式创建函数");
}

        这样会报错,说变量Func不是一个函数。这是因为函数表达式是在执行时才去创建函数的,只有对Func赋值为函数的那个语句执行之后,才可以调用Func()。
3. new函数对象
在JavaScript中,函数说到底也是一个对象。因此,可以通过New关键字来创建对象。

var max = new Function('num1', 'num2', 'return (num1 > num2) ? num1 : num2');
max(2,5);                     //返回5
但这种方式其实并不推荐,通常都是用函数声明的方法来创建函数。

上面的简单介绍涉及到匿名函数,下面的内容主要总结匿名函数的几种用法。
一、作为值来使用

var p1 = {
    name : "Liu",
    age: 22
}
var p2 = {
    name : "Yang",
    age : 25
}
function maxAge(property)
{
return function(obj1, obj2){ 
var obj1_Age = obj1[property];
var obj2_Age = obj2[property];
return ((obj1_Age > obj2_Age) ? obj1_Age : obj2_Age); }
}
var m = maxAge('age')(p1,p2); //此时,m的值为25
二、闭包
闭包和匿名函数其实还是不同的概念。匿名函数就是没有名字的函数,而闭包指的是能够访问另一函数作用域的变量的函数。要深入理解闭包的话,首先得清楚,函数执行时会创建一个活动对象,这个对象中包括arguments属性以及参数;同时也会创建其作用域链。拿上面一个例子代码来说,各个函数的作用域链如下图所示:

这幅图可以这么理解:
首先,一开始的时候是全局作用域,也就是在window对象下,此时左边window对象的活动对象为右边window活动对象;
下来是maxAge函数的执行,此时它的作用域有两个,本身的活动对象以及外部(window对象)的作用域,这里本身的活动对象在作用域链的顶端,越处在外部,其越靠近作用域链的末端;每个函数活动对象中都有一个arguments属性,这个属性保存传入的参数,它是一个类数组。同时也有函数内部的参数(局部变量)。
匿名函数也是如此。但是匿名函数的作用域链上有maxAge函数的活动对象,也就是说匿名函数可以访问maxAge中的变量property,即使maxAge函数已经执行返回,但其还在内存中保存着没有释放。这就是闭包,闭包可以访问其他函数中的变量,但是这也就存在一个问题,容易造成内存泄露。
三、块级作用域
在原生js中,没有块级作用域的概念。所有的变量都是共有的,只有在函数中才存在私有变量。其实,js也可以构造出私有作用域来。看下面代码:
function A(){
    for(var i = 0; i < 3; i++)
    {
	alert(i);
    }
    alert(i);
}

上面这段代码,有一个for循环,但这个循环块里面的i与外面的i其实是一个变量。那如何把for循环块里的 i 和外面的 i 隔离开呢?这就需要用到闭包。
function A(){
    (function(){
        for(var i = 0; i < 3; i++)
        {
	    alert(i);
        }
    })();   
    alert(i);                    //错误,i没有定义
}
这里使用了一个匿名函数立即执行的方式,所以也不会存在内存一直释放不了的问题。因为这个匿名函数执行结束时匿名函数内部所有的变量都会被释放。
我想到jQuery框架的最外层包了一个匿名函数,其目的就是避免其他变量或者函数与其冲突。因为我们在引入jQuery库之后还会自己再写一些JS代码来执行某些操作,因此jQuery是很有必要需要一个自己的作用域的。
四、特权函数

在JS中,没有私有成员的概念,但是有私有变量的概念,比如函数的参数,以及函数作用域中的变量或局部变量。这些私有变量在函数外部是不能访问的,但是如果在函数内部添加一个闭包,这个闭包可以访问该函数中的私有变量,利用这一点,就可以构造一个可以访问函数内私有变量的公有方法。看下面代码:

function Member(){
    var salary = 5000;
    function Up()
    {
        salary += 3000;
	return salary;
    }
    this.Method = function(){
	alert(salary);
	return Up();
    }
}
var m = new Member();
m.Method();

通过Member()构造函数创建一个对象,但该对象并不能访问m.salary和m.Up(),只能以m.Method()来访问其私有变量。这个Method方法又叫特权函数。
但上面方法存在与单纯使用构造函数有一个相同的缺点,就是创建多个对象时,会创建多个实质不同但命名相同的代码。其实,只需共享函数的一份拷贝就可以了,所以可以结合原型来做。【需深入了解可参考 上一篇博客

(function(){
    var salary = 5000;
    function Up()
    {
        salary += 3000;
	return salary;
    }
    Member = function(){};
    Member.prototype.Method = function(){
	alert(salary);
	return Up();
    }
})();

这样其实实现了所有私有变量和原型方法为实例对象所共享,如下的例子可以看出来:

var a = new Member();
a.Method();                     //弹出5000,返回值为8000,变量salary值现在为8000
var b = new Member();
b.Method();                     //弹出8000,返回值为11000,变量salary值现在为11000
var c = new Member();
c.Method();                     //弹出11000,返回值为14000,变量salary值现在为14000

这样感觉类似与C++类的静态成员变量,可以实现一个共享变量的目的。不知道是不是基于这个原因,JS中这种变量叫静态私有变量。(纯属个人猜测)


还有一种叫模块模式:

这种模式适合单例模式,所谓单例,就是说只有一个实例对象,这种对象一般都是通过对象字面量的方式创建的。

var Member = {
    name : value,
    method : function(){
    }
}


模块模式就是在函数中定义私有变量和函数,然后返回一个对象字面值。

var Member = (function(){
    var age = 3;
    function Year(){
        age++;
        return age;
    }
    return {
        method : function(){
            return Year();
        }
    }
})();

这部分个人感觉有点偏工程一些了,没做过大的工程,所以也没有实战经验。不过JS高程中的介绍真的很有深度,值得好好细究。书中表示,在Web应用中,经常会使用一个单例来管理应用程序级的信息,我理解是这样的,可能因为系统级的应用信息在整个系统中是独一无二的,所以应用单例模式来实现。因此,为了访问单例中定义的一些接口(私有变量或这函数),所以引入模式模块。

这种方式得到的单例是没有类型的,所以instanceOf()也是没有意义的。因为它是由Object类型继承来的。如果想要增加有类型的对象,又有人提出了增强的模式模块。这部分还没有真正掌握,所以这部分先部总结,后续加上。

















评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值