среда, 29 мая 2013 г.

Douglas Crockford о JavaScript

False в JavaScript только

false
null
undefined
"" (пустая строка)
0
NaN

все остальное в JavaScript будет равно True и являться объектом!

Object        'object'
Array        'object'
Function    'function'
String        'string'
Number     'number'
Boolean     'boolean'
null            'object'
undefined  'undefined'

Правило
var last = input || nr_items;
Если input равно truw, то вернется last = input, а если input равно false, то last = nr_items.

Побитовые операции в JavaScript работают медленно.

for (var key in obj) {
    if (obj.hasOwnProperty(key)) {alert('Это свойство принадлежит только данному объекту, а не наследуется от его родителей!');}
}

throw new Error('Error');

throw {
    name: 'Error',
    message: 'It is Error'
};

try {
    // some code
} catch(error) {
    switch(error) {
        case 'Error':        console.log(); break;
        case 'EvalError':   console.log(); break;
        case 'RangeError': console.log(); break;
        case 'SyntaxError': console.log(); break;
        case 'TypeError':    console.log(); break;
        case 'URIError':      console.log(); break;
        default: throw error;
    }
}


3 Способа создания пустого объекта.
new Object();
{};
object(Object.prototype);

В функции можно подставлять переменное число аргументов.
Если число аргументов больше, то лишние будут проигнорированы.
Если число аргументов меньше, то нехватающие получат значение undefined.

functions One (a, b, c) {
    console.log(arguments); // a, b, c
    console.log(arguments.length); // 3
}

Вызов внутренних функций внутри объекта

var obj = {
    outerFunc:  function () {
        this.outerFunc();

        function InnerFunc(){

            var thisObj = this;
                 thisObj.outerFunc();

        }

    }
};

Метод trim для работы со строками.

String.prototype.trim = function(){
    return this.replace(/^\s*(\S*(\s+\S+)*)\s*$/, "$1");
}

Простой шаблонизатор

var template = '<table>'
                   + <tr><td>{first}</td></tr>'
                   + <tr><td>{last}</td></tr>'
                   + '</table>';

var data = {
    first: 'Carl',
    last: 'Hollywood'
};

mydiv.innerHTML = template.supplant(data);

String.prototype.supplant = function(o) {
    return this.replace(/{([^{}]*)}/g,
        function(a, b) {
            var r = o[b];
            return typeof r === 'string' ?
                     r : a;
        }
    );
}

typeof array равен object
typeof null равен object

Используйте parseJSON() вместо eval().
eval('some') вызывает new Function('some');

Глобальный обект

var global = this;
var someVar = 1;
console.log(global.someVar);

В браузере глобальный объект - это window.

var someVar = 1;
console.log(window.someVar);

JSLint.com - это компилятор JavaScript, написанный на JavaScript, который может поверять код JavaScript.

Создание Namespace

var YAHOO = {}; // Это Namespace, внутри котрого мы создаем свой код.

YAHOO.my = 1;
YAHOO.func =  function(){}
YAHOO.TriviaObj = function () {
    var one;
    function two(){}

    return {
        one: one,
        two: two
    };
}();

console.log(YAHOO.my);
YAHOO.func();
console.log(YAHOO.TriviaObj.one);
YAHOO.TriviaObj.two();

Для преобразования чисел

var num = parseInt('08', 10); // 8

Стиль программирования javascript.croncford.com/code.html

Для сравнения использовать === вместо ==

Часть 2

produce.croncford.com/theory.ppt

Печатай просто
<script>alert(1);</script>
<script src="file.js"></script>
остальное игнорируется браузером.

Не используйте document.write();

name используется для элементов форм name=value&name=value
id используется только для идентификации одного любого элемента

document.getElementById('id');
document.getElementsByName('name');
document.getElementsByTagName('tag');

widnow.document.html.head.title.text
window.document.html.body.h1.text

h1.firstChild
h1.lastChild
h1.previousSibling
h1.nextSibling
h1.parentNode

Вызов функции func() для каждого элемента, имеющего класс в DOM.

function walkTheDOM(node, func) {
    func(node);
    node = node.firstChild;
    while (node) {
        walkTheDOM(node, func);
        node = node.nextSibling;
    }
}

window.document.getElementsByClassName(className) {
    var results = [];
    walkTheDOM(document.body, function(node) {
        var a, c = node.className, i;
        if (c) {
            a = c.split(' '); // Деление нескольких классов, если их несколько в строке class="one two three"
            for (i = 0; i < a.length; i += 1) {
                if (a[i] === className) {
                    results.push(node);
                    break;
                }
            }
        }
    }
    return results;
}

window.document.getElementsByClassName(className);

Выборка атрибутов элементов

console.log(img.src);
img.src = 'one';
node.style.stylename;

или

element.getAttribute('src');
element.setAttribute('src', 'one');

Создание элементов

document.createElement('tagName');
document.createTextNode('text');
node.cloneNode();

Вставка элементов.

node.appendChild(new);
node.insertBefore(new, sibling);
node.replaceChild(new, old);
old.parentNode.replaceChild(new, old);

Удаление элементов.

node.removeChild(old);

Вставка элементов, доступная во всех браузерах.

element.innerHTML = '<h1>one</h1>'; // element.setHTML('<h1>one</h1>');

Добавление событий Events

node["on" + type] = function(){}; // Этот способ работает везде

node.attachEvent();  // Этот способ работает только в IE

node.addEventListener(type, function(){}, false); // Этот способ не работает в IE
// false - выбирает погружение, true - всплытие bubble.
// Работают обе модели


Универсальное добавление обработчиков событий к элементу

function(e) {
    e = e || event;
    var target = e.target || e.srcElement;
    ... код обработчика события ...
}

Отмена всплытия

Использовать одновременно и e.cancelBubble и e.stopPropagation!!!

e.cancelBubble = true;
if (e.stopPropugation) {
    e.stopPropagation;
}

Отмена с обытия по умолчанию

Использовать одновременно все 3 варианта!!!

e.returnValue = false;
if (e.preventDefault) {
    e.preventDefault();
}
return false;

В IE 6 удаляйте обработчики событий перед удалением самих элементов removeChilde и replaceChild, чтобы не было утечек памяти.
Это исправлено в IE7.

Удаление событий элемента перед его удалением

function purgeEventHandlers(node){
    walkTheDOM(node, function(e) {
        for (var n in e) {
            if (typeof e[n] === 'function') {
                e[n] = null;
            }
        }
    });
}

alert(), confirm,(), prompt() не используйте в AJAX программах, поскольку они прерывают выполнение потока программы.
Передача данных между браузером и сервером может из-за них прерваться.

open() часто может не работать из-за PopUp блокеров.

Browser Detection - неудачная идея.
window.navigator.userAgent - часто подставляет ложную информацию

Поэтому лучше опрашивать объект в браузере какие свойства и методы он имеет и подставлять под них работающий вариант кода.

Часть 3

var oldObject = {
    first: function(){},
    second: function(){}
};

var newObject = object(oldObject);
     newObject.third =  function(){};

var otherObject = object(newObject);
     otherObject.first();

Паттерн Constructor объектов с оператором new

function Constructor() {
   this.member = 'initializer';
   return this; // optional
}

Constructor.prototype.first = function () {};
Constructor.prototype.second = function() {};

Constructor.method('third', function(){});

var newObject = new Constructor();
     newObject.first();

Наследование конструкторов объектов

function BiggerConstructor () {};
BiggerConstructor.prototype = new Constructor();

Определение конструктора обекта

console.log(myObject.prototype.constructor);

Функция наследования объектов

function object(o){
    function F(){}
    F.prototype = o;
    return F;
}

newObject = object(oldObject);

Любую функцию можно присоединить к любому объекту.

function Str (string) {
    this.member = string;
}

myObject.str = Str;

Сниглтон

var singleton = {
    func1: function(a, b){},
    func2: function(c){}
}

В JavaScript функции - это не только функции, но и объекты, и конструкторы объектов, и модули.
В JavaScript функции могут делать все!

Самый базовый паттерн в JavaScript

var singleton = (function(){

    var privateVariable;

    function privateFunction(x){
        ...privateVariable...
    }

    return {
        publicFunction1: function(a, b){...privateVariable...},
        publicFunction2: function(c){...privateVariable...}
    };

})();

Вместе с пространством имен

YAHOO.MyProperty = (function(){

    var privateVariable;

    function privateFunction(x){
        ...privateVariable...
    }

    return {
        publicFunction1: function(a, b){...privateVariable...},
        publicFunction2: function(c){...privateVariable...}
    };

})();

Power Constructor - создание нового класса объектов с наследованием

function powerConstructor(){

    var that = object(oldObject);

           var privateVariable;
    function privateFunction(){}

    that.publicVariable1 = privateVariable;
    that.publicFunction1 = function(){};
    that.publicFunction2 = function(){};

    return that;

}

var newObject = powerConstructor();

Паттерн Паразитическое наследование
Не используется new

Croncford использует new только в new Object()
В остальных случаях делается так (запуск функций конструкторов без new).

function Gizmo(id, secret) {

   var secret = secret || {};

    return {
        id: id,
        toString: function(){
            return 'gizmo ' + this.id;
        }
    };

}

function Hoozit(id) {

    var secret = {}; // Final

    var that = Gizmo(id, secret);

    var superParentMethod = that.tostring;

         that.test = function(testid) {
             return testid === this.id;
         }

         that.toString = function() {
             return superParentMethod.apply(that, []);
         }

    return that;

}

var newObject = Hoozit(1);

Метод later

Object.prototype.later = function(msec, method){
    var that = this;
    var args = Array.prototype.slice.apply(arguments, [2]);
    if (typeof method = 'string') {
        method = that[method];
    }
    setTimeout(functino(){
        method.apply(that, args); // это означает вызвать метод method(args), применительно к объекту that.
    }, msec);
    return that;
}

myObject.later(1000, 'erase', true);


// Array.prototype.slice.apply(arguments, [2]); - это означает вызвать метод slice(2), применительно к объекту arguments.

Вызов Дебагера

if (someyhing === 'wrong') {
    debugger;
}

Сложение строк через join происходит быстрее, чем через +, но только при больших объемах операций сложения.

var str = [
    'a'
  , 'b'
  , 'c'
].join(' ');

Множественный вызов функции

double (3)(4); // 7

function double (x) {
    return function(y) {
        return x + y;
    }
}

Object property get, set and delete

Get
object.name
object[expression]

Set
object.name = value;
object[expresion] = value;

Delete
delete object.name;
delete object[expression];

Внимание!!! Числа с плавающей точкой в JavaScript считаются неверно!!!

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

(a+b) + c  === a + (b + c) // false !!!

Пример object property expression

var = 1;
var result = Math[number >= 0 ? 'floor' : 'ceil'](number);

NaN === NaN; // false
NaN !== NaN;  // true

Multiline strings

var longString = 'This is a \
long string';

Number to String

var str = num.toString();
var str = String(num);

String to Number;

var num = Number(str);



typeof
instanceof

indexOf
valueOf

Добавление ногого значения в массив.

var arr = [1, 2, 3];
arr[arr.length] = 4; // arr.length - это expression внутри [ ]

Удаление элементов Array

var array = [1, 2, 3, 4];

delete array[numbers];  // Оставляет пустое место.

[1, 2, undefined, 4]

Поэтому удалять элементы Array лучше так

array.splice(number, 1);

[1, 2, 4];

undefined - это значение любой переменной по умолчанию!

var a;
console.log(a); // undefined

Array.isArray

alert(Array.isArray([])); // true

if (typeof Array.isArray !== 'function') {
    Array.isArray =  function(value) {
        return Array.prototype.toString.apply(value) === '[object Array]';
    };
}

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

Функции конструкторы, требующие new, всегда должны начинаться с заглавной буквы.

new Constructor();

но

constructor();

/ - slash
\ - backslash
* - star

Преобразование строк в числа

+'42' === 42 // Быстро преобразует строку в число без заморочек
Number('42') === 42
parseInt('42', 10) === 42
+'3' + (+'4') === 7

% - не просто деление по модулю

-1 % 8 // -1, а не 7

ВСЕГДА, ВСЕГДА используй === вместо ==, потому что == в очень многих ситуациях ощибается!!!

&&

if (a) {
    return a.member;
} else {
    return a;
}

Это может быть перезаписано так

return a && a.member;

var value = p && p.name; // Значение value будет получено из p, если p имеет значение и не вызывает ошибки.

||

if (input !== undefined) {
    return input;
} else {
    return nr_items;
}

Это может быть перезаписано так

input = undefined;
var a = input || nr_items;

В JavaScript нет значение типа Integer. Все значения являются Float (double).

Внутри switch в case могут быть числа, строки и выражения

switch(expression){
    case 1: break;
    case 'two': break;
    default: 'three';
}

Не использовать with.

Функции в JavaScript - это:
- Метод
- Класс
- Конструктор
- Модуль

Только функции могут скрывать внутри себя переменные, определяя область их видимости (scope).

Все переменные внутри функции лучше определять в самом начале, а функции определять перед их вызовом.

return

return expression;
или
return; // значение undefined

Функция может принимать любое число аргументов.

Arguments

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

var ten = sum(1, 2, 3, 4);

Новое в Arguments

function sum() {
    return arguments.reduce(function(a, b) {return a + b;}, 0);
}

var ten = sum(1, 2, 3, 4);

4 Способа вызвать функцию
- Function form
- Method form
- Constructor form
- Appy form

Function form

functionObject(arguments);

Method form

thisObject.methodName(arguments);
thisObject['methodName'](arguments);

Constructor form

new FunctionObject(arguments);

Appy form

functionObject.apply(thisObject, arguments); // Принимает массив Аргуменов! arguments Вызов функции functionObject у объекта thisObject с параметрами arguments
functionObject.call(thisObject, argument); // Только 1 Аргумент! argument

Преобразование call, чтобы могла принимать массив аргументов.

Function.prototype.call = function(thisObject) {
    return this.apply(thisObject, Array.prototype.slice.apply(arguments, [1]));
}

3 Способа создания объекта
- Object
- Constructor
- Literal

Object

var obj = new Object();

Constructor

var obj = new ConstructorFunction();

Literal

var obj = {};

Рекурсия - вызов функции самой себя.
Рекурсию можно использовать для сортировки массива на 2 группы в поисках какого-либо числа.
Делим массив на 2 группы, вызываем опять функцию, которая опять делит наши группы на 2 группы в поисках числа.
И так мы быстро пробегаемся по всему массиву.

Closures

Сокрытие переменных через замыкания и скрытие их от глобальной области видомости.
Позволяет создавать пространства имен и модули.

var digit_name = function(n){

    var names = ['zero', 'one', 'two'];

    return names[n];

};

alert(digit_name(2)); // 'two'

Но вызывая функцию каждый раз мы каждый раз создаем в памяти новый массив name.
Это может привести к утечке памяти.

Поэтому лучше делать так

var digit_name = (function(){

    var names = ['zero', 'one', 'two'];

    return function(n) {
        return names[n];
    }

})();

alert(digit_name(2)); // 'two'

Таким образом мы переопределяем функцию digit_name(), которая теперь создает массив names только 1 раз.
Так работает замыкание Closure, так как массив names теперь замкнут.

Так лучше не делать

var digit_name = function(){

    var names = ['zero', 'one', 'two'];

    digit_name = function(n) {
        return names[n];
    }

    return digit_name(n);

};

alert(digit_name(2)); // 'two'

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

Сокрытие значений внутри функции от посторонних глаз

function make_sealed(){

    // Сокрытые значения (хранилище значений)
    var boxes = []
       , values = [];

    return {
        sealer: function(value) { // Сокрытие значения
            var i= boxes.length
               , box = {};

            boxes[i] = box;
            values[i] = value;

            return box;
        },
        unsealer: function(box) { // Получение сокрытого значения
            return values[boxes.indexOf(box)];
        };
    };

}

Очень удобное и понятное Прототипное наследование

function new_constructor(extend, initializer, methods) {
    var func
       , prototype =  Object.create(extend && extend.prototype); // Это надо усовершенствовать для работы в старых браузерах.
   
    if (methods) {
        methods.keys().forEach(function(key){
            prototype[key] = methods[key];
        });
    }

    func = function () {
        var that = Object.create(prototype);
        if (typeof initializer === 'function') {
            initializer.apply(that, argument); // Добавить функцию к обекту со следующими аргументами.
        }
        return that;
    }

    func.prototype = prototype;
    prototype.constructor = func;
    return func;
}

var gizmo = new_constructor(
    Object                             // Родительский класс
  , function(id) {this.id = id;} // Переопределение родительского свойства
  , {                                    // Добавление новых свойств
         toString: function(){
         return 'gizmo ' + this.id;
    }
});

var hoozit = new_constructor(
    gizmo                              // Родительский класс
  , function(id) {this.id = id;} // Переопределение родительского свойства
  , {                                    // Добавление новых свойств
         toString: function(){
         return 'gizmo ' + this.id;
    }
});


Паттерн Модуль

var singleton = (function(){

    var privateVariable;

    function privateFunction(){
        return privateVariable;
    }

    return {
        publicFunction: function(){privateFunction();}
    };

})();

singleton.publicFunction();

или так

GLOBAL.methodical = (function(){

    var privateVariable;

    function privateFunction(){
        return privateVariable;
    }

    return {
        publicFunction: function(){privateFunction();}
    };

})();

GLOBAL.methodical.publicFunction();

Другой вариант


(function(){

    var privateVariable;

    function privateFunction(){
        return privateVariable;
    }

     GLOBAL.publicFunction = function(){privateFunction();}  

})();

GLOBAL.publicFunction();

Функционально наследование

function gizmo (id) {
    return {
        id: id,
        toString: function(){return 'gizmo ' + this.id;}
    };
}

function hoozit (id) {
    var that = gizmo(id);
    that.test = function(testid) {
        return testid === this.id;
    };
    return that;
}

Сокрытие параметров

function gizmo (id) {
    return {
        toString: function(){return 'gizmo ' + this.id;}
    };
}

function hoozit (id) {
    var that = gizmo(id);
    that.test = function(testid) {
        return testid === id;
    };
    return that;
}

Не создавайте функции внутри циклов!
Но присваивать вызовы функций внутри циклов можно.

Y Combinator

function y(le) {
    return (function(){
                  return f(f);
    })(function(f){
        return le(function(x){
            return f(f)(x);
        });
    });
}

var factorial = y(function(fac){
    return function(n){
        return n <= 2 ? n : n * fac(n-1);
    };
});

var number120 = factorial(5);

HTML-элементы в JavaScript - это объекты, имещие свойства.
Пример
img.src;
img.src = 'one';
img.height;
img.width;

Все как у обычных объектов. Есть геттеры и сеттеры.

У каждого элемента свой уникальный набор свойств.
Например, атрибуты div и ul отличаются от набора атрибутов img.

Элемент HTML сначала создается, но он не виден на странице.
Потом он вставляется в дерево тэгов страницы и после этого только становится виден на экране.

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

Больше всего времени затрачивается на перерисовку DOM, чем на выполнение кода JavaScript.
Поэтому любые манипуляции с DOM очень затратны по времени.

Эти программы позволяют замерять время перерисовки DOM во время выполнения сценария
SpeedTracer [Chrome]
dynaTrace [IE]

XSS атака
Если на сайт можно вставить тэг <script>, то он сможет загрузить JavaScript-файл с любого сервера на эту страницу.
<script src="evil.js"></script>
В результате атакующий получит доступ ко всему коду страницы, которую проматривает пользователь, включая cookie, через глобальный объект window.
Атакующий может запросить повторно ввести пользователя его логин и пароль через форму и отослать их злоумышленнику на любой сервер.
Пользователь будет думать, что работает с обычной страницей.
Например атаку XSS можно провести, воспользовавшись шаблонной системой сайта, например вставив
<?= "bang" ?>
или
{{ if myscript }}
в форму.
Сервер такой код не обработает и не почистит.

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

function funky(o) {
    o = null;
}

var x ={};
funcky(x);

alert(x); // x = {}, так как o хранит в себе значение x, а не является самой переменной x.

function swap(a, b) {
    var t = a;
         a = b;
         b = t;
}

var x = 1, y = 2;
swap(x, y);

alert(x); x = 1, так как a хранит в себе значение x, а не является самой переменной x.


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

var variable = {};
var a = variable;
var b = variable;
alert(a === b); //  Это один и тот же объект.

Callback

function do_it(inputs, callback) {
    ...
    callback(result);
}

do_it(my_inputs, function(result){my_object.blah = result;});

Storer

function do_it(inputs, callback) {
    ...
    callback(result);
}

function storer(obj, name) {
    return function (result) {
        obj[name] = result;
    };
}

do_it(my_inputs, storer(my_object, 'blah'));

Storer maker

function storer_maker(obj) {
    return function(name) {
        return function(result){
            obj[name] = result;
        };
    };
}

var my_storer = storer_maker(my_object);
do_it(my_inputs, my_storer('blah'));

или

do_it(my_inputs, storer_maker(my_object)('blah'));

Функция func будет выполнена только 1 раз

function once(func) {
    return function(){
        var f = func;
        func = null;
        return f.apply(this, arguments);
    };
}

do_it(my_inputs, once(storer(my_object, 'blah')));

Используй JSLint или JSHint!

Аналог while рекурсивная функция

while (<condition>) {
    <body>
}

(function whiler () {
    if (<condition>) {
        <body>
         return whiler();
    }
})();

Для программ, которые должны выполняться в браузере долго нужно:
Разбить задачу на маленькие опреации и выполнять их в очереди в нужной последовательности по таймауту.
Разбить задачи на workers.
Например, если нужно вставить на страницу огромную такблицу, то можно вставлять ее частями по 100 строк.
Это позволит не блокировать работу всего сайта на долгое время.

MongoDB, CounchDB - хорошая альтернатива MySQL.

Нн пиши код, который может содержвать потенциальные ошибки и сложен для понимания.
Чем проще, тем лучше и безопасней.

Ключи объектов можно поместить в массив, котороые можно отсортировать и вывести в нужном вам порядке.

var obj = new Object.create(null); // Объект не наследует никакаких свойств (даже стандартных).

Использем EcmaScript 5

'use strict'; // Использем EcmaScript 5

Но лучше писать так
function () {
     'use strict'; // Использем EcmaScript 5
}
из-за того, что разные библиотеки могут быть объединены в 1 файл и некоторые библиотеки могут быть не написаны в стандарте Strict JavaScript!
А также потому что к сайту могут быть подключены сторонние файлы Mash Ups, которые не поддерживают Strict Mode.

Strict Mode нет даже в IE9.
Strict Mode есть только в IE10.

arguments.caller и arguments.callee выброшены и запрещены.

Определение Strict Mode EcmaScript 5

function in_strict_mode(){
    return (function(){
        return !this;
    })();
}

function strict_mode_implemented(){
    return (function(){
        'use strict';
        return !this;
    })();
}

Объявлейте переменные в начале функции.

function a () {
    var b;
    function c () {}
    ...
    ...
}


Объявляйте переменные вне циклов.

var i;
for (i = 0; i < 100; i += 1){
    ...
}

Глобальные переменные должны писаться заглавными буквами.

Никогда не пишите так

var a = b = 0;

Пишите так

var a = 0, b = 0;

Используйте i += 1 вместо i++, поскольку i += 1 означает ++x !!!
Используйте i -= 1 вместо i--, поскольку i -= 1 означает --x !!!

Монады

function MONAD () {
    return function unit(value) {
        var monad = Object.create(null);
             monad.bind = function(func){
                 return func(value);
             };
        return monad;
    };
}

var identity = MONAD();
var monad = identity('Hello world.');
     monad.bind(alert);

или

MONAD()('Hello world.').bind(alert);

Результат: Alert, который выведет Hello world.

Ajax Monad

monad.bind(f).bind(g);

или

object.method1().method2();

Последовательность действий обрабатывает результат от предыдущего действия.

new Interform('text')
     .moveTo(100, 100)
     .setSize(400, 32)
     .moveInside()
     .setBgColor('pink')
     .select()
     .setZIndex(20000)
     .on('escape', 'erase');

Расширяем MONAD

funciton MONAD () {
    var prototype = Object.create(null);
    function unit(value){
        var monad = Object.create(prototype);
             monad.bind = function(func, args) {
                 return func.apply(undefined, [value].concat(Array.prototype.slice.apply(args || [])));
             };
        return monad;
    }
    // Динамическое добавление новых методов в исходный объект. (Динамическое изменени объекта.)
    unit.method = function(name, func){
        prototype[name] = func;
        return unit;
    };
    return unit;
}

Расширяем далее.

funciton MONAD () {
    var prototype = Object.create(null);
    function unit(value){
        var monad = Object.create(prototype);
             monad.bind = function(func, args) {
                 return func(value, ...args);
             };
        return monad;
    }
    // Динамическое добавление методов.
    unit.lift = function(name, func){
        prototype[name] = function(...args) {
            return unit (this.bind(func, args));
        };
        return unit;
    };
    return unit;
}

Пример использования. Расширяет исходный объект.
monad изначально не имеет метода alert();

var ajax = MONAD().lift('alert', alert);

var monad = ajax('Hello world.');

     monad.alert();

Добавляем модификатор в Монаду

funciton MONAD (modifier) {
    var prototype = Object.create(null);
    function unit(value){
        var monad = Object.create(prototype);
             monad.bind = function(func, args) {
                 return func(value, ...args);
             };
        if (typeof modifier === 'function'){
            modifier(monad, value)
        }
        return monad;
    }
    return unit;
}

var maybe = MONAD(function(monad, value){
    if (value === null || value === undefined) {
        monad.is_null = true;
        monad.bind = function(){
            return monad;
        };
    }
});

var monad = maybe(null);
     monad.bind(alert);

Мы динамически изменили метод монады и теперь наша программа в случае ошибки сработает нормально, пропустив неправильное значение.

Promise

var my_vow = VOW.make();
     my_vow.keep(value);
     my_vow.break(reason);
     my_vow.promise();
     my_vow.when(kept, broken);


В массив можно динамически помещать любые элементы, то есть динамически создавать массивы.

var a = 1;
var b = [a, 2];
console.log(b); // [1, 2]

Размещение картинок на сайте произвольного размера и цвета


placekitten.com/200/300 
dummyimage.com

пятница, 24 мая 2013 г.

JavaScript наследование и множественное наследование классов через prototype

// Родительский класс Animal и его метод sayName.

function Animal(name, numLegs) {
    this.name = name;
    this.numLegs = numLegs;
    this.boss = 3;
}

Animal.prototype.sayName = function() {
    console.log("Hi my name is "+this.name);
};

// Дочерний класс Penguin, который ниже наследует все свойства и методы родителя, но замещает их своими свойствами и методами.

function Penguin (name){
     this.name = name;
     this.numLegs = 2;  
}

// Установка наследования классом Penguin всех свойств и методов родительского класса Animal и замещение их своими.
Penguin.prototype = new Animal();

// Проверка
var penguin = new Penguin("Boris");

penguin.sayName(); // Метод от родителя Animal
console.log(penguin.numLegs); // Собственное свойство Penguin
console.log(penguin.boss); // Свойство от родителя Animal

Пример множественного наследования классов через prototype.

 function Animal(name, numLegs) {
    this.name = name;
    this.numLegs = numLegs;
    this.isAlive = true;
}

function Penguin(name) {
    this.name = name;
    this.numLegs = 2;
}

function Emperor(name) {
    this.name = name;
    this.saying = "Waddle waddle";
}

Penguin.prototype = new Animal();
Emperor.prototype = new Penguin();

var myEmperor = new Emperor("Boris");

четверг, 23 мая 2013 г.

JavaScript Revealing Module Pattern - Паттерн создания модулей

Паттерн создания модулей Revealing Module Pattern был разработан Richard Cornford на omp.lang.javascript.

Данный паттерн позволяет создавать классы в JavaScript подобно тому, как это делается в других языках программирования, допуская создания частных (private) и публичных (public) методов внутри класса.

Код паттерна создания класса с частным и публичным методами.

var module = (function() {

    var foo
        , bar;

    var private = function() {
        // ...
    };

    var public = function() {
        // ...
    };
   
    return {
        public: public
    };

})();

typeof module.public; // "function"
typeof module.private; // "undefined"

понедельник, 20 мая 2013 г.

Тестирование JavaScript-код через Mocha JS и Chai в браузере

Представим, что ваш проект имеет следующую структуру расположения папок и файлов:

/css
/js/jquery.js
/js/index.js
/index.html

Файл jquery.js вы можете скачать с сайта jquery.com

Код файла index.js

function sayOK(){
    return 'OK';
}

Код файла index.html

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript" src="js/jquery.js"></script>
<script type="text/javascript" src="js/index.js"></script>
<script type="text/javascript">
$(document).ready(function(){
    alert(sayOK());
});
</script>
<title>My Page</title>
</head>
<body>
<h1>My Page</h1>
</body>
</html>

При открытии файла index.html в браузере будет выполнен код функции sayOK() и выведено сообщение "ОК".

Для функции sayOK() нам необходимо будет написать тесты, которые будут выполняться через Mocha JS в браузере.

Для тестирования JavaScript-кода через Mocha JS в браузере необходимо скачать и распаковать архив проекта "Mocha in Browser", который расположен в GitHub по адресу

https://github.com/adomokos/mocha-in-browser/blob/master/mocha-in-browser.zip

Из распакованного архива вам потребуется взять всего 2 файла:
spec/lib/mocha.js - файл с кодом библиотеки Mocha JS.
spec/lib/browser/mocha.css - файл со стилями оформления для вывода результатов тестирования на страницу в браузере.

Файл mocha.js скопируйте в папку js. А файл mocha.css скопируйте в папку css.

После этого структура расположения папок и файлов в проекте будет выглядеть так:

/css/mocha.css
/js/mocha.js
/js/jquery.js
/js/index.js
/index.html

Далее в папке js создадим файл tests.js, к котором напишем тесты для функции sayOK() с применением Mocha JS.

Код файла tests.js

// Код функции запуска тестов

function runTests() {

    mocha.setup('bdd');  // Устанавливаем тип тестов BDD  
   
    // Определяем функцию assert, которую будем использовать для тестов ниже.

    function assert(expr, msg) {
        if (!expr) throw new Error(msg || 'failed');
    }

    // Создаем набор тестов, использующих Mocha JS
   
    describe("Say OK Tests", function() {
   
      describe("Function must return OK", function() {
        it("returns OK", function() {
          var result = sayOK();
          assert(result === 'OK');
        });
      });
   
      describe("Function return OKI", function() {
        it("returns OK", function() {
          var result = sayOK() + 'I';
          assert(result === 'OKI');
        });
      });
   
      describe("Function return Not a Number", function() {
        it("returns NaN", function() {
          var result = sayOK();
          assert(isNaN(result));
        });
      });
     
    });   
   
    var runner = mocha.run(); // Запускаем наш набор тестов

}

$(document).ready(function(){
    runTests(); // Запускаем наши тесты после загрузки страницы
});

Теперь структура нащего проекта будет выглядеть так:

/css/mocha.css
/js/mocha.js
/js/jquery.js
/js/index.js
/js/tests.js
/index.html

Далее создадим HTML-файл testrunner.html, который мы будем открывать в брузере для запуска тестов нашего кода.

Код файл testrunner.html

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<link href="css/mocha.css" rel="stylesheet" type="text/css" />
<script type="text/javascript" src="js/jquery.js"></script>
<script type="text/javascript" src="js/mocha.js"></script>
<script type="text/javascript" src="js/index.js"></script>
<script type="text/javascript" src="js/tests.js"></script>
<title>Test Runner</title>
</head>
<body>
    <div id="mocha"></div>
</body>
</html>

В файле testrunner.html производится загрузка файла библиотеки Mocha JS, файла index.js с кодом функции sayOK(), которую мы будем тестировать и набор тестов для этой функции в файле tests.js, которые автоматически запускаются после загрузки файла testrunner.html в браузере.

Результаты выполнения тестов помещаются в специальный блок <div id="mocha"></div> внутри тэга <body>.

Полная структура проекта в итоге будет выглядеть так:

/css/mocha.css
/js/mocha.js
/js/jquery.js
/js/index.js
/js/tests.js
/index.html
/testrunner.html

Теперь, если вместо определенной нами в файле tests.js функции

    function assert(expr, msg) {
        if (!expr) throw new Error(msg || 'failed');
    }

мы захотим использовать для тестирования JavaScript-кода библиотеку Chai, то скопируем в папку js файл chai.js, вкоторый можно взять по адресу

chaijs.com/chai.js

В результате структура нашего проекта станет выглядеть так:

/css/mocha.css
/js/mocha.js
/js/chai.js
/js/jquery.js
/js/index.js
/js/tests.js
/index.html
/testrunner.html

В HTML-файле для запуска тестов testrunner.html добавим ссылку на файл chai.js

Итоговый код файла testrunner.html

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<link href="css/mocha.css" rel="stylesheet" type="text/css" />
<script type="text/javascript" src="js/jquery.js"></script>
<script type="text/javascript" src="js/mocha.js"></script>
<script type="text/javascript" src="js/chai.js"></script>
<script type="text/javascript" src="js/index.js"></script>
<script type="text/javascript" src="js/tests.js"></script>
<title>Test Runner</title>
</head>
<body>
    <div id="mocha"></div>
</body>
</html>

В файле tests.js удалим код функции assert() и заменим старые тесты на тесты, использующие код билиотеки Chai.

Итоговый код файла tests.js

// Код функции запуска тестов

function runTests() {
   
    mocha.setup('bdd'); // Устанавливаем тип тестов BDD      
   
    // Создаем набор функций для тестирования с использованием библиотеки Chai.

    var assert = chai.assert,
          expect = chai.expect,
          should = chai.should(); // Обратите внимание, что should должна быть иметь скобки ()

    // Создаем набор тестов, использующих Mocha JS вместе с Chai

    describe('Say OK Tests', function() {
                               
      describe('Function sayOK()', function() {
                                      
        it('should work with assert', function() {
            assert.equal(sayOK(), 'OK');
        });
   
        it('should work with expect', function() {
            expect(sayOK()).to.equal('OK');
        })
   
        it('should work with should', function() {
            sayOK().should.equal('OK');
        });
       
      });
     
    });
   
    var runner = mocha.run();
   
}

$(document).ready(function(){
    runTests();
});

Таким образом для тестирования JavaScript-кода в браузере можно использовать Mocha JS вместе с Chai.