Методы переопределения JavaScript

87

Допустим, у вас есть следующий код:

function A() {
    function modify() {
       x = 300;
       y = 400;
    }

    var c = new C();
}

function B() {
    function modify(){
       x = 3000;
       y = 4000;
    }

    var c = new C();
}

C = function () {
   var x = 10;
   var y = 20;

   function modify() {
      x = 30;
      y = 40;
   };

   modify();
   alert("The sum is: " + (x+y));
}

Теперь вопрос в том, есть ли способ, которым я могу переопределить метод modifyиз Cметодов, которые находятся в Aи B. В Java вы бы использовали super-ключевое слово, но как добиться чего-то подобного в JavaScript?

Даниэль Настасе
источник
6
modifyэто не метод, а вложенная функция - между этими двумя есть разница ...
Шиме Видас
2
В Java вы используете superключевое слово для доступа к частным полям и методам суперкласса. Вы не используете его для их отмены.
FK82

Ответы:

138

Изменить: прошло шесть лет с тех пор, как был написан исходный ответ, и многое изменилось!

  • Если вы используете более новую версию JavaScript, возможно, скомпилированную с помощью такого инструмента, как Babel , вы можете использовать реальные классы .
  • Если вы используете конструкторы компонентов, подобные классам, предоставляемые Angular или React , вам нужно поискать в документации для этой платформы.
  • Если вы используете ES5 и вручную создаете «поддельные» классы с помощью прототипов, ответ ниже по-прежнему будет таким же правильным, как и раньше.

Удачи!


Наследование JavaScript немного отличается от Java. Вот как выглядит собственная объектная система JavaScript:

// Create a class
function Vehicle(color){
  this.color = color;
}

// Add an instance method
Vehicle.prototype.go = function(){
  return "Underway in " + this.color;
}

// Add a second class
function Car(color){
  this.color = color;
}

// And declare it is a subclass of the first
Car.prototype = new Vehicle();

// Override the instance method
Car.prototype.go = function(){
  return Vehicle.prototype.go.call(this) + " car"
}

// Create some instances and see the overridden behavior.
var v = new Vehicle("blue");
v.go() // "Underway in blue"

var c = new Car("red");
c.go() // "Underway in red car"

К сожалению, это немного некрасиво и не включает в себя очень хороший способ «супер»: вам нужно вручную указать метод родительского класса, который вы хотите вызвать. В результате есть множество инструментов, которые делают создание классов удобнее. Попробуйте посмотреть Prototype.js, Backbone.js или аналогичную библиотеку, которая включает более удобный синтаксис для выполнения ООП в js.

Адам
источник
2
Вместо того, чтобы использовать инструмент для «улучшения создания классов», вы вообще не можете создавать классы. Классическая объектно-ориентированная эмуляция в js всегда запутана.
Raynos
1
(неконструктивный комментарий) из-за того, что язык «низкого уровня» в мире браузеров, очень уродлив без причины. Все еще учусь, спасибо!
dnuske
9
Я считаю, что вместо Car.prototype = new Vehicle (); это должно быть Car.prototype = Object.create (Vehicle.prototype); нет?
Jordan
@Martin прав, см. Javascript-наследование
Матони
Одна из причин, почему Car.prototype = new Vehicle (); неверно из-за того, что в Vehicle () нечего передавать, пока он получает цвет.
Tushar Arora
62

Так как это лучший хит в Google, я хотел бы дать обновленный ответ.

Использование классов ES6 значительно упрощает наследование и переопределение методов:

'use strict';

class A {
    speak() {
        console.log("I'm A");
    }
}

class B extends A {
    speak() {
        super.speak();

        console.log("I'm B");
    }
}

var a = new A();
a.speak();
// Output:
// I'm A

var b = new B();
b.speak();
// Output:
// I'm A
// I'm B

superКлючевое слово относится к родительскому классу при использовании в наследуемом классе. Кроме того, все методы родительского класса привязаны к экземпляру дочернего, поэтому вам не нужно писать super.method.apply(this);.

Что касается совместимости: таблица совместимости с ES6 показывает только самые последние версии основных классов поддержки игроков (в основном). Браузеры V8 используют их с января этого года (Chrome и Opera), а Firefox, использующий движок SpiderMonkey JS, увидит классы в следующем месяце с официальным выпуском Firefox 45. Что касается мобильных устройств, Android по-прежнему не поддерживает эту функцию, а iOS 9, выпущенная пять месяцев назад, частично поддерживает.

К счастью, существует Babel , библиотека JS для повторной компиляции кода Harmony в код ES5. Классы и множество других интересных функций в ES6 могут сделать ваш код Javascript более читабельным и удобным в обслуживании.

Йотам Офек
источник
10
Этот ответ заслуживает большего внимания и в настоящее время является правильным.
Klompenrunner
6

Once следует избегать эмуляции классического объектно-ориентированного программирования и использовать вместо этого прототипный объектно-ориентированный объект. Хорошая служебная библиотека для прототипных объектно-ориентированных приложений - это traits .

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

Живой пример

var modifyA = {
    modify: function() {
        this.x = 300;
        this.y = 400;
    }
};

var modifyB = {
    modify: function() {
        this.x = 3000;
        this.y = 4000;
    }
};

C = function(trait) {
    var o = Object.create(Object.prototype, Trait(trait));

    o.modify();
    console.log("sum : " + (o.x + o.y));

    return o;
}

//C(modifyA);
C(modifyB);
Райнос
источник
10
Вы не отвечаете на вопрос. Во всяком случае, это должен быть комментарий.
FK82
3

modify () в вашем примере - это частная функция, которая будет доступна только в пределах вашего определения A, B или C. Вам нужно будет объявить это как

this.modify = function(){}

C не имеет ссылки на своих родителей, если вы не передадите его C. Если C настроен для наследования от A или B, он унаследует свои общедоступные методы (а не свои частные функции, как вы определили modify ()). Когда C наследует методы от своего родителя, вы можете переопределить унаследованные методы.

Алекс Хейд
источник
Modify - это локальная функция. В javascript нет приватных сообщений
Raynos
локальный / частный, разве это не одно и то же, просто другой термин?
Alex Heyd
2

метод, modify()который вы вызвали в последнем, вызывается в глобальном контексте, если вы хотите переопределить, modify()вы сначала должны наследовать Aили B.

Может, вы пытаетесь это сделать:

В этом случае CнаследуетA

function A() {
    this.modify = function() {
        alert("in A");
    }
}

function B() {
    this.modify = function() {
        alert("in B");
    }
}

C = function() {
    this.modify = function() {
        alert("in C");
    };

    C.prototype.modify(); // you can call this method where you need to call modify of the parent class
}

C.prototype = new A();
любит
источник
1
C.prototype.modify()будет иметь неправильное thisзначение. А именно C.prototypeвместо экземпляра c. Пожалуйста, используйте, .call(this)но тогда ваш ответ будет просто дубликатом :)
Raynos
1

Нет, если вы не сделаете все переменные «общедоступными», т.е. сделаете их членами Functionлибо напрямую, либо через prototypeсвойство.

var C = function( ) {
    this.x = 10 , this.y = 20 ;
    this.modify = function( ) {
        this.x = 30 , this.y = 40 ;
        console.log("(!) C >> " + (this.x + this.y) ) ;
    } ;
} ;

var A = function( ) {
    this.modify = function( ) {
       this.x = 300 , this.y = 400 ;
       console.log("(!) A >> " + (this.x + this.y) ) ;
    } ;
} ;
    A.prototype = new C ;

var B = function( ) {
    this.modify = function( ) {
       this.x = 3000 , this.y = 4000 ;
       console.log("(!) B >> " + (this.x + this.y) ) ;
    } ;
} ;


new C( ).modify( ) ;
new A( ).modify( ) ;
new B( ).modify( ) ; 

Вы заметите несколько изменений.

Самое главное, что вызов предполагаемого конструктора «суперклассов» теперь неявный внутри этой строки:

<name>.prototype = new C ;

Как Aи Bтеперь будут иметь индивидуально изменяемые элементы xи yкоторые не были бы в случае , если бы мы написали ... = Cвместо этого.

Тогда x, yи modifyвсе «публичные» члены так , чтобы назначить другой Functionк ним

 <name>.prototype.modify = function( ) { /* ... */ }

"переопределит" оригинал Functionпод этим именем.

Наконец, modifyнельзя выполнить вызов в Functionобъявлении, потому что неявный вызов «суперкласса» затем будет выполнен снова, когда мы установим предполагаемый «суперкласс» в prototypeсвойство предполагаемых «подклассов».

Но что ж, это более или менее похоже на то, как вы бы делали подобные вещи в JavaScript.

HTH,

FK

FK82
источник
В <name>.prototype = new C;любом случае, нет
смысла преследовать
@ Райнос: Да, есть. То есть все наследование Objectsбудет разделять один и тот же член, Cесли вы не создаете экземпляр Cобъекта. Таким образом, изменение xв Aизменится xна Cи, следовательно, изменится xна B. Что явно нежелательно.
FK82
вы упустили суть. Вы можете удалить строку, и код по-прежнему будет работать
Райнос
@Raynos: Боюсь, вы упускаете свою точку зрения. ;-) Мы хотим Aи Bнаследовать C. Если бы эта строка отсутствовала, это было бы не так. Фактически, единственный прототип, к которому в этом случае будут иметь доступ оба, Aа также B«тень» Object.prototype.
FK82
посмотрите на код. A и B не используют ни один из членов C в прототипе. Так что «наследование» от C бесполезно. Это потому , что А и В Redefine x, yи , modifyи , таким образом , тень всех Cчленов-х гг. Какой смысл использовать C в прототипе, если вы его не используете? Это мертвый код.
Raynos
0

function A() {
    var c = new C();
	c.modify = function(){
		c.x = 123;
		c.y = 333;
	}
	c.sum();
}

function B() {
    var c = new C();
	c.modify = function(){
		c.x = 999;
		c.y = 333;
	}
	c.sum();
}


C = function () {
   this.x = 10;
   this.y = 20;

   this.modify = function() {
      this.x = 30;
      this.y = 40;
   };
   
   this.sum = function(){
	this.modify();
	console.log("The sum is: " + (this.x+this.y));
   }
}

A();
B();

nghien_net 89
источник