简体   繁体   中英

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

I'll use the following JavaScript code to demonstrate what I am trying to do.

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()

( Run this code at: http://jsfiddle.net/zePq9/ )

The above code achieves three things:

  1. Avoids global variables and functions (except the global object App which is intentionally exposed to the global namespace).
  2. Hides the url , width and height variables and the init() function within the anonmyous function.
  3. Exposes only the run() function outside the anonymous function.

However, I do not like this approach much because the init() and run() functions have to work with variables like url , width and height . In a more involved and larger JavaScript code, it is difficult to see the variable url in a function and tell where it is coming from. One has to do a lot of scrolling to answer questions like: Is it a local variable? Is it a global variable? Is it local only to the anonmyous function?

Right now I am solving this problem with the following code.

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()

( Run this code at: http://jsfiddle.net/cXALf/ )

Now, I can clearly see self.url and tell that this variable is coming from the self object, the scope of which I know is limited to the anonmyous function due to my coding convention of using self object to hold together variables shared by all functions within an anonymous function. Note that self.url is still hidden within the anonymous function and is not visible outside it.

Ideally, I would like to use this.url , this.width , etc. instead of self.url , self.width , etc. But I am unable to arrive at a solution that would keep this.url , this.width , etc. invisible from outside the anonmyous function. Is it possible?

Depending on what you consider to be invisible you can use one of several solutions. However, each solution has its own failings.

What you must consider is why you want to use the keyword this in relation to your "private variables". In JavaScript, there is not really a concept of private variables (more on this later). Anything that is accessible from one context is accessible from another.

Bind to a closure-bound variable

This solution is closest to the solution you had originally, but using Function.prototype.bind to force the context to be a closure-internal object.

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
  };
}

The downfall of this approach is that every "member" function must be .bind() to self , or else the context of those functions will not be the closure-internal self that you have declared. This approach is also not very Javascript-y , due to having to create every method and property in what amounts to the constructor. This obviates the prototypal inheritance system that JavaScript has.

Use ES5 property definitions

If you are willing to stick to only ES5-compliant JS engines (This mostly means no IE less than 9), you can use ES5's Object.defineProperty() to hide properties of an object. You will still be able to access them from other contexts, but at least they won't show up in enumerations of the object properties.

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();

The advantage to this technique is that you are able to exactly control which properties are enumerable ('visible' to enumeration like for...in or Object.keys() ), and which ones are not. In addition, the construction of the App object takes place in its actual constructor, and the implementation of run() can now be overridden by child classes. The downside is that this requires an ES5 compatible engine, and that this does not prevent outside code from accessing or modifying url , width , or height , if they have access to the instance of App .

The only way to get true privacy in JavaScript is to declare variables inside a scope, and offer accessors that are also defined in the same scope. The scoping mechanism in JavaScript simply makes references to those variables impossible from outside the scope. Similarly, anonymous instances can do nearly the same thing... If you never use an instance of App beyond the only call to run , like:

new App().run();

Then the instance created by new App() will not be accessible anywhere else. This is not true privacy in that having reference to that instance still allows you access to all its properties, but it may be enough.

You can use bind to permanently set the this value of each function:

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
    }
}();

Note the older browser don't support bind, but you can use a polyfill to add it to browsers that don't supply it automatically.

Note that I don't necessarily think this code is a good idea , but it does do exactly what you want. I personally think there's nothing wrong with using self as an explicit container for your variables, and using this instead may make your code harder for others to understand (as they may assume App is meant to have instances).

Your app can be converted to something like this (it uses this and has no closures):

EDIT this solution ignores the hidden part, I skipped over the first part of the question. IMO true variable hiding is not worth the effort and overhead in JS. This code is leaner and has less overhead than others but no hiding.

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();

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM