JavaScript без this

Original author: Fanis Despoudis
  • Translation
Ключевое слово this в JavaScript можно назвать одной из наиболее обсуждаемых и неоднозначных особенностей языка. Всё дело в том, что то, на что оно указывает, выглядит по-разному в зависимости от того, где обращаются к this. Дело усугубляется тем, что на this влияет и то, включён или нет строгий режим.



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

Означает ли это, что «борьба с this» окончена? Нет конечно. Если поразмыслить, можно обнаружить, что задачи, которые традиционно решают с привлечением функций-конструкторов и this, можно решить и другим способом. Этот материал посвящён описанию именно такого подхода к программированию: никаких new и никаких this на пути к более простому, понятному и предсказуемому коду.

Сразу хотелось бы отметить, что этот материал предназначен для тех, кто знает, что такое this. Мы не будем рассматривать здесь основы JS, наша главная цель — описание особого подхода к программированию, исключающего использование this, и, благодаря этому, способствующего устранению потенциальных неприятностей, с this связанных. Если вы хотите освежить свои знания о this, вот и вот материалы, которые вам в этом помогут.

Есть идея


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

function makeAdder(base) {
  let current = base;
  return function(addition) {
    current += addition;
    return current;    
  }
}

Функция makeAdder() принимает параметр base и возвращает другую функцию. Эта функция принимает числовой параметр. Кроме того, у неё есть доступ к переменной current. Когда её вызывают, она прибавляет переданное ей число к current и возвращает результат. Между вызовами значение переменной current сохраняется.

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

Замыкания — весьма мощная возможность JavaScript. Их корректное использование позволяет создавать сложные программные системы с надёжно разделёнными уровнями абстракции.

Выше мы возвращали из функции другую функцию. С тем же успехом, вооружившись имеющимися у нас знаниями о замыканиях, из функции можно возвратить объект. Этот объект будет иметь доступ к локальному окружению. Фактически, его можно воспринимать как открытое API, которое даёт доступ к функциям и переменным, хранящимся в замыкании. То, что мы только что описали, называется «шаблон revealing module».

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

Вот пример:

let counter = (function() {
  let privateCounter = 0;
  function changeBy(val) {
    privateCounter += val;
  }
  return {
    increment: function() {
      changeBy(1);
    },
    decrement: function() {
      changeBy(-1);
    },
    value: function() {
      return privateCounter;
    }
  };   
})();
counter.increment();
counter.increment();
console.log(counter.value()); // выводит в лог 2

Как видите, переменная privateCounter — это данные, с которыми нам надо работать, скрытые в замыкании и недоступные напрямую извне. Общедоступные методы increment(), decrement() и value() описывают операции, которые можно выполнять с privateCounter.

Теперь у нас есть всё необходимое для программирования на JavaScript без использования this. Рассмотрим пример.

Двухсторонняя очередь без this


Вот простой пример использования замыканий и функций без this. Это — реализация известной структуры данных, которая называется двухсторонняя очередь (deque, double-ended queue ). Это — абстрактный тип данных, который работает как очередь, однако, расти и уменьшаться наша очередь может в двух направлениях.

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

Вот список типовых операций, которые должна уметь выполнять двухсторонняя очередь:

  • create: Создание нового объекта двухсторонней очереди
  • isEmpty: Проверка, пуста ли очередь
  • pushBack: Добавление нового элемента в конец очереди
  • pushFront: Добавление нового элемента в начало очереди
  • popBack: Удаление и возврат последнего элемента очереди
  • popFront: Удаление и возврат первого элемента очереди

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

Итак, для начала нужна переменная, назовём её data, которая будет хранить данные каждого элемента очереди. Кроме того, нам нужны указатели на первый и последний элементы, на голову и хвост очереди. Назовём их, соответственно, head и tail. Так как очередь мы создаём на основе связного списка, нам нужен способ связи элементов, поэтому для каждого элемента требуются указатели на следующий и предыдущий элементы. Назовём эти указатели next и prev. И, наконец, требуется отслеживать количество элементов в очереди. Воспользуемся для этого переменной length.

Теперь поговорим о группировке вышеописанных переменных. Каждому элементу очереди, узлу, нужна переменная с его данными — data, а также указатели на следующий и предыдущий узлы — next и prev. Исходя из этих соображений создадим объект Node, представляющий собой элемент очереди:

let Node = {
  next: null,
  prev: null,
  data: null
};

Каждая очередь должна хранить указатели на собственную голову и хвост (переменные head и tail), а также сведения о собственной длине (переменная length). Исходя из этого, определим объект Deque следующим образом:

let Deque = {
  head: null,
  tail: null,
  length: 0
};

Итак, у нас есть объект, Node, представляющий собой отдельный узел очереди, и объект Deque, представляющий саму двухстороннюю очередь. Их нужно хранить в замыкании:

module.exports = LinkedListDeque = (function() {
  let Node = {
    next: null,
    prev: null,
    data: null
  };
  let Deque = {
    head: null,
    tail: null,
    length: 0
  };
 // тут нужно вернуть общедоступное API
})();

Теперь, после того, как переменные помещены в замыкание, можно описать метод create(). Он устроен довольно просто:

function create() {
  return Object.create(Deque);
}

С этим методом мы разобрались. Нельзя не заметить, что очередь, которую он возвращает, не содержит ни одного элемента. Совсем скоро мы это исправим, а пока создадим метод isEmpty():

function isEmpty(deque) {
  return deque.length === 0
}

Этому методу мы передаём объект двухсторонней очереди, deque, и проверяем равняется ли его свойство length нулю.

Теперь пришло время метода pushFront(). Для того, чтобы его реализовать, надо выполнить следующие операции:

  1. Создать новый объект Node.
  2. Если очередь пуста, нужно установить указатели головы и хвоста очереди на новый объект Node.
  3. Если очередь не пуста, нужно взять текущий элемент очереди head и установить его указатель prev на новый элемент, а указатель next нового элемента установить на тот элемент, который записан в переменную head. В результате первым элементом очереди станет новый объект Node, за которым будет следовать тот элемент, который был первым до выполнения операции. Кроме того, надо не забыть обновить указатель очереди head таким образом, чтобы он ссылался на её новый элемент.
  4. Увеличить длину очереди, инкрементировав её свойство length.

Вот как выглядит код метода pushFront():

function pushFront(deque, item) {
  // Создадим новый объект Node
  const newNode = Object.create(Node);
  newNode.data = item;
  
  // Сохраним текущий элемент head
  let oldHead = deque.head;
  deque.head = newNode;
  if (oldHead) {
    // В этом случае в очереди есть хотя бы один элемент, поэтому присоединим новый элемент к началу очереди
    oldHead.prev = newNode;
    newNode.next = oldHead;
  } else {// Если попадаем сюда — очередь пуста, поэтому просто запишем новый элемент в tail.
    deque.tail = newNode;
  }
  // Обновим переменную length
  deque.length += 1;
  
  return deque;
}

Метод pushBack(), позволяющий добавлять элементы в конец очереди, очень похож на тот, что мы только что рассмотрели:

function pushBack(deque, item) {
  // Создадим новый объект Node
  const newNode = Object.create(Node);
  newNode.data = item;
  
  // Сохраним текущий элемент tail
  let oldTail = deque.tail;
  deque.tail = newNode;
if (oldTail) {
    // В этом случае в очереди есть хотя бы один элемент, поэтому присоединим новый элемент к концу очереди
    oldTail.next = newNode;
    newNode.prev = oldTail;
  } else {// Если попадаем сюда — очередь пуста, поэтому просто запишем новый элемент в head.
    deque.head = newNode;
  }
  // Обновим переменную length
  deque.length += 1;
  
  return deque;
}

После того, как методы реализованы, создадим общедоступное API, которое позволяет вызывать извне методы, хранящиеся в замыкании. Сделаем это, вернув соответствующий объект:

return {
 create: create,
 isEmpty: isEmpty,
 pushFront: pushFront,
 pushBack: pushBack,
 popFront: popFront,
 popBack: popBack
}

Здесь, помимо тех методов, которых мы описали выше, есть и те, которые пока не созданы. Ниже мы к ним вернёмся.

Как же всем этим пользоваться? Например, так:

const LinkedListDeque = require('./lib/deque');
d = LinkedListDeque.create();
LinkedListDeque.pushFront(d, '1'); // [1]
LinkedListDeque.popFront(d); // []
LinkedListDeque.pushFront(d, '2'); // [2]
LinkedListDeque.pushFront(d, '3'); // [3]<=>[2]
LinkedListDeque.pushBack(d, '4'); // [3]<=>[2]<=>[4]
LinkedListDeque.isEmpty(d); // false

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

Домашнее задание


Подозреваю, вы думали, что добрались до конца материала, и всё поняли, не написав ни строчки кода. Правда? Для того, чтобы добиться полного понимания того, о чём мы говорили выше, ощутить на практике предложенный подход к программированию, я советую вам выполнить следующие упражнения. Просто клонируйте мой репозиторий на GitHub и приступайте к делу. (Решений задачек там, кстати, нету.)

  1. Основываясь на рассмотренных выше примерах реализации методов, создайте остальные. А именно, напишите функции popBack() и popFront(), которые, соответственно, удаляют и возвращают первый и последний элементы очереди.

  2. В этой реализации двухсторонней очереди используется связный список. Ещё один вариант основан на обычных массивах JavaScript. Создайте все необходимые для двухсторонней очереди операции, используя массив. Назовите эту реализацию ArrayDeque. И помните — никаких this и new.

  3. Проанализируйте реализации двухсторонних очередей с использованием массивов и списков. Подумайте о временной и пространственной сложности используемых алгоритмов. Сравните их и запишите свои выводы.

  4. Ещё один способ реализации двухсторонних очередей заключается в одновременном использовании массивов и связных списков. Такую реализацию можно назвать MixedQueue. При таком подходе сначала создают массив фиксированного размера. Назовём его block. Пусть его размер будет 64 элемента. В нём будут храниться элементы очереди. При попытке добавить в очередь больше 64-х элементов создают новый блок данных, который соединяют с предыдущим с помощью связного списка по модели FIFO. Реализуйте методы двухсторонней очереди, используя этот подход. Каковы преимущества и недостатки такой структуры? Запишите свои выводы.

  5. Эдди Османи написал книгу «Шаблоны проектирования в JavaScript». Там он говорит о недостатках шаблона revealing module. Один из них заключается в следующем. Если приватная функция модуля использует общедоступную функцию того же модуля, эту общедоступную функцию нельзя переопределить извне, пропатчить. Даже если попытаться это сделать, приватная функция всё равно будет обращаться к исходной приватной реализации общедоступной функции. То же самое касается и попытки изменения извне общедоступной переменной, доступ к которой даёт API модуля. Разработайте способ обхода этого недостатка. Подумайте о том, что такое зависимости, как инвертировать управление. Как обеспечить то, чтобы все приватные функции модуля работали с его общедоступными функциями так, чтобы у нас была возможность контролировать общедоступные функции. Запишите свои идеи.

  6. Напишите метод, join, который позволяет соединять две двухсторонних очереди. Например, вызов LinkedListDeque.join(first, second) присоединит вторую очередь к концу первой и вернёт новую двухстороннюю очередь.

  7. Разработайте механизм обхода очереди, который не разрушает её и позволяет выполнять итерации по ней в цикле for. Для этого упражнения можете использовать итераторы ES6.

  8. Разработайте неразрушающий механизм обхода очереди в обратном порядке.

  9. Опубликуйте то, что у вас получилось, на GitHub, расскажите всем о том, что создали реализацию двухсторонней очереди без this, и о том, как хорошо вы во всём этом разбираетесь. Ну и про меня упомянуть не забудьте.

После того, как справитесь с этими основными заданиями, можете сделать и ещё несколько дополнительных.

  1. Используйте любой фреймворк для тестирования и добавьте тесты ко всем своим реализациям двухсторонних очередей. Не забудьте протестировать пограничные случаи.

  2. Переработайте реализацию двухсторонней очереди так, чтобы она поддерживала элементы с приоритетом. Элементам такой очереди можно назначать приоритет. Если такая очередь будет использоваться для хранения элементов без назначения им приоритета, её поведение ничем не будет отличаться от обычной. Если же элементам назначают приоритет, нужно обеспечить, чтобы после каждой операции последний элемент в списке имел бы наименьший приоритет, а первый — наибольший. Создайте тесты и для этой реализации двухсторонней очереди.

  3. Полином — это выражение, которое может быть записано в виде an * x^n + an-1*x^n-1 + ... + a1x^1 + a0. Здесь an..a0 — это коэффициенты полинома, а n…1 — показатели степени. Создайте реализацию структуры данных для работы с полиномами, разработайте методы для сложения, вычитания, умножения и деления полиномов. Ограничьтесь только упрощёнными полиномами. Добавьте тесты для проверки правильности решения. Обеспечьте, чтобы все методы, возвращающие результат, возвращали бы его в виде двухсторонней очереди.

  4. До сих пор предполагалось, что вы используете JavaScript. Выберите какой-нибудь другой язык программирования и выполните все предыдущие упражнения на нём. Это может быть Python, Go, C++, или что угодно другое.

Итоги


Надеюсь, упражнения вы выполнили и узнали с их помощью что-нибудь полезное. Если вы думаете, что преимущества отказа от использования this стоят усилий по переходу на новую модель программирования, взгляните на eslint-plugin-fp. С помощью этого плагина можно автоматизировать проверки кода. И, если вы работаете в команде, прежде чем отказываться от this, договоритесь с коллегами, иначе, при встрече с ними, не удивляйтесь их угрюмым лицам. Хорошего вам кода!

Уважаемые читатели! Как вы относитесь к this в JavaScript?
RUVDS.com
1,072.98
RUVDS – хостинг VDS/VPS серверов
Share post

Comments 61

    +7
    очень не хватает статьи китайца, который будет рассказывать о проблемах связанных с вилкой.
      0
      Похожим образом много пишут в тайпскрипте, в том смысле что его компилятор так написан много где.
      Когда в функции объявляются переменные а потом гора вложенных функций используется. По сути как инстанс класса со своими полями, только всё приватно и вложено тут.
      Пример. Другие примеры не отображаются на гихабе ввиду огромности размера файла.
      https://github.com/Microsoft/TypeScript/blob/master/src/compiler/tsc.ts#L99
      И работая с таким кодом повседневно, я не скажу что это лучшее решение. Хотя в некоторых местах катит. Но расширяемость и переиспользуемость сильно страдают. Иногда вынужден копипастить куски из компилера вместо того чтобы позвать.
        0

        А ещё где-то два года назад код без this перестал быть быстрее кода с ним.

        +13
        Вместо того чтобы разобраться в поведении this, и писать разборчивый код в котором будет легко понять «где» сейчас this — строить какие-то абстракции, нагромождения, обвязки. Ммм, классно. И ежу понятно что всё что написано с this — можно переписать таким образом чтобы его не использовать, вот только зачем?
          0
          Это точно. Очень жаль, что разрабы не придумали чего-то подобного для super.
            0

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

              +1

              А вы думаете он знает секрет идеального кода?

                +2
                Действительно, ведь делегирование в JS не нужно, bind, call и apply придумали неизвестно для кого, а new — вообще бесполезно использовать
                  0
                  Ну а есть другие мощные разработчики, которые пишут используя this и радуются жизни. Мнение одного человека ничего не значит, даже несмотря на то что это создатель языка.
                  0

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


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

                    +1
                    требует постоянно держать в уме контекст использования

                    Как будто это трудно.
                      0

                      Как будто это нужно.

                      0
                      Я доверяю рефакторинг профессионалам, а если человек не всегда знает чему равен this — возможно его не стоит брать в команду.
                    +11
                    За много лет работы с JS ни разу не было проблем с this, более того в умелых руках это очень мощный инструмент языка, а проблемы описанные в статье говорят лишь о том, что автор не умеет и не понимает JavaScript…

                    Теперь подумайте вот о чем:
                    1. На каждый экземпляр объекта созданного таким образом будут создаваться новые функции-методы. Это засоряет память, это убивает оптимизацию
                    Функции в js компилируются в момент первого вызова и оптимизируются при нескольких последующих вызовах. В случае Вашего подхода это будет делаться для каждого инстанса, что дорого, очень
                    Если же использовать правильный подход с прототипами функции будут созданы и скомпилированы только 1 раз, все инстансы будут использовать одну и ту же функцию. Мы экономим память. Инстанс создается быстрее. Инстанс работает быстрее, когда его методы уже скомпилированы при работе с предыдущим инстансом.
                    2. Как быть с наследованием? Допустим я хочу на базе Вашего двухсвязного списка сделать список с произвольным доступом на чтение и возможностью итерации. Если бы были прототипы — я бы относледовался от Вашего прототипа и добавил бы необходимые мне методы, но Ваш код я не смогу переиспользовать, это плохо
                      0

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

                        0
                        Замыкания не создают новых функций, они лишь хранят ссылки на контекст и на единожды созданную функцию…

                        Хранят где? Правильно, во вновь создаваемых функциях!

                          0

                          Замыкания, вопреки всеобщему заблуждению, — не функции. Это — структуры, хранящие две ссылки: на контекст и на собственно функцию (исполняемый код). Функция создаётся единожды при парсинге исходников.

                            +1
                            Если структура выглядит как функция (typeof x == 'function') и ведёт себя как функция (можно вызвать x(42)), то что же это, если не функция?
                              0
                              Чудесно. А причём здесь замыкание?
                                0
                                При том, что переменные внутри функции (но не объявленные в ней) имеют особые (динамически меняющиеся!) значения, зависящие от контекста, где была создана эта функция.
                                  0
                                  Вы путаете функцию и замыкание.
                                    0
                                    Спасибо за столь обоснованный, подробный, и, главное, хорошо аргументированный ответ!
                                0
                                let x = new Proxy( Function , {
                                    apply: function(){ console.log( 'i am callable, but not a function' ) }
                                } )
                                
                                x()
                                  0
                                  if(typeof(x) == 'function'){"i am a function"}
                                  


                                  Вот неожиданно, да? )
                                    0
                                    if( typeof( null ) === 'object' ) { console.log( "i am object!" ) }

                                    Внезапно!

                                      0
                                      капец как внезапно…

                                      typeof null // object (баг в ECMAScript, должно быть null)
                                      typeof undefined // undefined

                                      источник: https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/null
                                0
                                Впрочем чего я спорю! Если, как вы сказали, замыкания не создают новых функций и вы готовы подтвердить свои слова, то вам не составит труда привести пример замыкания без создания функции. На этом и спору конец.
                                  0
                                  Замыкание — это внутренний объект движка. Он не тождественен функции. Я лично, не знаю, что Вам показать, но может, кто-то из присутствующих копался в потрохах движка и знает способ увидеть замыкание непосредственно.
                                    0
                                    Зачем залезать в движок, когда можно залезть в спецификацию? Там никакого специального объекта нет. А детали конкретной реализации не имеют никакого значения. Это всего лишь абстракции разработчиков движка, нет смысла придавать им сакральный смысл.
                                      0
                                      Там никакого специального объекта нет.

                                      Боюсь ошибиться, но кажется именно из спеки я о нём и узнал.
                                        0
                                          0
                                          Прямо по ссылке слова «closure» нет. Зато в другом месте, где оно определяется, ваши слова прямо опровергаются: «Let closure be the result of creating a new Function object…»
                                            0

                                            Как вы думаете, что означает слово "let" в данном контексте?


                                            То, что мы привыкли называть "замыканием" в спеке называется "объект функции", а собственно код функции находится в скрытом свойстве [[Code]] этого объекта и он общий для всех замыканий из одного места в исходниках: http://www.ecma-international.org/ecma-262/5.1/#sec-13.2

                                              0
                                              Это уже ваша интерпретация. Более того, судя по словам «то, что мы привыкли называть «замыканием»», вы понимаете, что ваша терминология отличается от обычной. Не надо так, если хотите чтобы вас поняли.
                                                0

                                                Вы на вопрос ответьте.

                              0
                              Ну и развели же спор, попробуйте открыть консоль и выполнить функцию makeAdder из статьи.
                              А потом вот это
                              makeAdder(1) === makeAdder(1)
                              

                              странно что по вашему мнению одна и та же функция возвращаемая при выполнении не равна сама себе
                                0

                                Это объект функции. Сама функция как последовательность операторов — используется повторно.

                              0
                              Но ведь в данном случае инстансы не содержат функций. Вся реализация собрана в одном месте, а в инстансах только данные.
                                0

                                Любой объект (нода, класс, функция, массив), лежащий
                                в одном пространстве имён (ключей) с именами переменных является для них замыканием.

                                  0
                                  Вот здесь подробно про this и замыкания https://habrahabr.ru/company/hexlet/blog/266443/
                              +7
                              Отказом от использования this, объекты в JS легким движением руки превращаются в структуры из C.
                              Никаких методов. Никаких конструкторов.

                              И стиль из ООП превращается в процедурное программирование
                                0
                                Они скорее ближе к ассоциативным массивам, чем к структурам.
                                  0

                                  JIT компилятор будет стараться использовать именно структуры, пока вы не начнёте менять набор полей объекта.

                                +1
                                Чёт больше похоже на: Не знаю ООП, использовать не хочу, разбираться в тонкостях языка не хочу, буду делать так, как получается.
                                  +1
                                  Например — стрелочные функции и привязка this. Как результат, при разработке можно практически полностью обойтись без this.

                                  Вообще-то если не обращаться к this, то стрелочная функция ровно ничем не отличается от обычной:)


                                  Дальше можно не читать, в общем.

                                    0
                                    стрелочная функция еще не имеет своей arguments, а наследует его из замыкания
                                      +1

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


                                      Но, повторюсь, если кто-то ниасилил this в JS по десяткам статей, появляющихся каждый год, то разница минимальна.

                                    +2

                                    Э… Я вижу просто явную передачу this в качестве аргумента функции (вместо обычной неявной), и всё. Что изменилось, кроме увеличения размера кода?

                                      0
                                      Не понял, что нового в статье. Так писали 7-8 лет назад (а может и раньше), чтобы не заморачиваться с наследованием и чтобы симулировать private. Особенно такой стиль любят выходцы из С#/С++. На сегодня, с приходом классов, этот стиль уходит. Возможно в одном из следующих релизов можно будет декларировать в качестве члена класса что нибудь типа стрелочных функций с привязкой this
                                        0

                                        Уже можно через class properties:


                                        class Foo {
                                          bar = 'bar';
                                          bound = () => console.log(this.bar);
                                        }

                                        Очень часто используется в React.

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

                                            Да, только не привязанная к инстансу. А чтобы привязать для передачи метода, например, в виде колбэка, все-равно придется байндить руками на месте вызова (что порождает новую функцию на каждый вызов), либо в конструкторе. А класс-проперти — просто шорткат для конструктора.

                                              0
                                              Но в случаи с связыванием контекста не дублируется логика находящаяся в функциональном выражении.
                                        +3

                                        Пример того как писать неподдерживаемый/корявый код.

                                          0
                                          из функции можно возвратить объект. Этот объект будет иметь доступ к локальному окружению

                                          Это неверно. Объект ни к чему доступа иметь не может в принципе. Доступ к скоупу будут иметь только функции, в нём созданные. Всё. Они могут быть методами объекта, который вы вернёте, но сам объект тут ни при чём. Если вернуть объект вида { foo: 1 }, скоуп почистится сборщиком мусора. Если присвоить в качестве метода объекта функцию, объявленную снаружи, доступа к скоупу у неё тоже не будет.
                                            0
                                            шило на мыло… если сложно javscript-программисту использовать this, то в реальном проекте с кучей вот таких абстракций — получится работа кода проекта не более предсказуемо чем с this.
                                              0
                                              Вы посвятили параграф рассказывая про замыкания, но конкретно в вашем примере нет никакого смысла в замыкании.

                                              Тут не имеет смысла ничего замыкать, модуль в котором вы пишите этот код делает это за вас:
                                              module.exports = LinkedListDeque = (function() {
                                                let Node = {
                                                  next: null,
                                                  prev: null,
                                                  data: null
                                                };
                                                let Deque = {
                                                  head: null,
                                                  tail: null,
                                                  length: 0
                                                };
                                               // тут нужно вернуть общедоступное API
                                              })();
                                              

                                              Этот код, во всех случаях, будет работать так-же:
                                              let Node = {
                                                next: null,
                                                prev: null,
                                                data: null
                                              };
                                              let Deque = {
                                                head: null,
                                                tail: null,
                                                length: 0
                                              };
                                              module.exports = {
                                                // тут нужно вернуть общедоступное API
                                              };
                                              
                                                0
                                                Были минусы, и наверное, они по делу, я не удосужился объяснить детальнее. Посмотрите на код внимательнее. Это не фабрика, это не функция конструктор, это анонимная функция, которая сразу же, при запросе модуля, вызывается, и результат работы которой присваивается переменной LinkedListDeque, которая потом экспортируется. Замыкание которая эта функция создаёт, не несет никакого смысла к контексте модулей commonJS которые тут используются. Оно не предотвращает side effects и не инкасплуриует какой-либо функционал.
                                                Писать в таком стиле есть смысл, только, если помимо этого кода, в файле модуля есть еще какой-то функционал, от которого следует изолироваться, что уже было бы неправильно с точки зрения cohesion элементов внутри модуля.
                                                Статья приводит хороший пример из функционального программирования, в частности, о преимуществе pure functions. К основному примеру, замыкания описанные выше, не имеют никакого отношения. Но этот пример, мог бы отлично продемонстрировать то, как можно применить каррирование, которое является отличным примером паттерна функционального программирования, который может помочь избавиться от this.
                                                0
                                                Краткий курс по основам ООП, но без ООП? ))
                                                  0
                                                  На самом деле это паттерн модуль + паттерн открытия модуля.
                                                  Такой подход позволяет работать и с this (он есть в замыкании, просто в своем коде вы его не применяете).
                                                  Такой подход хорош для эмуляции защищенных свойств и методов. Но отсутсвие вынести общее поведение в прототип приводит к тому, что каждый инстанс расходует больше памяти, нежели работа с цепочкой прототипов.

                                                  Еще в примере кода возвращается инстанс, лучше возвращать конструктор. Чтобы можно было создать свой инстанс, если потребуется более 1 инстанса модуля и провайдить настройки в конструктор, если модуль настраиваемый.
                                                    0
                                                    Как раз недавно завёл issue. https://github.com/Microsoft/TypeScript/issues/17680

                                                    Посмотрите например на такой код:
                                                    https://github.com/Microsoft/TypeScript/blob/ceae613e4c0ba36829a2381687883ecdc6b169c3/src/services/services.ts#L1056
                                                    Такой код не расширяем и не переиспользуем от слова совсем. Есть примеры лучше, но там гитхаб не хочеть отображать большой файл.

                                                  Only users with full accounts can post comments. Log in, please.