Functions

Контексты исполнения

Контекст исполнения (Execution context) – это абстрактное понятие, используемое спецификацией ECMA, для типизации и разграничения исполняемого кода.Стандарт не задаёт чётких рамок на структуру и вид EC с точки зрения реализации; это задача JavaScript-движков, реализующих стандарт.

Объект переменных(Variable object, VO) - это связанный с контекстом исполнения объект, служащий хранилищем для:

  • переменных (var);

  • деклараций функций (FunctionDeclaration, сокращённо FD);

  • и формальных параметров функции,

var a = 10;
 
function test(x) {
  var b = 20;
};
 
test(30);
// Объект переменных глобального контекста
VO(globalContext) = {
  a: 10,
  test: <ссылка на функцию>
};
 
// Объект переменных контекста функции test
VO(test functionContext) = {
  x: 30,
  b: 20
};

Глобальный объект (Global object) — объект, который создаётся до входа в любой из контекстов исполнения. Данный объект существует в единственном экземпляре, свойства его доступны из любого места программы, жизненный цикл объекта завершается с завершением программы.

При создании, глобальный объект инициализируется такими свойствами, как Math, String, Date, parseInt и т.д., а также, дополнительными объектами, среди которых может быть и ссылка на сам глобальный объект — например, в BOM, свойство window глобального объекта ссылается на сам глобальный объект (однако, не во всех реализациях):

global = {
  Math: <...>,
  String: <...>
  ...
  ...
  window: global
};

В глобальном контексте, объектом переменных является сам глобальный объект

VO(globalContext) === global

var a = new String('test');
 
alert(a); // напрямую, будет найдено в VO(globalContext): "test"
 
alert(window['a']); // косвенно через global === VO(globalContext): "test"
alert(a === this.a); // true
 
var aKey = 'a';
alert(window[aKey]); // косвенно, имя свойства сформировано налету: "test"

Объект активации (Activation object, AO) — специальный объект, который создаётся при входе в контекст функции и инициализируется свойством arguments — Объект аргументов (Arguments object):

AO = {
  arguments: <ArgO>
};

Объект аргументов (Arguments object, ArgO) – объект, находящийся в объекте активации контекста функции и содержащий следующие свойства:

  • callee – ссылка на выполняемую функцию;
  • length – количество реально переданных параметров;

  • свойства-индексы (числовые, приведённые к строке), значения которых – есть формальные параметры функции (слева направо в списке параметров). Количество этих свойств-индексов == arguments.length. Значения свойств-индексов объекта arguments и присутствующие формальные параметры – взаимозаменяемы:
function foo(x, y, z) {
  alert(foo.length); 

  alert(arguments.length); 

  alert(arguments.callee === foo); 
 
  alert(x === arguments[0]); 
  alert(x); // 10
 
  arguments[0] = 20;
  alert(x); // 20
 
  x = 30;
  alert(arguments[0]); 
   
  z = 40;
  alert(arguments[2]); 
 
  arguments[2] = 50;
  alert(z); // 40
 
}
  
foo(10, 20);

Что выведет

каждый alert?

function foo(x, y, z) { 
  // количество описанных параметров функции (x, y, z)
  alert(foo.length); // 3
   
  // количество реально переданных параметров (только x, y)
  alert(arguments.length); // 2
   
  // ссылка функции на саму себя
  alert(arguments.callee === foo); // true
 
  // разделение параметров 
  alert(x === arguments[0]); // true
  alert(x); // 10 
  arguments[0] = 20;
  alert(x); // 20
 
  x = 30;
  alert(arguments[0]); // 30
   
  // однако, для не переданного параметра z,соответствующее 
  //свойство-индекс объекта arguments - не взаимозаменяемое 
  z = 40;
  alert(arguments[2]); // undefined
 
  arguments[2] = 50;
  alert(z); // 40 
}
  
foo(10, 20);

Детализация обработки кода контекста

Обработка кода контекста исполнения делится на два основных этапа:

  1. Вход в контекст исполнения;

  2. Непосредственно, интерпретация кода.

 Обработка этих двух стадий является общей для всех контекстов.

При входе в контекст исполнения (но до построчного выполнения его кода), VO наполняется следующими свойствами:

 * для каждого формального параметра функции (если мы находимся в контексте исполнения функции)

  – создаётся свойство VO с именем и значением формального параметра; для непереданных параметров – создаётся свойство VO с именем формального параметра и значением undefined;

 * для каждой декларации функции (FunctionDeclaration, FD)

– создаётся свойство VO, с именем функции и значением, являющимся ссылкой на объект-функцию; если в VO уже присутствовало свойство с таким именем, оно его значение и атрибуты заменяются значением функции;

 * для каждой переменной (var)

– создаётся свойство VO с именем переменной, и значением undefined; если в VO уже присутствовало свойство с таким именем, оно остаётся нетронутым.

( является общей для всех контекстов.)

Чем будет наполнен АО?

function test(a, b) {
  var c = 10;
  function d() {}
  var e = function _e() {};
  (function x() {});
}
  
test(10); // вызов
AO(test) = {
  a: 10,
  b: undefined,
  c: undefined,
  d: <ссылка на FunctionDeclaration "d">
  e: undefined
};

В AO не попала функция “x”. Это потому, что “x” является не декларацией функции, а функцией-выражением (FunctionExpression, сокращённо FE), которые не воздействуют на VO.

Интерпретация кода

function test(a, b) {
  var c = 10;
  function d() {}
  var e = function _e() {};
  (function x() {});
}
  
test(10); // вызов
AO(test) = {
  a: 10,
  b: undefined,
  c: 10,
  d: <ссылка на FunctionDeclaration "d">
  e: <ссылка на FunctionExpression "_e">
};
alert(x); 
 
var x = 10;
alert(x);
 
x = 20;
 
function x() {} 
alert(x);
alert(x); // function
 
var x = 10;
alert(x); // 10
 
x = 20;
 
function x() {} 
alert(x); // 20
if (true) {
  var a = 1;
} else {
  var b = 2;
}
 
alert(a); 
alert(b);
if (true) {
  var a = 1;
} else {
  var b = 2;
}
 
alert(a); // 1
alert(b); 
// undefined, 
// но не "b is not defined"

This

Является свойством контекста исполнения:

activeExecutionContext = {
  VO: {...},
  this: thisValue
};

где VO — это объект переменных (variable object). Значение this напрямую связанно с типом исполняемого кода контекста. Определяется оно при входе в контекст и на протяжении исполнения кода контекста, является неизменным.

This в глобальном контексте

В коде глобального контекста, значением this всегда является сам глобальный объект (global)

this.a = 10;
alert(a);
 
b = 20;
alert(this.b); 
 
var c = 30;
alert(this.c); 
// явное объявление свойства
// глобального объекта
this.a = 10; // global.a = 10
alert(a); // 10
 
// косвенное, посредством присваивания
// неопределённому до этого идентификатору
b = 20;
alert(this.b); // 20
 
// также косвенное, посредством объявления
// переменной, поскольку объектом переменных
// в глобальном контексте является сам глобальный объект
var c = 30;
alert(this.c); // 30

This  в коде функции

Особенность значения this в этом типе кода заключается в том, что оно  не связано статично с функцией

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

 

!!! На протяжении исполнения кода контекста, значение this является неизменным, т.е. нельзя присвоить ему новое значение динамически в рантайме

Что выведет каждый alert?

var foo = {x: 10};
 
var bar = {
  x: 20,
  test: function () {
 
    alert(this === bar); 
    alert(this.x); 
     
    this = foo;
  
    alert(this.x);
 
  }
 
};

bar.test(); 
 
foo.test = bar.test;
 
foo.test(); 
var foo = {x: 10};
 
var bar = {
  x: 20,
  test: function () { 
    alert(this === bar); // true
    alert(this.x); // 20
     
    this = foo; // ошибка, нельзя менять this
  
    alert(this.x); // если бы не было ошибки, было бы 10, а не 20 
  } 
};
 
// при входе в контекст, значение this
// определёно как объект "bar"; почему – будет
// подробно разобрано ниже 
bar.test(); // true, 20
 
foo.test = bar.test;
 
// однако, здесь уже this указывает
// на "foo" - при вызове той же функции 
foo.test(); // false, 10

От чего же зависит меняющееся значение this в коде функции?

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

Что выведет каждый alert?

var foo = {
  bar: function () {
    alert(this);
    alert(this === foo);
  }
};
 
foo.bar();
 
var exampleFunc = foo.bar;
 
alert(exampleFunc === foo.bar); 
 
exampleFunc(); 
var foo = {
  bar: function () {
    alert(this);
    alert(this === foo);
  }
};
 
foo.bar(); // foo, true
 
var exampleFunc = foo.bar;
 
alert(exampleFunc === foo.bar); // true
 
// опять же, при иной форме вызова той же
// функции, this уже установлен в другое значение
 
exampleFunc(); // global, false

This при вызове функции в качестве конструктора

function A() {
  alert(this); 
  this.x = 10;
}
 
var a = new A();
alert(a.x);
function A() {
  alert(this); // вновь сознанный объект, ниже - объект "a"
  this.x = 10;
}
 
var a = new A();
alert(a.x); // 10

Оператор new вызовет внутренний метод [[Construct]]функции A, который, в свою очередь, после создания объекта, вызовет внутренний метод [[Call]], всё той же функции А, передав в качестве значения this вновь созданный объект.

Оба они принимают в качестве первого параметра значение this, которое будет использовано в контексте вызова. Разница между этими методами несущественная: для первого из них вторым параметром обязательно должен быть массив (либо массиво-подобный объект, например, arguments), в свою очередь, call может принимать любые параметры. Обязательным параметром для обоих методов является лишь первый — значение this

Что  выведет каждый alert?

var b = 10;
 
function a(c) {
  alert(this.b);
  alert(c);
}
 
a(20); 
 
a.call({b: 20}, 30); 
a.apply({b: 30}, [40]) 
var b = 10;
 
function a(c) {
  alert(this.b);
  alert(c);
}
 
a(20); // this === Global, this.b == 10, c == 20
 
a.call({b: 20}, 30); 
// this === {b: 20}, this.b == 20, c == 30

a.apply({b: 30}, [40]) 
// this === {b: 30}, this.b == 30, c == 40

Scope, Scope chain

var x = 10;
 
function foo() {   
  var y = 20;
   
  function bar() {
    alert(x + y);
  }
   
  return bar; 
}
 
foo()(); 
var x = 10;
 
function foo() {   
  var y = 20;
   
  function bar() {
    alert(x + y);
  }
   
  return bar; 
}
 
foo()(); // 30

Цепь областей видимости (Scope, Scope chain, сокращённо SC) – это связанная с контекстом исполнения цепь объектов переменных, в которой происходит поиск переменных при разрешении имён идентификаторов.

Scope chain функции создаётся при её выполнении, и состоит из объекта активации и внутреннего свойства функции [[Scope]].

activeExecutionContext = {
    VO: {...}, // или AO
    this: thisValue,
    Scope: [ // Scope chain
      // список всех объектов переменных
      // для поиска идентификаторов
    ] 
};

[[Scope]] – это иерархическая цепь объектов переменных (VO), стоящих выше контекста функции; цепь записывается свойством в функцию при её создании (раз, и навсегда (до уничтожения функции). Т.е. функция может быть ни разу не вызвана, но свойство [[Scope]] в неё уже записано)

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

Что выведет alert?

var x = 10;
 
function foo() {
  alert(x);
}
 
(function () {
  var x = 20;
  foo();
})();
var x = 10;
 
function foo() {
  alert(x);
}
 
(function () {
  var x = 20;
  foo(); // 10, а не 20
})();
function foo() {
 
  var x = 10;
  var y = 20;
 
  return function () {
    alert([x, y]);
  }; 
}
 
var x = 30; 
var bar = foo();
 
bar(); 
function foo() {
 
  var x = 10;
  var y = 20;
 
  return function () {
    alert([x, y]);
  }; 
}
 
var x = 30; 
var bar = foo(); // возвратилась анонимная функция
 
bar(); // [10, 20]

Что выведет каждый alert?

function foo() {
  alert(x);
}
 
Object.prototype.x = 10;
 
foo();
function foo() {
  alert(x);
}
 
Object.prototype.x = 10;
 
foo(); // 10
function foo() { 
  var x = 20;
 
  function bar() {
    alert(x);
  }
 
  bar();
}
 
Object.prototype.x = 10;
 
foo();
function foo() { 
  var x = 20;
 
  function bar() {
    alert(x);
  }
 
  bar();
}
 
Object.prototype.x = 10;
 
foo(); // 20

Что выведет каждый alert?

var a = 10;
function testFn() {
  alert(a);
}
 
(function (funArg) {
  var a = 20; 
  funArg();
})(testFn);
var a = 10;
function testFn() {
  alert(a);
}
 
(function (funArg) {
  var a = 20;
  // "a" для funArg запомнилось статически из
  // порождаемого её (лексического) контекста,
  // поэтому:
  funArg(); // 10, а не 20
})(testFn);
var firstClosure;
var secondClosure;
 
function testFn() {
  var a = 1;
  firstClosure = function (){return ++a;};
  secondClosure = function (){return --a;};
  a = 2; 
  alert(firstClosure());
}
 
testFn();
alert(firstClosure()); 
alert(secondClosure());
var firstClosure;
var secondClosure;
 
function testFn() {
  var a = 1;
  firstClosure = function () { return ++a; };
  secondClosure = function () { return --a; };
  a = 2; // воздействие на VO["a"], который в [[Scope]] замыканий
  alert(firstClosure()); // 3, через firstClosure.[[Scope]]
}
 
testFn();
alert(firstClosure()); // 4
alert(secondClosure()); // 3
var data = [];
for (var k = 0; k < 3; k++) {
  data[k] = function () {
    alert(k);
  };
}
 
data[0]();
data[1]();
data[2]();

Что выведет каждый alert?

var data = [];
for (var k = 0; k < 3; k++) {
  data[k] = function () {
    alert(k);
  };
}
 
data[0](); // 3, а не 0
data[1](); // 3, а не 1
data[2](); // 3, а не 2
var data = [];
for (var k = 0; k < 3; k++) {
  data[k] = (function _helper(x) {
    return function () {
      alert(x);
    };
  })(k); 
}

data[0](); 
data[1](); 
data[2](); 
var data = [];
for (var k = 0; k < 3; k++) {
  data[k] = (function _helper(x) {
    return function () {
      alert(x);
    };
  })(k); // передаём "k"
}
 
// теперь всё в порядке
data[0](); // 0
data[1](); // 1
data[2](); // 2

Q & A

Functions

By Anna Protasevich

Functions

Scope, context, VO, closer

  • 1,133