пятница, 14 октября 2016 г.

JavaScript ES5. Пространство имен, наследование, защита от дублирования.

Защита от дублирования скриптов

Следующая функция предотвращает повторную загрузку файла JavaScript

  function scriptLoader(src, callback) {
     "use strict";
      var script = document.querySelector('script[src="'+src+'"]');
      if (script == null) {    
        var head= document.getElementsByTagName('head')[0];
        script= document.createElement('script');
        script.type= 'text/javascript';
        script.src= src;
        script.async = false;
        script.onreadystatechange= function () {
           if (this.readyState == 'complete') callback();
        }
        head.appendChild(script);
      }
      if (script.addEventListener) {
        script.addEventListener("load", callback); 
      } else if (script.attacheEvent) {
        script.addEventListener("onload", callback); 
      } 
  }

Предположим у нас есть следующий скрипт файле alerter.js:

  var Alerter = function() {};
  Alerter.prototype.say = function(smth) { alert(smth); };

Пример использования функции для загрузки alerter.js

  scriptLoader("alerter.js", function() {
    var a = new Alerter();
    a.say("Hi");
  });

  scriptLoader("alerter.js", function() {
    var a = new Alerter();
    a.say("Hi again");
  });

В результате функция выведет в диалоговом окне сначала Hi, затем - Hi again

Функцию можно усовершенствовать, задав на входе массив имен скриптовых файлов.
Также можно модифицировать ее для предотвращения повторной загрузки файлов стилей css.

Предотвращение повторного использования переменных

Для предотвращения повторного использования переменных и ухода от глобального контекста код лучше оборачивать в (function() { //код })();
Например следующий код:

<script>
  var a = 1;
  var v = 2;
</script>

Лучше переписать как:

<script>
(function() {
  var a = 1;
  var v = 2;
})();
</script>

Защита приватных полей

Для защиты приватных полей от внешнего доступа целесообразно использовать конструктор с замыканием. Примерно так:

var MyClass = function() {
  "use strict";
  var _name; // приватная переменная, захватывается функциями getName и setName
  this.getName = function() { return _name}
  this.setName = function(value) {
    _name = value;
  }
 
}

Пример выполнения:

var myObj = new MyClass();
myObj.setName("This is the name");
console.log(myObj.getName()); // Выведет This is the name

Пространство имен

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

function nameSpace(namespace)
{
  "use strict";
  var parts = namespace.split(".");
  var obj = window;
  for (var i = 0; i < parts.length; ++i) {
    obj[parts[i]] = obj[parts[i]] || {};
    obj = obj[parts[i]];
  };
  return obj;
};

Пример запуска:

   nameSpace("Compsphere.Demo");
   console.log(Compsphere.Demo); //Выводит в консоль Compsphere.Demo

Можно ее использовать и так:

   var obj = nameSpace("Compsphere.Demo");
   obj.NewClass = function() { this.x = "Hi!" }
   console.log((new Compsphere.Demo.NewClass).x); //Выводит в консоль Hi!

Генератор классов с пространством имен

Функцию nameSpace можно модифицировать, превратив ее в генератор классов в некоем пространстве имен.
Вот ее вид:
function createClass(_namespace, _constructor, _prototype) {
  var parts = _namespace.split(".");
  var obj = window;
  for (var i = 0; i < parts.length-1; ++i) {
    obj[parts[i]] = obj[parts[i]] || {};
    obj = obj[parts[i]];
  };

  if (typeof _constructor == "function") {
    obj[parts[i]] = _constructor;
    if (typeof _prototype != "undefined") {
      obj[parts[i]].prototype = _prototype;
    }
  } 
  else {
    obj[parts[i]] = obj[parts[i]] || {};
  }
  return obj[parts[i]];
};

На вход подается пространство имен, функция - конструктор класса и необязательный объект для прототипа. Если конструктор и объект для прототипа не переданы, то createClass отработает аналогично вышеописанной nameSpace. Если не передан только объект для прототипа, то прототип не создастся.

Примеры использования

(function() {
  var myclass = createClass(
    "Compsphere.Demo", 
     function(name) { alert(name) }, 
     {say: function(word) { alert(word); } } 
   ); 

  myclass.prototype.sayAgain = function(word) { alert(word) };
  (new Compsphere.Demo("constructor")).say("Hi");  //Выведет фразу Hi

  var myClassInstance = (new myclass("this is the constructor")); //Выведет фразу this is the constructor
  myClassInstance.say("'say' method"); //Выведет фразу this is the constructor 'say' method
  myClassInstance.sayAgain("'sayAgain' method"); //Выведет фразу 'sayAgain' method
})();