Каким будет хороший минималистичный метод наследования Javascript? - PullRequest
6 голосов
/ 10 сентября 2009

Я переписываю проект JavaScript и хочу иметь возможность использовать объектно-ориентированные методологии для организации беспорядка, которым является текущий код. Основная проблема заключается в том, что этот JavaScript должен работать как виджет на сторонних веб-сайтах, и я не могу конфликтовать с другими библиотеками JavaScript, которые могут использовать другие веб-сайты.

Итак, я ищу способ написать «классоподобное» наследование в JavaScript, которое имеет следующие требования:

  1. Нет внешних библиотек или объектов, которые могут конфликтовать с внешней библиотекой (что исключает копирование и вставку из внешней библиотеки).
  2. Минималистичный - я не хочу, чтобы код поддержки был больше, чем несколько строк кода, и я не хочу, чтобы разработчики нуждались в большом количестве шаблонов каждый раз, когда они определяют новый класс или методы.
  3. Следует учитывать динамическое расширение родительских объектов, чтобы дочерние объекты могли видеть изменения (прототип).
  4. Должен учитывать конструктор.
  5. Должен допускать вызовы super.
  6. Должен по-прежнему чувствовать JavaScript-код.

Изначально я пытался работать с простой цепочкой прототипов:

function Shape(x,y) {
  this.x = x;
  this.y = y;

  this.draw = function() {
    throw new Error("Arbitrary shapes cannot be drawn");
  }
}

function Square(x,y,side) {
  this.x = x;
  this.y = y;
  this.side = side;

  this.draw = function() {
    gotoXY(this.x,this.y); lineTo(this.x+this.side, this.y); ...
  }
}
Square.prototype = new Shape();

И это решает требования 1, 2 и 6, но id не разрешает супер вызовы (новые функции переопределяют родительские функции), конструктор, связывающий и динамически расширяющий родительский, не предоставляет новые методы дочернему классу.

Будут приветствоваться любые предложения.

Ответы [ 6 ]

5 голосов
/ 10 сентября 2009

Я бы предложил следующий шаблон, который использует clone функцию для наследования от прототипов, а не экземпляров:

function Shape(x, y) {
    this.x = x;
    this.y = y;
}

Shape.prototype.draw = function() {
    throw new Error('Arbitrary shapes cannot be drawn');
};

function Square(x,y,side) {
    Shape.call(this, x, y); // call super constructor
    this.side = side;
}

// inherit from `Shape.prototype` and *not* an actual instance:
Square.prototype = clone(Shape.prototype);

// override `draw()` method
Square.prototype.draw = function() {
    gotoXY(this.x,this.y); lineTo(this.x+this.side, this.y); // ...
};

Важно, чтобы методы находились в прототипе (как и должно быть в любом случае по соображениям производительности), чтобы вы могли вызывать методы суперкласса через

SuperClass.prototype.aMethod.call(this, arg1, arg2);

С помощью некоторого синтаксического сахара вы можете сделать JS похожим на классический язык на основе классов:

var Shape = Class.extend({
    constructor : function(x, y) {
        this.x = x;
        this.y = y;
    },
    draw : function() {
        throw new Error('Arbitrary shapes cannot be drawn');
    }
});

var Square = Shape.extend({
    constructor : function(x, y, side) {
        Shape.call(this, x, y);
        this.side = side
    },
    draw : function() {
        gotoXY(this.x,this.y); lineTo(this.x+this.side, this.y); // ...
    }
});
4 голосов
/ 10 сентября 2009

У Дугласа Крокфорда есть хорошие статьи о классическом и прототипе наследовании в Javascript, что должно стать хорошей отправной точкой.

1 голос
/ 06 июня 2015

Наиболее распространенный шаблон, который я обнаружил при исследовании этого вопроса, описан в Mozilla Developer Network . Я обновил их пример, чтобы включить вызов метода суперкласса и показать журнал в сообщении с предупреждением:

// Shape - superclass
function Shape() {
  this.x = 0;
  this.y = 0;
}

// superclass method
Shape.prototype.move = function(x, y) {
  this.x += x;
  this.y += y;
  log += 'Shape moved.\n';
};

// Rectangle - subclass
function Rectangle() {
  Shape.call(this); // call super constructor.
}

// subclass extends superclass
Rectangle.prototype = Object.create(Shape.prototype);
Rectangle.prototype.constructor = Rectangle;

// Override method
Rectangle.prototype.move = function(x, y) {
  Shape.prototype.move.call(this, x, y); // call superclass method
  log += 'Rectangle moved.\n';
}

var log = "";
var rect = new Rectangle();

log += ('Is rect an instance of Rectangle? ' + (rect instanceof Rectangle) + '\n'); // true
log += ('Is rect an instance of Shape? ' + (rect instanceof Shape) + '\n'); // true
rect.move(1, 1); // Outputs, 'Shape moved.'
alert(log);
  1. Через пять лет после того, как вы задали этот вопрос, похоже, что поддержка наследования в браузере улучшилась, поэтому я не думаю, что вам нужна внешняя библиотека.
  2. Это самая минимальная техника, которую я когда-либо видел, я не знаю, если вы считаете, что это слишком много шаблонного.
  3. В соответствии с запросом он использует прототип, поэтому добавление новых методов к родительскому объекту должно также предоставлять их дочерним объектам.
  4. Вы можете увидеть цепочку конструктора в примере.
  5. В этом примере также используются вызовы типа Super.
  6. Я не уверен, что это похоже на JavaScript, вам придется решить для себя.
1 голос
/ 10 сентября 2009

ОК, уловка с воспроизведением системы класса / экземпляра в JavaScript состоит в том, что вы можете использовать наследование прототипа только в экземплярах. Таким образом, вы должны иметь возможность создавать экземпляр «неэкземпляра», который используется только для наследования, и иметь метод инициализатора, отдельный от самой функции конструктора.

Это минимальная система, которую я использую (перед добавлением оборок), передавая специальное одноразовое значение в конструктор, чтобы он создавал объект без его инициализации:

Function.prototype.subclass= function() {
    var c= new Function(
        'if (!(this instanceof arguments.callee)) throw(\'Constructor called without "new"\'); '+
        'if (arguments[0]!==Function.prototype.subclass._FLAG && this._init) this._init.apply(this, arguments); '
    );
    if (this!==Object)
        c.prototype= new this(Function.prototype.subclass._FLAG);
    return c;
};
Function.prototype.subclass._FLAG= {};

Использование new Function() - это способ избежать образования ненужного замыкания над подклассом (). Вы можете заменить его более красивым выражением function() {...}, если хотите.

Использование сравнительно чисто, и, как правило, похоже на объекты в стиле Python только с немного неуклюжим синтаксисом:

var Shape= Object.subclass();
Shape.prototype._init= function(x, y) {
    this.x= x;
    this.y= y;
};
Shape.prototype.draw= function() {
    throw new Error("Arbitrary shapes cannot be drawn");
};

var Square= Shape.subclass();
Square.prototype._init= function(x, y, side) {
    Shape.prototype._init.call(this, x, y);
    this.side= side;
};
Square.prototype.draw= function() {
    gotoXY(this.x, this.y);
    lineTo(this.x+this.side, this.y); // ...
};

Обезвреживание патчей для встроенной функции (функции) немного сомнительно, но делает ее приятной для чтения, и никто не захочет for...in над функцией.

0 голосов
/ 18 апреля 2012

Вы можете использовать функциональный паттерн, предложенный Крокфордом в его книге «JavaScript хорошие части». Идея в том, чтобы использовать закрытые поля для создания приватных полей и использовать превалирующую функцию для доступа к этим полям. Вот одно из решений, которые отвечают вашим 6 требованиям:

    var people = function (info) {
    var that = {};
    // private
    var name = info.name;
    var age = info.age;
    // getter and setter
    that.getName = function () {
        return name;
    };
    that.setName = function (aName) {
        name = aName;
    };
    that.getAge = function () {
        return age;
    };
    that.setAge = function (anAge) {
        age = anAge;
    };
    return that;
};

var student = function (info) {
    // super
    var that = people(info);
    // private
    var major = info.major;
    that.getMajor = function () {
        return major;
    };
    that.setMajor = function (aMajor) {
        major = aMajor;
    };
    return that;
};

var itStudent = function (info) {
    // super
    var that = student(info);
    var language = info.language;
    that.getLanguage = function () {
        return language;
    };
    that.setLanguage = function (aLanguage) {
        language = aLanguage;
    };
    return that;
};

var p = person({name : "Alex", age : 24});
console.debug(p.age); // undefined
console.debug(p.getAge()); // 24

var s = student({name : "Alex", age : 24, major : "IT"});
console.debug(s.getName()); // Alex
console.debug(s.getMajor()); // IT

var i = itStudent({name : "Alex", age : 24, major : "IT", language : "js"});
console.debug(i.language); // Undefined
console.debug(i.getName()); // Alex
console.debug(i.getMajor()); // IT
console.debug(i.getLanguage()); // js
0 голосов
/ 10 сентября 2009

Также вдохновлен Крокфордом, но у меня был хороший опыт с тем, что он называет «функциональным наследованием», используя «функции конструктора». YMMV.

ОБНОВЛЕНИЕ : Извините, я забыл: вам все еще нужно дополнить объект методом superior, чтобы получить хороший доступ к супер-методу. Возможно, вам не подходит.

var makeShape = function (x, y) {
    that = {};
    that.x = x;
    that.y = y;
    that.draw = function() {
        throw new Error("Arbitrary shapes cannot be drawn");
    }
    return that;
};

var makeSquare = function (x, y, side) {
    that = makeShape(x, y);
    that.side = side;
    that.draw = function() {
        gotoXY(that.x,that.y); lineTo(that.x+that.side, that.y); ...
    }
    return that;
};
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...