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

你可能感兴趣的:(JavaScript)