Пост « Классы в Coffeescript » содержит интересное сопоставление кода CoffeeScript и JavaScript, на который он переведен. В этом посте более подробно рассматривается результат перевода, который хорошо иллюстрирует, как работает подклассы в JavaScript. Чтобы понять следующее, вы должны быть знакомы с прототипным наследованием JavaScript (объяснено здесь ).
Перевод
Приведен следующий код CoffeeScript суперкласса Ninja и подкласса Ronin.
class Ninja constructor: (@numOfShurikens) -> throwShuriken: -> @numOfShurikens-- class Ronin extends Ninja constructor: (numOfShurikens) -> super numOfShurikens+1 # ronins know to carry a spare
Создание подклассов в JavaScript немного утомительно, и CoffeeScript помогает. Приведенный выше код переводится на следующий JavaScript.
var Ninja, Ronin; var __hasProp = Object.prototype.hasOwnProperty, __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) { child[key] = parent[key]; } } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor; child.__super__ = parent.prototype; return child; }; Ninja = (function() { function Ninja(numOfShurikens) { this.numOfShurikens = numOfShurikens; } Ninja.prototype.throwShuriken = function() { return this.numOfShurikens--; }; return Ninja; })(); Ronin = (function() { __extends(Ronin, Ninja); function Ronin(numOfShurikens) { Ronin.__super__.constructor.call(this, numOfShurikens + 1); } return Ronin; })();
Понимание сгенерированного кода
Давайте разберем код и объясним каждый фрагмент:
var Ninja, Ronin;
Объявление переменных сейчас и назначение их позже делает подъем [3] явным. Я не вижу преимущества в объявлении двух переменных позже, когда выполняется каждое из назначений.
var __hasProp = Object.prototype.hasOwnProperty,
Ярлык для hasOwnProperty немного увеличивает производительность, потому что разыменование происходит реже. С другой стороны, для этого не нужно тратить глобальную переменную, поэтому, вероятно, было бы достаточно поместить ее непосредственно перед циклом for.
__extends = function(child, parent) { // (1) for (var key in parent) { if (__hasProp.call(parent, key)) { child[key] = parent[key]; } } // (2) function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor; // (3) child.__super__ = parent.prototype; return child; };
Помните, что функция конструктора C слабо соответствует классу в том смысле, что она содержит имя типа и что она используется для создания новых экземпляров. Однако методы экземпляра (которые являются общими для всех экземпляров) помещаются в C.prototype. Когда вы создаете подкласс, вы должны учитывать два уровня: с одной стороны, методы класса C.classMethod (), с другой стороны, методы экземпляра C.prototype.instanceMethod (). Имейте в виду, что свойство prototype в C имеет только шляпное имя, потому что оно станет прототипом объектов, созданных с помощью new C (). Таким образом, C.prototype является
не прототипом функции C, а скорее свойством объекта функции. Шаги, выполненные выше:
- Скопируйте методы класса из суперкласса в подкласс. Другого способа наследования таких методов в JavaScript нет.
- Убедитесь, что у объекта child.prototype есть прототип parent.prototype. Вы делаете это, создавая временный конструктор ctor и давая ему правильное значение для ctor.prototype. Фрагмент кода эквивалентен следующему коду ECMAScript 5 [1]:
child.prototype = Object.create(parent.prototype); child.prototype.constructor = child;
Свойство конструктора [2] правильно установлено в прототипе по умолчанию. Поскольку мы назначаем наш собственный объект-прототип, нам нужно настроить это свойство вручную.
- Учитывая суперкласс Super и субкласс Sub, вы обычно должны ссылаться на Super по имени в методах Sub. Назначая Super.prototype для Sub .__ super__, вы избегаете такого рода жесткого кодирования. Для этого функция конструктора Ronin () использует Ronin .__ super__.
Ninja = (function() { function Ninja(numOfShurikens) { this.numOfShurikens = numOfShurikens; } Ninja.prototype.throwShuriken = function() { return this.numOfShurikens--; }; return Ninja; })();
Обычное определение класса JavaScript, заключенное в IIFE [3]. Здесь нет никакой пользы от использования IIFE, кроме одного назначения (что имеет значение для литерала объекта, но не здесь). Обратите внимание, что идентификатор Ninja в первой строке — это переменная, отличная от идентификатора Ninja внутри IIFE (последующие строки).
Ronin = (function() { __extends(Ronin, Ninja); function Ronin(numOfShurikens) { Ronin.__super__.constructor.call(this, numOfShurikens + 1); } return Ronin; })();
Опять же, IFEE не нужен. Функция __extends (), которая была определена ранее, используется для выполнения всех необходимых соединений от подкласса до суперкласса. Предостережение: первый аргумент относится к функции Ronin () ниже, потому что эта функция
поднята [3] (грубо говоря: переместилась на вершину окружающей функции). Было бы лучше вызвать __extends () после объявления Ronin (), чтобы сделать этот факт явным. Вызов супер-конструктора является типичной техникой JavaScript: вы вызываете супер-конструктор как функцию (не новая!) И присваиваете ему текущее значение this, так что он добавляет свои свойства, но не создает новый экземпляр.
Связанное чтение
- Что нового в ECMAScript 5
- Что случилось со свойством «конструктор» в JavaScript?
- Область видимости переменной JavaScript и ее подводные камни [объясняет IIFE и подъем]
- Облегченные API наследования JavaScript [различные способы упрощения наследования JavaScript]
- CoffeeScript против JavaScript без паренов