Я не понимаю Крокфорда по JavaScript: путь вперед

В лекции под названием "Путь вперед" Дуглас Крокфорд разделяет, что он больше не использует "новый" в своем JavaScript и отлучает себя от "this". Он говорит, что хорошей функцией JavaScript было не столько прототипное наследование, сколько классное наследование. Он предлагает следующий фрагмент, который определяет его объекты:

function constructor(init) {
    var that = other_constructor(init),
        member,
        method = function () {
            // init, member, method
        };
    that.method = method;
    return that;
}

Он объясняет:


У меня есть функция, которая потребует некоторого значения для ее инициализации. Я рекомендую быть объектом (таким образом, вы можете использовать текст JSON, который вы можете использовать для создания новых экземпляров). Вы можете вызвать другой конструктор, если хотите наследовать его; в любом случае вы собираетесь создать объект и поместить его в переменную с именем "that" . Вы создадите все свои переменные-члены (вещи, которые станут свойствами объекта, ваши переменные метода, вещи, которые будут действовать на объект...), эти методы будут функциями, которые будут закрываться по значению инициализации по всем переменным-членам и всем переменным метода.

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


Почему этот сниппет делает следующее задание?

that.method = method

Какую цель выполняет метод? Как он инициализируется? Какова цель объявления объектов таким образом?

Его лекция была довольно общей, и он потратил минуту на эту часть этого, не уточнив свои рассуждения или ссылаясь на ресурсы. Может ли кто-нибудь пролить свет на мотивацию для этого стиля программирования?

Ссылка на видео: https://www.youtube.com/watch?v=3WgVHE5Augc Начните с отметки 37 минут.

Ответы

Ответ 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.

Ответ 2

Цель задания - сделать метод "общедоступным". Без этого назначения метод "private" для "класса".

Возможно, я могу попытаться сделать код более понятным:

function constructor(init) {
    // Call the mother constructor. Or just do that = init.
    var that = other_constructor(init);

    // Private members
    var member1, member2;

    // Methods
    var method1 = function() {
        do_stuff();
    };

    var method2 = function() {
        do_stuff();
    };

    // Make some method public.
    that.method1 = method1;

    return that;
}

Затем в вашем коде вы можете использовать свой конструктор следующим образом:

var obj = constructor(other_obj);
obj.method1();

Ответ 3

Очевидно, некоторые другие хорошие ответы здесь, но TL; DR:

Почему этот сниппет делает следующее задание?

this.method = method

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

function animal_constructor(init){
    return {};
}

function dog_constructor(init) {
    // Create a generic animal object
    var dog = animal_constructor(init),
        // Define a local function variable called 'bark'
        bark = function() {
            alert('woof!');
        };

    // Add the 'bark' function as a property of the generic animal
    dog.bark = bark;
    // Return our now-fancy quadriped
    return dog;
}

// Try it out
var init = {},
    animal = animal_constructor(init),  // generic animals can't bark
    yeller = dog_constructor(init);     // fancy dog-animals can!

yeller.bark();  // > woof!
animal.bark();  // > Uncaught TypeError: undefined is not a function 

Ответ 4

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

Конструктор для животных:

function Animal (specs) {
  // starting with an empty object, since I don´t want to inherit
  var that = {},

      // private attribute(s)
      name = specs.name,

      // public method(s)
      setName = function (newName) {
        name = newName;
      },
      getName = function () {
        return name;
      };

  // setting up my public interface
  // everything else is private
  that.setName = setName;
  that.getName = getName;

  return that;
}

Создание моего животного:

var anymal = Animal({ name: "Yoshi" });
console.log(anymal.name); // undefined, since it´s private
console.log(anymal.getName()); // "Yoshi"
anymal.setName("Sanic");
console.log(anymal.getName()); // "Sanic"

Конструктор для собаки:

function Dog (specs) {
  // now I want to inherit from Animal
  // the constructor will return the public interface for Animal
  var that = Animal(specs),

      // private attribute(s)
      bark = specs.bark,

      // public method(s)
      setBark = function (newBark) {
        bark = newBark;
      },
      getBark = function () {
        return bark;
      },
      barks = function () {  
        console.log(bark);
      };

  // these methods will be added to the Animal public interface
  that.setBark = setBark;
  that.getBark = getBark;
  that.barks = barks;

  return that;
}

Создание моей собаки:

var anydog = new Dog({ name: "Klonoa", bark: "Wah who!" });
console.log(anydog.getName()); // "Klonoa"
anydog.barks(); // "Wah who!"

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

Надеюсь, что это поможет!