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脚本没有 private
和 public
范围,就像 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 = 42
是 implied 因为它回退到 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 更深入,但仍然可以访问。它们都涵盖了模块模式。实际上,您会在这些资源和网络中找到许多模块模式。该模块允许您执行的操作类似于其他语言的 public
和 private
。我不会尝试解释它,因为我认为其他人比我做得更好。但是,我将只做一个非常简短的解释什么是模块。同样,其他资源可以更好地涵盖这一点。
模块利用称为闭包的 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" 字段,您可以从其他地方访问。
这篇文章很长,但希望能涵盖您入门所需的基础知识。
首先,我来自 .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脚本没有 private
和 public
范围,就像 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 = 42
是 implied 因为它回退到 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 更深入,但仍然可以访问。它们都涵盖了模块模式。实际上,您会在这些资源和网络中找到许多模块模式。该模块允许您执行的操作类似于其他语言的 public
和 private
。我不会尝试解释它,因为我认为其他人比我做得更好。但是,我将只做一个非常简短的解释什么是模块。同样,其他资源可以更好地涵盖这一点。
模块利用称为闭包的 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" 字段,您可以从其他地方访问。
这篇文章很长,但希望能涵盖您入门所需的基础知识。