Статьи

10 вещей, которые вы должны знать о Javascript

Еще в 1995 году Netscape хотел язык с простой моделью программирования, но достаточно гибкий для создания реальных масштабируемых приложений. Любопытно, что Брендан Эйч выполнил задачу за несколько недель. Итак, у нас есть JavaScript, который кажется настолько простым, что многие люди даже не удосуживаются изучать язык во время его использования. И все же это работает! Тем не менее, это оказалось одной из причин, почему JavaScript был неправильно понят, Да, это похоже на язык семейства C, и, начиная с JavaScript, программисты просто пишут на C, Java, PHP или в другом языковом стиле. Но JavaScript не является подмножеством любого из этих языков. Вы можете найти JavaScript даже на превосходном языке, но вы должны знать, как это делать, используя JavaScript. Давайте возьмем, например, ООП. Большинство программистов знакомы с ООП классической школы. Так что, переходя на JavaScript, люди смущаются отсутствием классов, интерфейсов и тому подобного. На самом деле это не означает, что JavaScript не является объектно-ориентированным, скорее наоборот. Однако ООП на основе прототипа отличается, и чтобы его использовать, нужно уметь это делать. Любое требование (области, пространства имен, видимость членов, наследование объектов и т. Д.) Может быть достигнуто с помощью хитрости. Вы не можете узнать их из документации.Для JavaScript нет централизованной документации, но есть спецификации EcmaScript (JavaScript — это реализация стандарта ECMA-262) и множество ссылок (например,MDN JavaScript ссылки ). Ссылки и, тем более, спецификации предназначены не для охвата шаблонов программирования, а для самого языка. Конечно, есть много книг, и вы можете найти некоторые полезные (например, «Секреты ниндзя JavaScript» Джона Резига , «Изучение шаблонов дизайна JavaScript» Адди Османи ). Кроме того, вы можете учиться прямо из кода, так как нам повезло, что у нас есть тонны значительных JavaScript-приложений, доступных в открытом коде ( jquery.com , yuilibrary.com/projects/yui3/ , backbonejs.org , modernizr.com , raphaeljs. коми так далее). В любом случае вы можете наткнуться на неизвестные вам методы программирования. Я собрал здесь некоторые часто используемые практики, которые могут вам помочь.

Стиль программирования

Код намного легче читать, если вы знакомы с его стилем кодирования. Хорошая новость заключается в том, что большая часть кода с открытым исходным кодом придерживается одного из нескольких руководств по стилю кодирования или, по крайней мере, стиля, основанного на них. Наиболее широко используется Idiomatic.js , JQuery Core , Руководства по стилю , Dojo Руководства по стилю и Конгрессы Дугласа Крокфорд КОДЕКС . На самом деле все они очень похожи друг на друга. Вот несколько распространенных существенных утверждений:

  • Операторы var должны быть первыми в теле функции.
  • Имена методов и переменных в camelCase
  • Функция конструктора в PascalCase
  • Символические имена констант в верхнем регистре
    (function() {
    var CONSTANT_EXAMPLE,
          variableExample,
          ConstructorExample = function() {
                 return {
                       methodExample: function() {
                       }
                 } 
          } 
    }());
  • Всегда предпочитайте `===` перед `==` (если только случай не требует свободной оценки типа)
  • Объявите статические объекты как {} и массив как []

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

variable = argumentVar || "Default value";

Если argumentsVar определен и истинен (например, не пустой), переменная получает аргументvarVar. В противном случае «Значение по умолчанию».

condition && foo();

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

Читая код, вы можете натолкнуться на следующую схему рекурсии:

(function( arg ){
    console.log( arg );
    if ( arg-- > 0 ) {
        arguments.callee( arg );
    }
})( 10 );

Не адаптируйте эту практику. В строгом режиме ECMAScript 5 вызываемый объект считается небезопасным действием и вызывает исключение.

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

// to a boolean
console.log( !!5 ); // true
// to a string
console.log( 5 + "" ); // "5"
// to a number
console.log( parseInt( "011", 10 ) ); // 11
console.log( +5 ); // "5"
// it also works through bitwise operators
console.log( ~~"5" ); // 5
console.log( "5" >> 0 ); // 5
console.log( "5" >>> 0 ); // 5
// a nodeList/arguments to array
console.log( [].slice.call( document.querySelectorAll( 'a' ), 0 ) );

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

var variable =  "string\
string\
string";

Я бы предпочел не рекомендовать этот подход. Просто завершающий пробел вызовет синтаксическую ошибку. В JavaScript 1.6 / E4X (ноябрь 2005 г.) было введено выражение, очень похожее на heredoc:

var variable =  <>
string
string
string
</>.toString();

Области применения

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

(function(window, undefined) {
    var document = window.document;
    // Module body
}(window));

Могут возникнуть некоторые вопросы относительно того, какие дополнительные аргументы мы передаем Я бы порекомендовал пропустить окно через локальную переменную, а не как глобальную. Это немного ускоряет процесс разрешения и может быть более эффективно минимизировано. Что касается неопределенного, таким образом мы гарантируем, что неопределенное не определено случайно. Да, JavaScript иногда совершенно несовместим: NaN (не число) — это число, неопределенный, будучи объектом, может быть переопределен и определен. Это было исправлено в EcmaScript 5 (JavaScript 1.8.5) и не было проблемой в новых браузерах. Тем не менее, мы должны позаботиться о старых браузерах.

Смотрите также Область в JavaScript

Объекты

В классическом JavaScript наследуемые объекты объявляются либо так:

var Constructor = function(){
     this.prop = 'Value'; 
}

или так:

var Constructor = function(){};
Constructor.prototype.prop = "Value";

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

var o = new Constructor();
console.log( o instanceof Constructor ); // true
console.log( o.prop ); // Value

или используя запись EcmaScript 5:

var o = Object.create( Constructor.prototype );
console.log( o instanceof Constructor ); // true
console.log( o.prop ); // Value

Любое выражение, которому предшествует оператор var, не будет доступно вне области действия конструктора и может рассматриваться как закрытый член:

var Constructor = function(){
    var privateProp = "Value",
        privateMethod = function() { 
            return privateProp;
        };
    this.publicMethod = function() {
        return privateMethod();
    }
},
o = new Constructor();
console.log( o instanceof Constructor ); // true
console.log( o.publicMethod() ); // Value

Увидеть больше частных членов в JavaScript от Дуглас Крокфорд

клонирование

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

Какая разница между клонированием и созданием экземпляров? Второй создает новый объект пустого состояния, а первый предоставляет копию.

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

var originalObj = { prop: "value" }, 
    clone = Object.create( originalObj );

Прототип ООП и псевдоклассическое наследование

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

Каждый производный объект имеет ссылку на свой прототип. Таким образом, когда метод или свойство запрашивается у объекта, система проходит по цепочке делегирования, пока не найдет ее в одном из формирующих прототипов. Кроме того, любой объект, присвоенный свойству другого, делает не копию, а ссылку. Это дает нам динамическую модель, в которой изменение одного из связанных объектов немедленно отражается на другом. Эта модель поддерживает как обобщающие (is-a), так и агрегирующие (has-a) типы отношений.

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

var o, 
    SuperTypeConstructor = function(){}, 
    SubTypeConstructor = function(){};
    
SuperTypeConstructor.prototype = {
    a : "supertype's a",
    b : "supertype's b"
};
SubTypeConstructor.prototype = new SuperTypeConstructor();
SubTypeConstructor.prototype.a = "subtype's a";

o = new SubTypeConstructor();

console.log( o instanceof SubTypeConstructor ); // true
console.log( o instanceof SuperTypeConstructor ); // true
console.log( o.a ); // subtype's a
console.log( o.b ); // supertype's b
console.log( o.__proto__.a ); // subtype's a
console.log( o.__proto__.__proto__.a ); // supertype's a

Здесь показан пример прогулки по цепочке прототипов объекта. На самом деле свойство __proto__ не является стандартным. Вы скорее не полагаетесь на это. В ECMA-262 до сих пор нет способа достичь глубокой иерархии наследования. Но это может измениться в 6 выпуске, все же. Это видно из примера, взятого из предложений EcmaScript Harmony :

class ConcreteClass extends BaseClass {
  constructor(x,y) {
    super();
  }
}

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

Модель, известная как модуль, широко распространена в настоящее время, во многом благодаря энтузиазму евангелизации Адди Османи .

var Module = (function() {
    var privateMember = "secret";
    return {
      publicMember: function() {
        return privateMember;
      }
    };
  })();

Мне очень нравится этот шаблон, потому что он делает ваши объекты чистыми и читаемыми. Я скучаю по псевдоклассическому наследству. Итак, я написал небольшую фабрику JSA, которая решает ее:

(function( jsa, window, undefined ) {
"use strict";
var AbstractModule = function () {
        return {
            inheritedProp : "inherited property",
            publicProp : "original property"
        };
    },
    ConcreteModule = function () {
        var _privateVar = "private";
        return {
            __extends__: AbstractModule,  // ConcreteModule extends AbstractModule
            getPrivate : function () {
                return _privateVar;
            },
            publicProp : "overriden property"
        };
    },
    // Make an instance of ConcreteModule
    o = ConcreteModule.createInstance();
    console.log( o instanceof ConcreteModule ); // true
    console.log( o instanceof AbstractModule ); // true
})( jsa, window );

Магические методы

Вы знакомы с магическими методами PHP? Это дает вам контроль над доступом к свойству и его изменением, вызовом метода и тому подобным. Язык становится намного более гибким, и вы можете применять новые модные шаблоны.

Ниже приведен пример перегрузки свойства:

var obj = {  
      a: null,  
      get member() { return this.a; },  
      set member( x ) { this.a = x; }  
};

Вы также можете использовать следующую форму, представленную в EcmaScript 5 (JavaScript 1.8.5, июль 2010 г.):

var obj = {};
Object.defineProperty( obj, "member", {
   get : function(){ 
        console.log( "Member accessor invoked" );
        return bValue; 
   },  
   set : function( newValue ){ 
        console.log( "Member mutator invoked" );   
        bValue = newValue; 
   },  
   enumerable : true,  
   configurable : true}
);  

obj.member = "value";
console.log( obj.member );

Method overloading for now is only available in Firefox and is recognized as non-standard. Pity, I do love their multiple inheritance simulation.

var obj = {
    __noSuchMethod__ : function( arg ){ 
        console.log( 'Unable to call "' + arg + '" function' );
    }
}
obj.undefinedMethod(); // Unable to call "undefinedMethod" function

To close the subject, here how you can define the primitive value of the object:

var obj1 = {
  valueOf: function () {
    return 1;
  }
}, obj2 = {
    valueOf: function () {
    return 2;
  }
};

console.log( obj1 + obj2 ); // 3

Cascade

If you are familiar with jQuery, you probably noted multiple statements like

$( "#exampleNode" )
    .addClass( "highlighted" )
    .css( "visibility", "visible" )
    .html( "Lorem ipsum" );

The technique is called method chaining (cascade) and make code cleaner. But how can it be achieved? Pretty easy. We just have to make every of chainable methods to return the object (the current object itself):

var $ = function( query ) {
    var $ = function( query ) {
        var node = document.querySelector( query );
        return {
            addClass: function( className ) {
                node.className = className;            
                return this;
            },
            css: function ( prop, value ) {
                node.style = prop + ":" + value;
                return this;
            },
            html: function( html ) {
                node.innerHTML = html;
                return this;
            }
        }
    }
    return new $( query );
}

Exceptions Handling

Try/catch technique is especially efficient when it’s conditional:

function Constructor1(){
}
function Constructor2(){
}

try {
    throw new Constructor1();
} catch ( err if err instanceof Constructor1 ) {
    console.log( 'Constructor1 thrown exception' )
} catch ( err if err instanceof Constructor2 ) {
    console.log( 'Constructor2 thrown exception' )
} catch(e) {
    throw(e);
}

Besides, JavaScript provides predefined Error object and its instances EvalError, RangeError, ReferenceError, SyntaxError, TypeError, URIError. You can inherit them in your own exception type:

var CustomException = function( message ) {  
        this.name = "CustomException";  
        this.message = message || "CustomException thrown";  
    }  
    CustomException.prototype = new ReferenceError();  
    CustomException.prototype.constructor = CustomException;  

    try {  
        throw new CustomException( "custom message" );  
    } catch (e) {  
        console.log( e.name );     // "CustomException"  
        console.log( e.message );  // "custom message"  
        console.log( e instanceof CustomException ); // true
        console.log( e instanceof ReferenceError ); // true
        console.log( e instanceof Error ); // true        
    }

Overriding global objects

JavaScript is freakishly dynamic. You can override any object or its member run-time (except if it is intentionally sealed). It can be even objects of global scope and first-class objects, what gives you enormous power over the language.

For example you can rewrite that clumsy alert function by your own one showing the message on a fancy-styled overlay:

window.alert = function( message ) {
    console.log( "Display " + message + " in some fancy way " );
}

alert( "my message" ); // Display my message in some fancy way

But you rather don’t override global objects unless you really have to.

Do you know about aspect-oriented programming paradigm? What’s relevant here that in AOP you can enable cross-cutting concerns such as logger on any parts of application whenever you wish. It turned out that JavaScript provides you with such trick. You can hook into any existing code, e.g. third-party made one, not changing it.

var jQueryBakup = window.jQuery
window.jQuery = function( selector, context ) {
    console.log( "jQuery is called with arguments: ", selector, context );
    return jQueryBakup( selector, context );
}

console.log(jQuery(window));

Non-blocking JavaScript

You probably know that if you keep script tags referring to external sources in page head section it may slow down page generation. So it’s better to put them at the end of page body. You can also achieve asynchronous loading by adding script dynamically:

var script = document.createElement("script");
script.type = "text/javascript";
script.src = "file.js";
document.body.appendChild(script);

You can even specify the loading chain: which script loads after which.

If your application comprises time-consuming processes, consider to perform them asynchronously, so that the application will not have to wait for them:

var runAsynchronous = function( fn, onCompleteCb ) {
        window.setTimeout(function() {
            fn();
            onCompleteCb();
        }, 0);
    }, 
    sleep = function( ms ) {
      var date = new Date(), curDate = null;
      do { 
          curDate = new Date(); 
      } while ( curDate - date < ms );
    }, 
    consumingFn = function() {
        sleep( 500 );    
    };

runAsynchronous( consumingFn, function() {
    console.log( "The process is complete" );
});
console.log( "The process is running" );

Conclusion

The article neither covers the programming patterns nor teaches JavaScript language. My intention was to show briefly the essential techniques, most commonly used in JavaScript. Something I would have liked to have a few years ago for myself. Since you’ve read this so far, you must be eager to evolve. That means, on your way to be a better JavaScript coder, you will read books, watch presentations, taking part in master-classes and study code. And everywhere you will meet these simple tricks I pointed in here. I hope you won’t stumble over them now, but will be ready to follow the authors in more complicated solutions.