Поиск…
замечания
Область охвата - это контекст, в котором переменные живут и могут быть доступны другим кодом в той же области. Поскольку JavaScript в значительной степени можно использовать в качестве функционального языка программирования, важно знать объем переменных и функций, поскольку он помогает предотвратить ошибки и непредвиденное поведение во время выполнения.
Разница между var и let
(Примечание: все примеры, использующие let
, также действительны для const
)
var
доступен во всех версиях JavaScript, а let
и const
являются частью ECMAScript 6 и доступны только в некоторых новых браузерах .
var
относится к содержащейся функции или глобальному пространству, в зависимости от того, когда она объявлена:
var x = 4; // global scope
function DoThings() {
var x = 7; // function scope
console.log(x);
}
console.log(x); // >> 4
DoThings(); // >> 7
console.log(x); // >> 4
Это означает, что он «ускользает», if
утверждения и все подобные блок-конструкции:
var x = 4;
if (true) {
var x = 7;
}
console.log(x); // >> 7
for (var i = 0; i < 4; i++) {
var j = 10;
}
console.log(i); // >> 4
console.log(j); // >> 10
Для сравнения, let
блок с областью:
let x = 4;
if (true) {
let x = 7;
console.log(x); // >> 7
}
console.log(x); // >> 4
for (let i = 0; i < 4; i++) {
let j = 10;
}
console.log(i); // >> "ReferenceError: i is not defined"
console.log(j); // >> "ReferenceError: j is not defined"
Обратите внимание, что i
и j
объявляются только в цикле for
и поэтому не объявляются вне него.
Есть еще несколько важных отличий:
Объявление глобальной переменной
В верхней области (вне любых функций и блоков) объявления var
помещают элемент в глобальный объект. let
не делает:
var x = 4;
let y = 7;
console.log(this.x); // >> 4
console.log(this.y); // >> undefined
Re-декларации
Объявление переменной дважды с помощью var
не приводит к ошибке (хотя это эквивалентно объявлению ее один раз):
var x = 4;
var x = 7;
С let
, это приводит к ошибке:
let x = 4;
let x = 7;
TypeError: Идентификатор
x
уже объявлен
То же самое верно, когда y
объявляется с помощью var
:
var y = 4;
let y = 7;
TypeError: идентификатор
y
уже был объявлен
Однако переменные, объявленные с let, могут быть повторно использованы (не повторно объявлены) во вложенном блоке
let i = 5;
{
let i = 6;
console.log(i); // >> 6
}
console.log(i); // >> 5
Внутри блока внешний i
может быть доступен, но если внутри блока есть объявление let
для i
, внешний i
не может быть доступен и будет вызывать ReferenceError
если он используется до объявления второй.
let i = 5;
{
i = 6; // outer i is unavailable within the Temporal Dead Zone
let i;
}
ReferenceError: i не определен
Подъемно
Переменные, объявленные как с var
и let
, подняты . Разница в том, что переменная, объявленная с помощью var
может быть указана перед ее собственным назначением, поскольку она автоматически присваивается (с undefined
значением), но let
не может - она специально требует, чтобы переменная была объявлена перед вызовом:
console.log(x); // >> undefined
console.log(y); // >> "ReferenceError: `y` is not defined"
//OR >> "ReferenceError: can't access lexical declaration `y` before initialization"
var x = 4;
let y = 7;
Область между началом блока и объявлением let
или const
известна как временная мертвая зона , и любые ссылки на переменную в этой области вызовут ReferenceError
. Это происходит, даже если переменная назначается перед объявлением :
y=7; // >> "ReferenceError: `y` is not defined"
let y;
В нестрогом режиме, присваивая значение переменной без какого-либо объявления, автоматически объявляет переменную в глобальной области . В этом случае вместо того, чтобы y
автоматически объявлялось в глобальной области, let
резервирует имя переменной ( y
) и не разрешает ему доступ или назначение до строки, в которой она объявлена / инициализирована.
Затворы
Когда функция объявляется, переменные в контексте ее объявления захватываются в своей области. Например, в приведенном ниже коде переменная x
привязана к значению во внешней области, а затем ссылка на x
фиксируется в контексте bar
:
var x = 4; // declaration in outer scope
function bar() {
console.log(x); // outer scope is captured on declaration
}
bar(); // prints 4 to console
Вывод проб:
4
Эта концепция «захвата» области интересна тем, что мы можем использовать и модифицировать переменные из внешней области даже после выхода внешнего пространства. Например, рассмотрим следующее:
function foo() {
var x = 4; // declaration in outer scope
function bar() {
console.log(x); // outer scope is captured on declaration
}
return bar;
// x goes out of scope after foo returns
}
var barWithX = foo();
barWithX(); // we can still access x
Вывод проб:
4
В приведенном выше примере, когда вызывается foo
, его контекст фиксируется в функциональной bar
. Таким образом, даже после того, как он вернется, bar
все еще может получить доступ и изменить переменную x
. Функция foo
, контекст которой фиксируется в другой функции, называется замыканием .
Частные данные
Это позволяет нам делать некоторые интересные вещи, такие как определение «частных» переменных, которые видны только определенной функции или набору функций. Придуманный (но популярный) пример:
function makeCounter() {
var counter = 0;
return {
value: function () {
return counter;
},
increment: function () {
counter++;
}
};
}
var a = makeCounter();
var b = makeCounter();
a.increment();
console.log(a.value());
console.log(b.value());
Пример вывода:
1 0
Когда makeCounter()
, снимок контекста этой функции сохраняется. Весь код внутри makeCounter()
будет использовать этот моментальный снимок при их выполнении. Таким образом, два вызова makeCounter()
создадут два разных моментальных снимка со своей собственной копией counter
.
Немедленно вызывается функциональные выражения (IIFE)
Закрытие также используется для предотвращения глобального загрязнения пространства имен, часто посредством использования выражений функции, вызванных немедленно.
Выражения с мгновенным вызовом (или, возможно, более интуитивно, самоисполняющиеся анонимные функции ) являются, по сути, замыканиями, которые вызывают сразу после объявления. Общая идея с IIFE заключается в том, чтобы вызвать побочный эффект создания отдельного контекста, доступного только для кода внутри IIFE.
Предположим, мы хотим иметь возможность ссылаться на jQuery
с помощью $
. Рассмотрим наивный метод, не используя IIFE:
var $ = jQuery;
// we've just polluted the global namespace by assigning window.$ to jQuery
В следующем примере используется IIFE, чтобы гарантировать, что $
привязан к jQuery
только в контексте, создаваемом закрытием:
(function ($) {
// $ is assigned to jQuery here
})(jQuery);
// but window.$ binding doesn't exist, so no pollution
См. Канонический ответ на Stackoverflow для получения дополнительной информации о закрытии.
Подъемно
Что такое подъем?
Подъем - это механизм, который перемещает все объявления переменных и функций в верхнюю часть их области. Однако переменные назначения все еще происходят там, где они изначально были.
Например, рассмотрим следующий код:
console.log(foo); // → undefined
var foo = 42;
console.log(foo); // → 42
Вышеупомянутый код такой же, как:
var foo; // → Hoisted variable declaration
console.log(foo); // → undefined
foo = 42; // → variable assignment remains in the same place
console.log(foo); // → 42
Обратите внимание, что из-за поднятия undefined
не совпадают с not defined
результате работы:
console.log(foo); // → foo is not defined
Аналогичный принцип применяется к функциям. Когда функции назначаются переменной (т. Е. Выражение функции ), объявление переменной поднимается, а присваивание остается в одном месте. Следующие два фрагмента кода эквивалентны.
console.log(foo(2, 3)); // → foo is not a function
var foo = function(a, b) {
return a * b;
}
var foo;
console.log(foo(2, 3)); // → foo is not a function
foo = function(a, b) {
return a * b;
}
При объявлении операторов функций возникает другой сценарий. В отличие от операторов функций, объявления функций поднимаются в верхней части их области. Рассмотрим следующий код:
console.log(foo(2, 3)); // → 6
function foo(a, b) {
return a * b;
}
Вышеупомянутый код такой же, как следующий фрагмент кода из-за подъема:
function foo(a, b) {
return a * b;
}
console.log(foo(2, 3)); // → 6
Вот несколько примеров того, что есть и что не поднимает:
// Valid code:
foo();
function foo() {}
// Invalid code:
bar(); // → TypeError: bar is not a function
var bar = function () {};
// Valid code:
foo();
function foo() {
bar();
}
function bar() {}
// Invalid code:
foo();
function foo() {
bar(); // → TypeError: bar is not a function
}
var bar = function () {};
// (E) valid:
function foo() {
bar();
}
var bar = function(){};
foo();
Ограничения подъема
Инициализация переменной не может быть поднята или просто JavaScript Объявляет декларации не инициализации.
Например: нижеприведенные скрипты выдают разные результаты.
var x = 2;
var y = 4;
alert(x + y);
Это даст вам результат 6. Но это ...
var x = 2;
alert(x + y);
var y = 4;
Это даст вам выход NaN. Поскольку мы инициализируем значение y, JavaScript-подъем не происходит, поэтому значение y будет неопределенным. JavaScript будет считать, что y еще не объявлен.
Итак, второй пример такой же, как и ниже.
var x = 2;
var y;
alert(x + y);
y = 4;
Это даст вам выход NaN.
Использование let in loops вместо var (пример обработчиков кликов)
Предположим, нам нужно добавить кнопку для каждой части массива loadedData
(например, каждая кнопка должна быть слайдером, показывающим данные, для простоты мы просто предупреждаем сообщение). Можно попробовать что-то вроде этого:
for(var i = 0; i < loadedData.length; i++)
jQuery("#container").append("<a class='button'>"+loadedData[i].label+"</a>")
.children().last() // now let's attach a handler to the button which is a child
.on("click",function() { alert(loadedData[i].content); });
Но вместо предупреждения каждая кнопка вызовет
TypeError: loadedData [i] не определен
ошибка. Это связано с тем, что область i
является глобальной областью (или областью функции) и после цикла i == 3
. Нам нужно не «помнить состояние i
». Это можно сделать, используя let
:
for(let i = 0; i < loadedData.length; i++)
jQuery("#container").append("<a class='button'>"+loadedData[i].label+"</a>")
.children().last() // now let's attach a handler to the button which is a child
.on("click",function() { alert(loadedData[i].content); });
Пример loadedData
для тестирования с помощью этого кода:
var loadedData = [
{ label:"apple", content:"green and round" },
{ label:"blackberry", content:"small black or blue" },
{ label:"pineapple", content:"weird stuff.. difficult to explain the shape" }
];
Вызов метода
Вызов функции как метода объекта, значением this
будет тот объект.
var obj = {
name: "Foo",
print: function () {
console.log(this.name)
}
}
Теперь мы можем вызывать печать как метод obj. this
будет obj
obj.print();
Таким образом, это будет регистрироваться:
Foo
Анонимный вызов
Вызов функции как анонимной функции, this
будет глобальный объект ( self
в браузере).
function func() {
return this;
}
func() === window; // true
В строгом режиме ECMAScript 5 this
будет undefined
если функция вызывается анонимно.
(function () {
"use strict";
func();
}())
Это приведет к выводу
undefined
Вызов конструктора
Когда функция вызывается как конструктор с new
ключевым словом, this
берет значение объекта, который строится
function Obj(name) {
this.name = name;
}
var obj = new Obj("Foo");
console.log(obj);
Это будет вести журнал
{name: "Foo"}
Вызов функции со стрелкой
При использовании функции стрелок this
принимает значение от контекста выполнения вшито это this
(то есть, this
в функциях стрелок имеет лексическую область , а не обычный динамический диапазон). В глобальном коде (код, который не принадлежит какой-либо функции) он будет глобальным объектом. И это сохраняется, даже если вы вызываете функцию, объявленную с помощью обозначения стрелки, из любого из описанных здесь методов.
var globalThis = this; //"window" in a browser, or "global" in Node.js
var foo = (() => this);
console.log(foo() === globalThis); //true
var obj = { name: "Foo" };
console.log(foo.call(obj) === globalThis); //true
Посмотрите, как this
наследует контекст, а не ссылается на объект, на который был вызван метод.
var globalThis = this;
var obj = {
withoutArrow: function() {
return this;
},
withArrow: () => this
};
console.log(obj.withoutArrow() === obj); //true
console.log(obj.withArrow() === globalThis); //true
var fn = obj.withoutArrow; //no longer calling withoutArrow as a method
var fn2 = obj.withArrow;
console.log(fn() === globalThis); //true
console.log(fn2() === globalThis); //true
Синтаксис и вызов Apply and Call.
Методы apply
и call
в каждой функции позволяют ему предоставлять настраиваемое значение для this
.
function print() {
console.log(this.toPrint);
}
print.apply({ toPrint: "Foo" }); // >> "Foo"
print.call({ toPrint: "Foo" }); // >> "Foo"
Вы можете заметить, что синтаксис обоих вышеупомянутых вызовов одинаковый. т.е. подпись похожа.
Но есть небольшая разница в их использовании, поскольку мы имеем дело с функциями и изменяем их области действия, нам все равно нужно поддерживать исходные аргументы, переданные функции. Как apply
и call
передачу аргументов поддержки для целевой функции следующим образом :
function speak() {
var sentences = Array.prototype.slice.call(arguments);
console.log(this.name+": "+sentences);
}
var person = { name: "Sunny" };
speak.apply(person, ["I", "Code", "Startups"]); // >> "Sunny: I Code Startups"
speak.call(person, "I", "<3", "Javascript"); // >> "Sunny: I <3 Javascript"
Обратите внимание, что apply
позволяет передать Array
или объект arguments
(array-like) в качестве списка аргументов, тогда как call
требует, чтобы вы передавали каждый аргумент отдельно.
Эти два метода дают вам свободу для того, чтобы получить такую же фантазию, как вы хотите, например, реализовать плохую версию встроенного bind
ECMAScript для создания функции, которая всегда будет вызываться как метод объекта из исходной функции.
function bind (func, obj) {
return function () {
return func.apply(obj, Array.prototype.slice.call(arguments, 1));
}
}
var obj = { name: "Foo" };
function print() {
console.log(this.name);
}
printObj = bind(print, obj);
printObj();
Это будет вести журнал
"Foo"
Функция bind
много продолжается
-
obj
будет использоваться как значениеthis
- переслать аргументы функции
- и затем вернуть значение
Связанный вызов
Метод bind
каждой функции позволяет вам создать новую версию этой функции с контекстом, строго привязанным к определенному объекту. Особенно полезно принудительно вызвать функцию как метод объекта.
var obj = { foo: 'bar' };
function foo() {
return this.foo;
}
fooObj = foo.bind(obj);
fooObj();
Это будет вести журнал:
бар