Статьи

Перевод классов CoffeeScript в JavaScript

Пост « Классы в 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, а скорее свойством объекта функции. Шаги, выполненные выше:

  1. Скопируйте методы класса из суперкласса в подкласс. Другого способа наследования таких методов в JavaScript нет.
  2. Убедитесь, что у объекта child.prototype есть прототип parent.prototype. Вы делаете это, создавая временный конструктор ctor и давая ему правильное значение для ctor.prototype. Фрагмент кода эквивалентен следующему коду ECMAScript 5 [1]:
        child.prototype = Object.create(parent.prototype);
        child.prototype.constructor = child;
    

    Свойство конструктора [2] правильно установлено в прототипе по умолчанию. Поскольку мы назначаем наш собственный объект-прототип, нам нужно настроить это свойство вручную.

  3. Учитывая суперкласс 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, так что он добавляет свои свойства, но не создает новый экземпляр.

Связанное чтение

  1. Что нового в ECMAScript 5
  2. Что случилось со свойством «конструктор» в JavaScript?
  3. Область видимости переменной JavaScript и ее подводные камни [объясняет IIFE и подъем]
  4. Облегченные API наследования JavaScript [различные способы упрощения наследования JavaScript]
  5. CoffeeScript против JavaScript без паренов

 

С http://www.2ality.com/2011/06/coffeescript-classes.html