Javascript 模块中的变量在外部可见吗?

Variables in a Javascript module visible outside of it?

首先,我来自 .NET 世界,那里有静态 classes(C#,在 VB 中也称为模块)和实例 classes - 那些你可以实例化的。

这个问题是关于 Javascript,我正在尝试重新创建我已经知道的模式并制作一个模块/静态 class。这是代码:

var MyModule = {
    variable1: 0,
    variable2: 1,
    method1: function() {
        //code goes here
    },
    method2: function() {
        //mode code goes here
    },
    method3: function() {
        //and some more just to prove the pattern        
    }
}

method1(); //error - no such method - ok, good, I expect that
variable1 = 5; //silently assigns a variable inside MyModule to 5 ?

请解释为什么在命名空间内声明的方法不可见,但变量可见?另外,有没有办法防止这些变量在 MyModule 之外可见,即使在同一个脚本中也是如此?

一如既往地在 Stack Overflow 上提问之前,I created a JSFiddle 附加到问题上,几个步骤之后才弄清楚问题是什么。这种情况其实经常发生。但这次我想与 public 分享我的发现。 JS 大师可能已经知道这一点,而一些经验不足的开发人员可能会发现这些信息很有帮助。

I had two instances of variablevariable1,一个属于MyModule,一个属于window(全局作用域)。当我在调试器中将鼠标悬停在 variable1 上时,即使它在 MyModule 内,调试器也会显示一个全局值。看起来像是 Visual Studio 2015 中的错误,但它是这样工作的。然后我在 MyModule 的一个函数中放置了一个断点,并注意到如果变量以 this 为前缀,它会显示不同的值。显然,JS 语法不允许在 MyModule 的声明期间放置 this,所以只需要忍受 JS 调试器在 Visual Studio.

中的工作方式

如果我的解释没有什么意义,例如,尝试将鼠标悬停在 C# 项目中任何 class 定义顶部的变量上。在调试期间的任何时候,无论谁声明了什么,您都会在那里看到最新和正确的值。 IDE 行为的差异让我感到困惑。如果您对变量声明不够小心,Javascript 也有创建全局变量的倾向。

我将首先尝试解决一些误解 - 您的模块实际上是 plain JavaScript object 使用文字初始化的。对象不隐藏任何信息 - Java脚本没有 privatepublic 范围,就像 Java 一样。此外,访问对象属性的唯一方法是通过它访问它们

method1();

这会失败,因为您正试图获取一个名为 method1 的变量并执行它。这不存在,因此错误。但是,您 可以 做的是 MyModule.method1()。这是因为 MyModule 是一个实际对象。在 Java 中,这类似于 Object MyModule = new Object()。因此,您可以在其上调用方法,例如 MyModule.toString() - 唯一的 "unconventional" 是实例以大写字母命名 - 通常 Java 和 JavaScript 中的变量以小写字母开头,但只是按照惯例 - 没有强制要求的语言功能。

接下来是这一行:

variable1 = 5; //silently assigns a variable inside MyModule to 5 ?

回复线上的评论——不,不是。上面这行相当于window.variable1 = 5。你所拥有的叫做 "implied global"。这个名字是相当自我描述的,但只是为了澄清 window.foo = 42 是一个明确的全局 foo = 42implied 因为它回退到 window 对象。但是,var foo = 42 不是 全局 - var keyword does not attach to or use the global window object. If you use the "use strict" 指令会导致 Java 脚本引擎在遇到隐含的全局时抛出错误.

现在,抛开这些误解,还有一个小错误,我只是掩饰一下,但它实际上是一个主要问题 - MyModule.method1 等等 函数 而不是方法。方法是子例程的面向对象术语,子例程也带有隐含状态,即分配给它的对象。然而,函数是一个自由浮动的可执行代码,本质上并不 "belong" 对象。这种区别可能看起来很学术,但它有很大的影响。但是,我会请您在这方面相信我,因为这个话题太宽泛,无法在此处涵盖。

所以,这就是问题所在。我们如何让它正确?首先,在范围上,因为它的其余部分需要它:

Java脚本有两个作用域——全局作用域和功能作用域。这是什么意思,确切地说:

var foo = 42;

function bar(input) {
    var foo = input;
    return foo;
}

console.log(bar(5)); //5
console.log(foo); //42

我们也有称为 foo 的变量 - 一个在函数内,一个不在函数内。分配一个不会改变另一个——这是因为它们在不同的范围内。这要归功于 var 关键字 - 如果在函数中省略了它,那么我们将访问相同的东西

var foo = 42;

function bar(input) {
    foo = input;
    return foo;
}

console.log(bar(5)); //5
console.log(foo); //5

这是因为内部 foo 没有声明在函数范围内,所以它访问外部(没有外部 - window.foo 属性)。

这很简单。然而,这里有一个隐藏的方面,虽然不是很明显但值得一提:只有 两个作用域。这就是我的意思

function bar(condition) {
    var foo = 5;
    if (condition) {
        var foo = 42;
    }
    return foo;
}

console.log( bar(false) ); //5
console.log( bar(true) ); //42

因此,即使我们看似在 if 语句中声明了一个新的 foo 属性,但实际上与第一个语句相同。其他语言在 { } 内有一个块范围,因此,在那里声明的变量,留在那里。 Java脚本中不正确。容易错过,所以一定要注意。

现在,解决您的问题。关于 JavaScript 模块模式有很多优秀的资源,仅举几例:Addy Osmani's "Learning JavaScript Design Patterns" book has a very good description, Todd Motto also has, what I believe is, a good article for beginners while the Adequately Good post 更深入,但仍然可以访问。它们都涵盖了模块模式。实际上,您会在这些资源和网络中找到许多模块模式。该模块允许您执行的操作类似于其他语言的 publicprivate。我不会尝试解释它,因为我认为其他人比我做得更好。但是,我将只做一个非常简短的解释什么是模块。同样,其他资源可以更好地涵盖这一点。

模块利用称为闭包的 Java脚本功能 更多信息 here and here(Whosebug 上投票最多的问题之一)。还记得我说过的,JavaScript 中有两个作用域吗?简单来说,闭包提供了一个功能范围。这是它的样子:

 (function() { /* your code goes here */ })()

为了简要剖析这一点,您有一个包含在 (/* function goes here */)() 中的代码函数。实际上,它所做的是执行函数,因此 inside 中的所有内容都被放入函数范围。然而,它非常强大,这是一个例子

var closure = (function() { 
    var privateVar = "secret";
    return {
       publicVar: 42,
       getPrivateVar() { return privateVar; }
    }
})()

console.log(closure.publicVar); //42
console.log(closure.getPrivateVar()); //"secret"
//let's change this
closure.publicVar = 5;
closure.privateVar = "changed";

console.log(closure.publicVar); //5
console.log(closure.getPrivateVar()); //"secret"

就像那样,我们有一个(类似的)私有字段。同样,var privateVar 将保留在函数范围内 "bottled up" 并且在外部无法访问。顺便说一句,这是模块模式的一个例子。我可以将其命名为 myModule 而不是 var closure。这是一个相当简单的看法,但事情是这样的——它并没有变得更复杂。所有不同的模块归结为非常相似的东西——你只是以不同的方式公开数据。然而,概念是相似的——"private" 字段保留在内部,而 "public" 字段,您可以从其他地方访问。

这篇文章很长,但希望能涵盖您入门所需的基础知识。