Методы переопределения 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


Одна из причин, почему 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


В <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();

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.