Javascript. Object oriented programming. Object literal

Как известно, javascript относится к объектно-ориентированному языку, хотя парадигма разрабатываемых структур программ (сценариев) может быть и не объектно - ориентированной. Но рано или поздно приходит осознание, что использование принципов объектно-ориентированного программирования (ООП) более естественно и более продуктивно в ходе разработки. ООП неразрывно связано с понятием классов, которые выступают с одной стороны как универсальные структуры данных (с точки зрения предметного подхода), а с другой стороны - как особый тип данных (с точки зрения архитектуры, "бинарный" подход). Именно в классах проявляются принципы ООП, реализуемые в соответствующих объектах, инстансах. В javascript основной акцент перемещен на менее абстрактный уровень - на уровень объектов. Рассмотрим как создаются объекты в C++:

class CRectangle {
    int width, height;
  public:
    void set_values (int, int);
    int area (void) {return (width * height);}
};

void CRectangle::set_values (int a, int b) {
  width = a;
  height = b;
}
 
 
И в основной точке инстанцируем следующие объекты:

CRectangle a;
CRectangle * b = new CRectangle; 
 
Не забывая при выходе вызвать явно деструктор для объекта b:

delete b;
 
Отсюда видим, что прежде, чем создавать тот или иной объект, нам необходимо заложить общую будущую структуру в виде класса, определив при необходимости конструктор, деструктор, поля с соответствующими уровнями доступа и методы, также имеющие уровни внешнего доступа (private, public и protected).
Теперь рассмотрим как сие происходит в javascript. Есть несколько способов создания объектов: - посредством литерала {}:

var Obj = {};

- либо посредством функции-конструктора:

var Obj = new Object();

Конструктор Object создает пустой объект, как если бы использовался литерал {}, возвращая ссылку на созданный объект. Стоит отметить, что Object относится к базовым встроенным конструкторам, к которым относятся и такие как Array, Date, RegExp и прочие, но помимо этого есть возможность и в определении собственных конструкторов, например вот так:

function Dog( name ) {
    this.name = name || '';
}

var MyDog = new Dog( 'Rex' );

Как видим в javascript шаблоном объекта выступает не класс, а объект ( функции в javascript являются объектами ). Конечно, второй способ, где есть возможность определять свой конструктор дает больше возможностей для дальнейших манипуляций с объектами. Есть немало литературы, где больше освещается использование именно данного способа. Но меня больше заинтересовало использование первого способа - литерала объекта (Object literal), так как в данном случае мы немного стеснены в своих возможностях в силу отсутствия своего конструктора.

Рассмотрим общее использование литерала объектов. Для этого создадим объект Obj с определенными свойствами:

var Obj = {
    // внутреннее свойство
    value: '',
    // установка значения свойства value
    setValue: fuction( value ) {
        this.value = value || '';
    },
    // извлечение значения свойства value
    getValue: function() {
        return this.value;
    }
};

Обращаться к свойству объекта можно через точку (.), напоминает статические объекты в C++:

Obj.setValue('Obj'); 
console.log( Obj.getValue() ); // output: Obj  

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


var Obj = {
    reference: Obj
};
console.log( Obj.reference ); // output: undefined

А если попытаться получить ссылку на себя так:

var Obj = {
    reference: this
};
console.log( Obj.reference ); // output: window

То как видим на выходе получаем ссылку на глобальный объект - window. Все это указывает на то, что во время определения свойств сам объект еще не с конструировался. Этого можно избежать путем создания пустого объекта, с последующим определением его свойств:

var Obj = {
    reference: null
};
Obj.reference = Obj;
console.log( Obj.reference ); // output: Object { reference={...}}

Теперь рассмотрим вопрос с организацией доступа к свойствам объектов. Как было продемонстрировано выше, изначально все свойства доступны извне, это сравнимо с уровнем доступа - public. Но как быть с такими уровнями как - private и protected. Сначала рассмотрим создание частных свойств. Для литералов объектов можно воспользоваться так называемыми - замыканиями (closure):

var A = {};

A.a = ( function(){
    var a = 16;
    A.getA = function(){ return a; };
    A.setA = function( value ){ a = value || 0 };
    return null;
})();

A.b = ( function(){
    var b = 16;
    A.getB = function(){ return b; };
    A.setB = function( value ){ b = value || 0 };
    return null;
})();


// a null   
// b null
// getA function()
// getB function()
// setA function()
// setB function()


В объекте A при обращении к свойствам a или b в попытках что-то прочитать или внести изменения никак не скажутся на сокрытых a или b в областях просмотра анонимных функций. Можно вообще скрыть ненужные свойства во внутренних объектах:

var B = {};

B.a = ( function(){
    var value = 0;
    return {
        getValue: function(){ return value; },
        setValue: function( val ){ value = val; }
    };
})();

B.b = ( function(){
    var value = 0;
    return {
        getValue: function(){ return value; },
        setValue: function( val ){ value = val; }
    };
})();
   
// a Object { getValue=function(), setValue=function()}
// b Object { getValue=function(), setValue=function()}


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

Теперь рассмотрим как можно использовать наследование между литерал объектами. Создание в одном из своих свойств ссылку на нужный объект - это не наследование:

var A = { name: 'A' }; // parent object
var B = { name: 'B', A: A }; // child object
console.log( B.A.name ); // output: A

В этом случае мы просто имеем ссылку на родительский объект ( к ссылочным типам в javascript относятся - Array, Object, Function ). В javascript у каждого конструктора объекта имеется свойство __proto__, в котором хранится ссылка на объект с конструктором (прототип). Это своего рода внутренняя ссылка. Если присвоить этому свойству ссылку на другой объект, то мы получим наследование. Причем в данном случае говорят о прототипном наследовании (prototype inheritance) за счет возможности образования прототипных цепочек (prototype chain):

var A = {
  x: 10,
  calculate: function ( z )
  {
    return this.x + this.y + z;
  }
};

var B = {
  y: 20,
  __proto__: A
};

var C = {
  y: 30,
  __proto__: A
};

// вызов унаследованного метода
console.log( B.calculate(30) ); // 60
console.log( C.calculate(40) ); // 80


Как видно из этого примера, объекты B и C за счет ссылки на  объект A унаследовали метод calculate, это произошло за счет прототипной цепочки: объект B не найдя у себя затребованного метода обращается к свойству __proto__ и по этой ссылке пытается обратиться к другому объекту, наружу, чтобы попытаться найти метод, если и там его не будет найдено, далее также будет обращение к этому свойству. Так и образуются цепочки: own -> proto -> proto -> proto -> null. В классовом же наследовании (абстрактно): own -> class -> superclass -> superclass -> null. В конечном счете окажется, что у некоторого объекта эта ссылка будет замкнутой ( можно в замыкающем объекте определить сие свойство в null значение ) и тогда мы получим либо undfined, либо сообщение об ошибочности нашего обращения. Также стоит обратить внимание на ключевое слово this. Оно всегда ссылается на конечный объект в цепочке прототипов. Обратите внимание на конечный результат таких цепочек в следующем примере.

var Parent = {
    a: 'Parent',
    getName: function()
    {        

          console.log( this );
    }
};

var Child = {
    b: 'Child',
    __proto__: Parent
};


// Object { b="Child", a="Parent", getName=function()}
Child.getName();


В итоге в наследуемом объекте Parent оказываются все свойства и методы наследника Child. Рассмотрим еще пример:

var Man = {
    head:
    {
        hair: 'black',
        eyes: 'black',
        nose: 'direct'
    },
    body:
    {
        hands: 'normal',
        feet: 'normal',
        brawn: 'normal'
    },
    getPattern: function(){
            console.log( 'Man pattern:' );
            console.log( this.head );
            console.log( this.body );
    },
    pattern:{}
};

Man.pattern.__proto__ = Man;


// output: Man pattern
// output: head Object{}
// output: body Object{}
Man.pattern.getPattern();


Вот так просто можно обращаться из вложенных (дочерних) объектов к объекту родителю.
Приведенный выше способ манипуляции с прототипами считается устаревшим, в новом стандарте javascript - ECMAScript 5th Edition ( JavaScript 1.8.5 ) для этих целей есть соответствующие методы: getPrototypeOf() и create(). Рассмотрим их подробней.
Метод create() используется в момент создания нового объекта с возможностью передачи в аргументе объекта, свойства которого необходимо унаследовать во вновь создаваемом объекте.

var A = {
    name: 'A'
};

var B = Object.create( A );

// output: A
console.log( B.name );

Если нет необходимости в унаследовании, то в аргументе передается null, так в прототипе явно уазывается, что он ни на кого не ссылается:

var C = Object.create( null );

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

var A = {
    name: 'A'
};   

var B = {};

Object.getPrototypeOf( B ).name = A.name;

// output: A
console.log( B.name );

В этом примере мы получаем прототип объекта B и сразу же  в него добавляем новое свойство name, которое ссылается на свойство name объекта A. Имя свойство необязательно должно совпадать с наследуемым свойством, но все же так правильней, логичней и прозрачней. Есть одно но: нельзя передавать непосредственно возвращаемому прототипу ссылки на свойства или объекты, интерпретатор будет ругаться ( связано это с тем, что возвращается объект ):

// либо так
Object.getPrototypeOf( B ) = A;

// либо так
Object.getPrototypeOf( B ) = A.name;

Оба варианта - нерабочие. Нужно обязательно обращаться через свойства.

Ниже приведу дополнительно примеры с этими методами:


//var myObj = {__proto__: {property1_OfProto: 1}}
var myObj = Object.create({property1_OfProto: 1});
 
//myObj.__proto__.property2_OfProto = 2
Object.getPrototypeOf(myObj).property2_OfProto = 2; 


Напоследок стоит сказать, что данные методы в определенных браузерах не поддерживаются, для решения данной проблемки можно воспользоваться библиотекой - github.com/kriskowal/es5-shim, также можно почитать в блоге Джона Резига - http://ejohn.org/blog/objectgetprototypeof/

  

PHP5 / Javascript. Сollaboration with Unix timestamps

В большинстве случаев реализация на серверной стороне связана с манипуляциями массивов дат. И удобнее всего использовать так называемые метки времени Unix (целое число, равное разнице в секундах между заданной датой/временем и началом так называемой Эпохи Unix (1 января 1970 00:00:00)). В PHP5 (PHP4) для этого можно воспользоваться следующими методами:

mktime - возвращает метку времени Unix для заданной даты

time - возвращает текущую метку времени Unix

strtotime - преобразует текстовое представление даты в метку времени Unix

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

Date.parse

Date.UTC

Date.valueOf()

getTime 

...

javascript. Date compare object

Задачка. Как можно адекватно сравнить две даты? К сожалению объект Date не имеет встроенных методов для решения такого рода задачек. Ниже представлен один из способов такого решения.

// вспомогательный объект для сравнения дат в заданном формате, например так - dd.mm.yyyy, или так - yy.dd.mm
var Dates = {
                format : "",   // формат сравниваемых дат
                formatPattern : /^(dd|mm|yy|yyyy)(\.|\-)(?!\1)(dd|mm|yy|yyyy)\2(?!\1)(?!\3)(dd|mm|yy|yyyy)$/gi,                         formatAssoc :     { 1 : 0, 3 : 1, 4 : 2 },   // порядок соответствия индексов формата

                position :         { year : 0, month : 1, day : 2 },  // порядок сравнения частей дат  (год, месяц, день)
                separator : "",                                                      // разделитель (. или -)
                status :         { less : -1, equal : 0, larger : 1 },   // статусные биты
                compare :     function( date1, date2, format )   // основной метод: сравнение дат по заданному формату                                                    
                            {
                                // получение сравниваемых дат
                                var date1 = date1,
                                date2 = date2;
                               
                                // сохранение заданного формата
                                this.setFormat( format );
                               
                                // сравнение дат с первоначальной проверкой заданного формата на валидность использования.
                                // сравнение идет по изначально определенным индесам заданного формата
                                if ( this.checkFormat() )
                                {
                                    // разделение дат по разделителю формата на части (год, месяц, день)
                                    date1 = date1.split( this.separator );
                                    date2 = date2.split( this.separator );
                                   
                                    // сравнение дат по годам:
                                   
                                    if ( date1[ this.position.year ] > date2[ this.position.year ] )
                                    {
                                        return this.status.larger;
                                    }

                                    if ( date1[ this.position.year ] < date2[ this.position.year ] )
                                    {
                                        return this.status.less;
                                    }

                                    if ( date1[ this.position.year ] = date2[ this.position.year ] )
                                    {

                                        // сравнение дат по месяцам:
                                   
                                        if ( date1[ this.position.month ] > date2[ this.position.month ] )
                                        {
                                            return this.status.larger;
                                        }

                                        if ( date1[ this.position.month ] < date2[ this.position.month ] )
                                        {
                                            return this.status.less;
                                        }

                                        if ( date1[ this.position.month ] = date2[ this.position.month ] )
                                        {

                                            // сравнение дат по дням:
                                       
                                            if ( date1[ this.position.day ] > date2[ this.position.day ] )
                                            {
                                                return this.status.larger;
                                            }

                                            if ( date1[ this.position.day ] < date2[ this.position.day ] )
                                            {
                                                return this.status.less;
                                            }

                                            if ( date1[ this.position.day ] = date2[ this.position.day ] )
                                            {
                                                return this.status.equal;
                                            }

                                        }

                                    }

                                }
                                // при отрицательном результате проверки формата на валидность
                                else
                                {
                                    return false;
                                }
                            },
                setFormat : function( format )    // сохранение формата
                            {
                                this.format = format;
                            },
                checkFormat : function()           // проверка заданного формата на валидность  дольнейшего использования 
                            {
                                // проверка формата по шаблону
                                var checkResult = this.formatPattern.exec( this.format );

                                // положительная проверка
                                if ( checkResult != null )
                                {
                                    // распределение индексов формата по порядку сравнения частей дат  (год, месяц, день)
                                    for ( var index in checkResult )
                                    {
                                        // по годам
                                        if ( checkResult[index] == 'yy' || checkResult[index] == 'yyyy' )
                                        {
                                            this.position.year = this.formatAssoc[index];
                                        }
                                        // по месяцам
                                        if ( checkResult[index] == 'mm' )
                                        {
                                            this.position.month = this.formatAssoc[index];
                                        }
                                        // по дням
                                        if ( checkResult[index] == 'dd' )
                                        {
                                            this.position.day = this.formatAssoc[index];
                                        }
                                    }

                                    // установка разделителя для частей дат
                                    this.separator = checkResult[2];

                                    // сброс ресурса
                                    delete checkResult;

                                   
                                    // сброс внутреннего индекса шаблона проверки
                                    this.formatPattern.lastIndex = 0;

                                    return true;

                                }
                                // отрицательная проверка
                                else
                                {
                                    return false;
                                }

                            }
            };


Обратите на одну особенность в проверке формата по шаблону в методе checkFormat


var checkResult = this.formatPattern.exec( this.format );

Каждый второй вызов данного метода будет приводить к неоднозначности и связано это будет со спецификой поиска совпадений: будет возвращаться null. Этого в нашем случае не требуется, чтобы этого избежать введен сброс внутреннего индекса шаблона проверки ():
  
this.formatPattern.lastIndex = 0;

Желаю Всем удачи в нашем нелегком деле :)

PHP5. Особые случаи в условных и циклических выражениях

Громко конечно сказано - особые случаи. Просто иногда приходиться сталкиваться с определенными конструкциями, в которых можно допустить ошибку.

1 случай. Неоднозначность результата использования составных предикатов в условиях

Для наглядности - пример. Пусть даны первоначальные параметры:

$flag = false;
$n = 1;


А также дано такого рода условие:

if ( $flag && $n++ )
{
    // some code
}


Что мы получим на выходе? Вроде бы $n за счет посткрементной формы должен увеличиться на 1, то есть должны получить число - 2, но этого не произойдет, даже если воспользуемся прекрементной формой (++$n).
Так как параметр $flag равен false, то интерпретатор далее условие не просматривает, он "знает", что если один из предикатов установлен в false, то конечный результат логического сравнения также даст false. Помните правило конъюнкции ( логическое умножение ) из булевой алгебры! :) Поэтому далее вычисление не происходит.

Похожая ситуация возникает и в следующем условии:

if ( $flag || $n++ )
{
    // some code
}


Если $flag установлен в false, то все хорошо - далее проходит вычисление, но если $flag будет установлен в true, тогда вычисление не произойдет. Снова имеем дело с булевой алгебры - правило дизъюнкции ( логическое сложение ): в данном случае если один из предикатов установлен в true, то независимо от состояния ( true или false ), результатом логического сравнения будет true. В этом причина отсутствия вычисления.

Такого рода проявления вызваны оптимизацией работы интерпретатора с условными выражениями: быстро посмотрел, оценил и принял соответствующее решение. Логично, не так ли?!

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

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

2 случай. Неоднозначность результата в циклах

Рассмотрим простой цикл.

for ( $i = 0; $i < 5; $i++ )
{
    if ( $i == 3 )
        continue;
       
    print( $i.'</br>' );       
}


Данный цикл отрабатывая выдает последовательность чисел: 0 1 2 4, 3 из этой последовательности выпадает: на этом шаге условие становится истинным, срабатывает инструкция continue; которая вынуждает цикл перейти к следующей итерации. Ну тут все понятно. Но что будет, если в иструкции continue; мы случайно
не проставим завершающее любое выражение точку с запятой, то есть:

for ( $i = 0; $i < 5; $i++ )
{
    if ( $i == 3 )
        continue
       
    print( $i.'</br>' );       


По идее, интерпретатор в этом случае должен выбросить исключение, но что же происходит на самом деле.
Если запустить данный цикл, то на выходе получим следующее: 3. Неожиданно, покрайней мере для меня это было неожиданно. Такой же результат можно получить и в следующем цикле:

for ( $i = 0; $i < 5; $i++ )
{
    if ( $i == 3 )
        break
       
    print( $i.'</br>' );       
}


С чем это может быть связано? Происходят такие чудеса по двум причинам: 1 - у блока условия явно не определена область действия, а инструкции continue и break в свою очередь пытаются отыскать завершающие ; и в это же время на пути
встречается инструкция print( $i.'</br>' ), которая выводит соответствующее число, и далее выражение завершается на искомом завершающем символе. Что в итоге получается? Получается, что интерпретатор расширил область действия
данного условия, поэтому только число 3 и выводится. Таким образом интерпретатор был введен в замешательство, такого не произошло бы, если мы имели дело с компилятором, например в C++.

Как можно избежать таких неоднозначностей? Казалось бы, нужно явно определять области действия, не полагаясь на случай, особенно если дело касается программирования. Например вот так:

for ( $i = 0; $i < 5; $i++ )
{
    if ( $i == 3 )
    {
        continue
    }
       
    print( $i.'</br>' );       
}


Если же теперь попытаться повторить приведенные выше "чудеса" - ничего не выйдет, интерпретатор укажет вам на наличие
ошибки. Но если внутри этого блока также проставить команду с завершающим ; все снова отработает как ни в чем не бывало. М-да.

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

Oracle SQL Developer. ORA-12705: Cannot access NLS data files or invalid environment specified. Resolve

Столкнулся с проблемой подключения Oracle SQL Developer к СУБД Oracle 11g XE. При попытке создать соединение к стандартному для  Oracle 11g XE экземпляру БД (SID) - XE получаю ошибку - ORA-12705: Cannot access NLS data files or invalid environment specified. И это не смотря на то, что подключение через SQL Plus проходит успешно. Проверил запись в tnsnames.ora. Вроде все хорошо.


Но все же проблема была найдена. Все дело в локали. Для устранения данной ошибки нужно в настройках SQL Developer - \sqldeveloper\ide\bin\ - в файлик - ide.conf - добавить следующие строчки:

AddVMOption -Duser.region=us
AddVMOption -Duser.language=en


Рестартуйте Ваш SQL Developer, если он был запущен, и вуаля - все готово. Ниже приведен скриншот успешного тест-коннекта. Всем успехов.