Доступ к частным переменным-членам из прототипов

Есть ли способ сделать переменные "private" (определенные в конструкторе) доступными для прототипов определенных методов?

TestClass = function(){
    var privateField = "hello";
    this.nonProtoHello = function(){alert(privateField)};
};
TestClass.prototype.prototypeHello = function(){alert(privateField)};

Это работает:

t.nonProtoHello()

Но это не делает:

t.prototypeHello()

Я использовал для определения моих методов внутри конструктора, но отступаю от этого по нескольким причинам.

Ответ 1

Нет, нет способа сделать это. Это, по существу, будет определяться в обратном порядке.

Методы, определенные внутри конструктора, имеют доступ к закрытым переменным, поскольку все функции имеют доступ к области, в которой они были определены.

Методы, определенные в прототипе, не определены в рамках конструктора и не будут иметь доступ к локальным переменным-конструкторам.

У вас все еще есть частные переменные, но если вы хотите, чтобы методы, определенные на прототипе, имели к ним доступ, вы должны определить геттеры и сеттеры на объекте this, которые будут использовать методы прототипа (вместе со всем остальным) доступ к. Например:

function Person(name, secret) {
    // public
    this.name = name;

    // private
    var secret = secret;

    // public methods have access to private members
    this.setSecret = function(s) {
        secret = s;
    }

    this.getSecret = function() {
        return secret;
    }
}

// Must use getters/setters 
Person.prototype.spillSecret = function() { alert(this.getSecret()); };

Ответ 2

Обновление: с ES6 существует лучший способ:

Короче говоря, вы можете использовать новый Symbol для создания частных полей.
Здесь большое описание: https://curiosity-driven.org/private-properties-in-javascript

Пример:

var Person = (function() {
    // Only Person can access nameSymbol
    var nameSymbol = Symbol('name');

    function Person(name) {
        this[nameSymbol] = name;
    }

    Person.prototype.getName = function() {
        return this[nameSymbol];
    };

    return Person;
}());

Для всех современных браузеров с ES5:

Вы можете использовать только Closures

Самый простой способ построения объектов - вообще избегать прототипального наследования. Просто определите частные переменные и публичные функции в закрытии, и все общедоступные методы будут иметь частный доступ к переменным.

Или вы можете использовать только прототипы

В JavaScript прототипное наследование - это в первую очередь оптимизация. Он позволяет нескольким экземплярам совместно использовать прототипы, а не каждый экземпляр, имеющий свои собственные методы.
Недостатком является то, что this - это только вещь, которая различается при каждом вызове прототипной функции.
Поэтому любые частные поля должны быть доступны через this, что означает, что они будут публичными. Поэтому мы просто придерживаемся соглашений об именах для полей _private.

Не мешайте смешиванию закрытий с прототипами

Я думаю, что вы не должны смешивать замыкающие переменные с прототипами. Вы должны использовать тот или иной.

Когда вы используете закрытие для доступа к частной переменной, методы прототипа не могут получить доступ к переменной. Таким образом, вы должны открыть закрытие на this, что означает, что вы публично раскрываете его так или иначе. Этого очень мало для достижения этого.

Какой из них выбрать?

Для действительно простых объектов просто используйте простой объект с закрытием.

Если вам требуется прототипное наследование - для наследования, производительности и т.д. - тогда придерживайтесь соглашения об именах "_private" и не заморачивайтесь с закрытием.

Я не понимаю, почему разработчики JS стараются сделать так, чтобы поля были действительно частными.

Ответ 3

Когда я прочитал это, это звучало как сложная задача, поэтому я решил найти способ. Я придумал CRAAAAZY, но он полностью работает.

Сначала я попытался определить класс в непосредственной функции, чтобы у вас был доступ к некоторым частным свойствам этой функции. Это работает и позволяет вам получать некоторые личные данные, однако, если вы попытаетесь установить личные данные, вы скоро обнаружите, что все объекты будут иметь одинаковое значение.

var SharedPrivateClass = (function() { // use immediate function
    // our private data
    var private = "Default";

    // create the constructor
    function SharedPrivateClass() {}

    // add to the prototype
    SharedPrivateClass.prototype.getPrivate = function() {
        // It has access to private vars from the immediate function!
        return private;
    };

    SharedPrivateClass.prototype.setPrivate = function(value) {
        private = value;
    };

    return SharedPrivateClass;
})();

var a = new SharedPrivateClass();
console.log("a:", a.getPrivate()); // "a: Default"

var b = new SharedPrivateClass();
console.log("b:", b.getPrivate()); // "b: Default"

a.setPrivate("foo"); // a Sets private to "foo"
console.log("a:", a.getPrivate()); // "a: foo"
console.log("b:", b.getPrivate()); // oh no, b.getPrivate() is "foo"!

console.log(a.hasOwnProperty("getPrivate")); // false. belongs to the prototype
console.log(a.private); // undefined

// getPrivate() is only created once and instanceof still works
console.log(a.getPrivate === b.getPrivate);
console.log(a instanceof SharedPrivateClass);
console.log(b instanceof SharedPrivateClass);

Ответ 4

см. страница Дуга Крокфорда в этом. Вы должны сделать это косвенно с чем-то, что может получить доступ к области частной переменной.

другой пример:

Incrementer = function(init) {
  var counter = init || 0;  // "counter" is a private variable
  this._increment = function() { return counter++; }
  this._set = function(x) { counter = x; }
}
Incrementer.prototype.increment = function() { return this._increment(); }
Incrementer.prototype.set = function(x) { return this._set(x); }

прецедент:

js>i = new Incrementer(100);
[object Object]
js>i.increment()
100
js>i.increment()
101
js>i.increment()
102
js>i.increment()
103
js>i.set(-44)
js>i.increment()
-44
js>i.increment()
-43
js>i.increment()
-42

Ответ 5

Я полагаю, что было бы неплохо описать "наличие назначения прототипа в конструкторе" как анти-шаблон Javascript. Думаю об этом. Это слишком рискованно.

То, что вы на самом деле делаете при создании второго объекта (т.е. b), переопределяет эту функцию прототипа для всех объектов, которые используют этот прототип. Это эффективно reset значение для объекта a в вашем примере. Он будет работать, если вам нужна общая переменная, и если вам приходится создавать все экземпляры объектов спереди, но это слишком рискованно.

Я обнаружил ошибку в некотором Javascript, над которым я работал недавно, что было связано с этим точным анти-шаблоном. Он пытался установить обработчик перетаскивания на конкретный объект, созданный, но вместо этого выполнял его для всех экземпляров. Нехорошо.

Решение Doug Crockford является лучшим.

Ответ 6

@Kai

Это не сработает. Если вы делаете

var t2 = new TestClass();

тогда t2.prototypeHello будет обращаться к частному сектору t.

@AnglesCrimes

Пример кода работает нормально, но на самом деле он создает "статический" частный элемент, общий для всех экземпляров. Возможно, это не решение, которое искали морганкоды.

До сих пор я не нашел простой и чистый способ сделать это, не введя частные хэш-функции и дополнительные функции очистки. Частная функция-член может быть смоделирована в определенной степени:

(function() {
    function Foo() { ... }
    Foo.prototype.bar = function() {
       privateFoo.call(this, blah);
    };
    function privateFoo(blah) { 
        // scoped to the instance by passing this to call 
    }

    window.Foo = Foo;
}());

Ответ 7

Да, это возможно. Дизайн PPF просто решает это.

PPF обозначает функции Private Prototype. Основной PPF решает эти проблемы:

  • Функции прототипа получают доступ к частным данным экземпляра.
  • Функции прототипа могут быть закрытыми.

Для первого просто:

  • Поместите все частные переменные экземпляра, которые вы хотите получить из функций прототипа внутри отдельного контейнера данных, и
  • Передайте ссылку на контейнер данных ко всем функциям прототипа в качестве параметра.

Это так просто. Например:

// Helper class to store private data.
function Data() {};

// Object constructor
function Point(x, y)
{
  // container for private vars: all private vars go here
  // we want x, y be changeable via methods only
  var data = new Data;
  data.x = x;
  data.y = y;

  ...
}

// Prototype functions now have access to private instance data
Point.prototype.getX = function(data)
{
  return data.x;
}

Point.prototype.getY = function(data)
{
  return data.y;
}

...

Подробнее читайте здесь:

Шаблон проектирования PPF

Ответ 8

Фактически вы можете добиться этого, используя Подтверждение доступа:

(function(key, global) {
  // Creates a private data accessor function.
  function _(pData) {
    return function(aKey) {
      return aKey === key && pData;
    };
  }

  // Private data accessor verifier.  Verifies by making sure that the string
  // version of the function looks normal and that the toString function hasn't
  // been modified.  NOTE:  Verification can be duped if the rogue code replaces
  // Function.prototype.toString before this closure executes.
  function $(me) {
    if(me._ + '' == _asString && me._.toString === _toString) {
      return me._(key);
    }
  }
  var _asString = _({}) + '', _toString = _.toString;

  // Creates a Person class.
  var PersonPrototype = (global.Person = function(firstName, lastName) {
    this._ = _({
      firstName : firstName,
      lastName : lastName
    });
  }).prototype;
  PersonPrototype.getName = function() {
    var pData = $(this);
    return pData.firstName + ' ' + pData.lastName;
  };
  PersonPrototype.setFirstName = function(firstName) {
    var pData = $(this);
    pData.firstName = firstName;
    return this;
  };
  PersonPrototype.setLastName = function(lastName) {
    var pData = $(this);
    pData.lastName = lastName;
    return this;
  };
})({}, this);

var chris = new Person('Chris', 'West');
alert(chris.setFirstName('Christopher').setLastName('Webber').getName());

Этот пример исходит из моего сообщения о Прототипные функции и частные данные и более подробно объясняется там.

Ответ 9

В текущем JavaScript я уверен, что есть один и только один способ иметь личное состояние, доступное из прототипа, без добавления чего-либо public до this. Ответ заключается в использовании шаблона "слабая карта".

Подводя итог: класс Person имеет одну слабую карту, где ключи являются экземплярами Person, а значения - это простые объекты, которые используются для частного хранилища.

Вот полнофункциональный пример: (играйте в http://jsfiddle.net/ScottRippey/BLNVr/)

var Person = (function() {
    var _ = weakMap();
    // Now, _(this) returns an object, used for private storage.
    var Person = function(first, last) {
        // Assign private storage:
        _(this).firstName = first;
        _(this).lastName = last;
    }
    Person.prototype = {
        fullName: function() {
            // Retrieve private storage:
            return _(this).firstName + _(this).lastName;
        },
        firstName: function() {
            return _(this).firstName;
        },
        destroy: function() {
            // Free up the private storage:
            _(this, true);
        }
    };
    return Person;
})();

function weakMap() {
    var instances=[], values=[];
    return function(instance, destroy) {
        var index = instances.indexOf(instance);
        if (destroy) {
            // Delete the private state:
            instances.splice(index, 1);
            return values.splice(index, 1)[0];
        } else if (index === -1) {
            // Create the private state:
            instances.push(instance);
            values.push({});
            return values[values.length - 1];
        } else {
            // Return the private state:
            return values[index];
        }
    };
}

Как я уже сказал, это действительно единственный способ достичь всех трех частей.

Однако есть два оговорки. Во-первых, это стоит затрат - каждый раз, когда вы обращаетесь к приватным данным, это операция O(n), где n - количество экземпляров. Поэтому вы не захотите это делать, если у вас есть большое количество экземпляров. Во-вторых, когда вы закончите с экземпляром, вы должны вызвать destroy; в противном случае экземпляр и данные не будут собраны в мусор, и вы получите утечку памяти.

И вот почему мой оригинальный ответ "Не должен" , я бы хотел придерживаться.

Ответ 10

Более простой способ заключается в использовании методов bind и call.

Установив частные переменные для объекта, вы можете использовать эту область видимости объекта.

Пример

function TestClass (value) {
    // The private value(s)
    var _private = {
        value: value
    };

    // `bind` creates a copy of `getValue` when the object is instantiated
    this.getValue = TestClass.prototype.getValue.bind(_private);

    // Use `call` in another function if the prototype method will possibly change
    this.getValueDynamic = function() {
        return TestClass.prototype.getValue.call(_private);
    };
};

TestClass.prototype.getValue = function() {
    return this.value;
};

Этот метод не лишен недостатков. Поскольку контекст области эффективно переопределяется, у вас нет доступа за пределами объекта _private. Однако это не невозможно, хотя все же предоставить доступ к области объектов экземпляра. Вы можете передать контекст объекта (this) в качестве второго аргумента bind или call, чтобы все еще иметь доступ к его общедоступным значениям в функции прототипа.

Доступ к общедоступным значениям

function TestClass (value) {
    var _private = {
        value: value
    };

    this.message = "Hello, ";

    this.getMessage = TestClass.prototype.getMessage.bind(_private, this);

}

TestClass.prototype.getMessage = function(_public) {

    // Can still access passed in arguments
    // e.g. – test.getValues('foo'), 'foo' is the 2nd argument to the method
    console.log([].slice.call(arguments, 1));
    return _public.message + this.value;
};

var test = new TestClass("World");
test.getMessage(1, 2, 3); // [1, 2, 3]         (console.log)
                          // => "Hello, World" (return value)

test.message = "Greetings, ";
test.getMessage(); // []                    (console.log)
                   // => "Greetings, World" (return value)

Ответ 11

Попробуйте!

    function Potatoe(size) {
    var _image = new Image();
    _image.src = 'potatoe_'+size+'.png';
    function getImage() {
        if (getImage.caller == null || getImage.caller.owner != Potatoe.prototype)
            throw new Error('This is a private property.');
        return _image;
    }
    Object.defineProperty(this,'image',{
        configurable: false,
        enumerable: false,
        get : getImage          
    });
    Object.defineProperty(this,'size',{
        writable: false,
        configurable: false,
        enumerable: true,
        value : size            
    });
}
Potatoe.prototype.draw = function(ctx,x,y) {
    //ctx.drawImage(this.image,x,y);
    console.log(this.image);
}
Potatoe.prototype.draw.owner = Potatoe.prototype;

var pot = new Potatoe(32);
console.log('Potatoe size: '+pot.size);
try {
    console.log('Potatoe image: '+pot.image);
} catch(e) {
    console.log('Oops: '+e);
}
pot.draw();

Ответ 12

Вот что я придумал.

(function () {
    var staticVar = 0;
    var yrObj = function () {
        var private = {"a":1,"b":2};
        var MyObj = function () {
            private.a += staticVar;
            staticVar++;
        };
        MyObj.prototype = {
            "test" : function () {
                console.log(private.a);
            }
        };

        return new MyObj;
    };
    window.YrObj = yrObj;
}());

var obj1 = new YrObj;
var obj2 = new YrObj;
obj1.test(); // 1
obj2.test(); // 2

Основная проблема с этой реализацией заключается в том, что она переопределяет прототипы при каждом instanciation.

Ответ 13

Существует очень простой способ сделать это

function SharedPrivate(){
  var private = "secret";
  this.constructor.prototype.getP = function(){return private}
  this.constructor.prototype.setP = function(v){ private = v;}
}

var o1 = new SharedPrivate();
var o2 = new SharedPrivate();

console.log(o1.getP()); // secret
console.log(o2.getP()); // secret
o1.setP("Pentax Full Frame K1 is on sale..!");
console.log(o1.getP()); // Pentax Full Frame K1 is on sale..!
console.log(o2.getP()); // Pentax Full Frame K1 is on sale..!
o2.setP("And it only for $1,795._");
console.log(o1.getP()); // And it only for $1,795._

Прототипы JavaScript являются золотыми.

Ответ 14

Я опаздываю на вечеринку, но я думаю, что могу внести свой вклад. Здесь проверьте это:

// 1. Create closure
var SomeClass = function() {
  // 2. Create `key` inside a closure
  var key = {};
  // Function to create private storage
  var private = function() {
    var obj = {};
    // return Function to access private storage using `key`
    return function(testkey) {
      if(key === testkey) return obj;
      // If `key` is wrong, then storage cannot be accessed
      console.error('Cannot access private properties');
      return undefined;
    };
  };
  var SomeClass = function() {
    // 3. Create private storage
    this._ = private();
    // 4. Access private storage using the `key`
    this._(key).priv_prop = 200;
  };
  SomeClass.prototype.test = function() {
    console.log(this._(key).priv_prop); // Using property from prototype
  };
  return SomeClass;
}();

// Can access private property from within prototype
var instance = new SomeClass();
instance.test(); // `200` logged

// Cannot access private property from outside of the closure
var wrong_key = {};
instance._(wrong_key); // undefined; error logged

Ответ 15

Вот что я придумал, пытаясь найти самое простое решение этой проблемы, возможно, это может быть полезно кому-то. Я новичок в javascript, поэтому могут возникнуть некоторые проблемы с кодом.

// pseudo-class definition scope
(function () {

    // this is used to identify 'friend' functions defined within this scope,
    // while not being able to forge valid parameter for GetContext() 
    // to gain 'private' access from outside
    var _scope = new (function () { })();
    // -----------------------------------------------------------------

    // pseudo-class definition
    this.Something = function (x) {

        // 'private' members are wrapped into context object,
        // it can be also created with a function
        var _ctx = Object.seal({

            // actual private members
            Name: null,
            Number: null,

            Somefunc: function () {
                console.log('Something(' + this.Name + ').Somefunc(): number = ' + this.Number);
            }
        });
        // -----------------------------------------------------------------

        // function below needs to be defined in every class
        // to allow limited access from prototype
        this.GetContext = function (scope) {

            if (scope !== _scope) throw 'access';
            return _ctx;
        }
        // -----------------------------------------------------------------

        {
            // initialization code, if any
            _ctx.Name = (x !== 'undefined') ? x : 'default';
            _ctx.Number = 0;

            Object.freeze(this);
        }
    }
    // -----------------------------------------------------------------

    // prototype is defined only once
    this.Something.prototype = Object.freeze({

        // public accessors for 'private' field
        get Number() { return this.GetContext(_scope).Number; },
        set Number(v) { this.GetContext(_scope).Number = v; },

        // public function making use of some private fields
        Test: function () {

            var _ctx = this.GetContext(_scope);
            // access 'private' field
            console.log('Something(' + _ctx.Name + ').Test(): ' + _ctx.Number);
            // call 'private' func
            _ctx.Somefunc();
        }
    });
    // -----------------------------------------------------------------

    // wrap is used to hide _scope value and group definitions
}).call(this);

function _A(cond) { if (cond !== true) throw new Error('assert failed'); }
// -----------------------------------------------------------------

function test_smth() {

    console.clear();

    var smth1 = new Something('first'),
      smth2 = new Something('second');

    //_A(false);
    _A(smth1.Test === smth2.Test);

    smth1.Number = 3;
    smth2.Number = 5;
    console.log('smth1.Number: ' + smth1.Number + ', smth2.Number: ' + smth2.Number);

    smth1.Number = 2;
    smth2.Number = 6;

    smth1.Test();
    smth2.Test();

    try {
        var ctx = smth1.GetContext();
    } catch (err) {
        console.log('error: ' + err);
    }
}

test_smth();

Ответ 16

Сегодня я столкнулся с одним и тем же вопросом, и после разработки первого ответа Скотта Риппи я придумал очень простое решение (IMHO), которое одновременно совместимо с ES5 и эффективным, это также безопасно для имени (используя _private кажется небезопасным).

/*jslint white: true, plusplus: true */

 /*global console */

var a, TestClass = (function(){
    "use strict";
    function PrefixedCounter (prefix) {
        var counter = 0;
        this.count = function () {
            return prefix + (++counter);
        };
    }
    var TestClass = (function(){
        var cls, pc = new PrefixedCounter("_TestClass_priv_")
        , privateField = pc.count()
        ;
        cls = function(){
            this[privateField] = "hello";
            this.nonProtoHello = function(){
                console.log(this[privateField]);
            };
        };
        cls.prototype.prototypeHello = function(){
            console.log(this[privateField]);
        };
        return cls;
    }());
    return TestClass;
}());

a = new TestClass();
a.nonProtoHello();
a.prototypeHello();

Протестировано с помощью ringojs и nodejs. Я очень хочу прочитать ваше мнение.

Ответ 17

var getParams = function(_func) {
  res = _func.toString().split('function (')[1].split(')')[0].split(',')
  return res
}

function TestClass(){

  var private = {hidden: 'secret'}
  //clever magic accessor thing goes here
  if ( !(this instanceof arguments.callee) ) {
    for (var key in arguments) {
      if (typeof arguments[key] == 'function') {
        var keys = getParams(arguments[key])
        var params = []
        for (var i = 0; i <= keys.length; i++) {
          if (private[keys[i]] != undefined) {
            params.push(private[keys[i]])
          }
        }
        arguments[key].apply(null,params)
      }
    }
  }
}


TestClass.prototype.test = function(){
  var _hidden; //variable I want to get
  TestClass(function(hidden) {_hidden = hidden}) //invoke magic to get
};

new TestClass().test()

Как это? Использование частного доступа. Только позволяет вам получать переменные, но не устанавливать их, зависит от варианта использования.

Ответ 18

У меня есть одно решение, но я не уверен, что это без ошибок.

Чтобы он работал, вы должны использовать следующую структуру:

  • Использовать 1 частный объект, содержащий все частные переменные.
  • Использовать 1 функцию экземпляра.
  • Применить замыкание к конструктору и всем функциям прототипа.
  • Любой созданный экземпляр выполняется вне определенного ограничения.

Вот код:

var TestClass = 
(function () {
    // difficult to be guessed.
    var hash = Math.round(Math.random() * Math.pow(10, 13) + + new Date());
    var TestClass = function () {
        var privateFields = {
            field1: 1,
            field2: 2
        };
        this.getPrivateFields = function (hashed) {
            if(hashed !== hash) {
                throw "Cannot access private fields outside of object.";
                // or return null;
            }
            return privateFields;
        };
    };

    TestClass.prototype.prototypeHello = function () {
        var privateFields = this.getPrivateFields(hash);
        privateFields.field1 = Math.round(Math.random() * 100);
        privateFields.field2 = Math.round(Math.random() * 100);
    };

    TestClass.prototype.logField1 = function () {
        var privateFields = this.getPrivateFields(hash);
        console.log(privateFields.field1);
    };

    TestClass.prototype.logField2 = function () {
        var privateFields = this.getPrivateFields(hash);
        console.log(privateFields.field2);
    };

    return TestClass;
})();

Как это работает, так это то, что он предоставляет функцию экземпляра "this.getPrivateFields" для доступа к частному объекту privateFields, но эта функция возвращает только объект privateFields внутри основного ограничения (также функции прототипа, this.getPrivateFields "должны быть определены внутри этого закрытия).

Хеш, созданный во время выполнения, и трудно угадать, используется в качестве параметров, чтобы убедиться, что даже если "getPrivateFields" вызывается за пределами области закрытия, он не вернет объект privateFields.

Недостаток заключается в том, что мы не можем расширять TestClass с помощью дополнительных прототипов вне закрытия.

Вот несколько тестовых кодов:

var t1 = new TestClass();
console.log('Initial t1 field1 is: ');
t1.logField1();
console.log('Initial t1 field2 is: ');
t1.logField2();
t1.prototypeHello();
console.log('t1 field1 is now: ');
t1.logField1();
console.log('t1 field2 is now: ');
t1.logField2();
var t2 = new TestClass();
console.log('Initial t2 field1 is: ');
t2.logField1();
console.log('Initial t2 field2 is: ');
t2.logField2();
t2.prototypeHello();
console.log('t2 field1 is now: ');
t2.logField1();
console.log('t2 field2 is now: ');
t2.logField2();

console.log('t1 field1 stays: ');
t1.logField1();
console.log('t1 field2 stays: ');
t1.logField2();

t1.getPrivateFields(11233);

EDIT: Используя этот метод, также можно "определить" частные функции.

TestClass.prototype.privateFunction = function (hashed) {
    if(hashed !== hash) {
        throw "Cannot access private function.";
    }
};

TestClass.prototype.prototypeHello = function () {
    this.privateFunction(hash);
};

Ответ 19

Играла с этим сегодня, и это было единственное решение, которое я мог найти, не используя Символы. Самое лучшее в этом - все это может быть полностью закрыто.

Решение основано на встроенном загрузчике модулей, который в основном становится посредником для кэша частного хранилища (с использованием слабой карты).

   const loader = (function() {
        function ModuleLoader() {}

    //Static, accessible only if truly needed through obj.constructor.modules
    //Can also be made completely private by removing the ModuleLoader prefix.
    ModuleLoader.modulesLoaded = 0;
    ModuleLoader.modules = {}

    ModuleLoader.prototype.define = function(moduleName, dModule) {
        if (moduleName in ModuleLoader.modules) throw new Error('Error, duplicate module');

        const module = ModuleLoader.modules[moduleName] = {}

        module.context = {
            __moduleName: moduleName,
            exports: {}
        }

        //Weak map with instance as the key, when the created instance is garbage collected or goes out of scope this will be cleaned up.
        module._private = {
            private_sections: new WeakMap(),
            instances: []
        };

        function private(action, instance) {
            switch (action) {
                case "create":
                    if (module._private.private_sections.has(instance)) throw new Error('Cannot create private store twice on the same instance! check calls to create.')
                    module._private.instances.push(instance);
                    module._private.private_sections.set(instance, {});
                    break;
                case "delete":
                    const index = module._private.instances.indexOf(instance);
                    if (index == -1) throw new Error('Invalid state');
                    module._private.instances.slice(index, 1);
                    return module._private.private_sections.delete(instance);
                    break;
                case "get":
                    return module._private.private_sections.get(instance);
                    break;
                default:
                    throw new Error('Invalid action');
                    break;
            }
        }

        dModule.call(module.context, private);
        ModuleLoader.modulesLoaded++;
    }

    ModuleLoader.prototype.remove = function(moduleName) {
        if (!moduleName in (ModuleLoader.modules)) return;

        /*
            Clean up as best we can.
        */
        const module = ModuleLoader.modules[moduleName];
        module.context.__moduleName = null;
        module.context.exports = null;
        module.cotext = null;
        module._private.instances.forEach(function(instance) { module._private.private_sections.delete(instance) });
        for (let i = 0; i < module._private.instances.length; i++) {
            module._private.instances[i] = undefined;
        }
        module._private.instances = undefined;
        module._private = null;
        delete ModuleLoader.modules[moduleName];
        ModuleLoader.modulesLoaded -= 1;
    }


    ModuleLoader.prototype.require = function(moduleName) {
        if (!(moduleName in ModuleLoader.modules)) throw new Error('Module does not exist');

        return ModuleLoader.modules[moduleName].context.exports;
    }



     return new ModuleLoader();
    })();

    loader.define('MyModule', function(private_store) {
        function MyClass() {
            //Creates the private storage facility. Called once in constructor.
            private_store("create", this);


            //Retrieve the private storage object from the storage facility.
            private_store("get", this).no = 1;
        }

        MyClass.prototype.incrementPrivateVar = function() {
            private_store("get", this).no += 1;
        }

        MyClass.prototype.getPrivateVar = function() {
            return private_store("get", this).no;
        }

        this.exports = MyClass;
    })

    //Get whatever is exported from MyModule
    const MyClass = loader.require('MyModule');

    //Create a new instance of `MyClass`
    const myClass = new MyClass();

    //Create another instance of `MyClass`
    const myClass2 = new MyClass();

    //print out current private vars
    console.log('pVar = ' + myClass.getPrivateVar())
    console.log('pVar2 = ' + myClass2.getPrivateVar())

    //Increment it
    myClass.incrementPrivateVar()

    //Print out to see if one affected the other or shared
    console.log('pVar after increment = ' + myClass.getPrivateVar())
    console.log('pVar after increment on other class = ' + myClass2.getPrivateVar())

    //Clean up.
    loader.remove('MyModule')

Ответ 20

Я просто знаком с javascripting (мне нужно познакомиться с ним). Я наткнулся на тот же вопрос. На данный момент я бы пошел на небольшую облегченную оболочку. Таким образом, каждый новый экземпляр будет только переопределять оболочку.

Я думаю, что это должно быть примерно так:

// your class
function TestClass() {

    // your field
    this.privateField = "hello";

    // create a local reference to this, 
    // because 'this' in the wrapper function will not be a TestClass
    var self = this;

    // implement the wrapper function and pass the 'self' to the static version
    this.showHello = () => TestClass.showHello(self);
}

// implement the 'big/complex' functionallity static. The wrapper will pass the instance
TestClass.showHello = function(self) {
    alert(self.privateField); 
}

Использование:

var t = new TestClass();
t.showHello();

Вы даже можете вызвать статический метод:

TestClass.showHello(t);

Ответ 21

Я разработал модуль, который помогает вам использовать ограничение доступа в классе JavaScript под названием Capsulable. (Частный и защищенный статический)

Если вы заинтересованы, проверьте пакет ниже. https://github.com/hmmhmmhm/capsulable

const Capsulable = require('capsulable')
const Field = Capsulable()

class A {
    constructor(_field){
        // Configure data fields.
        Field(this, _field)

        // The code below provides access to
        // the data fields when creating
        // functions within the class.
        Field(this).private
        Field(this).protected
        Field(this).protectedStatic
    }
}

module.exports = A

Ответ 22

Я знаю, что прошло более 1 десятилетия с тех пор, как об этом спросили, но я просто размышлял над этим n-й раз в своей жизни программиста и нашел возможное решение, которое мне пока неизвестно, понравится ли мне еще, Я не видел этой методологии, документированной ранее, поэтому я назову ее "паттерн частного/публичного доллара" или паттерн _ $/$.

var ownFunctionResult = this.$("functionName"[, arg1[, arg2 ...]]);
var ownFieldValue = this._$("fieldName"[, newValue]);

var objectFunctionResult = objectX.$("functionName"[, arg1[, arg2 ...]]);

//Throws an exception. objectX._$ is not defined
var objectFieldValue = objectX._$("fieldName"[, newValue]);

Концепция использует функцию ClassDefinition, которая возвращает функцию Constructor, которая возвращает объект Interface. Метод только для интерфейса - это $ который получает аргумент name для вызова соответствующей функции в объекте конструктора, любые дополнительные аргументы, передаваемые после name, передаются в вызове.

ClassValues определенная вспомогательная функция ClassValues сохраняет все поля в объекте по мере необходимости. Он определяет функцию _$ для доступа к ним по name. Это следует за коротким шаблоном get/set, поэтому, если value передано, оно будет использовано как новое значение переменной.

var ClassValues = function (values) {
  return {
    _$: function _$(name, value) {
      if (arguments.length > 1) {
        values[name] = value;
      }

      return values[name];
    }
  };
};

Глобально определенная функция Interface принимает объект и Values объекта для возвращения _interface с одной единственной функцией $, рассматривающим obj найти функцию имени параметра name и вызывает его values как контекстный объект. Дополнительные аргументы, переданные $ будут переданы при вызове функции.

var Interface = function (obj, values, className) {
  var _interface = {
    $: function $(name) {
      if (typeof(obj[name]) === "function") {
        return obj[name].apply(values, Array.prototype.splice.call(arguments, 1));
      }

      throw className + "." + name + " is not a function.";
    }
  };

  //Give values access to the interface.
  values.$ = _interface.$;

  return _interface;
};

В приведенном ниже примере ClassX присваивается результату ClassDefinition, который является функцией Constructor. Constructor может получить любое количество аргументов. Interface - это то, что получает внешний код после вызова конструктора.

var ClassX = (function ClassDefinition () {
  var Constructor = function Constructor (valA) {
    return Interface(this, ClassValues({ valA: valA }), "ClassX");
  };

  Constructor.prototype.getValA = function getValA() {
    //private value access pattern to get current value.
    return this._$("valA");
  };

  Constructor.prototype.setValA = function setValA(valA) {
    //private value access pattern to set new value.
    this._$("valA", valA);
  };

  Constructor.prototype.isValAValid = function isValAValid(validMessage, invalidMessage) {
    //interface access pattern to call object function.
    var valA = this.$("getValA");

    //timesAccessed was not defined in constructor but can be added later...
    var timesAccessed = this._$("timesAccessed");

    if (timesAccessed) {
      timesAccessed = timesAccessed + 1;
    } else {
      timesAccessed = 1;
    }

    this._$("timesAccessed", timesAccessed);

    if (valA) {
      return "valA is " + validMessage + ".";
    }

    return "valA is " + invalidMessage + ".";
  };

  return Constructor;
}());

Нет смысла иметь непрототипированные функции в Constructor, хотя вы можете определить их в теле функции конструктора. Все функции вызываются по общедоступной схеме доллара this.$("functionName"[, param1[, param2...]]). Доступ к закрытым значениям осуществляется с помощью шаблона частного доллара this._$("valueName"[, replacingValue]); , Поскольку Interface не имеет определения для _$, внешние значения не могут использоваться для доступа к значениям. Поскольку каждая функция тело прототипа this устанавливается на values объекта в функции $, вы получите исключение, если вы звоните функции одноуровневого Конструктора напрямую; паттерн _ $/$ должен соблюдаться и в телах прототипов функций. Ниже пример использования.

var classX1 = new ClassX();
console.log("classX1." + classX1.$("isValAValid", "valid", "invalid"));
console.log("classX1.valA: " + classX1.$("getValA"));
classX1.$("setValA", "v1");
console.log("classX1." + classX1.$("isValAValid", "valid", "invalid"));
var classX2 = new ClassX("v2");
console.log("classX1.valA: " + classX1.$("getValA"));
console.log("classX2.valA: " + classX2.$("getValA"));
//This will throw an exception
//classX1._$("valA");

И консольный вывод.

classX1.valA is invalid.
classX1.valA: undefined
classX1.valA is valid.
classX1.valA: v1
classX2.valA: v2

Шаблон _ $/$ обеспечивает полную конфиденциальность значений в полностью прототипированных классах. Я не знаю, буду ли я когда-нибудь этим пользоваться, и не будет ли у него недостатков, но это была хорошая головоломка!

Ответ 23

ES6 WeakMaps

С помощью простого шаблона, основанного на ES6, WeakMaps позволяет получить закрытые переменные-члены, доступные из функций прототипа.

Примечание. Использование WeakMaps гарантирует безопасность от утечек памяти, позволяя сборщику мусора идентифицировать и отбрасывать неиспользуемые экземпляры.

// Create a private scope using an Immediately 
// Invoked Function Expression...
let Person = (function() {

    // Create the WeakMap that will hold each  
    // Instance collection of private data
    let privateData = new WeakMap();
    
    // Declare the Constructor :
    function Person(name) {
        // Insert the private data in the WeakMap,
        // using 'this' as a unique acces Key
        privateData.set(this, { name: name });
    }
    
    // Declare a prototype method 
    Person.prototype.getName = function() {
        // Because 'privateData' is in the same 
        // scope, it contents can be retrieved...
        // by using  again 'this' , as  the acces key 
        return privateData.get(this).name;
    };

    // return the Constructor
    return Person;
}());

Ответ 24

Не можете ли вы поместить переменные в более высокую область?

(function () {
    var privateVariable = true;

    var MyClass = function () {
        if (privateVariable) console.log('readable from private scope!');
    };

    MyClass.prototype.publicMethod = function () {
        if (privateVariable) console.log('readable from public scope!');
    };
}))();

Ответ 25

Вы также можете попытаться добавить метод не непосредственно к прототипу, а к функции конструктора, как это:

var MyArray = function() {
    var array = [];

    this.add = MyArray.add.bind(null, array);
    this.getAll = MyArray.getAll.bind(null, array);
}

MyArray.add = function(array, item) {
    array.push(item);
}
MyArray.getAll = function(array) {
    return array;
}

var myArray1 = new MyArray();
myArray1.add("some item 1");
console.log(myArray1.getAll()); // ['some item 1']
var myArray2 = new MyArray();
myArray2.add("some item 2");
console.log(myArray2.getAll()); // ['some item 2']
console.log(myArray1.getAll()); // ['some item 2'] - FINE!

Ответ 26

Вам нужно изменить 3 вещи в вашем коде:

  • Замените var privateField = "hello" на this.privateField = "hello".
  • В прототипе замените privateField на this.privateField.
  • В не-прототипе также замените privateField на this.privateField.

Конечный код будет следующим:

TestClass = function(){
    this.privateField = "hello";
    this.nonProtoHello = function(){alert(this.privateField)};
}

TestClass.prototype.prototypeHello = function(){alert(this.privateField)};

var t = new TestClass();

t.prototypeHello()

Ответ 27

Вы можете использовать назначение прототипа в определении конструктора.

Переменная будет видна методу добавленного прототипа, но все экземпляры функций будут обращаться к той же переменной SHARED.

function A()
{
  var sharedVar = 0;
  this.local = "";

  A.prototype.increment = function(lval)
  {    
    if (lval) this.local = lval;    
    alert((++sharedVar) + " while this.p is still " + this.local);
  }
}

var a = new A();
var b = new A();    
a.increment("I belong to a");
b.increment("I belong to b");
a.increment();
b.increment();

Надеюсь, это может быть полезно.