Используйте замыкание, чтобы сделать методы частными в прототипе - PullRequest
0 голосов
/ 27 августа 2011

Я использую прототип 1.4 в нашем проекте, я использовал для создания класса следующим образом:

1) Manner1

var Person=Class.create();
Person.prototype={
    initialize:function(name,age){
        this._check(name,age);
        this.name=name;
        this.age=age;
    },
    say:function(){
        console.log(this.name+','+this.age);
    }
    _check:function(name,age){
        if(typeof(name)!='string' || typeof(age)!='number')
            throw new Error("Bad format...");
    }
}

Однако в приведенном выше коде метод Person "_check" может вызываться извне, что не является моим ожидаемым.

И в моем бывшем посте , спасибо за 'T.J. Crowder ', он сказал мне одно решение сделать метод полностью приватным:

2) Manner2

var Person=(function(){
    var person_real=Class.create();
    person_real.prototype={
        initialize:function(name,age){
            _check(name,age);
            this.name=name;
            this.age=age;
        },
        say:function(){
            console.log(this.name+','+this.age);
        }
    }

    //some private method
    function _check(name,age){
        //....the check code here
    }
    return person_real;
})();

Теперь "_check" не может быть выставлен наружу.

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

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

Теперь посмотрите на "Манер1":

Мы создаем класс «Person», затем помещаем все методы экземпляра в объект-прототип класса Person. Затем каждый раз, когда мы называем

var obj=new Person('xx',21);

объекту "obj" будут принадлежать методы из Person.prototype. Сам по себе «obj» не содержит никаких кодов.

Однако в "Manner2": Каждый раз, когда мы звоним:

var obj=new Person('xx',21);

Будет создан новый план, каждый раз будут создаваться частные методы, такие как _check. Это пустая трата памяти?

Примечание: возможно я ошибаюсь. Но я действительно смущен. Кто-нибудь может дать мне объяснение?

Ответы [ 2 ]

6 голосов
/ 27 августа 2011

Вы сталкиваетесь с парой общих проблем, с которыми люди сталкиваются в Javascript.

Второй вопрос первый, так как на него легче ответить. С прототипом наследования вы описываете только различия между двумя связанными объектами. Если вы создаете функцию на прототипе объекта, а затем делаете 10000 его клонов, остается только одна функция. Каждый отдельный объект будет вызывать этот метод, и из-за того, как «это» работает в Javascript, «это» в этих вызовах функций будет указывать на отдельные объекты, несмотря на функцию, живущую в единственном прототипе.

Конечно, когда у вас есть уникальные свойства для каждого элемента, уникальные значения для каждого объекта, тогда да, вы можете столкнуться с проблемами производительности (я не решаюсь использовать термин «экземпляр», потому что это не означает одно и то же в наследовании прототипов) по сравнению с наследованием на основе классов). Поскольку вы больше не пользуетесь одной единой общей функцией / набором данных, вы теряете это преимущество. Знание того, как минимизировать эти недостатки, является одной из ключевых частей умного, эффективного программирования на Javascript (и на любом языке, основанном на наследовании прототипов). Существует множество неочевидных приемов, позволяющих получить доступ к функциональным возможностям и данным с минимальным дублированием.

Первый вопрос - это общий класс путаницы в Javascript из-за почти всемогущества Function в Javascript. Функции в Javascript единолично выполняют работу многих различных конструкций на других языках. Точно так же Объекты в Javascript играют роль множества конструкций данных на других языках. Эта сосредоточенная ответственность приносит множество мощных возможностей, но делает Javascript своего рода бугиманом: он выглядит очень простым и действительно простым. Но это действительно просто, как покер очень просто. Это небольшой набор движущихся частей, но то, как они взаимодействуют, порождает гораздо более глубокую метагейм, который быстро становится изумительным.

Лучше понимать объекты / функции / данные в приложении / скрипте Javascript / любом другом как постоянно развивающуюся систему, так как это лучше помогает захватить наследование прототипа. Возможно, как футбольная (американская) игра, в которой текущее состояние игры невозможно отследить, не зная, сколько там падений, сколько осталось таймаутов, в каком квартале вы находитесь и т. Д. Вы должны двигаться с кодом вместо того, чтобы смотреть на него как на большинство языков, даже на динамические, такие как php.

В вашем первом примере все, что вы по существу делаете, - это используете Class.create для автоматического вызова функции initialize() и для автоматической обработки содержимого конструктора дедушки и бабушки, так что в остальном это похоже на простое выполнение:

var Person = function(){ this.initialize.call(this, arguments) };
Person.prototype = { ... }

Без вещей прародителя. Любые свойства, установленные непосредственно для объекта, будут общедоступными (если только вы не используете ES5 defineProperty для его конкретного управления), поэтому добавление _ в начало имени не будет ничего делать вне соглашений. В javascript нет такого понятия, как закрытый или защищенный член. Существует ТОЛЬКО публичное, поэтому, если вы определяете свойство объекта, оно является публичным. (опять же исключая ES5, который добавляет к этому некоторый контроль, но на самом деле он не предназначен для заполнения так же, как закрытые переменные в других языках, и его нельзя использовать или полагаться таким же образом).

Вместо частных членов мы должны использовать область действия для создания конфиденциальности. Важно помнить, что в Javascript only есть функция видимости. Если вы хотите иметь закрытые члены в объекте, вам нужно обернуть весь объект в большую область (функцию) и выборочно обнародовать то, что вы хотите . Это важное базовое правило, которое по какой-то причине я редко вижу, как оно объясняется. (Существуют и другие схемы, такие как предоставление открытого интерфейса для регистрации / добавления функций в частный контекст, но все они заканчиваются всем, что имеет общий доступ, с доступом к частному контексту, созданному функцией, и, как правило, намного сложнее, чем необходимо).

Думайте об этом так. Вы хотите поделиться этими членами внутри объекта, поэтому все члены объекта должны находиться в одной области видимости. Но вы не хотите, чтобы это делилось снаружи, поэтому они должны быть определены в их собственной области видимости. Вы не можете поместить их как члены в открытый объект, так как Javascript не имеет понятия о чем-либо, кроме общедоступных переменных (кроме ES5, которые все еще могут быть испорчены), у вас остается только область действия функции как единственный способ реализовать конфиденциальность.

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

Здесь сразу исполняемая функция выполняет роль привратника. Объекты и область действия не создаются до тех пор, пока функция не будет запущена, и часто единственной целью функции контейнера является предоставление области действия, поэтому анонимно выполняемая функция немедленно отвечает требованиям (однако анонимного или немедленного выполнения не требуется). Важно признать гибкость в Javascript с точки зрения построения «Объекта» или чего-либо еще. На большинстве языков вы тщательно строите структуру своего класса, и какие члены есть у каждого, и так далее, и так далее. В Javascript «если вы мечтаете об этом, вы можете сделать это» - это, по сути, название игры. В частности, я могу создать анонимную функцию с 20 различными объектами / классами / независимо от того, что встроено в нее, а затем специальную вишню выбрать метод или два из каждого и вернуть их как один открытый объект, а затем это объект / интерфейс хотя прошло всего две секунды, прежде чем я сделал return { ...20 functions from 20 different objects... }.

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

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

Вот почему такие вещи, как класс Prototype, становятся полезными. Если вы реализуете многоуровневое наследование, вы начинаете сталкиваться с дырами, где прототипы в цепочке упускают некоторые функциональные возможности. Это связано с тем, что хотя свойства / функции в цепочке прототипов автоматически обнаруживаются на дочерних элементах, функции конструктора не каскадируются аналогичным образом; свойство конструктора наследует то же, что и все остальное, но все они имеют имя constructor, поэтому вы получаете бесплатный конструктор прямого прототипа бесплатно. Кроме того, конструкторы должны быть выполнены, чтобы получить их специальный соус, тогда как прототип - это просто свойство. Конструкторы обычно содержат тот ключевой бит функциональности, который устанавливает собственные личные данные объектов, которые часто требуются для работы остальной функциональности. Наследование набора функций из цепочки прототипов не принесет пользы, если вы не запустите вручную цепочку конструктора (или используете что-то вроде Class) для настройки.

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

В этом заключается разница между мелкой копией и глубокой копией. $ .extend jQuery по умолчанию имеет малую глубину (я бы предположил, что большинство / все делают) Мелкая копия - это просто передача ссылок без дублирования функций. Это позволяет вам создавать такие объекты, как legos, и получить немного более прямой контроль, позволяющий объединять части нескольких объектов. Мелкая копия, как и использование 10000 объектов, созданных на основе прототипа, не должна сказываться на производительности или памяти. Это также хорошая причина, чтобы быть очень осторожным в том, чтобы случайно делать глубокое копирование, и понимать, что существует большая разница между мелким и глубоким (особенно когда речь идет о DOM). Это также одно из тех мест, где библиотеки Javascript заботятся о тяжелой работе. Когда есть ошибки браузера, которые приводят к сбою браузеров в обработке ситуаций, когда должен быть дешевым, чтобы сделать что-то, потому что должен использовать ссылки и эффективно собирать мусор. Когда это происходит, это обычно требует творческих или раздражающих обходных путей, чтобы гарантировать, что вы не пропускаете копии.

1 голос
/ 27 августа 2011

Будет создан новый план, каждый раз будут создаваться частные методы, такие как _check. Это пустая трата памяти?

Вы не правы. Вторым способом вы выполняете функцию окружения только один раз и затем присваиваете person_real значение Person. Код точно такой же, как и в первом случае (кроме формы _check, конечно). Рассмотрим этот вариант первого способа:

var Person=Class.create();
Person.prototype={
    initialize:function(name,age){
        _check(name,age);
        this.name=name;
        this.age=age;
    },
    say:function(){
        console.log(this.name+','+this.age);
    }
}

function _check(name,age){
    //....the check code here
}

Вы все еще говорите, что _check создается каждый раз, когда вы звоните new Person? Возможно нет. Разница здесь в том, что _check является глобальным и может быть доступен из любого другого кода. Помещая эту часть в функцию и немедленно вызывая ее, мы делаем _check локальным для этой функции.

Методы initialize и say имеют доступ к _check, потому что они являются замыканиями.

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

function getPersonClass(){
    var person_real=Class.create();
    person_real.prototype={
        initialize:function(name,age){
            _check(name,age);
            this.name=name;
            this.age=age;
        },
        say:function(){
            console.log(this.name+','+this.age);
        }
    }

    //some private method
    function _check(name,age){
        //....the check code here
    }
    return person_real;
}

var Person = getPersonClass();

getPersonClass вызывается только один раз. Поскольку _check создается внутри этой функции, это означает, что она также создается только один раз.

...