javascript 无法访问私有属性

IT技术 javascript
2021-01-21 15:50:53

我有以下代码,但我不明白为什么在重新声明 get 方法时无法访问私有属性。

(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(),但那肯定不是正确的方法吗?会喜欢任何帮助。

3个回答

没错。部分原因是 javascript 没有私有属性。你所做的不是宣布私有财产。您正在使用一种使用闭包来模拟私有属性的设计模式。

闭包超出了范围。范围是指变量的生命周期,而对象属性是指变量的绑定。

所以在讨论闭包之前,让我们先简单讨论一下作用域。

堆栈:

作用域与堆栈框架有关(在计算机科学中,它被称为“激活记录”,但大多数熟悉 C 或汇编的开发人员更了解它作为堆栈框架)。作用域之于堆栈框架就像类之于对象。我的意思是,在对象是类的实例的情况下,堆栈帧是作用域的实例。

让我们以一种虚构的语言为例。在这种语言中,就像在 javascript 中一样,函数定义了作用域。让我们看一个示例代码:

var global_var

function b {
    var bb
}

function a {
    var aa
    b();
}

当我们阅读上面的代码时,我们说变量aa在函数的作用域内,a变量bb在函数的作用域内b请注意,我们不称这个东西为私有变量。因为私有变量的对立面是公共变量,它们都是指绑定到对象的属性。相反,我们调用aabb 局部变量与局部变量相反的是全局变量(不是公共变量)。

现在,让我们看看调用时会发生什么a

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()不能访问 .in 中的局部变量b(),也不能访问a(). 唯一的例外是全局范围内的变量。从实现的角度来看,这是通过在不属于堆栈的内存区域中分配全局变量来实现的。这通常称为堆。所以为了完成图片,此时的内存看起来像这样:

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()成为局部变量 toc()而不是别的。调用的函数c()无法访问它们。

这意味着当您c像这样在调用者的范围内重新定义时

var c = function {/* new function */}

有时候是这样的:

                     somewhere in RAM:
                           ┌╶╶╶╶╶╶╶╶╶┐
                           ┆ var bb  ┆
                           └╶╶╶╶╶╶╶╶╶┘
The stack:
 ┌────────┐           ┌╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶┐
 │ var c╶╶├╶╶╶╶╶╶╶╶╶╶╶┆ /* new function */ ┆
 ╞════════╡           └╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶┘
 ┆        ┆

如您所见,不可能从调用中重新获得对堆栈帧的访问权,b()因为c属于它的作用域无权访问它。

解决方法:

解决方法是使用对象绑定来存储您的cfg对象,因为这是一个绑定(javascript 称之为上下文)问题而不是范围问题

不幸的是,javascript 没有私有变量。因此只能将其绑定为公共变量。解决此问题的解决方法是使用 Perl 约定告诉其他程序员不要接触该对象,除非他们正在修改实现本身。该约定是以下划线开头的变量名:

// WARNING: Private!
a._cfg = {
    currency: 'GBP',
    exponent: 2
};
刚刚发现这个漂亮的答案正在回顾/纠正我的旧答案。一个很好的解释。遗憾的是,这样的宝石在 stackoverflow 上丢失了。
2021-03-15 15:50:53
请注意,当我说在大多数编程语言中您无法访问其他人的堆栈框架时,我实际上是说我知道一些编程语言可以访问其他人的堆栈框架(嗯,至少您的调用者的堆栈框架)。
2021-03-22 15:50:53
您的答案在此处被广泛引用-请自行判断这种复制和粘贴级别是否可以,或者您是否想使用 mod :-)
2021-03-22 15:50:53
@Winchestro:说实话。这不是完全适合解释的问题。但解释是回答问题所必需的。这可能属于“闭包如何工作”问题之一,但是这些问题有很多,而且有数百个答案,因此即使在那里也会丢失。
2021-03-28 15:50:53
实际上,阅读 plalx 的答案后,这是一个相当不错的解决方法。
2021-04-03 15:50:53

嗯,你刚刚说了,不能访问私有属性。IIFE 中定义的变量不能从外部定义的函数访问。

如果你不想让cfg变量公开可用,也许你可以这样做:

(function(w, d, a, undefined) {
    var cfg = {
        currency: 'GBP',
        exponent: 2
    };
    var get = function() {
        return cfg; //must return cfg
    };
    a.init = function(settings) {
        for (var k in settings) {
            cfg[k] = settings[k];
        }
    };
    a.set = function(args) {
        get = args.get(get) || get;
    };
    a.get = function() {
        return get();
    };
})(window, document, window.fxc = window.fxc || {});

fxc.set({
    get: function(initialGet) {
        return function(msg) {
            var cfg = initialGet();
            console.log('custom get');
            return cfg;
        };
    }
});


console.log(fxc.get());
//custom get
//{currency: "GBP", exponent: 2}
@Christian 不是get直接传递覆盖函数,而是传入一个负责创建它的工厂函数。然后,当您使用 注册 get 的工厂函数时fxc.set(...),内部get函数将作为initialGet参数传递给工厂这允许get工厂创建的新函数保留initialGet并使用它来查询组件的内部状态。
2021-03-22 15:50:53
漂亮的FP!我没有考虑保存旧的访问器函数来提供对闭包的受限访问:)
2021-03-28 15:50:53
我不太确定这里发生了什么。我不知道什么时候 (initialGet) 只分配给 fxc.get()
2021-03-29 15:50:53

Javascript 没有私有属性。如果我正确理解了您的问题,从您的第二个脚本标签中,您想访问在您的第一个脚本标签中声明的“cfg”对象。

为此,您需要了解闭包的工作原理。在这种情况下,您在匿名函数中将“cfg”声明为变量。这意味着在该范围内声明的其他函数将可以访问“cfg”对象,但是,它不能在任何其他上下文中工作。

要解决此问题,请替换var cfg =a.cfg =

你为什么希望它是“私有的”?称其为 _cfg,人们就会知道他们不应该乱搞它。
2021-03-14 15:50:53
那会起作用,但会将 cfg 置于公共访问中
2021-04-09 15:50:53