(function(w,d,a,undefined){ var cfg = { currency: 'GBP',exponent: 2 }; var get = function () { return cfg; }; a.init = function (settings) { for (var k in settings) { cfg[k] = settings[k]; } }; a.set = function (args) { get = args.get || get; //eval(args) //works but why?? }; a.get = function () { return get(); }; })(window,document,window.fxc = {}); fxc.init({currency: 'EUR'}); // prints,Object { currency="EUR",exponent=2} console.log(fxc.get()); fxc.set({get: function(msg){ // cannot access private properties return cfg; }}); // prints,undefined console.log(fxc.get());
我一直试图找到正确的方法来做一段时间,但我似乎无法找到正确的组合.我确实有eval(),但肯定不是正确的方法?会喜欢任何帮助.
解决方法
闭包变得超出范围.范围是指变量的生命周期,而对象属性是指变量的绑定.
所以在讨论闭包之前,我们先简单讨论一下范围.
堆栈:
范围与堆栈框架有关(在计算机科学中它称为“激活记录”,但大多数熟悉C或程序集的开发人员更好地将其称为堆栈框架).范围是堆栈框架类对象的类型.我的意思是,在对象是类的实例的情况下,堆栈帧是范围的实例.
我们以一种虚构的语言为例.在这种语言中,就像在javascript中一样,函数定义范围.让我们来看一个示例代码:
var global_var function b { var bb } function a { var aa b(); }
当我们读取上面的代码时,我们说变量aa在函数a的范围内,变量bb在函数b的范围内.请注意,我们不会将此事称为私有变量.因为私有变量的反义是公共变量,并且都引用绑定到对象的属性.相反,我们称之为aa和bb local variables.局部变量的反面是全局变量(不是公共变量).
现在,让我们看看当我们调用时会发生什么:
调用a(),创建一个新的堆栈帧.为堆栈上的局部变量分配空间:
The stack: ┌────────┐ │ var aa │ <── a's stack frame ╞════════╡ ┆ ┆ <── caller's stack frame
a()调用b(),创建一个新的堆栈帧.为堆栈上的局部变量分配空间:
The stack: ┌────────┐ │ var bb │ <── b's stack frame ╞════════╡ │ var aa │ ╞════════╡ ┆ ┆
在大多数编程语言中,这包括javascript,函数只能访问自己的堆栈帧.因此a()不能访问b()中的局部变量,全局范围内的任何其他函数或代码都不能访问()中的变量.唯一的例外是全局范围内的变量.从实现的角度来看,这是通过在不属于堆栈的内存区域中分配全局变量来实现的.这通常称为堆.所以为了完成图片,此时的内存看起来像这样:
The stack: The heap: ┌────────┐ ┌────────────┐ │ var bb │ │ global_var │ ╞════════╡ │ │ │ var aa │ └────────────┘ ╞════════╡ ┆ ┆
(作为旁注,您还可以使用malloc()或new在函数内的堆上分配变量)
现在b()完成并返回,它的堆栈帧从堆栈中删除:
The stack: The heap: ┌────────┐ ┌────────────┐ │ var aa │ │ global_var │ ╞════════╡ │ │ ┆ ┆ └────────────┘
并且当a()完成时,它的堆栈帧发生同样的情况.这就是局部变量如何自动分配和释放 – 通过从堆栈中推送和弹出对象.
闭包:
闭包是一种更先进的堆叠框架.但是,一旦函数返回,正常的堆栈帧就会被删除,带闭包的语言只会将堆栈帧(或者它包含的对象)从堆栈中取消链接,同时保持对堆栈帧的引用,只要它是必需的.
现在让我们看一下带闭包的语言的示例代码:
function b { var bb return function { var cc } } function a { var aa return b() }
现在让我们看看如果我们这样做会发生什么:
var c = a()
调用第一个函数a(),然后调用b().创建堆栈帧并将其推入堆栈:
The stack: ┌────────┐ │ var bb │ ╞════════╡ │ var aa │ ╞════════╡ │ var c │ ┆ ┆
函数b()返回,因此它的堆栈帧从堆栈中弹出.但是,函数b()返回一个匿名函数,它在闭包中捕获bb.所以我们弹出堆栈框架,但不要从内存中删除它(直到所有对它的引用都被完全垃圾收集):
The stack: somewhere in RAM: ┌────────┐ ┌╶╶╶╶╶╶╶╶╶┐ │ var aa │ ┆ var bb ┆ ╞════════╡ └╶╶╶╶╶╶╶╶╶┘ │ var c │ ┆ ┆
a()现在将函数返回给c.因此,对b()的调用的堆栈帧被链接到变量c.请注意,它是链接的堆栈帧,而不是范围.有点像从类中创建对象,它是分配给变量的对象,而不是类:
The stack: somewhere in RAM: ┌────────┐ ┌╶╶╶╶╶╶╶╶╶┐ │ var c╶╶├╶╶╶╶╶╶╶╶╶╶╶┆ var bb ┆ ╞════════╡ └╶╶╶╶╶╶╶╶╶┘ ┆ ┆
另请注意,由于我们实际上没有调用函数c(),因此变量cc尚未在内存中的任何位置分配.在我们调用c()之前,它目前只是一个作用域,还不是堆栈帧.
现在当我们调用c()时会发生什么?正常创建c()的堆栈帧.但这次有一点不同:
The stack: ┌────────┬──────────┐ │ var cc var bb │ <──── attached closure ╞════════╤──────────┘ │ var c │ ┆ ┆
b()的堆栈帧附加到c()的堆栈帧.所以从函数c()的角度来看,它的堆栈还包含调用函数b()时创建的所有变量(再次注意,不是函数b()中的变量,而是函数b()时创建的变量调用 – 换句话说,不是b()的范围,而是调用b()时创建的堆栈框架.暗示只有一个可能的函数b()但是很多调用b()创建了许多堆栈框架.
但是局部和全局变量的规则仍然适用. b()中的所有变量都成为c()的局部变量,而不是其他任何变量.调用c()的函数无法访问它们.
这意味着当您在调用者的范围中重新定义c时,如下所示:
var c = function {/* new function */}
有时候是这样的:
somewhere in RAM: ┌╶╶╶╶╶╶╶╶╶┐ ┆ var bb ┆ └╶╶╶╶╶╶╶╶╶┘ The stack: ┌────────┐ ┌╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶┐ │ var c╶╶├╶╶╶╶╶╶╶╶╶╶╶┆ /* new function */ ┆ ╞════════╡ └╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶┘ ┆ ┆
如您所见,由于c所属的范围无法访问它,因此无法从对b()的调用中重新获得对堆栈帧的访问权限.
解决方法:
解决方法,因为这是一个绑定(javascript调用它上下文)问题而不是范围问题,是使用对象绑定来存储您的cfg对象.
不幸的是,javascript没有私有变量.因此,只能将其绑定为公共变量.解决方法解决此问题的方法是使用Perl约定来告诉其他程序员不要触摸该对象,除非他们正在修改实现本身.该惯例是使用下划线启动变量名称:
// WARNING: Private! a._cfg = { currency: 'GBP',exponent: 2 };