Ключевое слово 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?
Поделиться с друзьями
-->

Комментарии (58)


  1. vasIvas
    27.07.2017 14:32
    +7

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


  1. Aquahawk
    27.07.2017 14:58

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


    1. maeris
      28.07.2017 06:57

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


  1. vlreshet
    27.07.2017 15:20
    +13

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


    1. Zenitchik
      27.07.2017 16:38

      Это точно. Очень жаль, что разрабы не придумали чего-то подобного для super.


    1. Nakosika
      27.07.2017 20:38

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


      1. vintage
        27.07.2017 20:56
        +1

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


      1. 0shn1x
        27.07.2017 21:14
        +2

        Действительно, ведь делегирование в JS не нужно, bind, call и apply придумали неизвестно для кого, а new — вообще бесполезно использовать


      1. vlreshet
        27.07.2017 21:35

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


    1. search
      28.07.2017 12:04

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


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


      1. Zenitchik
        28.07.2017 14:03
        +1

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

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


        1. raveclassic
          28.07.2017 14:43

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


      1. rboots
        28.07.2017 22:12

        Я доверяю рефакторинг профессионалам, а если человек не всегда знает чему равен this — возможно его не стоит брать в команду.


  1. bingo347
    27.07.2017 15:39
    +11

    За много лет работы с JS ни разу не было проблем с this, более того в умелых руках это очень мощный инструмент языка, а проблемы описанные в статье говорят лишь о том, что автор не умеет и не понимает JavaScript…

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


    1. vintage
      27.07.2017 21:00

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


      1. Aingis
        28.07.2017 13:33

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

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


        1. vintage
          28.07.2017 14:44

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


          1. Aingis
            28.07.2017 14:52
            +1

            Если структура выглядит как функция (typeof x == 'function') и ведёт себя как функция (можно вызвать x(42)), то что же это, если не функция?


            1. Zenitchik
              28.07.2017 16:34

              Чудесно. А причём здесь замыкание?


              1. Aingis
                28.07.2017 18:14

                При том, что переменные внутри функции (но не объявленные в ней) имеют особые (динамически меняющиеся!) значения, зависящие от контекста, где была создана эта функция.


                1. Zenitchik
                  28.07.2017 23:51

                  Вы путаете функцию и замыкание.


                  1. Aingis
                    29.07.2017 12:46

                    Спасибо за столь обоснованный, подробный, и, главное, хорошо аргументированный ответ!


            1. vintage
              28.07.2017 23:37

              let x = new Proxy( Function , {
                  apply: function(){ console.log( 'i am callable, but not a function' ) }
              } )
              
              x()


              1. KirEv
                28.07.2017 23:45

                if(typeof(x) == 'function'){"i am a function"}
                


                Вот неожиданно, да? )


                1. vintage
                  28.07.2017 23:55

                  if( typeof( null ) === 'object' ) { console.log( "i am object!" ) }

                  Внезапно!


                  1. KirEv
                    29.07.2017 00:05

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

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

                    источник: https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/null


          1. Aingis
            29.07.2017 16:45

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


            1. Zenitchik
              29.07.2017 17:05

              Замыкание — это внутренний объект движка. Он не тождественен функции. Я лично, не знаю, что Вам показать, но может, кто-то из присутствующих копался в потрохах движка и знает способ увидеть замыкание непосредственно.


              1. Aingis
                29.07.2017 17:53

                Зачем залезать в движок, когда можно залезть в спецификацию? Там никакого специального объекта нет. А детали конкретной реализации не имеют никакого значения. Это всего лишь абстракции разработчиков движка, нет смысла придавать им сакральный смысл.


                1. Zenitchik
                  29.07.2017 18:32

                  Там никакого специального объекта нет.

                  Боюсь ошибиться, но кажется именно из спеки я о нём и узнал.


                  1. vintage
                    29.07.2017 18:58

                    1. Aingis
                      29.07.2017 22:33

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


                      1. vintage
                        29.07.2017 23:14

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


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


                        1. Aingis
                          30.07.2017 16:32

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


                          1. vintage
                            30.07.2017 16:53

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


    1. BerkutEagle
      27.07.2017 21:14

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


      1. babylon
        28.07.2017 11:07

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


        1. tronus
          31.07.2017 15:27

          Вот здесь подробно про this и замыкания https://habrahabr.ru/company/hexlet/blog/266443/


  1. Deamon87
    27.07.2017 15:46
    +7

    Отказом от использования this, объекты в JS легким движением руки превращаются в структуры из C.
    Никаких методов. Никаких конструкторов.

    И стиль из ООП превращается в процедурное программирование


    1. Zenitchik
      27.07.2017 16:31

      Они скорее ближе к ассоциативным массивам, чем к структурам.


      1. vintage
        27.07.2017 21:02

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


  1. AxisPod
    27.07.2017 16:40
    +1

    Чёт больше похоже на: Не знаю ООП, использовать не хочу, разбираться в тонкостях языка не хочу, буду делать так, как получается.


  1. k12th
    27.07.2017 17:05
    +1

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

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


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


    1. bingo347
      27.07.2017 17:08

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


      1. k12th
        27.07.2017 17:40
        +1

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


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


  1. aamonster
    27.07.2017 17:21
    +2

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


  1. urrri
    27.07.2017 18:57

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


    1. raveclassic
      27.07.2017 23:49

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


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

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


      1. vasIvas
        28.07.2017 00:47

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


        1. raveclassic
          28.07.2017 00:56

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


          1. vasIvas
            28.07.2017 10:35

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


  1. serf
    27.07.2017 20:11
    +3

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


  1. Shaco
    27.07.2017 22:21

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

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


  1. KirEv
    28.07.2017 00:19

    шило на мыло… если сложно javscript-программисту использовать this, то в реальном проекте с кучей вот таких абстракций — получится работа кода проекта не более предсказуемо чем с this.


  1. idoroshenko
    28.07.2017 13:29

    Вы посвятили параграф рассказывая про замыкания, но конкретно в вашем примере нет никакого смысла в замыкании.

    Тут не имеет смысла ничего замыкать, модуль в котором вы пишите этот код делает это за вас:

    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
    };
    


    1. idoroshenko
      28.07.2017 16:25

      Были минусы, и наверное, они по делу, я не удосужился объяснить детальнее. Посмотрите на код внимательнее. Это не фабрика, это не функция конструктор, это анонимная функция, которая сразу же, при запросе модуля, вызывается, и результат работы которой присваивается переменной LinkedListDeque, которая потом экспортируется. Замыкание которая эта функция создаёт, не несет никакого смысла к контексте модулей commonJS которые тут используются. Оно не предотвращает side effects и не инкасплуриует какой-либо функционал.
      Писать в таком стиле есть смысл, только, если помимо этого кода, в файле модуля есть еще какой-то функционал, от которого следует изолироваться, что уже было бы неправильно с точки зрения cohesion элементов внутри модуля.
      Статья приводит хороший пример из функционального программирования, в частности, о преимуществе pure functions. К основному примеру, замыкания описанные выше, не имеют никакого отношения. Но этот пример, мог бы отлично продемонстрировать то, как можно применить каррирование, которое является отличным примером паттерна функционального программирования, который может помочь избавиться от this.


  1. LiguidCool
    31.07.2017 15:27

    Краткий курс по основам ООП, но без ООП? ))


  1. AlexPTS
    31.07.2017 17:18

    На самом деле это паттерн модуль + паттерн открытия модуля.
    Такой подход позволяет работать и с this (он есть в замыкании, просто в своем коде вы его не применяете).
    Такой подход хорош для эмуляции защищенных свойств и методов. Но отсутсвие вынести общее поведение в прототип приводит к тому, что каждый инстанс расходует больше памяти, нежели работа с цепочкой прототипов.

    Еще в примере кода возвращается инстанс, лучше возвращать конструктор. Чтобы можно было создать свой инстанс, если потребуется более 1 инстанса модуля и провайдить настройки в конструктор, если модуль настраиваемый.