JavaScript语言提供了函数作用域,同时JavaScript允许嵌套的函数定义。 有趣的是,内部函数(inner function)的生命周期可能超过父级函数。 这时便会显示出JavaScript的一个特殊现象:闭包。 闭包为面向对象编程提供了另外一种有趣的封装方式,我们无需为私有变量声明 private 。 函数作用域程序设计语言中的 作用域 (scope)控制着变量和参数的可见性和生命周期。 它的重要性体现在避免命名冲突和自动内存管理两个方面,极大地减少了程序员的工作。 多数编程语言都拥有 块作用域 (block scope),由一对大括号限定其中变量的作用域。 比如C++的变量只在块内可见,变量被定义时执行内存申请和构造函数, 控制流退出代码块后内部的变量又被析构和内存回收。 不幸的是JavaScript提供了块语法,却不提供块作用域,而是提供函数作用域。 这意味着参数和变量在函数外部不可见,在函数内部始终可见。 闭包JavaScript允许在函数中嵌套定义函数,这意味着 内部函数(inner function)也可以访问父级函数的上下文(包括参数和变量) 。 有趣的是,有时内部函数拥有更长的生命周期。 来一个典型的面试题: function f(){ for(var i=0; i<3, i++){ setTimeout(function(){ console.log(i); }, 1000); } } 输出: i 是 function f 中定义的变量,在传递给 setTimeout 的函数中, i 仍然有效(其实它叫做 闭包变量 )。 所以1s后 console.log 时 i 仍然是 function f 中定义的那个 i ,这时它的值为 3 。 这意味着内部函数可以访问真正的外部变量,而不是外部变量的副本。 函数可以访问它被创建时的所有上下文变量,这就叫做 闭包现象 。 如果我们希望上述代码输出 123 ,我们需要开启一个子作用域。而JavaScript只提供函数作用域,所以我们需要添加一层函数: function f(){ for(var i=0; i<3, i++){ !function(i){ setTimeout(function(){ console.log(i); }, 1000); }(i); } } 这时输出便是 123 了。其中的 ! 是为了让JavaScript将这一句解析为表达式,而不是函数声明。 其实任何运算符都可以( +- ),但如果没有便是语法错。 变量提升一般编程语言建议将变量定义推迟,越晚越好。然而在JavaScript中不是这样, 应当将一切变量都在函数体最上方声明。例如: var p = {name: 'harttle'}; function foo(){ console.log(p.name); var p = {name: 'alice'}; } 上述代码将产生运行时错误: Uncaught TypeError: Cannot read property 'name' of undefined 这是因为在JavaScript的函数作用域实现中,所有变量声明( var xxx )都会被提升。 就像在函数体刚进入时声明这些变量一样。上述代码相当于: var p = {name: 'harttle'}; function foo(){ var p; console.log(p.name); p = {name: 'alice'}; } Note:变量声明与初始化是两回事,变量提升指的是声明提升,初始化/赋值并不会提升。 闭包封装与基于类声明的面向对象语言不通,JavaScript基于原型继承。 JavaScript并未提供public , protected , private 等关键字。 而JavaScript的闭包机制则可以完美地提供封装。你只需要:
例如: function Counter(){ var i = 0; return { count: function(){ i++; } get: function(){ return i; } } } var counter = Counter(); counter.count(); counter.get(); // 1 其中 i 便是 private , count 与 get 便是 public 。 这里JavaScript有一个设计错误:调用内部函数时 this 不会正确传递。 (责任编辑:好模板) |