javascript. Подъем функций

В javascript функцию можно построить либо на объявлении

function foo() {
  //...
}

либо на выражении

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

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

foo();

function foo() {
    console.log('function - declaration');
}


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

foo();

var foo = function () {
    console.log('function - expression');
};


Хоп, на выходе получаем сообщение об ошибке

TypeError: foo is not a function

То есть в данном случае поднятия определения функции не произошло, в отличии от имени. Вот еще пример с поднятием. И в этом случае :

foo();

function foo() {
    console.log('function - declaration');
}

var foo = function foo() {
    console.log('function - expression');
};


и в этом:

foo();

var foo = function foo() {
    console.log('function - expression');
};

function foo() {
    console.log('function - declaration');
}


мы будем получать сообщение - function - declaration. Но вот если вызов происходит ниже, то картина меняется на оборот:

var foo = function foo() {
    console.log('function - expression');
};

function foo() {
    console.log('function - declaration');
}

foo();


или вот так:

function foo() {
    console.log('function - declaration');
}

var foo = function foo() {
    console.log('function - expression');
};

foo();


мы будем получать сообщение - function - expression.
Правило подъема действует и внутри самих функций. Вот небольшой примерчик:

bar();

function bar() {


    (function (global) {
        global.foo();
        foo();
    })(window);
   
    function foo() {
        console.log('local function - declaration');
    }

   
}

function foo() {
    console.log('global function - declaration');
}


На выходе получим следующие сообщения:

global function - declaration
local function - declaration
global function - declaration
local function - declaration
global function - declaration
local function - declaration

javascript. Механизм подстановки точки с запятой

Знали ли Вы что интерпретатор javascript наделен механизмом подстановки точки с запятой в конце каждой строки. Причем этот механизм работает за Вашей спиной. Поэтому, там где это должны сделать Вы, то и делайте это сами, так как тут можно столкнуться с необычными результатами. Рассмотрим пример. Напишем функцию-выражение, которая будет возвращать простой объект:

var object = function (name) {
    return
    {
        name: name || ''
    };
};


Вроде ничего такого. Но это на первый взгляд. Попробуйте получить от нее обещанный объект:

var obj = object('Leonardo'); // undefined

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

var object = function (name) {
    return {
        name: name || ''
    };
};


Теперь все работает :)

javascript. Клонирование объектов

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

var Copier = {   
    copy: function (object) {       
        var newObject;   
       
        // for copying arrays
        if (object.constructor === Array) {
            newObject = [];
            for (var i = 0, item, size = object.length; i < size; i++) {
                if (object[i].constructor === Array || object[i].constructor === Object) {
                    item = this.copy(object[i]);
                }
                else {
                    item = object[i];
                }
                newObject = newObject.concat(item);
            }           
        }
        // for copying objects
        else if (object.constructor === Object) {
            newObject = {};           
            for (var property in object) {
                if (object.hasOwnProperty(property)) {
                    if (object[property].constructor === Array || object[property].constructor === Object) {
                        newObject[property] = this.copy(object[property]);
                    }
                    else {
                        newObject[property] = object[property];
                    }
                }
            }           
        }
        // for copying other elements
        else {       
            newObject = object;
        }
       
        return newObject;       
    }   
};


Данный объект Copier имеет только один метод copy, собственно который и делает всю основную работу по клонированию. На входе могут быть объекты разного типа: и массивы, и чистые объекты, и массивы, содержащие ссылки на объекты, и простые элементы, что возможно за счет глубинного рекурсивного просмотра.Стоит отметить, то в данном решении для проверки типов также можно воспользоваться следующим способом:

// if object is Object
Object.prototype.toString.call(object) === '[object Object]'

// if object is Array
Object.prototype.toString.call(object) === '[object Array]' 

Если не ошибаюсь, то такого рода проверки более предпочтительны по сравнению с такими как:

// if object is Object
object.constructor === Object
object instanceof Object

// if object is Array
object.constructor === Array
object instanceof Array

И связано это как Вы уже догадываетесь с различиями на уровне браузеров. В этом случае наше решение можно представить в следующем виде:

var Copier = {  
    copy: function (object) {      
        var newObject;  
      
        // for copying arrays
        if (this.isArray(object)) {
            newObject = [];
            for (var i = 0, item, size = object.length; i < size; i++) {
                if (this.isArray(object[i]) || this.isObject(object[i])) {
                    item = this.copy(object[i]);
                }
                else {
                    item = object[i];
                }
                newObject = newObject.concat(item);
            }          
        }
        // for copying objects
        else if (this.isObject(object)) {
            newObject = {};          
            for (var property in object) {
                if (object.hasOwnProperty(property)) {
                    if (this.isArray(object[i]) || this.isObject(object[i])) {
                        newObject[property] = this.copy(object[property]);
                    }
                    else {
                        newObject[property] = object[property];
                    }
                }
            }          
        }
        // for copying other elements
        else {      
            newObject = object;
        }
      
        return newObject;      
    },
    isArray: function (object) {
        return Object.prototype.toString.call(object) === '[object Array]';
    },
    isObject: function (object) {
        return Object.prototype.toString.call(object) === '[object Object]';
    }
};


Стоит сделать небольшое замечание по поводу массивов. Так как массивы - это также объекты, то проверка типа array instanceof Object будет успешной. Нужно быть бдительным. Вообще таким образом можно проверять и все остальные типы - Function, String, Number и прочее. Всем успехов :)

javascript. Наследование

В одной из статей - Javascript. Object oriented programming. Object literal - были затронуты вопросы применения ООП подходов в javascript. Ниже приведено дополнение этой темы и касаться оно будет вопроса наследования.
У Дугласа Крокфорда есть замечательная книжка - "JavaScript. Сильные стороны". В ней собрано очень много полезных советов касательно использования javascript, очень советую. Итак, Дуглас выделяет три вида наследования. Рассмотрим каждый из них.

1. Псевдоклассовое насследование

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

var Mammal = function(name) {
    this.name = name;
};

Mammal.prototype.getName = function() {
    return this.name;
};

Mammal.prototype.says = function() {
    return this.saying || '';
};

var Cat = function (name) {
    this.name = name;
    this.saying = 'meow';
};

Cat.prototype = new Mammal();

Cat.prototype.getName = function () {
    return this.says() + ' ' + this.name + ' ' + this.says();
};

var myMammal = new Mammal('Herb');
var myCat = new Cat('Henrietta');

console.log(myMammal.getName()); // Herb 
console.log(myMammal.says()); //

console.log(myCat.getName()); // meow Henrietta meow
console.log(myCat.says()); // meow


2. Прототипизированное насследование

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

if (typeof Object.create !== 'function') {
    Object.create = function(o) {
        var F = function() {};
        F.prototype = o;
        return new F();
    };
}

var myMammal = {
    name: 'Herb the Mammal',
    getName: function() {
        return this.name;
    },
    says: function() {
        return this.saying || '';
    }
};

var myCat = Object.create(myMammal);
myCat.name = 'Henrietta';
myCat.saying = 'meow';
myCat.getName = function() {
    return this.says() + ' ' + this.name + ' ' + this.says();
};

console.log(myMammal.getName()); // Herb 
console.log(myMammal.says()); //

console.log(myCat.getName()); // meow Henrietta meow
console.log(myCat.says()); // meow


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

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

var mammal = function(obj) {
    var that = {};
   
    that.getName = function() {
        return obj.name;
    };
   
    that.says = function() {
        return obj.saying || '';
    };
   
    return that;
};

var cat = function(obj) {
    obj.saying = obj.saying || 'meow';
    var that = mammal(obj);
   
    that.getName = function() {
        return that.says() + ' ' + obj.name + ' ' + that.says();
    };
    return that;
};

var myMammal = mammal({name: 'Herb'});
var myCat = cat({name: 'Henrietta'});

console.log(myMammal.getName()); // Herb 
console.log(myMammal.says()); //

console.log(myCat.getName()); // meow Henrietta meow
console.log(myCat.says()); // meow

PHP5. Композиция на примере паттерна Стратегия

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

    abstract class Duck {
       
        protected $flyBehavior;
        protected $quackBehavior;
       
        public function __construct() {}
       
        public abstract function display();
       
        public function setFlyBehavior(FlyBehavior $fb) {
            $this->flyBehavior = $fb;
        }
       
        public function setQuackBehavior(QuackBehavior $qb) {
            $this->quackBehavior = $qb;
        }
       
        public function performFly() {
            $this->flyBehavior->fly();
        }
       
        public function performQuack() {
            $this->quackBehavior->quack();
        }
       
        public function swim() {
            echo "All ducks float, even decoys!";
            echo "<br/>";
        }
       
    }
   
    class MallardDuck extends Duck {
   
        public function __construct() {
            $this->flyBehavior = new FlyWithWings();
            $this->quackBehavior = new Quack();
        }
       
        public function display() {
            echo "I`m a real Mallard duck";
            echo "<br/>";
        }
   
    }
   
    interface FlyBehavior {
        public function fly();
    }
   
    class FlyWithWings implements FlyBehavior {
        public function fly() {
            echo "I`m flying";
            echo "<br/>";
        }
    }
   
    class FlyNoWay implements FlyBehavior {
        public function fly() {
            echo "I can`t fly";
            echo "<br/>";
        }
    }
   
    interface QuackBehavior {
        public function quack();
    }
   
    class Quack implements QuackBehavior {
        public function __construct() {}
        public function quack() {
            echo "Quack";
            echo "<br/>";
        }
    }
   
    class MuteQuack implements QuackBehavior {
        public function __construct() {}
        public function quack() {
            echo "<< Silence >>";
            echo "<br/>";
        }
    }
   
    class Squeak implements QuackBehavior {
        public function __construct() {}
        public function quack() {
            echo "Squeak";
            echo "<br/>";
        }
    }
   
    class ModelDuck extends Duck {
   
        public function __construct() {
            $this->flyBehavior = new FlyNoWay();
            $this->quackBehavior = new Quack();
        }
   
        public function display() {
            echo "I`m a model duck";
            echo "<br/>";
        }
   
    }
   
    class FlyRocketPowered implements FlyBehavior {
   
        public function fly() {
            echo "I`m flying with a rocket";
            echo "<br/>";
        }
   
    }

    $mallard = new MallardDuck();
    $mallard->performQuack();                            // Quack
    $mallard->performFly();                                // I`m flying
   
    $model = new ModelDuck();
    $model->performFly();                                // I can`t fly
    $model->setFlyBehavior(new FlyRocketPowered());
    $model->performFly();                                // I`m flying with a rocket


Более общее представление можно получить по следующему рисунку (обратите внимание на композитные связи)