this
в JavaScript можно назвать одной из наиболее обсуждаемых и неоднозначных особенностей языка. Всё дело в том, что то, на что оно указывает, выглядит по-разному в зависимости от того, где обращаются к this
. Дело усугубляется тем, что на this
влияет и то, включён или нет строгий режим.![](https://habrastorage.org/web/cec/dea/d3a/cecdead3acf64588a27bce951b7a3aa4.jpg)
Некоторые программисты, не желая мириться со странностями
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()
. Для того, чтобы его реализовать, надо выполнить следующие операции:- Создать новый объект
Node
.
- Если очередь пуста, нужно установить указатели головы и хвоста очереди на новый объект
Node
.
- Если очередь не пуста, нужно взять текущий элемент очереди
head
и установить его указательprev
на новый элемент, а указательnext
нового элемента установить на тот элемент, который записан в переменнуюhead
. В результате первым элементом очереди станет новый объектNode
, за которым будет следовать тот элемент, который был первым до выполнения операции. Кроме того, надо не забыть обновить указатель очередиhead
таким образом, чтобы он ссылался на её новый элемент.
- Увеличить длину очереди, инкрементировав её свойство
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 и приступайте к делу. (Решений задачек там, кстати, нету.)
- Основываясь на рассмотренных выше примерах реализации методов, создайте остальные. А именно, напишите функции
popBack()
иpopFront()
, которые, соответственно, удаляют и возвращают первый и последний элементы очереди.
- В этой реализации двухсторонней очереди используется связный список. Ещё один вариант основан на обычных массивах JavaScript. Создайте все необходимые для двухсторонней очереди операции, используя массив. Назовите эту реализацию
ArrayDeque
. И помните — никакихthis
иnew
.
- Проанализируйте реализации двухсторонних очередей с использованием массивов и списков. Подумайте о временной и пространственной сложности используемых алгоритмов. Сравните их и запишите свои выводы.
- Ещё один способ реализации двухсторонних очередей заключается в одновременном использовании массивов и связных списков. Такую реализацию можно назвать
MixedQueue
. При таком подходе сначала создают массив фиксированного размера. Назовём егоblock
. Пусть его размер будет 64 элемента. В нём будут храниться элементы очереди. При попытке добавить в очередь больше 64-х элементов создают новый блок данных, который соединяют с предыдущим с помощью связного списка по модели FIFO. Реализуйте методы двухсторонней очереди, используя этот подход. Каковы преимущества и недостатки такой структуры? Запишите свои выводы.
- Эдди Османи написал книгу «Шаблоны проектирования в JavaScript». Там он говорит о недостатках шаблона revealing module. Один из них заключается в следующем. Если приватная функция модуля использует общедоступную функцию того же модуля, эту общедоступную функцию нельзя переопределить извне, пропатчить. Даже если попытаться это сделать, приватная функция всё равно будет обращаться к исходной приватной реализации общедоступной функции. То же самое касается и попытки изменения извне общедоступной переменной, доступ к которой даёт API модуля. Разработайте способ обхода этого недостатка. Подумайте о том, что такое зависимости, как инвертировать управление. Как обеспечить то, чтобы все приватные функции модуля работали с его общедоступными функциями так, чтобы у нас была возможность контролировать общедоступные функции. Запишите свои идеи.
- Напишите метод,
join
, который позволяет соединять две двухсторонних очереди. Например, вызовLinkedListDeque.join(first, second)
присоединит вторую очередь к концу первой и вернёт новую двухстороннюю очередь.
- Разработайте механизм обхода очереди, который не разрушает её и позволяет выполнять итерации по ней в цикле
for
. Для этого упражнения можете использовать итераторы ES6.
- Разработайте неразрушающий механизм обхода очереди в обратном порядке.
- Опубликуйте то, что у вас получилось, на GitHub, расскажите всем о том, что создали реализацию двухсторонней очереди без
this
, и о том, как хорошо вы во всём этом разбираетесь. Ну и про меня упомянуть не забудьте.
После того, как справитесь с этими основными заданиями, можете сделать и ещё несколько дополнительных.
- Используйте любой фреймворк для тестирования и добавьте тесты ко всем своим реализациям двухсторонних очередей. Не забудьте протестировать пограничные случаи.
- Переработайте реализацию двухсторонней очереди так, чтобы она поддерживала элементы с приоритетом. Элементам такой очереди можно назначать приоритет. Если такая очередь будет использоваться для хранения элементов без назначения им приоритета, её поведение ничем не будет отличаться от обычной. Если же элементам назначают приоритет, нужно обеспечить, чтобы после каждой операции последний элемент в списке имел бы наименьший приоритет, а первый — наибольший. Создайте тесты и для этой реализации двухсторонней очереди.
- Полином — это выражение, которое может быть записано в виде
an * x^n + an-1*x^n-1 + ... + a1x^1 + a0
. Здесьan..a0 —
это коэффициенты полинома, аn…1 —
показатели степени. Создайте реализацию структуры данных для работы с полиномами, разработайте методы для сложения, вычитания, умножения и деления полиномов. Ограничьтесь только упрощёнными полиномами. Добавьте тесты для проверки правильности решения. Обеспечьте, чтобы все методы, возвращающие результат, возвращали бы его в виде двухсторонней очереди.
- До сих пор предполагалось, что вы используете JavaScript. Выберите какой-нибудь другой язык программирования и выполните все предыдущие упражнения на нём. Это может быть Python, Go, C++, или что угодно другое.
Итоги
Надеюсь, упражнения вы выполнили и узнали с их помощью что-нибудь полезное. Если вы думаете, что преимущества отказа от использования
this
стоят усилий по переходу на новую модель программирования, взгляните на eslint-plugin-fp. С помощью этого плагина можно автоматизировать проверки кода. И, если вы работаете в команде, прежде чем отказываться от this
, договоритесь с коллегами, иначе, при встрече с ними, не удивляйтесь их угрюмым лицам. Хорошего вам кода!Уважаемые читатели! Как вы относитесь к
this
в JavaScript?Комментарии (58)
Aquahawk
27.07.2017 14:58Похожим образом много пишут в тайпскрипте, в том смысле что его компилятор так написан много где.
Когда в функции объявляются переменные а потом гора вложенных функций используется. По сути как инстанс класса со своими полями, только всё приватно и вложено тут.
Пример. Другие примеры не отображаются на гихабе ввиду огромности размера файла.
https://github.com/Microsoft/TypeScript/blob/master/src/compiler/tsc.ts#L99
И работая с таким кодом повседневно, я не скажу что это лучшее решение. Хотя в некоторых местах катит. Но расширяемость и переиспользуемость сильно страдают. Иногда вынужден копипастить куски из компилера вместо того чтобы позвать.
vlreshet
27.07.2017 15:20+13Вместо того чтобы разобраться в поведении this, и писать разборчивый код в котором будет легко понять «где» сейчас this — строить какие-то абстракции, нагромождения, обвязки. Ммм, классно. И ежу понятно что всё что написано с this — можно переписать таким образом чтобы его не использовать, вот только зачем?
Zenitchik
27.07.2017 16:38Это точно. Очень жаль, что разрабы не придумали чего-то подобного для super.
Nakosika
27.07.2017 20:38Недавно читал интервью с создателем ява скрипта на хабре, говорит у него самого код без this выходит лучше. Ему толще посоветуете разобраться?
0shn1x
27.07.2017 21:14+2Действительно, ведь делегирование в JS не нужно, bind, call и apply придумали неизвестно для кого, а new — вообще бесполезно использовать
vlreshet
27.07.2017 21:35Ну а есть другие мощные разработчики, которые пишут используя this и радуются жизни. Мнение одного человека ничего не значит, даже несмотря на то что это создатель языка.
search
28.07.2017 12:04Крокфорд как-то ответил на ваш вопрос: в JS есть неоднозначные вещи, использование которых требует дисциплины и сакрального знания. this — одна из таких вещей (требует постоянно держать в уме контекст использования). То что требует повышенной дисциплины подвержено поломкам в результате человеческого фактора. Такие дела.
Кстати, очень легко проследить связь между "я пишу элегантный код, используя весь потенциал языка" и "я никому не доверю рефакторинг своего кода".
Zenitchik
28.07.2017 14:03+1требует постоянно держать в уме контекст использования
Как будто это трудно.
rboots
28.07.2017 22:12Я доверяю рефакторинг профессионалам, а если человек не всегда знает чему равен this — возможно его не стоит брать в команду.
bingo347
27.07.2017 15:39+11За много лет работы с JS ни разу не было проблем с this, более того в умелых руках это очень мощный инструмент языка, а проблемы описанные в статье говорят лишь о том, что автор не умеет и не понимает JavaScript…
Теперь подумайте вот о чем:
1. На каждый экземпляр объекта созданного таким образом будут создаваться новые функции-методы. Это засоряет память, это убивает оптимизацию
Функции в js компилируются в момент первого вызова и оптимизируются при нескольких последующих вызовах. В случае Вашего подхода это будет делаться для каждого инстанса, что дорого, очень
Если же использовать правильный подход с прототипами функции будут созданы и скомпилированы только 1 раз, все инстансы будут использовать одну и ту же функцию. Мы экономим память. Инстанс создается быстрее. Инстанс работает быстрее, когда его методы уже скомпилированы при работе с предыдущим инстансом.
2. Как быть с наследованием? Допустим я хочу на базе Вашего двухсвязного списка сделать список с произвольным доступом на чтение и возможностью итерации. Если бы были прототипы — я бы относледовался от Вашего прототипа и добавил бы необходимые мне методы, но Ваш код я не смогу переиспользовать, это плохоvintage
27.07.2017 21:00Замыкания не создают новых функций, они лишь хранят ссылки на контекст и на единожды созданную функцию. Тем не менее создание и хранение замыканий не бесплатно, да.
Aingis
28.07.2017 13:33Замыкания не создают новых функций, они лишь хранят ссылки на контекст и на единожды созданную функцию…
Хранят где? Правильно, во вновь создаваемых функциях!
vintage
28.07.2017 14:44Замыкания, вопреки всеобщему заблуждению, — не функции. Это — структуры, хранящие две ссылки: на контекст и на собственно функцию (исполняемый код). Функция создаётся единожды при парсинге исходников.
Aingis
28.07.2017 14:52+1Если структура выглядит как функция (
typeof
x
==
'function'
) и ведёт себя как функция (можно вызватьx(42)
), то что же это, если не функция?vintage
28.07.2017 23:37let x = new Proxy( Function , { apply: function(){ console.log( 'i am callable, but not a function' ) } } ) x()
KirEv
28.07.2017 23:45if(typeof(x) == 'function'){"i am a function"}
Вот неожиданно, да? )vintage
28.07.2017 23:55if( typeof( null ) === 'object' ) { console.log( "i am object!" ) }
Внезапно!
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
Aingis
29.07.2017 16:45Впрочем чего я спорю! Если, как вы сказали, замыкания не создают новых функций и вы готовы подтвердить свои слова, то вам не составит труда привести пример замыкания без создания функции. На этом и спору конец.
Zenitchik
29.07.2017 17:05Замыкание — это внутренний объект движка. Он не тождественен функции. Я лично, не знаю, что Вам показать, но может, кто-то из присутствующих копался в потрохах движка и знает способ увидеть замыкание непосредственно.
Aingis
29.07.2017 17:53Зачем залезать в движок, когда можно залезть в спецификацию? Там никакого специального объекта нет. А детали конкретной реализации не имеют никакого значения. Это всего лишь абстракции разработчиков движка, нет смысла придавать им сакральный смысл.
Zenitchik
29.07.2017 18:32Там никакого специального объекта нет.
Боюсь ошибиться, но кажется именно из спеки я о нём и узнал.vintage
29.07.2017 18:58Не бойтесь: http://www.ecma-international.org/ecma-262/5.1/#sec-10.4.3 :-)
Aingis
29.07.2017 22:33Прямо по ссылке слова «closure» нет. Зато в другом месте, где оно определяется, ваши слова прямо опровергаются: «Let closure be the result of creating a new Function object…»
vintage
29.07.2017 23:14Как вы думаете, что означает слово "let" в данном контексте?
То, что мы привыкли называть "замыканием" в спеке называется "объект функции", а собственно код функции находится в скрытом свойстве [[Code]] этого объекта и он общий для всех замыканий из одного места в исходниках: http://www.ecma-international.org/ecma-262/5.1/#sec-13.2
BerkutEagle
27.07.2017 21:14Но ведь в данном случае инстансы не содержат функций. Вся реализация собрана в одном месте, а в инстансах только данные.
Deamon87
27.07.2017 15:46+7Отказом от использования this, объекты в JS легким движением руки превращаются в структуры из C.
Никаких методов. Никаких конструкторов.
И стиль из ООП превращается в процедурное программирование
AxisPod
27.07.2017 16:40+1Чёт больше похоже на: Не знаю ООП, использовать не хочу, разбираться в тонкостях языка не хочу, буду делать так, как получается.
k12th
27.07.2017 17:05+1Например — стрелочные функции и привязка this. Как результат, при разработке можно практически полностью обойтись без this.
Вообще-то если не обращаться к this, то стрелочная функция ровно ничем не отличается от обычной:)
Дальше можно не читать, в общем.
bingo347
27.07.2017 17:08стрелочная функция еще не имеет своей arguments, а наследует его из замыкания
k12th
27.07.2017 17:40+1Ваша правда, немного погорячился. Плюс там по идее вообще не создается отдельный контекст, как для толстых функций.
Но, повторюсь, если кто-то ниасилил this в JS по десяткам статей, появляющихся каждый год, то разница минимальна.
aamonster
27.07.2017 17:21+2Э… Я вижу просто явную передачу this в качестве аргумента функции (вместо обычной неявной), и всё. Что изменилось, кроме увеличения размера кода?
urrri
27.07.2017 18:57Не понял, что нового в статье. Так писали 7-8 лет назад (а может и раньше), чтобы не заморачиваться с наследованием и чтобы симулировать private. Особенно такой стиль любят выходцы из С#/С++. На сегодня, с приходом классов, этот стиль уходит. Возможно в одном из следующих релизов можно будет декларировать в качестве члена класса что нибудь типа стрелочных функций с привязкой this
raveclassic
27.07.2017 23:49Уже можно через class properties:
class Foo { bar = 'bar'; bound = () => console.log(this.bar); }
Очень часто используется в React.
vasIvas
28.07.2017 00:47Только количество функциональных выражений указанных в качестве значения свойствам, равно количеству экземпляров. в то время как у prototype одна функция на все экземпляры.
raveclassic
28.07.2017 00:56Да, только не привязанная к инстансу. А чтобы привязать для передачи метода, например, в виде колбэка, все-равно придется байндить руками на месте вызова (что порождает новую функцию на каждый вызов), либо в конструкторе. А класс-проперти — просто шорткат для конструктора.
vasIvas
28.07.2017 10:35Но в случаи с связыванием контекста не дублируется логика находящаяся в функциональном выражении.
Shaco
27.07.2017 22:21из функции можно возвратить объект. Этот объект будет иметь доступ к локальному окружению
Это неверно. Объект ни к чему доступа иметь не может в принципе. Доступ к скоупу будут иметь только функции, в нём созданные. Всё. Они могут быть методами объекта, который вы вернёте, но сам объект тут ни при чём. Если вернуть объект вида { foo: 1 }, скоуп почистится сборщиком мусора. Если присвоить в качестве метода объекта функцию, объявленную снаружи, доступа к скоупу у неё тоже не будет.
KirEv
28.07.2017 00:19шило на мыло… если сложно javscript-программисту использовать this, то в реальном проекте с кучей вот таких абстракций — получится работа кода проекта не более предсказуемо чем с this.
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 };
idoroshenko
28.07.2017 16:25Были минусы, и наверное, они по делу, я не удосужился объяснить детальнее. Посмотрите на код внимательнее. Это не фабрика, это не функция конструктор, это анонимная функция, которая сразу же, при запросе модуля, вызывается, и результат работы которой присваивается переменной LinkedListDeque, которая потом экспортируется. Замыкание которая эта функция создаёт, не несет никакого смысла к контексте модулей commonJS которые тут используются. Оно не предотвращает side effects и не инкасплуриует какой-либо функционал.
Писать в таком стиле есть смысл, только, если помимо этого кода, в файле модуля есть еще какой-то функционал, от которого следует изолироваться, что уже было бы неправильно с точки зрения cohesion элементов внутри модуля.
Статья приводит хороший пример из функционального программирования, в частности, о преимуществе pure functions. К основному примеру, замыкания описанные выше, не имеют никакого отношения. Но этот пример, мог бы отлично продемонстрировать то, как можно применить каррирование, которое является отличным примером паттерна функционального программирования, который может помочь избавиться от this.
AlexPTS
31.07.2017 17:18На самом деле это паттерн модуль + паттерн открытия модуля.
Такой подход позволяет работать и с this (он есть в замыкании, просто в своем коде вы его не применяете).
Такой подход хорош для эмуляции защищенных свойств и методов. Но отсутсвие вынести общее поведение в прототип приводит к тому, что каждый инстанс расходует больше памяти, нежели работа с цепочкой прототипов.
Еще в примере кода возвращается инстанс, лучше возвращать конструктор. Чтобы можно было создать свой инстанс, если потребуется более 1 инстанса модуля и провайдить настройки в конструктор, если модуль настраиваемый.
vasIvas
очень не хватает статьи китайца, который будет рассказывать о проблемах связанных с вилкой.