Encapsulating in JavaScript, does it exist?

2020-03-07 09:40发布

I have an experience with the C# programming language, but I also have to work with the JS now and it's rather new for me.

I have tried to develop a simple class emulation in JS, like this:

http://jsfiddle.net/T74Zm/

function A( inputValue ) {
    this.Init( inputValue );
    this.Print();
}
A.prototype = {
    value: null,
    Init: function( inputValue ) {
        this.value = inputValue;
    },
    Print: function () {
        console.log( this.value );
    }
}

var obj = new A(40);

I've tried to encapsulate the variable value in A.prototype, but it seems to be by JavaScript specification that all objects are available.

So my questions are:

1). How can I make an encapsulation , which is very close to the static languages with OOP/access modifiers support?

2). How can I emulate some access modifiers in JS, like private for e.g.?

4条回答
Deceive 欺骗
2楼-- · 2020-03-07 10:13

If you want to use inheritance, you cannot (at least AFAIK). For objects with no inheritance chain you can however use closures to get exactly the same effect. The question is if you actually really need properties to actually be completely private.

The closures approach

You can execute a function of which closure contains the variables you want to be private. These private variables do not actually belong to the object, but are only accesible by the methods of the object. For instance:

var getPersonInstance = function (name) {

    // Those are "private" properties
    var myName = name;

    return {
        getName: function () {
            return myName
        },
        setName: function (name) {
            myName = name;
        },
        sayHello = function () {
            alert('hello! my name is ' + myName);
        }
    }    
};

var person = getPersonInstance('Juan');
person.getName(); // Returns Juan
person.myName = 'Don Vito' // This sets the person.myName property, which is not the one in the closure
person.setName('John') // Works
person.sayHello(); // alert hello! my name is John

You can check it out here:

http://jsfiddle.net/MLF7v/1/

If you feel more comfortable with the Constructor function notation, you can do something like this:

(Not tested)

function Person(name) {
    // Anything that is not attached to this will be private
    var myName = name;


    this.getName = function () { return myName;};
    this.setName = function (newName) {myName = newName;};
    this.sayHello = function () {alert('hey there, my name is' + myName);};
}

Which is pretty much the same as above, since the prototype is not used and the methods are copied directly into the object.

However, the closure approach are is memory and time consuming, and the worst: they make use of variables that do not in fact belong to the object you are working with... This is an important "semantic" problem (does this prop belong to me, or not?) that makes inheritance a headache. The reason for this, is that the methods of the extended objects either dont have access to that private pseudo-property (because they are not defined in the superobject closure), or have access to a common private variable from the "superobject" (the method was defined in the superobject and therefore access the closure of the superobject). That is a nonsense.

The "cultural" approach

In my humble opinion, encapsulation does not prevent anybody from messing with your code if he/she really wants to, so I prefer to follow the python philosophy "we are all mature people" which consists of using a convention to say "I would like this property not to be used from the outside". For instance, I prefix the private properties with '_', meaning that they're private, protected, or whatever but just stay away from it. And you don't touch what you should not touch.

This approach, appart from being the simplest and the most efficient, allows you for working with inheritance chains, since the properties are in the object and not confined in the closure.

var Person = function (name) {
    this._name = name;
}
Person.prototype.sayHello = function () {...};
Person.prototype.getName = function () {...};
Person.prototype.setName = function () {...};
查看更多
Emotional °昔
3楼-- · 2020-03-07 10:16

Javascript still does not support encapsulation out of the box. However there is a proposal to make it so.

ECMA TC39 had proposed a private syntax. According to the proposal, in order to make a javascript class field private you need to prefix it with a hash '#'. This is to provide some sort of runtime encapsulation.

Example:

class B {
  #hidden = 0;
  m() {
    return this.#hidden;
  }
}

Chrome supports this since Chrome v74. If this private-syntax becomes a standart we will be able to benefit from runtime encapsulation in javascript.

查看更多
祖国的老花朵
4楼-- · 2020-03-07 10:19

You can use closures to encapsulate variables.

function MyObjectFactory() {
  var obj = {},
    count = 0;

  obj.getCounter = function () {
    return count;
  };

  obj.setCounter = function (val) {
    count = val;
  };

  obj.incrementCounter = function () {
    count++;
  };

  obj.decrementCount = function () {
    count--;
  };

  return obj;  
}

You can also imitate property getters and setters in generic way.

function MyOtherObjectFactory() {
  var obj = {}, props = {};

  obj.prop = function (name) {
    return props[name];
  };

  obj.setProp = function (name, val) {
    props[name] = val;
    // call other functions if you'd like
  };


  // or even better, have a single function that works getter and setter regarding the params
  obj.autoProp = function () {
    if (arguments[1]) {
      // setter
      props[arguments[0]] = arguments[1]; // do check first argument is a hashable value
    } else if (arguments[0]) {
      // make sure we have a key
      // getter
      return props[arguments[0]];
    }
  }
}

PS: Please don't set prototype directly, it breaks the prototype chain.

查看更多
\"骚年 ilove
5楼-- · 2020-03-07 10:30

Encapsulation does not mean the computer is rigorously enforcing that you don't access something. It can be achieved simply by modules and classes not accessing each other's internals. If your program has that property then you are using encapsulation. Or at the very least you are gaining every benefit that encapsulation is said to give - potato, potahto.

If you need help with that you can use documentation and conventions like underscore prefixing so you know more easily what is internal or not.

Think about it - if you did global replace of "private" to "public" and recompiled any of your C# programs, it would function exactly the same. And you are somehow ignoring that even in C# one can access private through .SetAccessible if they so want. Since you seem to be ok with that, what is the problem?

The emulation of "privates" through closures gives you far more and worse problems than it solves which I will list if you are not already convinced by the above.

I will also quote Martin Fowler for some argument of authority:

Access control does not control access

If you have a field that's private it means no other class can get at it. Wrong! If you really want to you can subvert the access control mechanisms in almost any language. Usually the way through is via reflection. The rationale is that debuggers and other system tools often need to see private data, so usually the reflection interfaces allow you to do this.

C++ doesn't have this kind of reflection, but there you can just use direct memory manipulation since C++ is fundamentally open memory.

The point of access control is not to prevent access, but more to signal that the class prefers to keep some things to itself. Using access modifiers, like so many things in programming, is primarily about communication.

http://martinfowler.com/bliki/AccessModifier.html#AccessControlDoesNotControlAccess

查看更多
登录 后发表回答