Примечание переводчика: Тема наследования в JavaScript является одной из самых тяжелых для новичков. С добавлением нового синтаксиса с ключевым словом class, понимание наследования явно не стало проще, хотя кардинально нового ничего не появилось. В данной статье не затрагиваются нюансы реализации прототипного наследования в JavaScript, поэтому если у читателя возникли вопросы, то рекомендую прочитать следующие статьи: Основы и заблуждения насчет JavaScript и Понимание ООП в JavaScript [Часть 1]
По всем замечаниям, связанным с переводом, обращайтесь в личку.
JavaScript является очень мощным языком. Настолько мощным, что в нем сосуществует множество различных способов проектирования и создания объектов. У каждого способа есть свои плюсы и минусы и я бы хотел помочь новичкам разобраться в этом. Это продолжение моего предыдущего поста, Хватит «классифицировать» JavaScript . Я получил много вопросов и комментариев с просьбами привести примеры, и для именно этой цели я решил написать эту статью.
JavaScript использует прототипное наследование
Это означает, что в JavaScript объекты наследуются от других объектов. Простые объекты в JavaScript, созданные с использованием {} фигурных скобок, имеют только один прототип:
Object.prototype
.
Object.prototype
, в свою очередь тоже объект, и все свойства и методы
Object.prototype
доступны для всех объектов.
Массивы, созданные с помощью квадратных скобок, имеют несколько прототипов, в том числе Object.prototype
и Array.prototype
. Это означает, что все свойства и методы Object.prototype
и Array.prototype
доступны для всех массивов. Одноименные свойства и методы, например .valueOf
и .ToString
, вызываются из ближайшего прототипа, в этом случае из Array.prototype
.
Определения прототипа и создание объектовСпособ 1: Шаблон конструктор
JavaScript имеет особый тип функции называемых конструкторами, которые действуют так же, как и конструкторы в других языках. Функции-конструкторы вызываются только с помощью ключевого слова
new
и связывают создаваемый объект с контекстом функции-конструктора через ключевое слово
this
. Типичный конструктор может выглядеть следующим образом:
function Animal(type){
this.type = type;
}
Animal.isAnimal = function(obj, type){
if(!Animal.prototype.isPrototypeOf(obj)){
return false;
}
return type ? obj.type === type: true;
};
function Dog(name, breed){
Animal.call(this, "dog");
this.name = name;
this.breed = breed;
}
Object.setPrototypeOf(Dog.prototype, Animal.prototype);
Dog.prototype.bark = function(){
console.log("ruff, ruff");
};
Dog.prototype.print = function(){
console.log("The dog " + this.name + " is a " + this.breed);
};
Dog.isDog = function(obj){
return Animal.isAnimal(obj, "dog");
};
Использование этого конструктора выглядит также как и создание объекта в других языках:
var sparkie = new Dog("Sparkie", "Border Collie");
sparkie.name; // "Sparkie"
sparkie.breed; // "Border Collie"
sparkie.bark(); // console: "ruff, ruff"
sparkie.print(); // console: "The dog Sparkie is a Border Collie"
Dog.isDog(sparkie); // true
bark
и
print
методы прототипа, которые применяются для всех объектов созданных с помощью конструктора
Dog
. Свойства
name
и
breed
инициализируются в конструкторе. Это общепринятая практика, когда все методы определяются в прототипе, а свойства инициализируются конструктором.Способ 2: Определение класса в ES2015 (ES6)
Ключевое слово
class
было зарезервировано в JavaScript с самого начала и вот наконец-то пришло время его использовать. Определения классов в JavaScript схоже с другими языками.
class Animal {
constructor(type){
this.type = type;
}
static isAnimal(obj, type){
if(!Animal.prototype.isPrototypeOf(obj)){
return false;
}
return type ? obj.type === type: true;
}
}
class Dog extends Animal {
constructor(name, breed){
super("dog");
this.name = name;
this.breed = breed;
}
bark(){
console.log("ruff, ruff");
}
print(){
console.log("The dog " + this.name + " is a " + this.breed);
}
static isDog(obj){
return Animal.isAnimal(obj, "dog");
}
}
Многие люди считают этот синтаксис удобным, потому что он объединяет в одном блоке конструктор и объявление статичных и прототипных методов. Использование точно такое же, как и в предыдущем способе.
var sparkie = new Dog("Sparkie", "Border Collie");
Способ 3: Явное объявление прототипа, Object.create, фабричный метод
Этот способ показывает, что на самом деле новый синтаксис с ключевым словом
class
использует прототипное наследование. Также этот способ позволяет создать новый объект без использования оператора
new
.
var Animal = {
create(type){
var animal = Object.create(Animal.prototype);
animal.type = type;
return animal;
},
isAnimal(obj, type){
if(!Animal.prototype.isPrototypeOf(obj)){
return false;
}
return type ? obj.type === type: true;
},
prototype: {}
};
var Dog = {
create(name, breed){
var proto = Object.assign(Animal.create("dog"), Dog.prototype);
var dog = Object.create(proto);
dog.name = name;
dog.breed = breed;
return dog;
},
isDog(obj){
return Animal.isAnimal(obj, "dog");
},
prototype: {
bark(){
console.log("ruff, ruff");
},
print(){
console.log("The dog " + this.name + " is a " + this.breed);
}
}
};
Этот синтаксис удобен, потому что прототип объявляется явно. Понятно что определено в прототипе, а что определено в самом объекте. Метод
Object.create
удобен, потому что он позволяет создать объект от указанного прототипа. Проверка с помощью
.isPrototypeOf
по-прежнему работает в обоих случаях. Использование разнообразно, но не чрезмерно:
var sparkie = Dog.create("Sparkie", "Border Collie");
sparkie.name; // "Sparkie"
sparkie.breed; // "Border Collie"
sparkie.bark(); // console: "ruff, ruff"
sparkie.print(); // console: "The dog Sparkie is a Border Collie"
Dog.isDog(sparkie); // true
Способ 4: Object.create, фабрика верхнего уровня, отложенный прототип
Этот способ является небольшим изменение способа 3, где сам класс является фабрикой, в отличии от случая когда класс является объектом с фабричным методом. Похоже, на пример конструктора (способ 1), но использует фабричный метод и
Object.create
.
function Animal(type){
var animal = Object.create(Animal.prototype);
animal.type = type;
return animal;
}
Animal.isAnimal = function(obj, type){
if(!Animal.prototype.isPrototypeOf(obj)){
return false;
}
return type ? obj.type === type: true;
};
Animal.prototype = {};
function Dog(name, breed){
var proto = Object.assign(Animal("dog"), Dog.prototype);
var dog = Object.create(proto);
dog.name = name;
dog.breed = breed;
return dog;
}
Dog.isDog = function(obj){
return Animal.isAnimal(obj, "dog");
};
Dog.prototype = {
bark(){
console.log("ruff, ruff");
},
print(){
console.log("The dog " + this.name + " is a " + this.breed);
}
};
Этот способ интересен тем, что похож на первой способ, но не требует ключевого слова
new
и работает с оператором
instanceOf
. Использование такое же, как и в первом способе, но без использования ключевого слова
new
:
var sparkie = Dog("Sparkie", "Border Collie");
sparkie.name; // "Sparkie"
sparkie.breed; // "Border Collie"
sparkie.bark(); // console: "ruff, ruff"
sparkie.print(); // console: "The dog Sparkie is a Border Collie"
Dog.isDog(sparkie); // true
СравнениеСпособ 1 против Способа 4
Существует довольно мало причин, для того чтобы использовать Способ 1 вместо Способа 4. Способ 1 требует либо использование ключевого слова
new
, либо добавление следующей проверки в конструкторе:
if(!(this instanceof Foo)){
return new Foo(a, b, c);
}
В этом случае проще использовать
Object.create
с фабричным методом. Вы также не можете использовать функции
Function#call
или
Function#apply
с функциями-конструкторами, потому что они переопределяют контекст ключевого слова
this
. Проверка выше, может решить и эту проблему, но если вам нужно работать с неизвестным заранее количеством аргументов, вы должны использовать фабричный метод.Способ 2 против Способа 3
Те же рассуждения о конструкторах и операторе
new
, что были упомянуты выше, применимы и в этом случае. Проверка с помощью
instanceof
необходима, если используется новый синтаксис
class
без использования оператора
new
или используются
Function#call
или
Function#apply
.Мое мнение
Программист должен стремиться к ясности своего кода. Синтаксис Способа 3 очень четко показывает, что именно происходит на самом деле. Он также позволяет легко использовать множественное наследование и стековое наследования. Так как оператор
new
нарушает принцип открытости/закрытости из-за несовместимости с
apply
или
call
, его следует избегать. Ключевое слово
class
скрывает прототипный характер наследования в JavaScript за маской системы классов.
«Простое лучше мудреного», и использование классов, потому что оно считается более «изощренным» является просто ненужной, технической головомойкой.
Использование
Object.create
является более выразительным и ясным, чем использование связки
new
и
this
. Кроме того, прототип хранится в объекте, который может быть вне контекста самой фабрики, и таким образом может быть более легко изменен и расширен добавлением методов . Прям как классы в ES6.
Ключевое слово
class
, возможно будет наиболее пагубной чертой в JavaScript. Я испытываю огромное уважение к блестящим и очень трудолюбивым людям, которые были вовлечены в процесс написания стандарта, но даже блестящие люди иногда делают неправильные вещи. - Eric Elliott
Добавление чего-то ненужного и возможно пагубного, противоречащего самой природе языка является необдуманным и ошибочным.
Если вы решите использовать
class
, я искренне надеюсь, что мне никогда не придется работать с вашим кодом. На мой взгляд, разработчики должны избегать использования конструкторов,
class
и
new
, и использовать методы, которые более естественны парадигме и архитектуре языка. Глоссарий
Object.assign(a, b)
копирует все перечислимые (enumerable) свойства объекта
b
в объект
a
, а затем возвращает объект
a
Object.create(proto)
создает новый объект от указанного прототипа
proto
Object.setPrototypeOf(obj, proto)
меняет внутреннее свойство
[]
объекта
obj
на
proto
Теги:
Добавить метки
В WordPress повсюду используются шаблоны и Javascript там не исключение. В этой заметке поговорим про встроенную в WordPress возможность создавать HTML шаблоны, которые затем можно использовать в JS. Создаются и используются такие шаблоны очень просто, впрочем как и многое другое в WordPress.
Есть много способов создавать шаблоны в Javascript, для них даже придумана отдельная спецификация именуемая Mustache . Она реализована на многих языках, включая Javascript. Например, библиотека Handlebars использует эту спецификацию и даже немного её расширяет. Или популярная мини-библиотека Underscore .
С версии 3.5 WordPress уже имеет в своем ядре удобный шаблонизатор для JS. Он например используется в админке при создании блоков для медиа-загрузчика. В основе лежит вышеупомянутая библиотека Underscore , синтаксис немного переделан, чтобы больше соответствовать спецификации Mustache .
Для создания шаблонов в WordPress есть метод wp.template
wp.template(id)
Создает объект шаблона из HTML кода. Чтобы получить готовый HTML код для использования в JS, в созданный объект нужно передать данные для заполнения шаблона.
Возвращает
Function. Функцию, в которую нужно передать данные для интерполяции шаблона.
Использование
var template = wp.template(id);
var HTML = template(data);
id
(строка)
Идентификатор HTML элемента который содержит HTML код шаблона. HTML элемент должен иметь указанный тут атрибут id с префиксом tmpl- .
Например, если тут указать foo , то HTML элемент должен иметь id id="tmpl-foo" .
Data(объект)
JS объект данных, которые будут использованы для заполнения шаблона. Например: { text: "Привет" } .
Заполнение шаблона (интерполяция)
- {{{data.unescaped}}} - неочищенные данные.
- {{data.escaped}} - очищенные данные.
- - обработать js (eval).
Префикс data.
data в шаблоне - это объект исходных данных. В шаблоне нужно использовать именно ключ data .
Чтобы соответствовать структуре данных возвращаемых функциями: wp_send_json_success() и wp_send_json_error() , wp.template оборачивает все полученные данные в переменную data . Поэтому перед каждым параметром в шаблоне нужно указывать data. , иначе мы получим ошибку: {property} is not defined .
Правильно {{{data.name}}}
Неправильно {{{name}}}
Пример шаблона
Это будет просто выведено.
Выведем значение переменной escapedValue {{data.escapedValue}}.
Если данные содержат разметку, выводим без экранирования:
{{{data.unescapedValue}}}
Когда нужно выполнить какую-то логику.
Будет выведено, только если data.trueValue = true.
Создание и генерация шаблона
Создание шаблона
Чтобы шаблон никак не фигурировал в DOM дереве, его принято создавать в теге script с указанием типа type="text/html" .
Привет {{{data.name}}}
Атрибут id должен начинаться с tmpl- , все что после этого префикса будет затем использовано в функции wp.template("my-template") .
Создание шаблона в теге script это хак, который отлично подходит для создания html элемента, который никак не используется браузером. Когда указан непонятный для браузера тип он просто игнорирует html тег, а это нам и нужно.
Шаблон также можно создать в любом другом HTML элементе (например в , который затем можно скрыть), единственное что нужно это указать id атрибут.
Также для создания шаблонов есть специальный HTML тег , однако он не поддерживается в IE . Но в целом он довольно актуален.
Генерация шаблона
wp.template() возвращает функцию, поэтому не пытайтесь передать результат в html элемент или вывести результат в консоль. Обычно результат wp.template() передается в переменную, а затем эта переменная используется как функция и в неё передаются данные, которыми должен быть заполнен шаблон.
Пример (шаблон указан выше)
// JS
var template = wp.template("my-template"),
data = { name: "Виктор" };
jQuery(".my-element").html(template(data));
В результате получим в HTML:
Привет Виктор
Пример комментирования на AJAX с использование шаблона
Создаем шаблон и подключаем скрипт в файле темы functions.php: