Как создать свойства защищенного объекта в JavaScript

#javascript #prototype-programming

#javascript #прототип-программирование

Вопрос:

Существует ли шаблон JavaScript, который имитирует свойства «защищенного» объекта, подобные тому, что вы видите в таких языках, как C ??

В принципе, я хотел бы создать объект A, который имеет ряд «защищенных» свойств объекта, к которым можно получить доступ ТОЛЬКО из методов, которые определены из прототипа объекта A. т. е. — НЕДОСТУПНЫ публично из не-прототипированных методов A.

Например, в идеале было бы так:

 function A(){
    var prop1 = 1;      
}

A.prototype.myFunc = function(){
    var newVar = this.prop1;   //newVar now is equivalent to 1
}

var instanceOfA = new A();
var newVar2 = instanceOfA.prop1;  //error given as prop1 is "protected"; hence undefined in this case
  

Кстати — я не хочу, чтобы шаблон привилегированных функций-членов обращался к частным свойствам, поскольку функция-член все еще является общедоступной.

Комментарии:

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

2. Кстати, объекты-прототипы могут быть дополнены (они не запечатаны) — ничто не мешает злоумышленнику добавлять новые методы к объекту-прототипу. Таким образом, наличие свойства, доступного только с помощью методов прототипа, в любом случае было бы небезопасно (даже если бы это было возможно).

3. JavaScript не ориентирован на класс, он объектно-ориентированный — не в смысле объектно-ориентированный, который относится к экземплярам класса, а в смысле … просто объектов. В Java или C нет таких функций, как обычные ключевые слова для членов класса, вместо этого вам нужно разработать свою реализацию на основе объектной и событийной природы языка.

4. Существуют решения, основанные на новых функциях ES6. Например, смотрите здесь: philipwalton.com/articles /…

Ответ №1:

Не существует свойства объекта, доступ к которому можно получить только из прототипированных методов A , а не из не-прототипированных методов A . Язык не имеет такого типа функций, и я не знаю ни о каком обходном пути / взломе для его реализации.

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

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

Ответ №2:

Вы не можете сделать это в Javascript.

Комментарии:

1. Можете ли вы объяснить, почему я получил значение -1? То, что я написал, полностью соответствует действительности в JS. Вы не можете создать защищенные свойства в JS. Больше в этом ничего нет.

2. Это правда, пытаться притворяться иначе гораздо вреднее, чем принимать этот простой факт.

3. Цитирование не требуется. Это факт. Вы не найдете никаких упоминаний о защищенных переменных в спецификации Javascript (что означает, что ее невозможно процитировать).

4. В Javascript очень возможно создать защищенные элементы с помощью WeakMap. Также возможно эмулировать WeakMap в ES5 (еще в 2011 году, когда вы сделали этот комментарий). gist.github.com/Benvie/3179490

5. Вы можете создать реализацию класса, которая поддерживает защищенные и закрытые члены, например: github.com/philipwalton/mozart . Используя подобный инструмент, вы можете определять классы, которые имеют защищенные и частные свойства в определении класса, и эти свойства недоступны снаружи. Итак, это возможно! (Но, возможно, не так идеально, как в Java или TypeScript).

Ответ №3:

Я нашел способ создания защищенных элементов. Для этого я вызываю базовый конструктор и одновременно возвращаю объект с защищенными элементами:

 var protected = BaseClass.call(this); 
  

Вот пример:

 function SignedIntegerArray(size)
{
    var public = this;
    var protected = {};

    // private property:
    var _maxSize = 10000;
    // protected property:
    protected.array = [];
    // public property:
    public.Length = size;

    if(!isInteger(size) || size < 0 || size > _maxSize) { throw "argument exception"; }
    for(var index = 0; index != size; index  ) { protected.array[index] = 0; }

    // private method:
    function isInteger(i) { return i == i   0 amp;amp; i == ~~i; }
    // protected method:
    protected.checkIndex = function(index) { return index >= 0 amp;amp; index < size; }
    // public methods:
    public.SetValue = function(index, value) { if(protected.checkIndex(index) amp;amp; isInteger(value)) { protected.array[index] = value; } };
    public.GetValue = function(index) { if(protected.checkIndex(index)) { return protected.array[index]; } else { throw "index out of range exception"; }}

    return protected;
}

function FloatArray(size, range)
{
    var public = this;
    var protected = SignedIntegerArray.call(this, size); // call the base constructor and get the protected members 

    // new private method, "isInteger" is hidden...
    function isFloat(argument) { return argument != ~~argument; }
    // ...but "checkIndex" is accessible
    public.SetValue = function(index, value) { if(protected.checkIndex(index) amp;amp; isFloat(value) amp;amp; value >= public.MinValue amp;amp; value <= public.MaxValue) { protected.array[index] = value; } };

    // new public properties:
    public.MinValue = -range;
    public.MaxValue = range;

    return protected; // for sub-classes
}

function newObject(className, args) { return new function() { className.apply(this, args)}} // you need to use function.call or function.apply to initialize an object. otherwise the protected-object is empty.
window.addEventListener("load", function()
{
    var o = newObject(FloatArray, [4, 50.0]);
    o.SetValue(3, 2.1);
    console.log(o.GetValue(3));
    console.log(o.Length); // property from the base-class
});
  

Комментарии:

1. Недостатком этого подхода является то, что вы тратите память на каждое использование, потому что каждый экземпляр имеет дублированные функции, а не повторно использует методы в прототипах. Итак, для 100 экземпляров у вас есть 100 версий функций, определенных в конструкторе, тогда как с методами-прототипами у вас будет только один экземпляр функции. Смотрите этот метод, который не тратит впустую память: github.com/philipwalton/mozart

Ответ №4:

Вероятно, это то, что вы ищете:http://javascript.crockford.com/private.html

Комментарии:

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

2. Я также хотел бы отметить, что, хотя вы можете делать то, что Крокфорд предлагает в этой статье, это не значит, что вы должны это делать. Программируйте на языке X так, как будто вы программируете для языка X, а не для языка Y. Тем не менее, в JS есть несколько законных применений для (! КХМ!) частных переменных (я думаю, у меня просто потекла кровь, когда я это сказал), но я не буду вдаваться в подробности, поскольку это использование на самом деле не имеет ничего общего с дизайном класса, так сказать.

Ответ №5:

 function ClassA(init)
{
    var protected = {};
    protected.prop = init * 10;
    if(this.constructor != ClassA) { return protected; }
}

function ClassB()
{
    var protected = ClassA.call(this, 5); //console.log(protected.prop);
}

//var a = new ClassA(123);
//var b = new ClassB();
  

Ответ №6:

Мне было интересно найти способ ответить на ваш вопрос, и вот что я смог сделать.

Вам понадобится этот помощник:

 var ProtectedHandler = (function () {
    /// <Sumarry>
    /// Tool to handle the protected members of each inheritance.
    /// </Summary>
    /// <param name="current">Current protected variable.</param>
    /// <param name="args">The arguments variable of the object.</param>
    /// <param name="callback">The function to initialise the variable in the 'object'.</param>
    /// <param name="isParent">Is this the ultimate base object.</param>
    function ProtectedHandler(current, args, callback, isParent) {
        this.child = getChild(args);
        if (callback)
            this.callback = callback;

        if (isParent)
            this.overrideChild(current);
    }

    // Get the ProtectedHandler from the arguments
    var getChild = function (args) {
        var child = null;
        if (args.length > 0 amp;amp; (child = args[args.length - 1]) amp;amp; child.constructor === ProtectedHandler)
            return child;
    };

    // Chain Initialise the protected variable of the object and its inheritances.
    ProtectedHandler.prototype.overrideChild = function (newValue) {
        if (this.callback != null) {
            this.callback(newValue);
        }
        if (this.child != null) {
            this.child.overrideChild(newValue);
        }
    };

    // Static function to create a new instance of the protectedHandler object.
    ProtectedHandler.handle = function (protected, arguments, callback, isParent) {
        return new ProtectedHandler(protected, arguments, callback, isParent);
    };

    return ProtectedHandler;
})();
  

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

Чтобы доказать вам, что это работает, вот пример:

 // That's the default extends function from typescript (ref: http://www.typescriptlang.org/)
var __extends = this.__extends || function (d, b) {
    for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
    function __() { this.constructor = d; }
    __.prototype = b.prototype;
    d.prototype = new __();
};

var BaseClass = (function () {        
    function BaseClass() {
        // Members
        var private = {},
            protected = {},
            public = this;

        // Constructor
        ProtectedHandler.handle(protected, arguments, function () {
            protected.type = "BaseClass";
        }, true);

        // Methods
        protected.saySomething = function () {
            return "Hello World";
        };

        public.getType = function () {
            return protected.type;
        };
    }

    return BaseClass;
})();



var Person = (function (_super) {
    __extends(Person, _super);

    function Person(name) {
        // Members
        var private = {},
            protected = {},
            public;

        // Constructor
        _super.call(public = this, ProtectedHandler.handle(protected, arguments, function (p) {
            protected = p; //This is required to copy the object from its base object.
            protected.name = name;
            protected.type = "Person";
        }));

        //Method
        public.getName = function () {
            return protected.name;
        };

        public.saySomething = function () {
            return protected.saySomething();
        };
    }
    return Person;
})(BaseClass);


var Child = (function (_super) {
    __extends(Child, _super);

    function Child(name) {
        // Members
        var private = {},
            protected = {},
            public;

        // Constructor
        _super.call(public = this, name, ProtectedHandler.handle(protected, arguments, function (p) {
            protected = p; //This is required to copy the object from its base object.
            protected.type = "Child";
        }));

        //Method
        public.setName = function (value) {
            return protected.name = value;
        };
    }
    return Child;
})(Person);
  

И вот тесты:

 var testBase = new BaseClass();
testBase.getType(); //"BaseClass"
testBase.saySomething; //undefined

var testPerson = new Person("Nic");
testPerson.getType(); //"Person"
testPerson.saySomething(); //"Hello World"
testPerson.name; //undefined
testPerson.getName() //"Nic"
testPerson.setName; //undefined

var testChild = new Child("Bob");
testChild.getType(); //"Child"
testChild.saySomething(); //"Hello World"
testChild.name; //undefined
testChild.getName(); //"Bob"
testChild.setName("George");
testChild.getName(); //"George"
  

Ответ №7:

Мне понравился шаблон, который работает не так, как protected access в большинстве языков, но обеспечивает аналогичное преимущество.

В принципе, используйте метод builder для создания замыкания для свойств, а затем попросите этот метод создать «полный» объект с широким доступом, а также «открытый» объект с более ограниченным доступом. Поместите открытый объект в свойство полного объекта и верните этот полный объект вызывающей стороне.

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

Надуманный пример…

 // Ring employs a typical private/public pattern while
// RingEntry employs a private/exposed/full access pattern.

function buildRing( size ) {
  var i
    , head = buildRingEntry( 0 )
    , newEntry;
  ;
  head.setNext( head );
  for( i = size - 1; i ; i-- ) {
    newEntry = buildRingEntry( i );
    newEntry.setNext( head.getNext() );
    head.setNext( newEntry );
  }
  function getHead() { return head.exposed; }
  return {
      getHead : getHead
  }
}

function buildRingEntry( index ) {
  var next
    , exposed
  ;
  function getIndex() { return index; }
  function setNext( newNext ) { next = newNext; }
  function getNextFullEntry() { return next; }
  function getNextExposedEntry() { return next.exposed; }
  exposed = {
      getIndex : getIndex
    , getNext  : getNextExposedEntry
  };
  return {
      getIndex : getIndex
    , setNext  : setNext
    , getNext  : getNextFullEntry
    , exposed  : exposed
  };
}
  

Если мы используем это для построения кольца из 4 записей, ring = buildRing(4); то ring.getHead().getIndex() это дает нам 0, ring.getHead().getNext().getIndex() дает нам 1, ring.getHead().getNext().getNext().getIndex() дает нам 2 и т.д.

Однако, если мы попытаемся выполнить ring.getHead().setNext({}) или ring.getHead().getNext().setNext({}) , мы получим ошибку, потому что setNext это не свойство открытого объекта ввода.

Предостережение:

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

Комментарии:

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

Ответ №8:

Взгляните на обходной путь, предложенный Максом на его веб-сайте: Эмуляция защищенных элементов в JavaScript

Он эмулирует protected уровень доступа к методам и свойствам объекта.