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

Три роли функций

Функции в JS могут использоваться в трёх ролях:

1. Собственно функции
2. Методы
3. Конструкторы

Функция-метод практически непригодна к использованию как обычная функция, особенно если использует this, т.к. в случае вызова вне контекста объекта this будет указывать на глобальный объект (или будет равен undefined в strict mode).

Основной способ использования конструктора - вызов его для создания нового объекта с ключевым словом new. Использование без new является ошибкой, о способах противодействия этому можно узнать в отдельном разделе блога.

Конструктор при вызове с new неявно получает новый объект при вызове (на него ссылается this) и неявно возвращает новый объект, если return не используется в коде конструктора или же если return возвращает не объект.

Фактически, конструктор может вернуть произвольный объект любого типа, если в качестве аргумента оператора return будет использоваться объект:

mouse = { name: "Jerry", likesCheese: true, voice: function() { console.log("pi-pi-pi") } };

function Cat(name) {
    this.name = name;
    return mouse;
}

tom = new Cat("Tom");
tom.name;           // > "Jerry"
tom.voice();        // > pi-pi-pi
tom instanceof Cat; // > false

Одно из применений для подобного поведения - паттерн Singleton, позволяющий в конструкторе реализовать создание только единственного экземпляра объекта.

function Singleton() {

    if ( Singleton.instance ) {
        return( Singleton.instance );
    }
    return ( Singleton.instance = this );
 }

Только для такой (или подобной) реализации объекта две переменных, создаваемых через new, будут ссылаться на один и тот же объект, а следовательно будут равны.

var a = new Singleton();
var b = new Singleton();

a === b; // > true

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

var c = {}, 
    d = {};

c === d; // > false

суббота, 27 сентября 2014 г.

Методы, this

При создании объекта можно задать его методы:

var itchy = {
     color: "black",
     tail: true,
     voice: function { console.log("Meow!"); }
}

Методы можно вызывать двумя способами:

itchy.voice();  // > Meow!
itchy["voice"]; // > Meow!

Метод может создаваться и позже при помощи присваивания (обратить внимание на синтаксис):

var itchy = {
     color: "black",
     tail: true
}

itchy.voice = function() { console.log("Meow!"); };
itchy.voice();    // Meow!
itchy['voice'](); // Meow!

При необходимости обращаться к свойствам и методам объекта, нужно вызывать их по имени объекта и метода:

var itchy.whatColor = function() { console.log(color); }; 
// > SyntaxError.

var itchy.whatColor = function() { console.log(itchy.color); };
itchy.whatColor(); // > black

Но при удалении оригинальной переменной возникнет ошибка:

var scratchy = itchy;
itchy = null;
scratchy.whatColor(); // > TypeError

Для предотвращения таких ошибок нужно использовать this.
Ключевое слово this, использованное в методе объекта, ссылается на текущий объект. Можно обращаться к свойствам и методам объекта изнутри:

var itchy.whatColor = function { console.log(itchy.color); };
var scratchy = itchy;
itchy = null;
srcatchy.whatColor(); // > black

Некоторые объекты позволяют вызывать свои методы цепочкой:

maze.up().up().left().down().right();

Для этого каждый метод должен возвращать this:

maze = {
    up: function() { console.log("up!"); return this; },
    down: function() { console.log("down!"); return this; },
    left: function() { console.log("left!"); return this; },
    right: function() { console.log("right!"); return this; },
}

this возвращает ссылку на объект, являющийся текущим контекстом вызова.

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

1. Неявно через вызов метода object.method(...);         //this = object
2. Неявно через вызов new   new constructor(...); //this = новый объект
3. Явно через call         function.call(object, ...); //this = object
4. Явно через apply        function.apply(object, ...);//this = object

Если ни один из этих способов не использован, то this указывает на Global Object для браузера - window для Node.js - global.

В случае 1 один и тот же метод-функция может быть присвоен разным объектам.

var itchy = { color : "black" };
var scratchy = { color : "white" };

var c = function() { console.log(this.color); };

itchy.whatColor = c;
scratchy.whatColor = c;

При вызове метода whatColor this для каждого объекта будет свой.

itchy.whatColor();
scratchy.whatColor();


В случае 2 конструктор может быть вызван и как обычная функция (без new). При этом this в обычном режиме будет ссылаться на глобальный объект (window, global), а в strict mode будет равен undefined.


Функции call и apply являются стандартными методами объекта типа function. Они позволяют (разово) выполнить произвольные функции в контексте объекта, указанного в качестве первого аргумента методов call и apply. Функция будет выполнена как метод объекта, даже если у объекта уже есть метод с таким именем. Назначения функции постоянным методом объекта как в случае с присваиванием не происходит.

var obj = { 
    prop: "value", 
    method: function() { console.log ("Do something!"); } 
};

var method = function() { console.log("Do something else!") };

obj.method();     // Do something!
method.call(obj); // Do something else!
obj.method();     // Do something!


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

func.call(context, arg1, arg2, ..., argN);
func.apply(context, [arg1, arg2, ..., argN);


Если первый аргумент call/apply равен null или undefined, то функция выполнится в контексте глобального объекта (с this равным window или global) в обычном режиме и с this равным null или undefined в strict mode.

При помощи apply можно сделать универсальную переадресацию вызова:

function f(a, b) {
    g.apply(this, arguments);
}

Функция g вызывается в том же контексте, что и f и с теми же аргументами. Код будет таким же для любого количества аргументов f.

Объекты в JavaScript: Конструкторы


Для создания любого объекта можно использовать функцию конструктор вида:

function Cat(color, tail) {
    this._color = color;
    this._tail  = tail;
}

Фактически, конструктор является инициализатором свойств объекта. 
И затем создать конструктор при помощи оператора new.

var scratchy = new Cat("black", true);

Во время создания объекта контекст this устанавливается на созданный объект, а сам он возвращается в результате выполнения new, если не указано конкретное возвращаемое значение.

scratchy instanceof Cat; // > true

Методы объекта создаются через прототипы:

Cat.prototype.describe = function() {
    return this.color + " cat " + 
           (this._tail ? "with" : "without") + " tail";
}

Если забыть оператор new при создании объекта, то Cat выполнится как обычная функция, возвращающая значение undefined (return отсутствует) и устанавливающая значения _color и _tail для глобального объекта. Чтобы этого не произошло, конструктор желательно создавать с директивой "use strict"; которая выбросит исключение при попытке установки свойств глобального объекта.

Что можно предпринять против "забывчивости"?

1. Создание объекта можно перенести внутрь конструктора и тогда не понадобится использовать new при объявлении - это произойдет в коде конструктора:

function Cat(color, tail) {
    "use strict";

    if (this instanceof Cat) {
        this._color = color;
        this._tail = tail;
    } else {
        return new Cat(color, tail)
}

При вызове без new выполнение пойдёт сначала по ветви else, а потом уже с вновьсозданным объектом по ветви if

пятница, 26 сентября 2014 г.

Функции JavaScript. Часть 2: аргументы

Функции в JavaScript могут содержать произвольное число параметров. Для всех используется одно и то же описание. 

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

Объект arguments (это объект, а не массив, его ключи - числовые ключи объекта, а не индексы массива) содержит все аргументы, реально полученные функцией. Этот объект доступен только из самой функции и создается вместе с ней. В том случае, когда число параметров неизвестно или может меняться, а функция проводит над параметрами однотипные действия, удобнее всего использовать его вместо объявления параметров в заголовке.

function sum() {
    var i, s = 0;
    for (i = 0; i < arguments['length']; i += 1) {
        s += arguments[i];
    }
    return s;
};


Каждая функция имеет встроенный метод apply. Обычно он используется для вызова функции в контексте некоторого объекта и, в отличие от аналогичного метода call, позволяет передавать аргументы функции в виде массива.  Альтернативное применение метода - применение функции с переменным числом аргументов к массиву, который, может возвращать некоторая другая функция. В этом случае в качестве первого аргумента apply используется null:

function getRandoms(n) {
    var a, i;
    for (i = 0; i < n; i += 1) {
      a[i] = Math.random();
    }
    return a;
}

var s = sum.apply(null, getRandoms(10));

Если получено меньше аргументов, чем объявлено, все недополученные аргументы инициализируются значением undefined

Если получено аргументов больше, чем объявлено, "лишние" аргументы можно получить из объекта-псевдомассива arguments.

arguments является именно объектом, свойства которого имеют числовые имена. Благодаря синтаксису JS мы можем обращаться к свойствам объекта в стиле обращения к элементам массива: arguments[0] вместо arguments['0'] (для массивов работает и обратное правило). Для работы с объектом arguments как с настоящим массивом со всеми применимыми к массиву методами можно использовать следующую конструкцию:

var args = [].slice.call(arguments);
var args = Array.prototype.slice.call(arguments); // быстрее

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

function f() {
    console.log(arguments.length);
}

f(1, 2, 3);  // > 3
console.log(f.length); // > 0

Для вариативных функций, позволяющих вызов без параметров, и использующих только объект arguments, f.length() будет возвращать 0, несмотря на способность принимать любое количество параметров.

Важно! Т.к. arguments является только псевдомассивом, его свойство length не обновляется при удалении свойств arguments.

(function(a, b, c) {
  delete arguments[0];

  return arguments.length;
})(1, 2, 3); // > 3

В strict mode arguments содержит копию всех значений объявленных параметров. Изменяя его свойства мы не изменяем параметры из заголовка функции. В обычном режиме arguments и объявленные параметры связаны (параметры являются псевдонимами соответствующих значений arguments):

function f(param) {
     arguments[0] = 10;
     console.log(param); 
}

f(5); // > 10

function f(param) {
    "strict mode";
    argumetns[0] = 10;
    console.log(param);
}

f(5); // > 5

Для определения имени функции, которая в данный момент вызвана, можно использовать свойство arguments.callee. Для определения имени функции, которая вызвала данную функцию, свойство arguments.callee.caller. Оба эти свойства не рекомендуются к использованию и недоступны в strict mode.

В том случае, если определение функции содержит много аргументов, можно перепутать порядок их вызова при использовании функции. Кроме того, ряд аргументов может быть опциональным (для этого обычно используют значение null), поэтому при вызове для всех таких аргументов нужно проставить null. Чтобы избежать этого, функции можно передать объект, содержащий в качестве свойств только те аргументы, с которыми реально нужно запустить функцию.

Допустим, объявлена функция:

function f(foo, bar, param1, param2) {};

Вместо вызова:

f(100, null, "value1) 

можно объявить ее иначе:

function f(args) {
  var foo = args.foo || 200;  // инициализация значениями по 
  var bar = args.bar || 350;  // умолчанию вместо undefined
  var param1 = args.param1 || "string1";
  var param2 = args.param2 || "string2";
}

и использовать вызов:

f({ foo: 100, param1: "value1" });

или же

var opts = { foo: 100, param1: "value" };
f(opts);

Использование объекта в качестве аргумента функции эквивалентно технике именованных аргументов (именованных параметров) других языков.

Переменные в JavaScript, область видимости

Переменные объявляются при помощи ключевого слова var.

var x = 42;
var z;
var a = 1, b = 2, c;

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

Возможно объявление констант, которые не могут меняться во время выполнения. Ключевое слово - const. Инициализация констант должна происходить только в момент определения. Т.к. константы поддерживаются только в IE11+, принято именовать константы большими буквами. В ФП все переменные, по сути, являются константами, т.к. не переопределяются после создания.

Каждая переменная имеет свою область видимости. 

Два типа области видимости - глобальная и область видимости функции (блочной видимости нет). 

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

При наличии переменных с одинаковыми именами предпочтение отдается тем, кто расположен на одном уровне, далее - на уровень выше вплоть до глобального. Область видимости передается при помощи скрытого объекта, все переменные функции являются его свойствами. Для глобвальных переменных это объект window (или global).

При выполнении скрипта происходит разделение объявления и инициализации. Объявление помещается в начало области видимости - под заголовок функции или в начало файла (поэтому переменная, объявленная в коде в конце функции будет видна в ее начале), инициализация в виде присваивания остается на прежнем месте. Из-за этого возможны различные нежелательные эффекты:

if (!("a" in window)) {
    var a = 1;
}
alert(a);

превращается в 

var a;
if (!("a" in window)) {
    a = 1;
}
alert(a);

Поэтому программа выводит undefined, т.к. переменная не инциализируется.

Однако в Node.JS код работает верно.

В обычном режиме все переменные, инициализированные без объявления, считаются глобальными (становятся свойствами глобального объекта), в strict mode инициализация без объявления невозможна.

четверг, 25 сентября 2014 г.

Функции JavaScript. Часть 1: function declaration and function expression

Объявление (function declaration):

function имя(параметры, через, запятую) {
 код функции;
 [return [значение];]
}

Внутри функции можно объявить переменную с локальной областью видимости. При совпадении имен с внешними переменными они переопределяются.

Функция, выполняющаяся без return или с return без значения возвращает undefined
Если параметр не передан при вызове, он считается undefined. Таким параметрам можно присваивать “значение по умолчанию”:

if (param === undefined) { param = value; }
или
param = param || value;

Фактически при подобном объявлении создается переменная (объект типа function) с которой связывается некоторый набор команд, составляющий функцию. Данный объект имеет свойство name, содержащее в себе имя функции. Функцию можно присвоить новой переменной, обнулив старую:

function f() { return 10; }
g = f;
f = null;
g();    // > 10
g.name; // > f
f();    // > TypeError - object is not function

Функции, объявленные как function declaration, создаются интерпретатором до выполнения основного кода, следовательно невозможно, например. их условное определение.

Существует иной формат объявления - функциональное выражение (function expression).  В этом случае объявление входит в состав какого-либо выражения:

var n = function() {};
(function m(){});
+function() {};

Из контекста интерпретатору должно быть очевидно, что это не function declaration. Функциональные выражения могут быть именованными или анонимными:

x = function() {};   // Anonymous function expression
x.name;              // > 
f = function g() {}; // Named function expression
f.name;              // > g
g();                 // > ReferenceError 
                     // До IE9 создавался дополнительный объект g

Во втором случае переменной f типа function присваивается ссылка на некоторый код (в данном случае пустой), f.name присваивается значение ‘g’. Имя функционального выражения становится доступным только внутри функции (при обращении к нему будет получен код функции или [Function: name] для js.node).

Имя g() доступно из самой функции, это применяется в рекурсивных вызовах и при отладке..

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

(function (arg) { return arg * arg; })(4); // > 16
+function (arg) { return arg * arg; }(4);  // > 16
!function (arg) { return arg * arg; }(4);  // > false

В последнем случае оператор ! способствует тому, чтобы объявление функции воспринималось как function expression, но одновременно производит действия над результатом функции (!16 === 0).

Возможны также вариант с по сути анонимным конструктором:

new function(arg) { console.log ( arg * arg ) }(4);

и даже эзотерический

new function() { 
return function (arg2) { return arg2 * arg2 } 
}()(4); // > 16

Первый вариант пригоден только в качестве процедуры, т.к. возвращает объект не пригодный к использованию, второй возвращает значение.

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


вторник, 23 сентября 2014 г.

== vs ===

Во всех случаях рекомендуется использовать строгое сравнение во избежание ряда ошибок.


== - сравнение с попыткой преобразования к типам друг друга.
=== - сравнение при сторогом совпадении типов.

1. Даже если известно, что типы сравниваемых значений совпадают, нет смысла использоваться нестрогим сравнением. Строгое сравнение как минимум не медленнее, при этом не нарушается единообразие подхода (зачем использовать два типа сравнений?).

2. undefined == null // true, 
но
undefined === null // false
Часто эти значения следует различать. Если же допустимы оба, то лучше это подчеркнуть отдельно:
if (x === undefined || x === null)

Допустим вариант if (!x) ... "пропускающий" значения undefined, null, false, 0, "".

3. При сравнении чисел и строк можно принудительно приводить переменную к нужному типу, что повышает ясность кода:

if (Number(x) === 123) ...
if (String(x) === "abc") ...

4. При сравнении литералов с примитивами срабатывает только нестрогое сравнение.

var s = new String("abc");
console.log(s == "abc");  // true
console.log(s === "abc"); // false

Однако здесь возможны варианты с принудительным приведением:

console.log(String(s) === "abc"); // true
console.log(s.ValueOf() === "abc"); // true

5. В некоторых случаях предпочтительна жесткая проверка на типы с соответствующей реакцией. Можно выбирать различные варианты:

function is123Explicit(x) {
        x = Number(x);
        return x === 123;

    }

или

function is123Defensive(x) {
        if (typeof x !== "number") {
            throw new TypeError("Not a number: "+x);
        }
        return x === 123;
    }

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

БАЗОВЫЕ ТИПЫ ДАННЫХ JavaScript

Неформальное определение: тип - внутренняя встроенная характеристика, которая позволяет определить некоторое значение и однозначно отличать его от прочих значений как движку, так и разработчику. Например, если и разработчик и движок трактуют значение 42 иначе, чем "42", эти значения относятся к разным типам.

Важно! Переменные не имеют типа. Его имеют только значения, которые содержат эти переменные.

typeof - встроенный оператор языка, возвращающий строку, содержащую название типа. Определяется тип значения (value) той конструкции, которую передали оператору (это может быть переменная, выражение или примитив).

Встроенные примитивы языка:

1. Объекты (typeof {}; // > object)

Важно! Массивы и функции являются разновидностями объектов ( typeof [] === "object" ) с особыми свойствами. Функции могут исполняться (благодаря внутреннему свойств [[Call]], содержащему код функции), массивы имеют целочисленные свойства и автоматически изменяемое свойство length.

2. Строковые литералы (typeof "abc"; // > string)
3. Числа (все - с плавающей точкой двойной точности, typeof 123; // > number)
4. Булевские значения - true, false (typeof true; // > boolean)

Специальные значения:
5. undefined - значение по умолчанию, предопределенная переменная, назначается автоматически, неожиданно отсутствующие данные, в том месте, где они предполагаются (type of undefined; // undefined).

Получается следующим образом:
- Значение переменной по умолчанию (объявление без присвоения).
- Обращение к несуществующему свойству объекта или отсутствующему элементу массива.
- Обращение к отсутствующему аргументу функции.
- Результат выполнения функции без оператора return или с оператором return без параметра.
- Результат использования оператора void expr;

Важно! Фактические могут находиться в двух состояниях - undefined (переменная объявлена, но не имеет значения) и uneclared (переменная не объявлена). Обращение к переменной в состоянии undefined не вызывает ошибки (вернётся undefined), а в состоянии undeclared вызывает ReferenceError. Проблема заключается в том, что typeof для обоих состояний возвращает undefined (причём без ошибки для необъявленных переменных).

6. null - намеренно отсутствующие данные в том месте, в котором они предполагаются, фактически - оператор, однако typeof null === "object". Это известная ошибка языка (фактически должна возвращаться строка "null"), однако она вряд ли будет исправлена.
Объекту, который перестали использовать, лучше сразу присваивать значение null, чтобы освобождать память и не вызывать дополнительно GC и чтобы вылавливать ошибки с повторным обращением к объекту, которых не должно быть.

7. Стандарт ES6 вводит также тип Symbol. ( typeof Symbol() === "symbol" )

Встроенные объекты языка:

String - обёртка для string
typeof "abc"; //"string"
typeof String("abc"); //"string"
typeof new String("abc"); //"object"
typeof (new String("abc")).valueOf(); //"string"

Number - обёртка для number
typeof 123; //"number"
typeof Number(123); //"number"
typeof new Number(123); //"object"
typeof (new Number(123)).valueOf(); //"number"

Boolean - обёртка для boolean
typeof true; //"boolean"
typeof Boolean(true); //"boolean"
typeof new Boolean(true); //"object"
typeof (new Boolean(true)).valueOf(); //"boolean"

Прочие встроенные объекты:

Array        // var a = new Array или var a = [];
Date 
Function   // var f = new Function() или var f = function() {};
RegExp    // var s = new Regexp(/\s*/) или var r = /\s*/;
Math
JSON

Object       // var o = new Object или var o = {}; 
Error

Важно! Объект является базовым типом языка, использование оператора new всегда возвращает объект. Примитивы были введены с целью повышения скорости работы движка. Только объекты имеют свойства и методы. При обращении к методам примитивов (причем только числовых, строковых и логических) они временно приводятся к объектам и методы корректно исполняются. Однако попытки присвоить значения свойствам примитивов ничего не дают - в дальнейшем эти свойства возвращают undefined.

Обращение к свойствам и методам значений unedifned и null (или переменных, содержащих эти значения) вызовет ошибку TypeError.

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

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

var a = { x: 10 }
var b = { x: 10 }
var c = a;
console.log(a == b);  // false
console.log(a === b); // false
console.log(a == c);  // true
console.log(a === c); // true

typeof возвращает "object" для всех типов кроме Function, Object и Error, для которых возвращается "function".
Единственный способ избежать ReferenceError для необъявленной переменной - предварительно использовать typeof переменная (вернет "undefined"). При этом реально переменная является undeclared.

value instanceof Constructor

эквивалентно

Constructor.protoptype.isPrototypeOf(value

Следующая функция возвращает более конкретный тип для каждого объекта, включая встроенные:


var toType = function(obj) {
  return ({}).toString.call(obj).match(/\s([a-zA-Z]+)/)[1].toLowerCase()
}


toType({a: 4}); //"object"
toType([1, 2, 3]); //"array"
toType(null); //"null"
toType(undefined); //"undefined"
(function() {console.log(toType(arguments))})(); //arguments
toType(new ReferenceError); //"error"
toType(new Date); //"date"
toType(/a-z/); //"regexp"
toType(Math); //"math"
toType(JSON); //"json"
toType(new Number(4)); //"number"
toType(new String("abc")); //"string"
toType(new Boolean(true)); //"boolean"

Однако возможны нюансы при работе с хост-объектами, которые создаются браузерами и не подчиняются стандарту:


toType(window);
//"global" (Chrome) "domwindow" (Safari) "window" (FF/IE9) "object" (IE7/IE8)
toType(document);
//"htmldocument" (Chrome/FF/Safari) "document" (IE9) "object" (IE7/IE8)
toType(document.createElement('a'));
//"htmlanchorelement" (Chrome/FF/Safari/IE) "object" (IE7/IE8)
toType(alert);
//"function" (Chrome/FF/Safari/IE9) "object" (IE7/IE8)

Модифицированный вариант функции:

function getTypeName(value) {
  if (value === null) {
    return "null";
  }
  var t = typeof value;
  switch(t) {
    case "function":
    case "object":
      if (value.constructor) {
        if (value.constructor.name) {
          return value.constructor.name;
        } else {
          // Internet Explorer
          // Anonymous functions are stringified 
          // as follows: 'function () {}'
          // => the regex below does not match
          var match = value.constructor.toString().match(/^function (.+)\(.*$/);
          if (match) {
            return match[1];
          }
        }
      }
      // fallback, for nameless constructors etc.
      return Object.prototype.toString.call(value).match(/^\[object (.+)\]$/)[1];
    default:
      return t;
  }
}