跳至侧栏

Ch7 Function Expressions ( JavaScript 中的函数表达式 )

    本文为书籍《Professional JavaScript for Web Developers, 3rd Edition》英文版第 7 章:“Function Expressions” 个人学习总结,主要介绍 JavaScript 中的闭包、局部变量(局部作用域)和私有变量等内容。

 

.闭包
JavaScript中的闭包,是指一个函数可以访问另一个函数作用域中 的变量。这通常通过将一个函数定义在另一个函数内部来完成。如:
function createComparisonFunction(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; } }; } //create function var compareNames = createComparisonFunction("name"); //call function var result = compareNames({ name: "Nicholas" }, { name: "Greg" }); //dereference function - memory can now be reclaimed compareNames = null;
闭包会引用外部函数作用域,会占用更多的内存,过度使用闭包, 会导致性能问题。所以,仅当必要时才使用闭包。对产生闭包的函数, 使用后应该解除引用。
1.闭包与变量
闭包的作用域链有一个明显的副作用。闭包总是获得外部函数变量 的最终值。如:
function createFunctions() { var result = new Array(); for (var i = 0; i < 10; i++) { result[i] = function () { return i; }; } return result; } var funcArr = createFunctions(); alert(funcArr[0]()); // 10 alert(funcArr[1]()); // 10 // ... alert(funcArr[9]()); // 10 上面的代码中,外部函数产生一个函数数组并返回。函数数组中 的每个元素都是一个函数,每个函数都返回 i 变量。看起来,每个 函数应该返回每次循环的 i 值,依次返回 1 10,但事实情况是, 函数数组中每个函数的返回结果都是 10。这是因为,每个内部函数 返回的是变量 i,而不是 i 在某个时刻的特定值。而 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 funcArr = createFunctions(); alert(funcArr[0]()); // 0 alert(funcArr[1]()); // 1 // ... alert(funcArr[9]()); // 9 2.闭包与 this 对象
在闭包内使用 this 对象将产生一些复杂的行为。this 对象的值 基于函数所在的执行环境在运行时决定:在全局函数中使用时,this 等于 window (非严格模式) undefined (严格模式);而当作为对象 的方法调用时, this 等于这个对象。如:
var name = "The Window"; var object = { name: "My Object", getNameFunc: function () { return function () { return this.name; }; } }; alert(object.getNameFunc()()); //"The Window" -- 非严格模式下 在上面的代码中,最后一行执行的结果并不是我们所期望的"My Object" 而是"The Window" 每个函数一旦被调用,它将自动获得 this arguments 两个变量。 一个内部函数是不能直接从外部函数访问到这两个变量的。可以通过将 this 对象存储在另一个变量中来解决这个问题。如:
var name = "The Window"; var object = { name: "My Object", getNameFunc: function () { var that = this; //将 this存储在that中 return function () { return that.name; //通过that访问name }; } }; alert(object.getNameFunc()()); //"My Object" 要让闭包访问外部函数的 this arguments 对象,可以通过将它 们的引用存储在另一个变量中来完成。
3.内存泄露
在IE9 之前的IE浏览器中,通过闭包访问 HTML 元素会导致元素不能 被垃圾回收器销毁。如:
function assignHandler() { var element = document.getElementById("someElement"); element.onclick = function () { alert(element.id); }; }
上面的代码产生一个闭包,匿名函数保持对 element 变量的引用, 使其占用的内存不能被释放。可通过下面的方法来解决这个问题:
function assignHandler() { var element = document.getElementById("someElement"); var id = element.id; element.onclick = function () { alert(id); }; element = null; }
.模拟块级作用域和私有作用域
JavaScript中没有直接的块级作用域,然而,可以使用匿名函数表 达式来模拟块级作用域,任何定义在匿名函数中的变量在匿名函数执行 完之后都将被销毁,在匿名函数外访问这些变量将会产生错误。如:
function outputNumbers(count) { (function () { for (var i = 0; i < count; i++) { alert(i); } })(); alert(i); //causes an error }
匿名函数表达式提供了创建私有作用域的方法。这种技术通常应用 在函数外部的全局作用域中,防止变量和函数添加到全局作用域中。在 大型应用中,可以避免命名冲突。如:
(function () { var now = new Date(); if (now.getMonth() == 0 && now.getDate() == 1) { alert("Happy new year!"); } })();
上例中,匿名函数内的 now 变量只能在匿名函数内被访问。成为 局部变量。 这种模式不会有闭包产生的内存问题,因为不存在对匿名函数的 引用。当函数执行完成后,作用域链将被立即销毁。 上例中的匿名函数实际上是一种立即执行的匿名函数表达式。
.私有变量
JavaScript中没有“私有成员”的概念,但却有“私有变量”的概念。 私有变量包括函数参数、局部变量和定义在函数内部的函数。私有变量 只能在函数内部访问,不能在外部访问。闭包可以访问私有变量。 利用这个特点,可以在对象上定义公共方法访问对象的私有变量, 这种公共方法被称作“特权方法”。利用这种模式,能够隐藏不可以被直 接改变的数据。 有两种定义特权方法的方式:第一种方式是在对象的构造函数内定 义,如:
function MyObject() { //private variables and functions var privateVariable = 10; function privateFunction() { return false; } //privileged methods this.publicMethod = function () { privateVariable++; return privateFunction(); }; }
因为构造函数模式所产生的方法不能在多个实例间共享,使用静 态私有变量可以解决这个问题。
1.静态私有变量
特权函数可以通过函数表达式来实现。函数表达式产生私有作用域, 其内部定义的私有变量将在对象的各实例间共享,因而成为静态私有变 量。函数表达式内定义的对象方法成为特权方法(也是闭包),可以访问 私有变量。对象的构造函数使用命名函数表达式定义,但不使用 var 键字声明,这样可以在全局范围内访问,不过,这样做在严格模式下会 产生错误。如:
(function () { //private variables and functions var privateVariable = 10; function privateFunction() { return false; } //constructor MyObject = function () { }; //public and privileged methods MyObject.prototype.publicMethod = function () { privateVariable++; return privateFunction(); }; })();
这种模式的私有变量和公共方法都会在所有实例间共享,如:
(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" 2.模块模式
模块模式也是一种单例模式。单例模式在JavaScript中通常使用下 面的方法产生:
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(); } }; } ();
模块模式在单例模式需要进行初始化和访问私有变量时很有用。在 Web 应用程序中,通常使用单例模式管理应用程序级的信息。如:
function BaseComponent() { } function OtherComponent() { } 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); } } }; } (); application.registerComponent(new OtherComponent()); alert(application.getComponentCount()); //2 模块模式的实例都是 Object,不能使用 instanceof 运算符有效地 判断类型。
3.模块增强模式
这也是一种模块模式,但在返回所产生的对象之前,可以向其添加 属性和/或方法。如:
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; } ();
在模块模式中,如果 application 对象需要是一个BaseComponent 实例,可以使用下面的方式:
function BaseComponent() { } function OtherComponent() { } var application = function () { //private variables and functions var components = new Array(); //initialization components.push(new BaseComponent()); //create a local copy of application var app = new BaseComponent(); //public interface app.getComponentCount = function () { return components.length; }; app.registerComponent = function (component) { if (typeof component == "object") { components.push(component); } }; //return it return app; } (); alert(application instanceof BaseComponent); application.registerComponent(new OtherComponent()); alert(application.getComponentCount()); //2
posted @ 2013-01-02 15:50  JiayangShen  阅读(1188)  评论(0编辑  收藏  举报
Top
推荐
收藏
关注
评论