隐藏匿名JavaScript函数中的变量,但使用`this`访问它们

时间:2013-07-31 14:47:33

标签: javascript oop encapsulation

我将使用以下JavaScript代码来演示我要做的事情。

var App = function() {

    var url
    var width
    var height

    function init()
    {
        url = window.location.href
        width = document.body.clientWidth
        height = document.body.clientHeight
    }

    function run()
    {
        init()
        alert(url + ' - ' + width + 'x' + height)
    }

    return {
        run: run
    }
}()

App.run()

(运行此代码:http://jsfiddle.net/zePq9/

上面的代码实现了三件事:

  1. 避免全局变量和函数(有意暴露给全局命名空间的全局对象App除外)。
  2. 在anonmyous函数中隐藏urlwidthheight变量以及init()函数。
  3. 仅在匿名函数之外公开run()函数。
  4. 但是,我不太喜欢这种方法,因为init()run()函数必须使用urlwidthheight等变量。在更复杂和更大的JavaScript代码中,很难在函数中看到变量url并告诉它来自何处。人们必须做很多滚动来回答这样的问题:它是一个局部变量吗?它是一个全局变量吗?它只是局部的无功功能吗?

    目前我正在使用以下代码解决此问题。

    var App = function() {
    
        var self = {
            url: '',
            width: 0,
            height: 0
        }
    
        function init()
        {
            self.url = window.location.href
            self.width = document.body.clientWidth
            self.height = document.body.clientHeight
        }
    
        function run()
        {
            init()
            alert(self.url + ' - ' + self.width + 'x' + self.height)
        }
    
        return {
            run: run
        }
    }()
    
    App.run()
    

    (运行此代码:http://jsfiddle.net/cXALf/

    现在,我可以清楚地看到self.url并告诉我这个变量来自self对象,由于我使用{的编码约定,我所知道的范围仅限于无穷大函数{1}}对象将匿名函数中所有函数共享的变量保存在一起。请注意,self仍隐藏在匿名函数中,并且在其外部不可见。

    理想情况下,我想使用self.urlthis.url等代替this.widthself.url等。但我无法找到解决方案会保持self.widththis.url等不受异常功能的影响。有可能吗?

3 个答案:

答案 0 :(得分:2)

根据您认为不可见的内容,您可以使用多种解决方案之一。但是,每种解决方案都有其自身的缺陷。

您必须考虑的是为什么要使用与“私人变量”相关的关键字this。在JavaScript中,并没有真正的私有变量概念(稍后会详细介绍)。任何可从一个上下文访问的内容都可以从另一个上下文访问。

绑定到闭包变量

此解决方案最接近您最初的解决方案,但使用Function.prototype.bind强制上下文成为闭包内部对象。

function App() {
  var self = {
    url: '',
    width: 0,
    height: 0
  };

  var init = function() {
    this.url = window.location.href;
    this.width = document.body.clientWidth;
    this.height = document.body.clientHeight;
  }.bind(self);

  var run = function() {
    init();
    console.log(this.url + ' - ' + this.width + 'x' + this.height);
  }.bind(self);

  return {
    run: run
  };
}

这种方法的缺点是每个“成员”函数必须是.bind()self,否则这些函数的上下文不会是你的内部self。宣布了。这种方法也不是非常 Javascript-y ,因为必须创建构造函数的每个方法和属性。这避免了JavaScript具有的原型继承系统。

使用ES5属性定义

如果您愿意坚持只使用符合ES5标准的JS引擎(这通常意味着没有小于9的IE),您可以使用ES5的Object.defineProperty()来隐藏对象的属性。您仍然可以从其他上下文访问它们,但至少它们不会显示在对象属性的枚举中。

function App() {
  this.url = window.location.href;
  this.width = document.body.clientWidth;
  this.height = document.body.clientHeight;
}

Object.defineProperties(App.prototype, {
  url: {
    enumerable: false,
    configurable: true,
    writable: true,
    value: ""
  },
  width: {
    enumerable: false,
    configurable: true,
    writable: true,
    value: 0
  },
  height: {
    enumerable: false,
    configurable: true,
    writable: true,
    value: 0
  }
});

App.prototype.run = function() {
  console.log(this.url + ' - ' + this.width + 'x' + this.height);
};

new App().run();

这种技术的优势在于,您可以精确控制哪些属性是可枚举的(for...inObject.keys()等枚举“可见”,哪些不可。此外,App对象的构造发生在它的实际构造函数中,run()的实现现在可以被子类覆盖。缺点是这需要ES5兼容引擎,并且这不会阻止外部代码访问或修改urlwidthheight,如果他们有权访问实例App

在JavaScript中获得真正的隐私的唯一方法是在范围内声明变量,并提供也在同一范围内定义的访问器。 JavaScript中的作用域机制只是在范围之外无法引用这些变量。类似地,匿名实例可以做几乎相同的事情......如果你从未使用App的实例超出对run的唯一调用,例如:

new App().run();

然后,new App()创建的实例将无法在其他任何位置访问。这不是真正的隐私,因为引用该实例仍允许您访问其所有属性,但这可能就足够了。

答案 1 :(得分:1)

您可以使用bind永久设置每个功能的this值:

var App = function() {

    var self = {
        url: '',
        width: 0,
        height: 0
    };

    var init = function()
    {
        this.url = window.location.href;
        this.width = document.body.clientWidth;
        this.height = document.body.clientHeight;
    }.bind(self);

    var run = function()
    {
        init();
        alert(self.url + ' - ' + self.width + 'x' + self.height);
    }.bind(self);

    return {
        run: run
    }
}();

请注意旧版浏览器不支持bind,但您可以use a polyfill将其添加到不自动提供的浏览器中。

请注意,我不一定认为此代码是好主意,但它确实能够完全符合您的要求。我个人认为使用self作为变量的显式容器没有任何问题,而使用this可能会使您的代码更难以理解(因为他们可能认为App是有实例)。

答案 2 :(得分:-1)

您的应用可以转换为类似的内容(它使用this并且没有关闭):

编辑此解决方案忽略隐藏的部分,我跳过了问题的第一部分。 IMO真正的变量隐藏不值得JS的努力和开销。这段代码比其他代码更精简,开销更少,但没有隐藏。

function App() {
    this.init();
}

App.prototype = {
    url: '',
    width: 0,
    height: 0,

    init: function() {
        this.url = window.location.href
        this.width = document.body.clientWidth
        this.height = document.body.clientHeight
    },

    run: function() {
        console.log(this.url + ' - ' + this.width + 'x' + this.height)
    }
}

new App().run();