Javascript模块中的变量在它外面可见?

Neo*_*isk 2 javascript scope module global-variables static-classes

首先,我来自.NET世界,其中有静态类(C#,也称为VB中的模块)和实例类 - 可以实例化的类.

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

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 ?
Run Code Online (Sandbox Code Playgroud)

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

VLA*_*LAZ 7

我将首先尝试解决一些误解 - 您的模块实际上是使用文字初始化的纯JavaScript对象.一个对象不会隐藏任何信息 - JavaScript没有privatepublic范围相同,比如Java.此外,访问对象属性的唯一方法是通过它访问它们

method1();
Run Code Online (Sandbox Code Playgroud)

这会失败,因为您正在尝试获取一个被调用的变量method1并执行它.这不存在,因此错误.但是,你可以做的是MyModule.method1().这是因为它MyModule是一个实际的对象.在Java中,这与拥有类似Object MyModule = new Object().你可以调用它上面的方法,比如MyModule.toString()- 唯一的"非传统"的东西,就是实例是用大写字母命名的--Java和JavaScript中的变量通常以小写字母开头,但只是按惯例开头 - 没有任何语言特性要求它.

接下来,有这一行:

variable1 = 5; //silently assigns a variable inside MyModule to 5 ?
Run Code Online (Sandbox Code Playgroud)

要回答关于这条线的评论 - 不,它没有.上面这行等同于做window.variable1 = 5.你所拥有的是一种"隐含的全球".这个名称是相当自我描述的,但只是澄清是隐含window.foo = 42的显式全局foo = 42,因为它回退到对象.然而,是不是一个全球性的-该变种的关键字不重视或不使用全局对象.如果使用"use strict"指令,则会导致JavaScript引擎在遇到隐含的全局时抛出错误.windowvar foo = 42window

现在,除了这些误解之外,还有另一个次要问题,我只会掩盖它,但实际上它是一个重要的东西 - MyModule.method1等等是功能而不是方法.方法是面向对象的子程序术语,它也带有隐含状态,这是它所分配的对象.但是,函数是一个自由浮动的可执行代码,它本身并不属于对象.这种区别似乎相当具有学术性,但它具有很大的影响.但是,我会请你相信我,因为这个主题太广泛了.

所以,那是错的.我们如何做?首先,关于范围,其余部分需要:

JavaScript有两个范围 - 全局和功能.这就是这意味着:

var foo = 42;

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

console.log(bar(5)); //5
console.log(foo); //42
Run Code Online (Sandbox Code Playgroud)

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

var foo = 42;

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

console.log(bar(5)); //5
console.log(foo); //5
Run Code Online (Sandbox Code Playgroud)

这是因为内部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
Run Code Online (Sandbox Code Playgroud)

所以,即使我们似乎foo在if语句中声明了一个新属性,它实际上与第一个属性相同.其他语言里面有一个块范围,{ }所以在那里声明的变量就在那里.在JavaScript中不正确.容易错过,所以重要的是要注意.

现在,解决你的问题.有关JavaScript模块模式的优秀资源,仅举几例:Addy Osmani的"学习JavaScript设计模式"一书有一个非常好的描述,Todd Motto也有,我相信,对于初学者而言,是一篇很好的文章帖子更深入但它仍然可以访问.它们都涵盖了模块模式.实际上,您将在这些资源和Web上找到许多模块模式.模块允许您执行的操作publicprivate其他语言类似.我不会试图解释它,因为我认为其他人已经做得比我更好.但是,我将简要解释一下模块是什么.同样,其他资源可以更好地涵盖这一点.

模块在这里这里利用名为Closure的JavaScript功能更多信息(最受欢迎的StackOverflow问题之一).还记得我说过,JavaScript中有两个范围吗?简单地说,一个闭包提供了一个功能范围.这是它的样子:

 (function() { /* your code goes here */ })()
Run Code Online (Sandbox Code Playgroud)

简单地剖析一下,你有一个函数,你的代码包含在里面(/* function goes here */)().实际上这是执行函数所以内部的所有内容都被放入功能范围.然而,它非常强大,这是一个例子

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"
Run Code Online (Sandbox Code Playgroud)

就像我们拥有(类似的东西)一个私人领域.同样,var privateVar将在功能范围内保持"装瓶"状态,并且无法在外部访问.顺便提一下,这是模块模式的一个例子.而不是var closure我可以称之为myModule.这是一个相当简单的看法,但是,这是事情 - 它不会变得更复杂.所有不同的模块归结为非常相似的东西 - 您只是以不同的方式公开数据.然而,这个概念是相似的 - "私人"字段留在里面,而"公共"字段,你可以从其他地方访问.

这很长,但希望能涵盖你需要的基础知识.