JavaScript权威指南学习笔记(四)函数表达式

主要内容:
* 函数表达式特点
* 函数递归
* 使用闭包的私有变量

函数定义提升:

//函数提升,函数定义是先读取的,不会报错
sayHi();
function sayHi(){
    alert("Hi!");
}
//此时会报错
sayHi(); //error – function doesn’t exist yet
var sayHi = function(){
    alert("Hi!");
};

理解函数提升是理解函数声明和函数定义区别的关键,下面这些有时会显得奇怪:

//这种写法不行,会报错
if(condition){
    function sayHi(){
        alert("Hi!");
    }
    } else {
    function sayHi(){
        alert("Yo!");
    }
}
//这种可以
var sayHi;
if(condition){
    sayHi = function(){
        alert("Hi!");
    };
    } else {
    sayHi = function(){
        alert("Yo!");
    };
}

闭包

匿名函数和闭包的术语经常错误地混用。

闭包是什么?

“Closures are functions that have access to variables from another function’s scope.”

  1. 闭包是函数。
  2. 这个函数能够访问另外一个函数作用域中的变量。
  3. 经常通过构造一个函数中的函数来实现。
function createComparisonFunction(propertyName) {
    return function(object1, object2){
        var value1 = object1[propertyName];//exp1
        var value2 = object2[propertyName];//exp2
        if (value1 < value2){
            return -1;
        } else if (value1 > value2){
            return 1;
        } else {
            return 0;
        }
    };
}

返回的函数(匿名函数)里面的exp1和exp2访问了外部表达式中的变量,即使内部的函数已经结束了(returned),并且在其它地方使用,它依然对那个变量有访问权。这是因为内部函数的作用域包含createComparisonFunction()的作用域。考虑一下函数第一次被调用的时候发生了什么:

理解作用域链(scope chains)如何构建和使用对理解闭包非常有帮助。
当一个函数被调用时,一个可执行上下文(execution context)被创建了,并且它的作用域也被创建了。内部函数的激活对象被参数初始化,外部函数的激活对象是作用域链的第二个对象,这个过程会随着所有包含在外面的函数一直持续下去,直到到达全局可执行上下文时才停止。

function compare(value1, value2){
    if (value1 < value2){
        return -1;
    } else if (value1 > value2){
        return 1;
    } else {
        return 0;
    }
}
var result = compare(5, 10);

这里在全局可执行上下文定义了一个全局函数compare(),当compare()第一次调用时,一个新的激活对象被创建,这个对象包含arguments、value1和value2,全局可执行上下文变量对象紧挨着compare()的可执行上下文的作用域链,它包括this,result和compare。

这里写图片描述

在代码运行的背后,一个对象代表着每个可执行上下文的变量。全局上下文的变量总是存在,相对的,本地上下文变量的对象就如compare()的对应的那些,只有在函数被调用的时候才存在。当compare()定义时,它的作用域就被创建了,预加载全局变量对象,然后保存在内部的[[Scope]]属性内。当这个函数调用时,一个可执行上下文被创建,然后通过函数[[Scope]]属性中的对象来创建它的作用域链。这之后,一个表现得如变量对象的激活对象被构造,然后被添加到上下文作用域链的前端。在这个例子中,compare()函数的可执行上下文在它的作用域有两个变量对象:当前激活对象和全局变量对象。记住,作用域链本质上是一组指向对象的指针而不是真正地包含这些对象。

无论什么时候一个变量在函数中被访问时,内部就会根据这个名字在这个作用域里搜索。一旦这个函数结束,当前激活的对象被销毁,内存就只剩下了全局的作用域。然而,闭包则表现的不同。

在另一个函数中定义的函数将外部函数的激活对象添加到作用域链中,所以,在createComparisonFunction()中,匿名函数的作用域链实际包含了createComparisonFunction()对应的激活对象的引用。

var compare = createComparisonFunction("name");
var result = compare({ name: "Nicholas" }, { name: "Greg" });

下图展示了上面两行代码执行时发生了什么:

这里写图片描述

当匿名函数从createComparisonFunction()返回的时候,它的作用域链就会被初始化,包括createComparisonFunction()的对象和全局对象,这就使得匿名函数可以访问createComparisonFunction()的所有变量。另外一个有趣的副作用就是当createComparisonFunction()结束执行时,从它那里获得的激活对象是不会被销毁的,因为在匿名函数中依然有对它的引用。当createComparisonFunction()执行结束时,它的上下文的作用域链被销毁,但是它的激活对象在匿名函数被销毁之前始终保存在内存中。

//create function
var compareNames = createComparisonFunction("name");
//call function
var result = compareNames({ name: "Nicholas" }, { name: "Greg"});
//dereference function - 内存被回收
compareNames = null;

在这里,比较函数被创建,保存在变量compareNames中,将变量赋值为null,这样就解引用了这个函数,这样就可以让回收进程将其清除。作用域链将会被destroy,作用域也会被安全的destroy。

由于闭包的出现总会伴随着包含它的函数的作用域,这样会占用很多内存,所以过度使用闭包会造成内存过度消耗,所以作者建议大家在非常必要的时候再使用闭包。

闭包和变量

经典问题:

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

由于返回数组中每个函数在它的作用域链中都包含createFunctions()的激活对象,它们都指向同一个变量i,当createFunctions()函数结束运行时,i的值为10,这时候执行arri时,匿名函数返回的就是最终的i。如果想要每个函数都返回它对应数组的索引值,可以这样做:

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 test = createFunctions();
for(var i=0;i<10;i++){
    alert(test[i]())
}//0,1,2,3,4,5,6,7,8,9

这里i按值传递给带num参数的匿名函数,我认为关键是在将带参匿名函数执行的结果赋值给了“result[i]”

this 对象

在闭包里使用this对象会导致一些复杂的行为。this会根据上下文的运行时来决定的,当在全局函数中使用时,在非strict模式下,this等于window,在strict模式下是未定义的。当在对象的方法中,this指代的是此对象。匿名函数在这种上下文中并不会绑定在某个对象上,这就意味着匿名函数中的this指向window(strict模式下undefined)。如:

var name = "The Window";
var object = {
    name : "My Object",
    getNameFunc : function(){
        return function(){
        return this.name;
    };
}
};
alert(object.getNameFunc()()); //"The Window" (in non-strict mode)

函数一被调用的时候,就马上会自动获得两个变量 this 和 arguments,一个内部函数从来都不能通过外面的函数直接获取到这两个变量。可以通过如下方法获得:

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

做一点小小的改变可能就达不到预期的结果了:

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" in non-strict mode

最后一行行可以看成下面的形式:

object.getName = function(){
        return this.name;//非严格模式下this指向了window 
    }

## 内存泄露(Memory Leaks)

模仿块级作用域

js里是没有块级作用域的,

function outputNumbers(count){
    for (var i=0; i < count; i++){
        alert(i);
    }
    alert(i); //count
}

js对重复声明同一个变量时不会提示信息,它会忽略随后的声明。

利用匿名函数创造块作用域:

(function(){
    //block code here
})();

这个语法定义了一个立即执行的匿名函数,这种函数有时被称为immediately invoked function。可以用它模仿块级作用域:

function outputNumbers(count){
    (function () {
        for (var i=0; i < count; i++){
            alert(i);
        }
    })();
    alert(i); //causes an error
}

这个模式也限制了内存问题,因为没有对匿名函数的引用,因此作用域在函数执行完之后就结束了。

私有变量

严格来说,JS没有私有成员;所有的对象属性都是public的。然而,还是有私有变量的概念。函数里的变量通常被认为私有的,因为函数外无法访问到。

封装效果:

function Person(name){
    this.getName = function(){
        return name;
    };
    this.setName = function (value) {
        name = value;
    };
}
var person = new Person("Nicholas");
alert(person.getName()); //"Nicholas"
person.setName("Greg");
alert(person.getName()); //"Greg"

这两个都是闭包。

静态私有变量

上代码:

(function(){
    var name = "";
    Person = function(value){
        name = value;
    };
    Person.prototype.getName = function(){
        return name;
    };
    Person.prototype.setName = function (value){
        name = value;
    };
})();
var person1 = new Person("Nicholas");
alert(person1.getName()); //"Nicholas"
person1.setName("Greg");
alert(person1.getName()); //"Greg"
var person2 = new Person("Michael");
alert(person1.getName()); //"Michael"
alert(person2.getName()); //"Michael"

这里使用了私有作用域,这样内部定义的变量和函数外部访问,除了。。

这里提一点,(这里Person的赋值没有使用var),初始化一个未定义的变量会产生一个全局变量,所以Person就变成了全局变量,这样在私有作用域外面就可以访问到它,(严格模式下这种赋值会报错)

这里的name就成了静态变量,创建私有静态变量允许通过prototypes进行更好的代码复用。

模块模式

单例模式,传统字面表达式:

var singleton = {
    name : value,
    method : function () {
        //method code here
    }
};

下面看模块模式(单例模式增强版):

var singleton = function(){
    //private variables and functions
    var privateVariable = 10;
    function privateFunction(){
        return false;
    }
    //privileged/public methods and properties
    return {
        publicProperty: true,
        publicMethod : function(){
            privateVariable++;
            return privateFunction();
        }
    };
}();

模块模式利用了匿名函数返回了一个对象。匿名函数中先定义函数和变量,然后,返回一个字面对象。对象只包含public的属性和方法。由于对象定义在匿名函数中,所以所有的公共方法都能访问这些私有变量和函数。本质上,对象表达式定义了单例的公共接口。这对那些需要各种初始化操作和访问私有变量的单例很有用,如:

var application = function(){
    //private variables and functions
    var components = new Array();
    //initialization
    components.push(new BaseComponent());
    //public interface
    return {
        getComponentCount : function(){
            return components.length;
        },
        registerComponent : function(component){
            if (typeof component == "object"){
                components.push(component);
            }
        }
    };
}();

在web级应用中,拥有一个能够处理application级信息的单例是非常普遍的。这段代码构造了一个处理组件的应用。模块模式适合创建一个唯一对象,这个对象只需一些数据来初始化,暴露可以访问内部数据的一些公共接口。因为最终返回的是一个object literal,所以每一个用这种方式构造的单例都是一个对象的实例。单例全局都可以访问。

模块增强模式

待读。。。

var singleton = function(){
    //private variables and functions
    var privateVariable = 10;
    function privateFunction(){
        return false;
    }
    //create object
    var object = new CustomType();
    //add privileged/public properties and methods
    object.publicProperty = true;
    object.publicMethod = function(){
        privateVariable++;
        return privateFunction();
    };
    //return the object
    return object;
}();
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值