Подтвердить что ты не робот

Как реализовать модификаторы доступа С# в javascript?

  • Резюме

    Я пытался добиться наследования и инкапсуляции должным образом в javascript, как это было в языке на основе классов, таких как c #.

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

    Если это возможно, я хочу избавиться от transmit и transfer в моем коде Function.extend.

  • Обновление Для людей, которые заинтересованы в цитировании или исследованиях, здесь хранилище исходного кода:

    https://github.com/kenkins/Function.extend

  • История

    Поскольку сборки могут быть концепцией, которая выходит за рамки javascript, я не принимаю во внимание модификатор internal, но public, protected и private.

    Модификаторы public и private не так сложны; но с наследованием, protected значительно сложнее. Тем не менее, это не рекомендуется делать с javascript, в большинстве статей, которые я прочитал, говорится о префиксе со специальным символом и документировании.

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

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

    Скажем, у нас есть три прототипа A, D и G, это выглядит как

    BANxt.png

    Поскольку невозможно, чтобы объект был экземпляром типа также другого типа, которого нет в цепочке прототипов; способ, который я выбрал, состоит в том, чтобы связать уровень protected по горизонтали и скопировать элементы из прототипа декларирующего типа. Это делает возможным класс вложенности, потому что члены, объявленные с менее производным типом, могут быть распространены на более производные типы; метод transmit в моем коде должен сделать это. Если A, D и G имеют своих защищенных членов, это будет выглядеть так:

    bhcsI.png

    Закрытие для доступа к частному экземпляру - this['']. Он принимает аргумент для идентификации класса. Держатель модификаторов - это просто идентификатор класса, названный y в Function.extend и _ в тестовом коде, его нельзя показывать вне объявления класса. Он также используется в качестве ярлыка this[''].

    _['base'] фактически является не только инициатором базового конструктора, но и создателем частных экземпляров. Он создает частные экземпляры и обновляет this[''] для каждого конструктора с наследованием, поэтому его всегда следует вызывать в конструкторах.

    Хотя частный экземпляр будет иметь доступ к открытым членам, его не следует использовать для их изменения, поскольку не гарантируется, что this[''] будет вызываться при доступе к открытым членам. Но доступ к частному экземпляру есть; recent запоминает последний доступный частный экземпляр и обновляет защищенные элементы, если есть изменения.

    У меня вопрос, как я могу избавиться от такого рода обновления защищенных членов? Есть ли лучшие идеи для более реалистичной инкапсуляции?

    ps.: Я на самом деле не хочу решения, которое использует нестандартные методы/свойства... и было бы лучше, если есть полифиллы, если используемые методы/свойства слишком модны для старых браузеров.


  • Function.extend

    Function.extend=function(base, factory) {
        factory.call(initializeClass);
        updateStaticMembersOfDerivedInnerClasses(y['public'].constructor);
        transfer(y['protected'], y['public']);
        return y['public'].constructor;
    
        function y($this) {
            return $this[''](y);
        }
    
        function transfer(target, source, descriptor) {
            if(target!==source?
                'undefined'!==typeof target?
                    'undefined'!==typeof source:
                        false:false) {
                var keys='undefined'!==typeof descriptor? descriptor:source;
    
                for(var key in keys) {
                    if(Object.prototype.hasOwnProperty.call(source, key)) {
                        target[key]=source[key];
                    }
                }
            }
        }
    
        function updateStaticMembersOfDerivedInnerClasses(outer) {
            var member, inner;
    
            for(var key in outer) {
                if(Object.prototype.hasOwnProperty.call(outer, key)?
                    (member=outer[key]) instanceof outer?
                        outer!==(inner=member.constructor):
                            false:false) {
                    transfer(inner, outer);
                }
            }
        }
    
        function initializeInstance() {
            var $this=Object.create(y['private']);
            var derivedGet=this[''];
            var recent=$this;
    
            this['']=function(x) {
                var value=y!==x? derivedGet.call(this, x):$this;
    
                if(value!==recent) {
                    transfer(value, recent, x['protected']);
                    recent=value;
                }
    
                transfer(value, this);
                return value;
            };
    
            base.apply(this, arguments);
            $this['']=this[''];
        }
    
        function initializeClass(derived) {
            y['public']=Object.create(base.prototype);
            y['public'].constructor=derived;
    
            if(Object.prototype.hasOwnProperty.call(base, 'transmit')) {
                base.transmit(y);
            }
            else {
                y['protected']=Object.create(y['public']);
            }
    
            y['private']=Object.create(y['protected']);
            y['base']=initializeInstance;
            transfer(derived, base);
    
            derived.transmit=function(x) {
                if(x['public'] instanceof derived) {
                    x['protected']=Object.create(y['protected']);
                    x['protected'].constructor=x['public'].constructor;
                }
            };
    
            derived.prototype=y['public'];
            return y;
        }
    };
    
  • тестовый код

    'use strict';
    
    var BaseClass=Function.extend(Object, function () {
        var _=this(BaseClass);
    
        var NestedClass=Function.extend(BaseClass, function () {
            var _=this(NestedClass);
    
            function NestedClass(x, y, z) {
                _['base'].apply(this, arguments);
                _(this).Y=y;
                _(this).Z=z;
            }
    
            _['public'].SetX=function (x) {
                _(this).InternalSetX(x);
            };
    
            _['public'].GetX=function () {
                return _(this).InternalGetX();
            };
    
            _['public'].GetY=function () {
                return _(this).Y;
            };
    
            _['public'].SetZ=function (z) {
                _(this).Z=z;
            };
    
            _['public'].GetZ=function () {
                return _(this).Z;
            };
    
            _['private'].Y=0;
        });
    
        function BaseClass(x) {
            _['base'].apply(this, arguments);
            _(this).X=x;
        }
    
        _['protected'].InternalSetX=function (x) {
            _(this).X=x;
        };
    
        _['protected'].InternalGetX=function () {
            return _(this).X;
        };
    
        _['private'].X=0;
        _['protected'].Z=0;
    
        BaseClass.Sample=new NestedClass(1, 2, 3);
    });
    
    var DerivedClass=Function.extend(BaseClass, function () {
        var _=this(DerivedClass);
    
        function DerivedClass(x, y, z) {
            _['base'].apply(this, arguments);
        }
    });
    
    var o=DerivedClass.Sample;
    alert(o.GetX());
    alert(o.GetY());
    alert(o.GetZ());
    o.SetX(3);
    o.SetZ(1);
    alert(o.GetX());
    alert(o.GetY());
    alert(o.GetZ());
    
4b9b3361

Ответ 1

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

Это не точно С#, но обеспечивает более строгую экосистему. И некоторые другие продвинутые функции js в легком решении.

https://github.com/iamlothian/rucksack.js

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

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

Часть 2:

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

Вот пример быстрого макета, демонстрирующий, как вы можете реализовать public | protect | private inheritance. Я пытаюсь решить, что погода реализует часть этого как встроенную функцию или оставляю ее пользователям для реализации собственного расширения объекта, как в примере.

http://plnkr.co/edit/ao2hTyBV1b3nYIwr7ZS5

Реализация выполняется в файле scripts.js. просмотрите консоль, чтобы увидеть, что происходит.

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

текущие функции:

  • Модульная
  • Инъекция зависимостей
  • Factory конструктор (экземпляр объекта)
  • Конструктор служб (статические объекты)
  • Lazy loading
  • Простое ведение журнала ошибок (все ошибки в модулях фиксируются и могут передаваться)
  • Пространства имен
  • Уплотняемые модули и пространства имен (модули, к которым нельзя получить доступ из-за пределов пространства имен)
  • Глобальное событие ожидания для модуля
  • Интерфейс для необязательного объекта конфигурации
  • Дополнительные строгие проверки интерфейса для инъекций

Ответ 2

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

Идея использования проста:

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

В итоге вы получите что-то вроде этого:

function A() {
    var protected = {
        protectedA: function() { }
    };

    this.getProtected = (function() {
        var allow = true;

        //privileged function.
        return function() {
            if (allow) {
                allow = false;
                return protected;
            }
        };
    });
}

//B - derives from (extends) A
function B() {
    var base = {}; //acquiring a base omitted - depends on your implementation.

    var protected = {
        protectedB: function() { }
    };
    //"extend" simply copies new members into protected:
    protected = $.extend(protected, base.getProtected()); 

    this.getProtected = function() { 
        /* privileged function - similar to A.getProtected */ 
    };
}

В этом случае JavaScript имеет ограниченные возможности, поэтому сахара protected все равно имеет некоторую стоимость.

Ответ 3

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

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

Мое мышление: Некоторые модификаторы доступа не были созданы для использования в javascript из-за того, что разработчики уже знают, что, возможно, это может быть бесполезно, потому что JavaScript не "путешествует" в другие места (страницы), если только вы используете переменную сеанса.

И о том, что модификаторы:

  • Частный

  • Защищенный

  • Public

Я могу сказать вам, что знаю некоторые модификаторы javascript, которые имеют некоторое сходство с ними:

Local:

var Variable = 0;

Автоматически это преобразуется в переменную Integer, потому что она принимает значение Integer, и тоже это LOCAL-переменная из-за модификатора var, объявляющего эту переменную таким образом, что вы не можете получить доступ к значению это, если только вы находитесь внутри той же функции, что и эта переменная.

Пример:

Если вы объявляете эти функции таким образом, с модификаторами по умолчанию:

function conflict(){
  i = 2;
  changeI();
  alert(i);
}
function changeI(){
  i = 0;
}

В этом случае i является той же переменной для двух функций.

Итак, если вы выполните conflict();, вы получите сообщение о появлении 0.

НО, если вы объявите i с помощью модификатора var:

function conflict(){
  var i = 2;
  changeI();
  alert(i);
}
function changeI(){
  var i = 0;
}

В этом случае у вас есть две переменные i, потому что они ограничены для использования только внутри их функции, поэтому, если вы выполните conflict(); сейчас, вы получите предупреждение со значением 2.

Переменная класса:

this.Variable = "a";

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

Пример:

function TClass()
{
  this.getVar = function()
  {
    try
    {
      return "test";
    }
    catch(err)
    {
      return false;
    }
  }
  this.alertVar = function()
  {
    try
    {
      alert(this.getVar());
    }
    catch(err)
    {
      alert('error');
    }
  }
}
var $Class = new TClass();

Как вы видите выше, я создал класс TClass и некоторые переменные, содержащие в нем функции (закрытие javascript), и добавил к ним модификатор this., чтобы привязать их к TClass и, как вы видите на alertVar(), я получаю доступ к alert(this.getVar()); функции, которая находится из TClass, которая равна this в этом контексте.

И эта часть: var $Class = new TClass(); Я создаю класс, поскольку вы, вероятно, знали, что, чтобы иметь доступ к его методам, делая это, я могу выполнить, чтобы проверить:

$Class.alertVar();

И получив как результат, оповещение, содержащее "тест", как вы можете видеть:

the test alert

Обратите внимание, что вы не можете получить доступ к методам TClass по-другому, вы можете получить доступ к нему, создав класс и получив доступ к нему.

Поэтому я надеюсь, что вы поняли удобство использования модификатора this.

Global:

window.Variable = true;

Автоматически javascript объявляет, что эта переменная является логической, потому что она получает логическое значение. Модификатор window, как он говорит, вы можете получить к нему доступ, независимо от того, что вы находитесь в окне, которое вы есть, потому что переменные javascript при объявлении, они переходят в DOM в окно, см., Что такое DOM:

DOM (Document Object Model): DOM - это многоплатформенная платформа, которая представляет, как разметка html, xhtml и xml организована и читается браузером, который вы используете. Другими словами, если вы обращаетесь к DOM, вы можете увидеть каждую собственность, каждую переменную или такую ​​вещь, которая существует в браузере на данный момент.

В отличие от других переменных переменные window могут назначить другое значение и получить доступ к фактическому значению, независимо от того, что вы есть, внутри функции или нет внутри файла js или нет.

Пример глобального (окна):

Выполните в onLoad событие страницы код, объявляющий переменную window, или объявите ее самостоятельно с помощью консоли браузера:

window.Variable = true;

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

function testGlobalVar(){
  if (Variable)
    alert("it works!");
}

Когда вы выполняете testGlobalVar(), вы получите предупреждение, но только потому, что вы объявили его как "окно", иначе ничего не получите.

Модификатор по умолчанию:

Variable = 0.5

Автоматически эта переменная объявляется как Float beacuse, она получает значение Float. Я не знаю, знаете ли вы, но переменные javascript, объявленные как обычный способ, имеют модификатор по умолчанию, который делает переменную похожей на переменные window, но вы не можете получить к ней доступ из того, что вы есть, но в большинстве случаев вы можете ее использовать, особенно, я не знаю всех случаев, когда вы не можете получить к нему доступ, но я знаю, что вы не можете, когда вы загрузили файл js, и он был объявлен внутри него. Только если вы запустите функцию, объявившую ее, и после этого попытайтесь добиться успеха.

Кстати, я вижу, что вы хотите знать модификаторы, которые соответствуют трем, которые вы сказали, но при моем решении некоторые из тех модификаторов, которые я вам сказал, можно использовать для того, чтобы сделать то же, что и ваши модификаторы С#.

Я надеюсь, что вы поймете, что я говорю.

А, и если вы были смущены, когда увидели функцию внутри переменной, изучите Javascript Closures, вы поймете после этого:).

Ответ 4

Как родительский и дочерний классы взаимодействуют друг с другом

  • Расширяющийся дочерний класс вызывает super.call, функцию, которая создает экземпляр родителя.

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

  • Подкласс также может вызвать super.fetch(), который возвращает объект полей/функций, который родительский класс передал this.share


Чтобы проиллюстрировать мою технику, следующий код демонстрирует некоторые существенные для ООП простые примеры class Dog extends Animal

Некоторые основные функции для этой объектно-ориентированной модели

// runs in both node.js and browser
var global_namespace = ('undefined'==typeof module)? window: global;

// put a no-operation function in the value for `share` in case nothing is extending a class
var not_extendable = {share:function(){}};

// when something is extending a class...
var extendable = function(constructor) {

    // create a space for protected fields
    var protected_space = {};

    // the following is what will get passed as `this` to the parent constructor
    var sharing = {
        share: function(fields) {
            protected_space = fields;
        },
    };

    // the following is what will get passed as the first arg to the child constructor
    return {

        // enables child to call its parent constructor
        call: function(args) {
            return constructor.apply(sharing, args);
        },

        // allows child to access protected fields shared by its parent
        fetch: function() {
            return protected_space;
        },
    };
};

Animal класс

// class definition for `Animal`
(function(namespace) {

    // construct an instance of this class
    var constructor = function(name, weight, info) {

        // private fields
        var color = (info && info.color) || 'unknown';

        // for protected fields
        var protect = {
            weight: weight,
            noise: function() {
                return 'nothing';
            },
        };

        // share the protected fields with any subclass that might be extending this
        this.share(protect);

        // public fields and methods
        return {
            speak: function() {
                console.log(name+' says '+protect.noise());
            },
            feed: function() {
                console.log(name+' is not hungry');
            },
            weigh: function() {
                console.log(name+' weighs '+protect.weight+' lbs');
            },
            toString: function() {
                return '{Animal}';
            },
        };
    };


    // handle calls to: `Animal()`
    namespace.Animal = function() {

        // called with new operator: `new Animal(...)`
        if(this !== namespace) {

            // construct simple instance of this class
            return constructor.apply(not_extendable, arguments);
        }

        // static call: `Animal(...)`, means the caller wants to extend this class
        else {

            // reference child constructor
            var child_constructor = arguments[0];

            // return a wrapped constructor function
            return function() {

                // call child constructor and allow it to call the super constructor
                return child_constructor.apply({}, [extendable(constructor), arguments]);
            };
        }
    };

})(global_namespace);

Dog класс

// class definition for `Dog`
(function(namespace) {

    // class `Dog` extends class `Animal`
    var constructor = Animal(function(super_class, args) {

        // private field
        var been_fed = false;

        // call super constructor
        var operator = super_class.call(args);

        // inherit parent protected members
        var parent = super_class.fetch();

        // override a protected method
        parent.noise = function() {
            return 'bark!';
        };

        // override a public method
        operator.feed = function() {
            been_fed = true;
            parent.weight += 5;
        };

        // extend a public method
        var super_weigh = operator.weigh;
        operator.weigh = function() {
            super_weigh();
            if(been_fed) console.log('\t'+args[0]+' has been eating :)');
            else console.log('\t'+args[0]+' has not been fed yet');
        };

        // override another public method
        operator.toString = function() {
            return '{Dog}';
        },

        // return the operator (interfacable instance object)
        return operator;
    });


    // handle calls to: `Dog()`
    namespace.Dog = function() {

        // called with new operator: `new Dog()`
        if(this !== namespace) {
            return constructor.apply(this, arguments);
        }

        // static call: `Dog()`
        else {

            // we do no allow extending class `Dog`
            return false;
        }
    };

})(global_namespace);

Итак, теперь мы можем сделать это:

var giraffe = new Animal('Mr. Giraffe', 720);
giraffe.speak(); // "Mr. Giraffe says nothing"
giraffe.weigh(); // "Mr. Giraffe weighs 720 lbs"

var buddy = new Dog('Buddy', 50);
buddy.speak(); // "Buddy says bark!"
buddy.weigh(); // "Buddy weighs 50 lbs"
                   //   "Buddy has not been fed yet"
buddy.feed();
buddy.weigh(); // "Buddy weighs 55 lbs"
                   //   "Buddy has been eating :)"

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

console.log(giraffe); // "{Animal}"
console.log(buddy);   // "{Dog}" 

Ответ 5

Я работал над другим интересным проектом javascript и реализовал то, что может быть ближе к тому, что вы ищете.

Implement.js

Заинтересованы в ваших мыслях.