Поиск…


замечания

Область охвата - это контекст, в котором переменные живут и могут быть доступны другим кодом в той же области. Поскольку 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
5

В строгом режиме 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"}

Вызов функции со стрелкой

6

При использовании функции стрелок 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 много продолжается

  1. obj будет использоваться как значение this
  2. переслать аргументы функции
  3. и затем вернуть значение

Связанный вызов

Метод bind каждой функции позволяет вам создать новую версию этой функции с контекстом, строго привязанным к определенному объекту. Особенно полезно принудительно вызвать функцию как метод объекта.

var obj = { foo: 'bar' };

function foo() {
    return this.foo;
}

fooObj = foo.bind(obj);

fooObj();

Это будет вести журнал:

бар



Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow