Доступ к закрытым переменным-членам из функций, определенных прототипом

StackOverflow https://stackoverflow.com/questions/436120

  •  22-07-2019
  •  | 
  •  

Вопрос

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

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

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

t.nonProtoHello()

Но это не так:

t.prototypeHello()

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

Это было полезно?

Решение

Нет, это невозможно сделать. По сути, это будет обзор в обратном направлении.

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

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

Вы по-прежнему можете иметь закрытые переменные, но если вы хотите, чтобы методы, определенные в прототипе, имели доступ к ним, вы должны определить методы получения и установки для объекта 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()); };

Другие советы

Обновлять:С 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:

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

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

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

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

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

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

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

Что мне выбрать?

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

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

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

Когда я прочитал это, это показалось мне сложной задачей, поэтому я решил найти способ.То, что я придумал, было СУМАСШЕДШИЙ но это полностью работает.

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

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

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

Если вам абсолютно необходим доступ к переменным в частном пространстве имен из ваших методов, определенных в прототипе, вы можете попробовать этот шаблон.

var PrivateNamespaceClass = (function() { // immediate function
    var instance = 0, // counts the number of instances
        defaultName = "Default Name",  
        p = []; // an array of private objects

    // create the constructor
    function PrivateNamespaceClass() {
        // Increment the instance count and save it to the instance. 
        // This will become your key to your private space.
        this.i = instance++; 
        
        // Create a new object in the private space.
        p[this.i] = {};
        // Define properties or methods in the private space.
        p[this.i].name = defaultName;
        
        console.log("New instance " + this.i);        
    }

    PrivateNamespaceClass.prototype.getPrivateName = function() {
        // It has access to the private space and it's children!
        return p[this.i].name;
    };
    PrivateNamespaceClass.prototype.setPrivateName = function(value) {
        // Because you use the instance number assigned to the object (this.i)
        // as a key, the values set will not change in other instances.
        p[this.i].name = value;
        return "Set " + p[this.i].name;
    };

    return PrivateNamespaceClass;
})();

var a = new PrivateNamespaceClass();
console.log(a.getPrivateName()); // Default Name

var b = new PrivateNamespaceClass();
console.log(b.getPrivateName()); // Default Name

console.log(a.setPrivateName("A"));
console.log(b.setPrivateName("B"));
console.log(a.getPrivateName()); // A
console.log(b.getPrivateName()); // B

// private objects are not accessible outside the PrivateNamespaceClass function
console.log(a.p);

// the prototype functions are not re-created for each instance
// and instanceof still works
console.log(a.getPrivateName === b.getPrivateName);
console.log(a instanceof PrivateNamespaceClass);
console.log(b instanceof PrivateNamespaceClass);

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

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

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

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

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

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

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

Решение Дуга Крокфорда - лучшее.

@Кай

Это не сработает.Если ты это сделаешь

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

Да, это возможно.Шаблон проектирования PPF как раз решает эту проблему.

PPF означает функции частного прототипа.Базовый PPF решает следующие проблемы:

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

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

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

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

// 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

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

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

Этот пример взят из моего поста о Функции прототипа & amp; Личные данные и там более подробно объясняется.

В текущем JavaScript я вполне уверен, что есть один и только один способ получить личное состояние , доступное из > прототип функций, без добавления чего-либо публичного в этот . Ответ заключается в том, чтобы использовать «слабую карту»; шаблон.

Подводя итог: класс 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 ; в противном случае экземпляр и данные не будут собираться мусором, что приведет к утечке памяти.

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

Существует более простой способ использования методов 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)

Попробуй!

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

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

(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

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

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

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's only for $1,795._");
console.log(o1.getP()); // And it's only for $1,795._

Прототипы JavaScript золотые.

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

// 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

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

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

Вот кое-что, что я придумал, пытаясь найти самое простое решение для этой проблемы, возможно, это могло бы быть кому-то полезно. Я новичок в 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();

Сегодня я столкнулся с точно таким же вопросом, и после того, как подробно остановился на первоклассном ответе Скотта Риппи, я пришел к очень простому решению (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. Я готов прочитать ваше мнение.

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

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

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

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

<Ол>
  • Используйте 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);
    

    РЕДАКТИРОВАТЬ: Используя этот метод, также можно "определить" частные функции.

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

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

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

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

    Я знаю, что прошло более десяти лет с тех пор, как об этом спросили, но я только что подумал об этом в 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]);
    

    В концепции используется Определение класса функция, которая возвращает Конструктор функция, которая возвращает Интерфейс объект.Единственный метод интерфейса $ который получает name аргумент для вызова соответствующей функции в объекте-конструкторе, любые дополнительные аргументы, передаваемые после name передаются при вызове.

    Глобально определенная вспомогательная функция ClassValues сохраняет все поля в а объект по мере необходимости.Он определяет _$ функция доступа к ним с помощью name.Это соответствует короткому шаблону получения/установки, поэтому, если 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 объект в функции $, вы получите исключения, если напрямую вызовете родственные функции Constructor;тот _$/$ шаблон необходимо соблюдать и в телах прототипированных функций.Ниже пример использования.

    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
    

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

    Слабые карты ES6

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

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

    // Create a private scope using an Immediately 
    // Invoked Function Expression...
    let Person = (function() {
    
        // Create the WeakMap that will hold each  
        // Instance collection's 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's contents can be retrieved...
            // by using  again 'this' , as  the acces key 
            return privateData.get(this).name;
        };
    
        // return the Constructor
        return Person;
    }());

    Более подробное объяснение этого шаблона можно найти здесь

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

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

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

    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!
    

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

    <Ол>
  • Замените var privateField = " здравствуйте " на this.privateField = " здравствуйте " .
  • В прототипе замените 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()
    

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

    Переменная будет видна для добавленного метода-прототипа, но все экземпляры функций будут иметь доступ к одной и той же переменной 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();
    

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

    Лицензировано под: CC-BY-SA с атрибуция
    Не связан с StackOverflow
    scroll top