函数表达式

来源:互联网 发布:c语言经典笔试题 编辑:程序博客网 时间:2024/04/26 17:15

函数表达式

  定义函数的两种方式:
函数声明:

1
2
3
function functionName(arg0,arg1,arg2){
//函数体
}

在Firefox Chrome Safari Opera都给函数定义了一个非标准的name属性。

1
alert(functionName.name);//"functionName"

  关于函数声明,一个重要的属性就是函数声明提升,会在执行代码之前会提前读取函数的声明,所以可以先使用后定义。

  第二中创建函数的方法是使用函数表达式。例如:

1
2
3
var functionName=function(arg0,arg1,arg2){
//函数体
};

  函数表达式必须先赋值再使用。可以将函数赋值给变量,也可以把函数作为返回值返回。

递归

  我们可以利用arguments.callee来实现递归函数,arguments.callee是一个指向正在执行的函数的指针,可以用其实现函数的递归调用。下面举例阶乘函数:

1
2
3
4
5
6
7
8
function factorial(num){
if(num<=1){
return 1;
}else{
return num * arguments.callee(num-1);
}
}

  在严格模式下,不能通过arguments.callee访问脚本,可以通过下面的版本来实现递归函数,我们还是以递归函数举例:

1
2
3
4
5
6
7
var factorial=(function f(num){
if(num<=1){
return 1;
}else{
return num*f(num-1);
}
});

  上面的代码创建了一个名为f()命名函数,然后将其赋值给变量factorial。这样即使把函数赋值给另外的变量,也不会出现错误。

闭包

  闭包是指有权访问另一个函数作用域中变量的函数。创建闭包的常见方式就是在一个函数里面创建另一个函数。下面举例:

1
2
3
4
5
6
7
function createComparisonFunction(propertyName) {
return function (object1,object2) {
var value1=object1[propertyName];
var value2=object2[propertyName];
return value1-value2;
}
}

  在这段代码中,内部函数访问到了外部函数中的变量propertyName。即使这个内部函数被返回了,但是仍然可以访问到变量propertyName。之所以还能访问到这个变量,是因为内部函数的作用域链包含createComparsionFunction()的作用域。
  当函数被调用的时候,会创建一个执行环境及其相应的作用域链。然后使用arguments和其他命名参数的值来初始化函数的活动对象。外部函数的活动对象位于第二位,外部函数的外部函数的活动对象位于第三位,直到作为作用域终点的全局执行环境。后台的每个执行环境都有一个环境对象。在创建函数时,会预先创建一个包含全局对象的作用链,这个作用域链被包含在内部的[[Scope]]属性。调用时,会为函数创建一个执行环境。通过复制函数的[[Scope]]属性中的对象构建起执行环境的作用域链。此后,又有一个活动对象被创建并推入执行环境作用域的前端。作用域链本质是一个执行变量对象的指针列表。
  一般来讲,函数执行结束后,局部活动对象就会被销毁,内存中仅保存全局作用域,闭包的情况有所不同。例如:

1
2
var compare=createComparsionFunction("name");
var result=compare({name:"Nicholas"},{name:"Greg"});

  匿名函数从createComparsionFunciton函数中返回后,作用域链被初始化包含为函数的活动对象和全局变量对象。更重要的是,创建函数从createComparsionFunciton在执行完毕后,其活动对象也不会被销毁,因为匿名函数仍然在引用这个活动对象,也就是从createComparsionFunciton函数返回之后,其执行环境的作用域链被销毁,但是他的活动对象会被留在内存中,直到匿名函数被销毁后,才会被一同销毁。下图是函数的作用域链。
  屏幕快照 2016-08-28 下午1.05.00.png-142.6kB

闭包和变量

下面举出一个闭包会出现的问题:

1
2
3
4
5
6
7
8
9
function createFunctions(){
var result=new Array();
for(var i=0;i<10;++i){
result[i]=function(){
return i;
};
}
return result;
}

  上面的代码看起来似乎是函数数组的每一个值都返回自己的索引值。但是实际上是每个函数都返回10。因为每个函数的作用域链中都保存着createFunctions()函数的活动变量,所以它们引用的都是同一个变量i。所以每一个函数内部i的值都是10。我们可以转换一种方式,让函数数组返回预期的值。

1
2
3
4
5
6
7
8
9
10
11
function createFunctions(){
var result=new Array();
for(var i=0;i<10;++i){
result[i]=function(num){
return function(){
return num;
};
}(i);
}
return result;
}

  这个函数之所以会按照预期来运行,是因为没有把闭包赋值给数组,而是定义一个匿名函数,并且将立即执行该匿名函数的结果赋值给数组。在调用每一个匿名函数时,我们传入了变量i,而在匿名函数内部又创建了一个访问num的闭包。这样一来,result数组中国的每一个函数都有自己的num变量的副本。

this对象

  在闭包中使用this对象也会导致一些问题,this对象运行时基于函数的执行环境绑定。在全局函数中,this等于window。当函数被作为每个对的方法调用时,this等于那个对象。匿名函数的执行环境具有全局性,因此this对象通常指向window。例如

1
2
3
4
5
6
7
8
9
10
var name='The window';
var object={
name:"My Object",
getNameFunc:function(){
return function(){
this.name;
};
}
};
alert(object.getNameFunc()());//'The Window'

  函数在调用时候都会自动的获取两个特殊变量:this和arguments。内部函数在搜索这两个变量是,只会搜索到其活动对象为止。因此永远不会访问到外部函数中的这两个变量。
  但在下面方式中:

1
2
3
4
5
6
7
8
9
10
11
var name='The Window';
var object={
name:"My Object",
getNameFunc:function(){
var that=this;
return function(){
return that.name;
};
}
};
alert(object.getNameFunc()());//MyObject

  上面的例子中,我们把this对象赋值给了that变量。而在定义了闭包之后,闭包仍然可以访问这个变量,that引用者object。所以调用后返回了’My Object’;
  下面我么举出一个特殊的例子:

1
2
3
4
5
6
7
8
9
10
var name='The Window';
var object={
name:'My Object',
getName:function(){
return this.name;
}
};
object.getName();//'My Object'
(object.getName)();//'My Object'
(object.getName=object.getName)();//'The window'

  • 第一个调用代码调用了object.getName()。返回的是’My Object’,this.name就是object.name
  • 第二个调用代码在方法前加上了括号,虽然加了括号之后,就好像只是在引用一个函数,this值得到了维持。
  • 第三个调用代码先执行了一条赋值语句。然后在调用赋值后的结果。应为这个赋值表达式的值是函数本身。所以this的值不能得到维持,所以结果返回了’The window’.
    ####闭包中的内存泄露
      因为IE9之前的版本对JScript对象和COM对象使用不同垃圾回收机制,因此在IE9之前的版本中,会出现一些特殊的问题。例如:
    1
    2
    3
    4
    5
    6
    function assignHandler() {
    var element = document.getElementById("someElement");
    element.onclick = function () {
    alert(element.id);
    };
    }

  上面代码创建了element元素时间处理程序的闭包,闭包有穿件了循环引用。由于匿名函数保存了对assignHandler()活动对象的引用。因此就会导致无法减少element的引用数。只要匿名函数存在,element引用数至少为1.永远不会被清除。解决的方法可以将element.id的一个副本保存在一个变量中,并且在函数结尾将element置为null。

####模仿块级作用域
  JavaScript没有块级作用域的概念,但是可以通过匿名函数的方式来进行模拟。

1
2
3
(function(){
//块级作用域
})();

  上面的代码通过定义一个匿名函数并立即调用,将函数声明包含在一个圆括号中,随后立即调用。匿名函数中的变量就可以模仿块级作用域。这种技术经常在全局作用域中被用在函数外部,从而限制想全局作用域中添加过多的变量和函数。   

私有变量

  本质上,JavaScript没有私有成员变量的概念,所有的对象属性都是公有的。但是任何定义在函数中的变量都是私有变量,b不能在函数外部访问这些变量。例如:

1
2
3
4
function add(num1,num2){
var sum=num1+num2;
return sum;
}

  其中函数中的num1,num2,sum都是私有变量,在函数外部是不能访问的。在函数内部创建一个闭包就可以访问这些变量,利用这一点我们可以创建访问私有变量的公有方法。

1
2
3
4
5
6
7
8
9
10
function MyObject() {
var privateVariable=10;
function privateFunction() {
return false;
}
this.publicMethod=function () {
privateVariable++;
return privateFunction();
}
}

对上面的例子来讲,变量privateVariable和函数privateFunction只能通过特权方法publicMethod来访问,没有其余方法可以访问。并且利用私有和特权成员,可以隐藏那些不应该被直接修改的数据,例如:

1
2
3
4
5
6
7
8
9
10
11
12
function Person(name) {
this.getName=function () {
return name;
};
this.setName=function (value) {
name=value;
};
}
var person=new Person("MrErHu");
alert(person.getName());
person.setName("mrerhu");
alert(person.getName());

  上面例子私有变量name在Person的每一个实例都不相同,因为每次调用构造函数都会重新创建这两个方法。但是具有构造函数的缺点,例如每个实例都会创建一组新的方法。可以使用静态私有变量来实现特权方法。

####静态私有变量
  通过在私有作用域中定义私有变量和函数,同样可以创建特权方法。例如

1
2
3
4
5
6
7
8
9
10
11
12
13
(function () {
var privateVariable=10;
function privateFunction() {
return false;
}
MyObject=function () {
};
MyObject.prototype.publicMethod=function () {
privateVariable++;
return privateFunction();
};
})();

  在上面的例子中,公有方法是在原型上创建的,体现了原型的模式。需要注意的是,MyObject没有使用var关键字,是一个全局变量,可以在外部访问到。这种方式的一个特点是私有变量和函数是由实例共享的,特权方法是在原型上定义的,所以所有的实例共享同一个函数。

模块模式(module pattern)

  模块模式是为单例创建私有变量和方法,其中单例是指只有一个实例的对象,按照惯例是通过对象字面量实现的。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var singleton=function () {
//私有变量和私有函数
var privateVariable=10;
function privateFunction() {
return false;
}
//特权/共有属性和方法
return {
publicProperty:true,
publicMethod:function () {
privateVariable++;
return privateFunction();
}
};
}();

  模块使用了一个返回对象的匿名函数,在其中定义了私有变量和函数,然后将一个对象字面量作为函数返回。从本质上讲,这个对象字面量定义的是单例的公共接口,这种模式在需要进对单例进行初始化并且同时需要维护私有变量非常有用。


原文地址: https://mrerhu.github.io/2016/12/16/%E5%87%BD%E6%95%B0%E8%A1%A8%E8%BE%BE%E5%BC%8F/

0 0
原创粉丝点击