Методи перевизначення 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-keyword, але як ви можете досягти чогось подібного в 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
В якості альтернативи використанню інструменту для «покращення створення класів» ви взагалі не могли б робити класи. Класична емуляція OO в js завжди стає брудною.
Raynos

1
(не конструктивний коментар), оскільки мова "низького рівня" у світі браузерів дуже потворна без причини. Все ще вчимось, дякую!
dnuske

9
Я вважаю, що замість Car.prototype = new Vehicle (); це має бути Car.prototype = Object.create (Vehicle.prototype); немає?
Йорданія

@Martin має рацію, див. Javascript-спадщина
matoni

Одна з причин, чому Car.prototype = new Vehicle (); є неправильним, оскільки йому немає чого передавати Vehicle (), поки він отримує колір.
Тушар Арора

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

Одного разу слід уникати наслідування класичного ОО та використовувати замість нього прототипові ОО. Приємною бібліотекою утиліт для прототипових ОО є риси .

Замість того, щоб перезаписувати методи та налаштовувати ланцюжки успадкування (завжди слід віддавати перевагу композиції об’єктів над об’єктом успадкування об’єктів), ви повинні об’єднувати повторно використовувані функції в ознаки та створювати об’єкти з ними.

Живий приклад

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 немає приватного повідомлення
Райнос,

місцеві / приватні, хіба це не одне і те ж, просто інший термін?
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)але тоді ваша відповідь - лише дублікат :)
Райнос,

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під такою назвою.

Нарешті, виклик to modifyне може бути здійснений у Functionдекларації, оскільки неявний виклик "супер-класу" буде виконаний знову, коли ми встановимо передбачуваний "супер-клас" у prototypeвластивість передбачуваних "підкласів".

Але добре, це більш-менш як ви робите подібні речі в JavaScript.

HTH,

FK


У <name>.prototype = new C;будь-якому випадку немає
сенсу відстежувати

@Raynos: Так, є. Для того, щоб успадкувати Objectsбуло б спільним членом, Cякщо ви не створили екземпляр CОб’єкта. Таким чином, зміна xв Aзміниться xв Cі таким чином змінити xв B. Що очевидно небажано.
FK82

ви пропустили суть. Ви можете видалити рядок, і код все одно буде функціонувати
Райнос,

@Raynos: Боюся, ви втрачаєте свою думку. ;-) Ми хочемо Aі Bуспадкувати від C. Якщо цей рядок буде відсутній, це не буде так. Насправді єдиним прототипом, як Aі B"тінь", мав би доступ у такому випадку Object.prototype.
FK82

подивіться на код. A і B не використовують жодного з членів 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.