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

Hide variables within an anonymous JavaScript function but access them using `this`

本文关键字:this 访问 JavaScript 藏匿 函数 变量 隐藏      更新时间:2023-09-26

我将使用以下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. 在匿名函数中隐藏urlwidthheight变量以及init()函数
  3. 仅暴露匿名函数之外的run()函数

然而,我不太喜欢这种方法,因为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对象,我所知道的范围仅限于匿名函数,因为我的编码约定是使用self对象将匿名函数中所有函数共享的变量放在一起。注意,self.url仍然隐藏在匿名函数中,在它之外不可见

理想情况下,我希望使用this.urlthis.width等来代替self.urlself.width等。但我无法找到一个解决方案,使this.urlthis.width等在匿名函数之外不可见。有可能吗?

根据您认为的不可见,您可以使用几种解决方案之一。然而,每种解决方案都有其自身的缺陷。

您必须考虑的是,为什么要在"私有变量"中使用关键字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引擎(这主要意味着IE不小于9),你可以使用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兼容的引擎,并且如果外部代码可以访问App的实例,这不会阻止它们访问或修改urlwidthheight

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

new App().run();

那么new App()创建的实例在其他任何地方都无法访问。这不是真正的隐私,因为引用该实例仍然可以访问其所有属性,但这可能已经足够了。

您可以使用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
    }
}();

请注意,旧的浏览器不支持绑定,但您可以使用polyfill将其添加到不自动提供的浏览器中。

请注意,我不一定认为这段代码是一个好主意,但它确实可以满足您的要求。我个人认为使用self作为变量的显式容器没有错,而使用this可能会使其他人更难理解您的代码(因为他们可能认为App意味着有实例)。

你的应用程序可以转换成这样的东西(它使用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();