Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Язык JavaScript часть1.pdf
Скачиваний:
189
Добавлен:
22.03.2016
Размер:
8.92 Mб
Скачать

К решению

[[Scope]] для new Function

Присвоение [[Scope]] для new Function

Есть одно исключение из общего правила присвоения [[Scope]], которое мы рассматривали в предыдущей главе.

При создании функции с использованием new Function, её свойство [[Scope]]ссылается не на текущий LexicalEnvironment, а на window.

Пример

Следующий пример демонстрирует как функция, созданная new Function, игнорирует внешнюю переменную aи выводит глобальную вместо неё:

var a = 1;

function getFunc() { var a = 2;

var func = new Function('', 'alert(a)');

return func;

}

getFunc()(); // 1, из window

Сравним с обычным поведением:

var a = 1;

function getFunc() { var a = 2;

var func = function() { alert(a); };

return func;

}

getFunc()(); // 2, из LexicalEnvironment функции getFunc

Почему так сделано?

Продвинутые знания

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

Эта особенность new Function, хоть и выглядит странно, на самом деле весьма полезна.

Представьте себе, что нам действительно нужно создать функцию из строки кода. Текст кода этой функции неизвестен на момент написания скрипта (иначе зачем new Function), но станет известен

позже, например получен с сервера или из других источников данных.

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

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

То есть, если внутри функции есть var userName, то минификатор заменит её на var a(или другую

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

…Но если бы new Functionмогла обращаться к внешним переменным, то при попытке доступа к userNameв сжатом коде была бы ошибка, так как минификатор переименовал её.

Получается, что даже если бы мы захотели использовать локальные переменные в new Function, то после сжатия были бы проблемы, так как минификатор переименовывает локальные переменные.

Описанная особенность new Functionпросто-таки спасает нас от ошибок.

Ну а если внутри функции, создаваемой через new Function, всё же нужно использовать какие-то

данные — без проблем, нужно всего лишь предусмотреть соответствующие параметры и передавать их явным образом, например так:

var sum = new Function('a, b', ' return a + b; '); var a = 1, b = 2;

alert( sum(a, b) ); // 3

Итого

Функции, создаваемые через new Function, имеют значением [[Scope]]не внешний объект переменных, а window.

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

Локальные переменные для объекта

Замыкания можно использовать сотнями способов. Иногда люди сами не замечают, что использовали замыкания — настолько это просто и естественно.

В этой главе мы рассмотрим дополнительные примеры использования замыканий и задачи на эту

тему.

Счётчик-объект

Ранее мы сделали счётчик.

Напомню, как он выглядел:

function makeCounter() { var currentCount = 1;

return function() { return currentCount++;

};

}

var counter = makeCounter();

// каждый вызов возвращает результат, увеличивая счётчик alert( counter() ); // 1

alert( counter() ); // 2 alert( counter() ); // 3

Счётчик получился вполне рабочий, но вот только возможностей ему не хватает. Хорошо бы, чтобы можно было сбрасывать значение счётчика или начинать отсчёт с другого значения вместо 1или…

Да много чего можно захотеть от простого счётчика и, тем более, в более сложных проектах.

Чтобы добавить счётчику возможностей — перейдём с функции на полноценный объект:

function makeCounter() { var currentCount = 1;

return { // возвратим объект вместо функции getNext: function() {

return currentCount++; },

set: function(value) { currentCount = value;

},

reset: function() { currentCount = 1;

}

};

}

var counter = makeCounter();

alert( counter.getNext() ); // 1 alert( counter.getNext() ); // 2

counter.set(5);

alert( counter.getNext() ); // 5

Теперь функция makeCounterвозвращает не одну функцию, а объект с несколькими методами:

getNext()— получить следующее значение, то, что раньше делал вызов counter().

set(value)— поставить значение.

reset()— обнулить счётчик.

Все они получают ссылку [[Scope]]на текущий (внешний) объект переменных. Поэтому вызов любого из этих методов будет получать или модифицировать одно и то же внешнее значение

currentCount.

Объект счётчика + функция

Изначально, счётчик делался функцией во многом ради красивого вызова: counter(), который увеличивал значение и возвращал результат.

К сожалению, при переходе на объект короткий вызов пропал, вместо него теперь counter.getNext(). Но он ведь был таким простым и удобным…

Поэтому давайте вернём его!

function makeCounter() { var currentCount = 1;

//возвращаемся к функции function counter() {

return currentCount++;

}

//...и добавляем ей методы! counter.set = function(value) {

currentCount = value; };

counter.reset = function() { currentCount = 0;

};

return counter;

}

var counter = makeCounter();

alert( counter() ); // 1 alert( counter() ); // 2

counter.set(5);

alert( counter() ); // 5

Красиво, не правда ли? Получился полноценный объект, который можно вдобавок ещё и вызывать.

Этот трюк часто используется при разработке JavaScript-библиотек. Например, популярная библиотека jQuery предоставляет глобальную переменную с именем jQuery (доступна также под коротким именем $), которая с одной стороны является функцией и может вызываться как

jQuery(...), а с другой — у неё есть различные методы, например jQuery.type(123)возвращает тип аргумента.

Задачи на понимание замыканий

Задачи

Сумма через замыкание

важность: 4

Напишите функцию sum, которая работает так: sum(a)(b) = a+b.

Да, именно так, через двойные скобки (это не опечатка). Например:

sum(1)(2) = 3 sum(5)( 1) = 4

К решению

Функция – строковый буфер

важность: 5

В некоторых языках программирования существует объект «строковый буфер», который аккумулирует внутри себя значения. Его функционал состоит из двух возможностей:

1.Добавить значение в буфер.

2.Получить текущее содержимое.

Задача — реализовать строковый буфер на функциях в JavaScript, со следующим синтаксисом:

Создание объекта: var buffer = makeBuffer();.

Вызов makeBufferдолжен возвращать такую функцию buffer, которая при вызове buffer(value) добавляет значение в некоторое внутреннее хранилище, а при вызове без аргументов buffer()

— возвращает его.

Вот пример работы:

function makeBuffer() { /* ваш код */ }

var buffer = makeBuffer();

//добавить значения к буферу buffer('Замыкания'); buffer(' Использовать'); buffer(' Нужно!');

//получить текущее значение

alert( buffer() ); // Замыкания Использовать Нужно!

Буфер должен преобразовывать все данные к строковому типу:

var buffer = makeBuffer(); buffer(0);

buffer(1); buffer(0);

alert( buffer() ); // '010'

Решение не должно использовать глобальные переменные.

Открыть песочницу с тестами для задачи.

К решению

Строковый буфер с очисткой

важность: 5

Добавьте буферу из решения задачи Функция – строковый буфер метод buffer.clear(), который будет очищать текущее содержимое буфера:

function makeBuffer() {

...ваш код...

}

var buffer = makeBuffer();

buffer("Тест");

buffer(" тебя не съест ");

alert( buffer() ); // Тест тебя не съест

buffer.clear();

alert( buffer() ); // ""

Открыть песочницу с тестами для задачи.

К решению

Сортировка

важность: 5

У нас есть массив объектов:

var users = [{ name: "Вася", surname: 'Иванов', age: 20

}, {

name: "Петя", surname: 'Чапаев', age: 25

}, {

name: "Маша", surname: 'Медведева', age: 18

}];

Обычно сортировка по нужному полю происходит так:

//по полю name (Вася, Маша, Петя) users.sort(function(a, b) {

return a.name > b.name ? 1 : 1; });

//по полю age (Маша, Вася, Петя) users.sort(function(a, b) {

return a.age > b.age ? 1 : 1; });

Мы хотели бы упростить синтаксис до одной строки, вот так:

users.sort(byField('name')); users.forEach(function(user) {

alert( user.name ); }); // Вася, Маша, Петя

users.sort(byField('age')); users.forEach(function(user) {

alert( user.name ); }); // Маша, Вася, Петя

То есть, вместо того, чтобы каждый раз писать в sortfunction...— будем использовать

byField(...)

Напишите функцию byField(field), которую можно использовать в sortдля сравнения объектов по полю field, чтобы пример выше заработал.

К решению

Фильтрация через функцию

важность: 5

1.Создайте функцию filter(arr, func), которая получает массив arrи возвращает новый, в который входят только те элементы arr, для которых funcвозвращает true.

2.Создайте набор «готовых фильтров»: inBetween(a,b)— «между a,b», inArray([...])— «в массиве [...]». Использование должно быть таким:

filter(arr, inBetween(3,6))— выберет только числа от 3 до 6,

filter(arr, inArray([1,2,3]))— выберет только элементы, совпадающие с одним из значений массива.

Пример, как это должно работать:

/* .. ваш код для filter, inBetween, inArray */ var arr = [1, 2, 3, 4, 5, 6, 7];

alert(filter(arr, function(a) { return a % 2 == 0

})); // 2,4,6

alert( filter(arr, inBetween(3, 6)) ); // 3,4,5,6

alert( filter(arr, inArray([1, 2, 10])) ); // 1,2

Открыть песочницу с тестами для задачи.

К решению

Армия функций

важность: 5

Следующий код создает массив функций-стрелков shooters. По замыслу, каждый стрелок должен выводить свой номер:

function makeArmy() {

var shooters = [];

for (var i = 0; i < 10; i++) {

var shooter = function() { // функция стрелок alert( i ); // выводит свой номер

}; shooters.push(shooter);

}

return shooters;

}

var army = makeArmy();

army[0](); // стрелок выводит 10, а должен 0 army[5](); // стрелок выводит 10...

// .. все стрелки выводят 10 вместо 0,1,2...9

Почему все стрелки́выводят одно и то же? Поправьте код, чтобы стрелки работали как задумано. Предложите несколько вариантов исправления.

Открыть песочницу с тестами для задачи.

К решению