понедельник, 28 сентября 2020 г.

Контекст вызова (this)

Источник 

В JavaScript this — это текущий контекст исполнения функции.

Функцию можно вызвать четырьмя способами:

  • вызов функции:
    alert('Hello World!'),
  • вызов метода:
    console.log('Hello World!'),
  • вызов конструктора:
    new RegExp('\\d'),
  • непрямой вызов:
    alert.call(undefined, 'Hello World!'),

и каждый из них определяет свой контекст, и поведение this слегка не соответствует ожиданиям начинающих разработчиков. Кроме того, strict mode (строгий режим) также влияет на контекст исполнения.

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

  1. При вызове функции значение this (контекст вызова) равен глобальному объекту или undefined в строгом режиме.
  2. При вызове метода значение this (контекст вызова) равен объекту, которому принадлежит данный метод. (Следует избегать характерных ошибок - см. ниже [1] и [2]).
  3.  При вызове конструктора значение this (контекст вызова) равен созданному при помощи ключевого слова new объекту (экземпляр класса).
  4.  При непрямом вызове - с помощью методов .call() или .apply()- значение this (контекст вызова) равен первому аргументу методов .call() или .apply(), соответственно.
  5. Связанная функция - функция, возвращаемая с помощью метода .bind(). Значение this (контекст вызова) при вызове такой функции равен первому аргументу метода .bind().
  6.  Значение this (контекст вызова) стрелочной функции - это контекст, в котором определена данная стрелочная функция, т.е. стрелочная функция заимствует this из внешней функции, в которой она определена, либо глобальный объект или undefined в строгом режиме. При этом контекст стрелочной функции изменить нельзя.


[1] "Отделение" метода:

let user = {
  name: "Джон",
  hi() { alert(this.name); }
};
 
// вызов метода объекта
user.hi(); // Вывод: "Джон"
// разделим получение метода объекта и его вызов
let hi = user.hi;
hi(); // Ошибка, потому что значением this является undefined

Для работы вызовов типа user.hi(), JavaScript использует трюк – точка '.' возвращает не саму функцию, а специальное значение «ссылочного типа», называемого Reference Type.

Этот ссылочный тип (Reference Type) является внутренним типом (мы не можем явно использовать его, но он используется внутри языка).

Значение ссылочного типа – это «триплет»: комбинация из трёх значений
(
base, name, strict), где:

base – это объект,
name – это имя свойства объекта,
strict – это режим исполнения. Является true, если действует строгий режим (use strict).

Результатом доступа к свойству user.hi является не функция, а значение ссылочного типа. Для user.hi в строгом режиме оно будет таким:

// значение ссылочного типа (Reference Type)
(user, "hi", true)

Когда скобки () применяются к значению ссылочного типа (происходит вызов), то они получают полную информацию об объекте и его методе, и могут поставить правильный this (т.е. user в данном случае, по base).

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

При любой другой операции, например, присваивании hi = user.hi, ссылочный тип заменяется на собственно значение user.hi (функцию), и дальше работа уже идёт только с ней. Поэтому дальнейший вызов происходит уже без this.

Таким образом, значение this передаётся правильно, только если функция вызывается напрямую с использованием синтаксиса точки obj.method() или квадратных скобок obj['method']() (они делают то же самое). Существуют различные варианты решения проблемы потери значения this. Например, такие как func.bind().

[2] this во внутренней функции:

var numbers = {
   numberA: 5,
   numberB: 10,
   sum: function() {
     console.log(this === numbers); // => true
     function calculate() {
       // this is window or undefined in strict mode
       console.log(this === numbers); // => false
       return this.numberA + this.numberB;
     }
     return calculate();
   }
};

// NaN or throws TypeError in strict mode
console.log(numbers.sum()); 

numbers.sum() — это вызов метода объекта, поэтому контекстом sum является объект numbers. Функция calculate определена внутри sum, поэтому можно было бы ожидать, что this — это объект numbers и в calculate(). Тем не менее, calculate() — это вызов функции, а не метода, и поэтому его this — это глобальный объект window или undefined в strict mode. Даже если контекстом внешней функции sum является объект numbers, он не "передается" во вложенную функцию.

среда, 14 августа 2019 г.

2. Строгий режим

В стандарте ECMAScript 5 (ES5) для исключения проблем с обратной совместимостью появилась поддержка "строго режима". Этот режим предназначен для написания кода в соответствии со стандартом не ниже ES5, без необходимости поддержки старого кода. Анализатор JS применяет к коду в этом случае более строгие правила проверки.
Включается строгий режим добавлением в первой строке кода "use strict"; (с кавычками, двойными или одинарными) и действует на весь файл. Перед строкой "use script"; допускаются только комментарии.
// file: ok.js
"use strict";     // действует на весь файл
alert("ok");
...
Существует возможность включения строгого режима для отдельной функции, добавлением этой строки непосредственно в начале описания данной функции.
(function() {
  "use strict";   // действует только в пределах функции
  alert("ok");
}());
Также строгий режим действует по-умолчанию (без необходимости каких-либо дополнительных инструкций) в любом модуле (экспортированном коде).

Особенности строго режима:
  1. Требование явного объявления переменных

  2. Запрет дублирования аргументов функций

  3. "Заморозка" arguments

  4. Запрет восьмеричных литералов с помощью предшествующего числу нуля (для это начиная с ES5 необходимо добавлять '0o')


  5. повторное объявление ключей объекта
  6. использование оператора with
  7. использование оператора delete к переменной

понедельник, 12 августа 2019 г.

1. Объявления

Объявления переменных, функций и объектов в JavaScript до стандарта ES6 (ES2015) обеспечивало ключевое слово var:
var a, b;
var c = 'string';
var fn = function() {
  var local = 0;
  return a * local + b;
}
var obj = {
  var name = 'Ivan';
  var age = 25;
}

С выходом стандарта ES6 (ES2015) использование var стало не желательным (но не запрещенным), а вместо него предлагается использовать ключевые слова let или const:
let a, b;
const c = 'string';
let fn = function() {
  let local = 0;
  return a * local + b;
}
const obj = {
  let name = 'Ivan';
  let age = 25;
}

Кратко:
Переменная, объявленная с помощью var, имеет область видимости функции.
Переменная, объявленная с помощью let (const), имеет область видимости блока.
Глобальные переменные, созданные с помощью var, добавляются, как свойства, к DOM-объекту window.
Область видимости Можно обновить значение Можно переобъявить
var функция да да
let блок да нет
const блок нет нет


Отличия подробнее:
  1. Область видимости
    ключевое слово:
    var let const
    добавление к объекту window
    YES NO NO
    область видимости функции
    YES YES YES
    область видимости блока
    NO YES YES
    может ли быть переназначена
    YES YES NO
    var   userId = 3;
    let   postId = 200;
    const siteId = 4;
    
    console.log(window.userId);  // 3
    console.log(window.postId);  // undefined
    console.log(window.siteId);  // undefined
    function getIds() {
      var   userId = 3;
      let   postId = 200;
      const siteId = 4;
    }
    
    console.log(userId);  // Error, is not defined
    console.log(postId);  // Error, is not defined
    console.log(siteId);  // Error, is not defined
    {
      var   userId = 3;
      let   postId = 200;
      const siteId = 4;
    
      console.log(userId);  // 3
      console.log(postId);  // 200
      console.log(siteId);  // 4
    }
    
    console.log(userId);  // 3
    console.log(postId);  // Error, is not defined
    console.log(siteId);  // Error, is not defined
    const post = {},
          ids = [1, 2, 3],
          a = 1;
    
    post = "Hello World!";       // Error, assignment to constant variable
    post.title = "Hello World!"; // Allowed. Adds new field "title"
    ids = [7, 8, 9];             // Error, assignment to constant variable
    ids.push(4);                 // Allowed
    a++;                         // Error, assignment to constant variable

  2. Переменная, объявленная с помощью ключевого слова var
    - вне функции имеет глобальную область видимости;
    - в случае использования JS-кода для работы с браузером такая переменная становится полем объекта window;
    - применяется "всплытие" ("поднятие") - если в некоторой области видимости объявляют переменную с помощью var, JavaScript резервирует место для неё ещё до того, как будет выполнена команда с её участием.
  3. Переменная, объявленная с помощью ключевого слова let
    - более ограниченная область видимости, по сравнению с переменной, объявленной с помощью var;
    - не применяется "всплытие" - переменную необходимо объявлять до использования.
  4. Переменная, объявленная с помощью ключевого слова const, аналогична переменной, объявленной с помощью let, за одним исключением - её значение нельзя менять.
    При этом, если переменная содержит объект, то нельзя заменять объект, на который указывает эта переменная, а изменение содержимого объекта - допускается.
Использование "строго режима" ("use strict") добавляет особенностей при работе с переменными.

Вывод:
Для объявления переменных используйте по-умолчанию ключевое слово const, в противном случае, если необходимо, let. Используйте var только, если в этом действительно есть необходимость, и Вы понимаете, что делаете.

Источники:
  1. var, let или const? Проблемы областей видимости переменных и ES6;
  2. Ключевые слова var, let и const