Konspekt

Konspekt


Advanced JavaScript

Заметки


JavaScript Clean Code


You don’t know JS

Ссылка


Область видимости и замыкания


1. Что такое область видимости?


Область видимости — набор правил поиска движком переменных по их идентификатору


Этапы «упрощенной» компиляции:


  • Разбиение на лексемы (Tokenizing/Lexing)
  • Парсинг — превращение лексем в AST (abstract syntax tree)
  • Генерация кода — превращение AST в исполняемый код


Сущности, участвующие в обработке программы:


  • Движок — отвечает за компиляцию и выполнение программы
  • Компилятор — парсит и генерирует код
  • Область видимости — собирает и обсулживает список поиска всех объявленных идентификаторов


var a = 2 

Компилятор:


  • Разбивает на лексемы
  • Парсит в дерево
  • Обращается к области видимости с проверкой на существование а в коллекции
  • Генерирует код для движка

Движок:


  • Спрашивает насчет a у области видимости, если есть — присваивает значение 2, если нет — идет в область выше до глобальной, если нет в глобальной — исключение


Типы поиска:


  • LHS — когда переменная появляется с левой стороны операции присваивания (цель присваивания): a = 2 
  • RHS — с правой стороны (источник присваивания): console.log(a)


Если RHS-поиск не найдет переменную в любой из вложенных областей видимости — возврат RefrenceError

LHS в случае отсутствия переменной создаст ее в глобальной области видимости (если в strict mode — RefrenceError)


TypeError — попытка выполнения нелегального действия с переменной


Strict mode


2. Лексическая область видимости


Определяется во время разбора на лексемы. Основана на том, где написаны блоки области видимости.


Динамическая ОВ — определяется во время выполнения. Основана на том, откуда вызываются функции. В JavaScript отсутствует, механизм this немного похож на ДОВ.


Обман ЛОВ — with , eval(..)

Лишают движок возможности оптимизации во время компиляции


3. Область видимости: функции против блоков



  • Область видимости из функции
  • IIFE создает область видимости
  • Блоки как области видимости (try/catch (в catch), let/const)


4. Всплытие переменных (Hoisting)


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

Остерегаться дублей объявлений.


5. Замыкание области видимости


Замыкание — когда функция имеет доступ к ЛОВ, даже если она выполняется вне своей ЛОВ. Конкретно, замыкание — ссылка на эту ЛОВ.


Простой пример:

function foo() {

var a = 2;

return function() { console.log(a); }

}

var bar = foo();

bar(); // 2


Циклы + замыкания: IIFE с передачей итератора, let (определяется для каждой итерации)


Паттерн проектирования «Модуль»:

function Module() {

var a = 2;

function doSomething() { console.log(a) }

function doAnother() { ... } 

return { 

doSomething: doSomething,

doAnother: doAnother

};

}

foo = Module(); foo.doSomething() // 2

Требования:


  • Должна быть вызвана внешняя окружающая функция
  • Окружающая функция должна возвращать хотя бы одну внутреннюю функцию, у которой есть замыкание на приватную область видимости


ES6-модули, определяются в отдельных файлах. Импорт: 


  • функции — import hello from “module”
  • модуля целиком — module foo from “module”



This и прототипы объектов


1. Что такое this?


this — привязка, которая создается во время вызова функции, и на что она ссылается определяется тем, где и при каких условиях функция была вызвана.


2. Определение this по месту и способа вызова



  • Если функция вызвана с new — используется только что созданный объект
  • Вызвана с помощью call / apply / bind — используется указанный объект
  • Вызвана с объектом контекста, владеющего вызовом функции obj.func() — используется этот объект контекста this == obj
  • По умолчанию: undefined в strict mode , в остальных global


Безопасное игнорирование привязки — передача в call / apply / bind ø = Object.create(null) 


Лексический this: стрелочные функции ES6 используют ЛОВ для привязки this , являются заменой self = this в до-ES6 коде


Просто функции обратного вызова теряют свою привязку, так как передается ссылка на функцию обратного вызова без контекста ее исполнения.


Вызов функции с new:


  • Создается новый объект
  • Сконструированный объект связывается с прототипом
  • Сконструированный объект устанавливается как привязка this для этого вызова функции
  • Возвращает сконструированный объект (исключение, если возвращается свой собственный альтернативный объект)


3. Объекты


Декларативное (литеральное) создание — {} , создание конструктором — new Object()


Основные типы: string, number, boolean, null, undefined, object

Подтипы объектов, встроенные объекты: String, Number, Boolean, Object, Function, Array, Date, RegExp, Error


Автоматическое преобразование литерала в объект-обертку при обращении к методам: 32.359.toFixed(2)


Вычисляемые имена свойств ES6:

var prefix = "foo";

var myObject = { [prefix + "bar"]: "hello" }; // myObject["foobar"] == hello


При работе со свойствами объекта движок вызывает стандартные операции [[Get]] и [[Put]] 


У свойств есть характеристика, оправляемые через дескриптор свойств:

Object.defineProperty( myObject, "a", {

  value: 2,

  writable: true,

  configurable: true,

  enumerable: true

});

Object.getOwnPropertyDescriptor( myObject, "a" );


Управление изменияемостью объекта и свойств:


  1. writable: false и configurable: false — неизменяемое свойство
  2. Object.preventExtensions(..) — запрет на добавление новых свойств
  3. Object.seal(..) — запечатывание: 2) с configurable: false, writable: true для всех свойств
  4. Object.freeze(..) — заморозка: 3) с writable: false для всех свойств


Геттеры, сеттеры:

var myObject = {

  get a() { return this._a_; },

set a(val) { this._a_ = val * 2; }

};

myObject.a = 2;

myObject.a; // 4


Symbol.iterator

Собственный итератор для перебора объекта:

var myObject = { a: 2, b: 3 };

Object.defineProperty(myObject, Symbol.iterator, {

  enumerable: false,

  writable: false,

  configurable: true,

  value: function() {

    var o = this;

    var idx = 0;

    var ks = Object.keys( o );

    return {

      next: function() {

        return {

          value: o[ks[idx++]],

          done: (idx > ks.length)

        };

      }

    };

  }

});

// перебираем `myObject` вручную

var it = myObject[Symbol.iterator]();

it.next(); // { value:2, done:false }

it.next(); // { value:3, done:false }

it.next(); // { value:undefined, done:true }

// перебираем `myObject` с помощью `for..of`

for (let v of myObject) {

  console.log(v);

}

// 2

// 3

Таким образом, можно использовать for..of для объекта: ищет @@iterator, содержащий метод next()


4. В JS нет классов


Класс — шаблон проектирования.


  • Наследование
  • Полиморфизм — переопределение/расширение родительских свойств/методов
  • Инкапсуляция 

В традиционных объектно-ориентированных языках ребенок получает копию содержимого родителя при наследовании. В JS ребенок не получает копию автоматически. Придется писать костыли (mixin), которые принесут дополнительные сложности. 


5. Прототипы


Внутрення ссылка [[Prototype]] некоторого объекта задает направление поиска для операции [[Get]]. Каскад ссылок от объекта к объекту образует «цепочку прототипов».


У обычных объектов есть встроенный объект Object.prorotype


Установка и затенение свойств:

obj.foo = "bar";


  1. Если у obj есть обычное свойство foo, то изменяется значение существующего свойства
  2. Если у obj и цепочки прототипов нет свойства, то свойство со значением добавляется в obj
  3. Если свойство находится выше по цепочке и не отмечено как {writable: false} , то новое свойство добавляется в obj (затенение)
  4. Если свойство находится выше по цепочке и отмечено как {writable: false} , то ничего не произойдет, в strict ошибка
  5. Если свойство находится выше по цепочке и является сеттером, то всегда будет вызываться сеттер

В 4 и 5 случаях для затенения вместо присваивания использовать Object.defineProperty(..)

Неявное затенение: 

var obj1 = {a: 2};

var obj2 = Object.create(obj1);

obj2.a++; //obj1.a == 2; obj2.a == 3 (obj2.a = obj2.a + 1)


Способы связать два объекта:


  • function Foo() { .. }
  • var a = new Foo();
  • Object.getProrotypeOf(a) === Foo.prototype; // true
  • Вызов new Foo() создает новые объект, связанный внутренней ссылкой [[Prototype]] с объектом Foo.prototype.
  • Object.create(somObj) — создает пустой объект с ссылкой [[Prototype]] на someObj.


.constructor — обратная ссылка на функцию, с которой связан объект. Не безопасен! Может принадлежать другому в цепочке объекту. Ручное восстановление свойства.


Делегирование («прототипом наследование»):


  • Bar.prototype = Object.create(Foo.prototype)
  • Object.setPrototypeOf(Bar.prototype, Foo.prototype); // ES6+

Неудачные


  • Bar.prototype = Foo.prototype; // Замена ссылки на объект
  • Bar.prototype = new Foo(); // Работает хорошо, но с побочными эффектами, если прописаны


Интроспекция


  • instanceof (учитывать особенности при использовании с bind)
  • Foo.prototype.isPrototypeOf(a);
  • Object.getPrototypeOf(a);
  • a.__proto__ === Foo.prototype // true


Частичный полифил Object.create(..):

if (!Object.create) {

Object.create = function(o) {

function F(){}

F.prototype = o;

return new F();

};

}


Совет использовать шаблон делегирования:

function Foo() { this.doCool = function() { this.cool() }; }

Foo.prototype.cool = function() { console.log("cool") };

var a = new Foo();

a.doCool(); // "cool"

или

var anotherObject = { 

  cool: function() { console.log("cool"); }

};

var myObject = Object.create(anotherObject);

myObject.doCool = function() { this.cool(); };

myObject.doCool(); // "cool"


6. Делегирование поведения


Особенности OLOO стиля программирования:


  • Только объекты, связанные с другими объектами
  • Свойства хранятся в делегирующих объектах, а не делегатах
  • Избегать одинаковых имен свойств на разных уровнях цепочки. При затенении код становится хрупким.
  • Доступны методы общего назначения из делегатов


Классический «прототипный» OO стиль, пытаемся в class-way:

function Foo(who) { this.me = who; }

Foo.prototype.identify = function() { return "I am " + this.me; };


function Bar(who) { Foo.call( this, who ); }

Bar.prototype = Object.create( Foo.prototype );

Bar.prototype.speak = function() { alert( "Hello, " + this.identify() + "." ); };


var b1 = new Bar( "b1" ); var b2 = new Bar( "b2" );

b1.speak(); b2.speak();



OLOO стиль:

var Foo = {

init: function(who) { this.me = who; },

identify: function() { return "I am " + this.me; }

};


var Bar = Object.create( Foo );

Bar.speak = function() { alert( "Hello, " + this.identify() + "." ); };


var b1 = Object.create( Bar ); b1.init( "b1" ); 

var b2 = Object.create( Bar ); b2.init( "b2" );

b1.speak(); b2.speak();



Реальные примеры кода и до конца главы


7. class в ES6


class Widget {

constructor() { … }

render() { … }

}

class Button extends Widget {

constructor() { super(); … }

render() { super.render(); … }

onClick(e) { … }

}


Какие проблемы решает:


  1. Больше нет отсылок к .prototype
  2. Унаследование (extends) вместо Object.create() или установки .__proto__
  3. super(..) дает относительный полиморфизм
  4. Не позволяет указать свойства, только методы. Защищает от ошибок
  5. extends позволяет расширить даже встроенные подтипы


Глюки:


  1. Это всего лишь сахар поверх существующего механизма делегирования, классы ненастоящие
  2. Не предоставляет способа объявить свойства экземпляра класса, приходится делать через .prototype
  3. super в некоторых случаях неочевидно привязывается, может понадобиться привязать вручную через toMethod (код ниже)
  4. Не является статическим!


class P { foo() { console.log( "P.foo" ); } }

class C extends P { foo() { super(); } }

var c1 = new C();

c1.foo(); // "P.foo"


var D = { foo: function() { console.log( "D.foo" ); } };

var E = { foo: C.prototype.foo };

// Ссылка от E к D для делегирования

Object.setPrototypeOf( E, D );


E.foo(); // "P.foo"


Исправляем привязку:


var D = { foo: function() { console.log( "D.foo" ); } };

var E = Object.create( D );

// вручную связать `[[HomeObject]]` из `foo` в виде

// `E`, а `E.[[Prototype]]` — это `D`, так что

// `super()` — это `D.foo()`

E.foo = C.prototype.foo.toMethod( E, "foo" );


E.foo(); // "D.foo"


Типы и синтаксис


1. Типы


Встроенные типы: null, undefined, boolean, string, number, object, symbol (ES6)


var a;

a; // undefined

b; // ReferenceError: b is not defined


if (DEBUG) console.log( "Debugging is starting" ); // ошибка

if (typeof DEBUG !== "undefined") console.log( "Debugging is starting" ); // безопасно

if (window.DEBUG) // безопасно, т.к. обращение к свойству объекта, вернет undefined, если нету


2. Некоторые встроенные типы


Обработка массивоподобных объектов

var arr = Array.prototype.slice.call(arguments);

var arr = Array.from(arguments); // ES6


Правильный способ обращения к символу строки str.charAt(1)


join и map можно одолжить у массива для строки

var s = Array.prototype.join.call( str, "-" );


0.1 + 0.2 === 0.3 // false

if (!Number.EPSILON) Number.EPSILON = Math.pow(2,-52);

function numbersCloseEnoughToEqual(n1,n2) {

return Math.abs( n1 - n2 ) < Number.EPSILON;

}

var a = 0.1 + 0.2; var b = 0.3;

numbersCloseEnoughToEqual( a, b );


Безопасной макисмальное целое число Number.MAX_SAFE_INTEGER: 2^53 - 1


Оператор void сознает значение undefined из любого значения


NaN — спец. числовое значение, не равняется самому себе

ES6 Абсолютное равенство Object.is(..)

var a = 2 / "foo"; var b = -3 * 0;

Object.is( a, NaN ); // true

Object.is( b, -0 ); // true

Object.is( b, 0 ); // false


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


3. Обертки (Natives)


Смысла использовать обертки просто так нет. В JS все оптимизировано, круто работает и без моих кривых рук.

Unboxing: явно var a = new String('abc'); a.valueOf() //'abc', неявно, когда оператор требует примитив

var a = new Array(3); с одним аргументом не использовать


Использовать литеральную нотацию, даже для регулярок (Можно new RegExp, когда шаблон опр. динамически)


Date() и Error() наиболее полезные конструкторы

Date() без new возвращает строку с текущим временем и датой


By documentation convention, String.prototype.XYZ is shortened to String#XYZ


4. Приведение (Coercion)


Приведение только к (скалярным) примитивам string, number, boolean


  • Неявное приведение (не стоит изучать весь этот бред, просто запомни безопасные и легко читаемые способы)
  • Явное приведение 


a || b; // roughly equivalent to: a ? a : b;

a && b; // roughly equivalent to: a ? b : a;


Таблица эквивалентности:



5. Грамматика


Текст
















Report Page