Ответ 1
Почему этот сниппет делает следующее задание?
that.method = method
Какую цель выполняет
method
? Как он инициализируется?
Он инициализируется как переменная несколькими строками вверх:
method = function () { ... }
Затем строка, которую вы указали, назначает значение этой переменной (ссылку на функцию) на свойство объекта that
, поэтому оно может использоваться как "метод" объекта. Поэтому вы можете сделать это:
var x = constructor(42);
x.method(); // <== Here where we use the function assigned to the property as a method
Подробнее (в моем блоге): Мифические методы
Какова цель объявления объектов таким образом?
Crockford не любит функции JavaScript-конструктора и поэтому не использует их. Поэтому он делает это вместо этого. Одна из самых замечательных особенностей JavaScript - насколько она гибкая. Вы можете использовать его как почти чисто функциональный язык, вы можете использовать его в качестве прототипического языка, и вы даже можете использовать его очень похоже на язык на основе класса, даже если он не основан на классах, поскольку его прототипные функции дают вам очень почти все, что вам нужно для этого (с ES6, он будет иметь все необходимое для использования на основе классов, если захочет). И вы можете смешивать эти подходы, когда считаете нужным это делать. Это просто гибкий.
В отличие от Crockford, мне нравятся функции конструктора и ключевое слово new
. Но мне также очень не нравится использовать их, когда они не являются подходящим инструментом, которого они часто не являются.
Повторите свой комментарий ниже:
Не могли бы вы случайно представить пример функционального фрагмента функции конструктора Crockford? Этот метод используется для инициализации этого чата, или я полностью отключен здесь?
Нет that.member
. member
в коде, который вы показали, не является свойством объекта, это просто переменная. Функция method
, созданная в вызове constructor
, имеет доступ к этой переменной, потому что это замыкание в контексте вызова constructor
, но ничего, что имеет доступ только к возвращенному объекту, может видеть member
. Таким образом, member
действительно является частным для функций, созданных в constructor
. Еще две статьи, которые могут быть полезны здесь: Закрытие не сложно из моего блога, в котором объясняется, что такое "закрытие", и Crockford Частные члены в JavaScript, в котором описывается способ использования личной информации, связанной с объектами, которые он использует в приведенном примере. (Я упоминаю другой способ получения частной информации об объектах в конце этого ответа.)
В примере, который вы цитировали, демонстрируются две существенные не связанные вещи:
-
Средство создания добавленных (не полученных) объектов
-
Средство предоставления действительно частной информации, связанной с этими объектами
Образец, который он показывает, не единственный способ сделать это, но то, что он показывает.
Повторите конкретный пример:
Предположим, мы хотим создать объекты "вещь". Неважно, что это такое, но у них есть свойство под названием "имя" (которое не обязательно должно быть частным). (У них, вероятно, также есть методы, но это не имеет значения, поэтому мы оставим их для краткости и ясности.) Итак, мы начнем с чего-то вроде этого:
// Very simple Crockford-style constructor
function createThing(name) {
return {name: name}; // Again, there'd probably be more to it, this is simple on purpose
}
// Usage
var t = createThing("foo");
console.log(t.name); // "foo"
Пока все хорошо. Теперь мы хотим также иметь возможность создавать вещи, к которым мы можем добавить счетчик, и метод, который "использует" вещь и считает, что использует, возвращая новый счет использования. (Да, этот есть надуманный пример.) Наивная версия, снова используя что-то похожее на способ Крокфорда сделать это, может выглядеть так:
// Naive approach
function createThingWithCounter(name) {
var that = createThing(name);
that.useCounter = 0;
that.use = function() {
// ...do something with `that`...
// Return the new number of times we've "used" the thing
return ++that.useCounter;
};
return that;
}
// Usage
var t = createThingWithCounter("foo");
console.log(t.name); // "foo"
console.log(t.use()); // 1
console.log(t.use()); // 2
Опять же, пока все хорошо. Но проблема в том, что useCounter
является публичным свойством объекта. Таким образом, мы можем общаться с ним вне кода createThingWithCounter
:
var t = createThingWithCounter("foo");
console.log(t.name); // "foo"
console.log(t.use()); // 1
t.useCounter = 0;
console.log(t.use()); // 1 -- uh oh!
Мы не хотим, чтобы useCounter
был общедоступным. Теперь существуют различные подходы к тому, чтобы сделать его приватным, в том числе не делать его приватным вообще, а использовать соглашение об именах (обычно начинать его с подчеркивания, например _useCounter
), что означает "оставьте это в покое или еще!", Но шаблон, который мы 're look on позволяет нам сделать useCounter
по-настоящему конфиденциальным, используя тот факт, что метод use
является замыканием в контексте вызова createThingWithCounter
. Это, плюс немного переупорядочить источник, чтобы лучше соответствовать кавычки, дает нам следующее:
function createThingWithCounter(name) {
var that = createThing(name),
useCounter = 0,
use = function() {
// ...do something with `that`...
// Return the new number of times we've "used" the thing
return ++useCounter;
};
that.use = use;
return that;
}
Теперь useCounter
не является свойством объекта вообще. Он действительно закрытый, ничего за пределами createThingWithCounter
не видит или не изменяет его:
var t = createThingWithCounter("foo");
console.log(t.name); // "foo"
console.log(t.use()); // 1
t.useCounter = 0; // <== Has absolutely no effect on the real counter
console.log(t.use()); // 2
Итак, наш конкретный (если надуманный) пример. Здесь, как он сопоставляется с кавычками:
-
constructor
=createThingWithCounter
-
otherConstructor
=createThing
-
member
=useCounter
-
method
=use
Теперь я хочу подчеркнуть, что в этом нет ничего, что нельзя делать с обычными конструкторскими функциями вместо new
. Это даже не выглядит иначе:
// Doing the same thing with normal constructor functions and `new`
function Thing(name) {
this.name = name;
}
// Usage
var t = new Thing("foo");
console.log(t.name); // "foo"
// Augmented things
function ThingWithCounter(name) {
var useCounter = 0;
Thing.call(this, name);
this.use = function() {
// ...do something with `this`...
// Return the new number of times we've "used" the thing
return ++useCounter;
};
}
// Usage of augmented things
var t = new ThingWithCounter("foo");
console.log(t.name); // "foo"
console.log(t.use()); // 1
t.useCounter = 0; // <== Has absolutely no effect on the real counter
console.log(t.use()); // 2
Это просто разные способы достижения аналогичных целей.
Есть и другой способ: деривация, а не дополнение. И, подобно дополнению, вывод может быть выполнен в стиле Крокфорда или через стандартные функции конструктора. Это прекрасная гибкость языка.: -)
Последнее примечание о личной информации: В обоих стилях выше, useCounter
действительно является частным, но это не свойство объекта. Это не на объект вообще. И есть несколько затрат, связанных с тем, как мы получаем эту конфиденциальность: во-первых, мы должны создать функцию use
для каждого экземпляра. Эти функции не разделяются между экземплярами, такими как функции, связанные с прототипами (и другими способами). В 2014 году стоимость там минимально минимальна, современные двигатели достаточно умны в оптимизации; это было бы намного дороже 15 лет назад.
Другая стоимость заключается в том, что функция use
не может быть повторно использована в другом месте, что ставит ее в противоречие с подавляющим большинством функций в JavaScript. Если вы посмотрите на спецификацию, вы увидите этот язык примерно для каждого предопределенного метода JavaScript:
ПРИМЕЧАНИЕ. Функция xyz намеренно является общей; он не требует, чтобы его значение this являлось объектом Whatsit. Поэтому он может быть передан другим типам объектов для использования в качестве метода.
Итак, если у меня есть что-то похожее на массив, но не массив (например, объект JavaScript arguments
), я могу наложить на него методы из Array.prototype
и, если мой объект достаточно массивный, они будут работать отлично:
var arrayLikeObject = {
length: 2,
0: "one",
1: "two",
indexOf: Array.prototype.indexOf
};
console.log(arrayLikeObject.indexOf("two")); // 1
Наш метод use
не может быть повторно использован таким образом. Он закрыт для экземпляра "вещь с счетчиком", к которому он относится. Если бы мы попытались использовать его таким образом, мы закончили бы странный перекрестный разговор между объектом, на который мы его положили, и оригинальным объектом "вещь с счетчиком", из которого мы его взяли. Странный перекрестный разговор такого рода - это отличный способ иметь действительно неприятные, трудоемкие, неудобные ошибки в проектах любого размера.
Следующая версия стандарта JavaScript, ECMAScript6, дает нам способ иметь частные свойства. То есть, фактические свойства объектов, которые являются частными (или как частные, как информация попадает в современные языки/среды). Поскольку свойства являются действительно свойствами объекта, нам не нужно полагаться на функцию use
, являющуюся замыканием, и мы можем определить ее на объекте прототипа и/или повторно использовать его на других объектах — то есть ни одна из вышеуказанных затрат не применяется.
И даже лучше, довольно умный образец, который они используют, чтобы добавить эту функцию, можно использовать прямо сейчас, чтобы получить ~ 90% преимуществ, которые она предлагает. Итак, если это тема, которая вас интересует, последнее сообщение блога для вас: Частные объекты в ES6 - и ES3 и ES5.