2010-01-21 85 views
85

在PHP/Java的人可以做的:如何從JavaScript中的類繼承?

class Sub extends Base 
{ 
} 

並自動所有的公共/保護方法,屬性,字段,超類成爲可以根據需要被重寫的子類的一部分等。

Javascript中的等價物是什麼?

+2

鏈接做繼承: http://www.crockford.com/javascript/inheritance.html或 http://phrogz.net/JS/Classes/OOPinJS2.html – 2010-01-21 07:32:20

+1

看看這裏:http://stackoverflow.com/questions/1908443/what-are-good-javascript-oop-resources – 2010-01-21 07:33:02

+0

鏈接:http://ncombo.wordpress.com/2013/07/11/javascript-inheritance-done-right/ – Jon 2013-07-11 13:52:10

回答

75

我已經改變了我現在的做法,我儘量避免使用構造函數和它們的屬性,但我從2010年的舊回答仍然在底部。我現在更喜歡Object.create()Object.create適用於所有現代瀏覽器。

我應該注意到Object.create通常是much slower比使用new帶有函數構造函數。

//The prototype is just an object when you use `Object.create()` 
var Base = {}; 

//This is how you create an instance: 
var baseInstance = Object.create(Base); 

//If you want to inherit from "Base": 
var subInstance = Object.create(Object.create(Base)); 

//Detect if subInstance is an instance of Base: 
console.log(Base.isPrototypeOf(subInstance)); //True 

jsfiddle

對使用​​的Object.create是能夠在defineProperties的說法,它使您可以在類屬性如何可以被訪問,並列舉了顯著控制傳遞很大的好處,而且我還使用函數來創建實例,這些函數以某種方式用作構造函數,因爲您可以在最後進行初始化,而不僅僅是返回實例。

var Base = {}; 

function createBase() { 
    return Object.create(Base, { 
    doSomething: { 
     value: function() { 
     console.log("Doing something"); 
     }, 
    }, 
    }); 
} 

var Sub = createBase(); 

function createSub() { 
    return Object.create(Sub, { 
    doSomethingElse: { 
     value: function() { 
     console.log("Doing something else"); 
     }, 
    }, 
    }); 
} 

var subInstance = createSub(); 
subInstance.doSomething(); //Logs "Doing something" 
subInstance.doSomethingElse(); //Logs "Doing something else" 
console.log(Base.isPrototypeOf(subInstance)); //Logs "true" 
console.log(Sub.isPrototypeOf(subInstance)); //Logs "true 

jsfiddle

這是我原來的答覆從2010年:

function Base () { 
    this.color = "blue"; 
} 

function Sub () { 

} 
Sub.prototype = new Base(); 
Sub.prototype.showColor = function () { 
console.log(this.color); 
} 

var instance = new Sub (); 
instance.showColor(); //"blue" 
+5

sub.prototype.constructor值如何?我認爲它應該設置爲子值。 – maximus 2013-03-26 03:22:45

+0

除此之外,您還正在使用保留關鍵字(「超級」)作爲類名,我是不是能夠得到您的運行例如:http://jsbin.com/ixiyet/8/edit – MOnsDaR 2013-06-09 14:43:21

+0

@MOnsDaR我給它改名基地 – 2013-06-12 19:11:22

3

你不能(在古典意義上的)。 Javascript是一種原型語言。你會發現你永遠不會在Javascript中聲明一個「類」;你只需定義一個對象的狀態和方法。爲了產生繼承,你需要一些對象並將其原型化。原型擴展了新的功能。

182

在JavaScript中你沒有但你可以在很多方面得到繼承和行爲重用:

僞經典的繼承(通過原型):

function Super() { 
    this.member1 = 'superMember1'; 
} 
Super.prototype.member2 = 'superMember2'; 

function Sub() { 
    this.member3 = 'subMember3'; 
    //... 
} 
Sub.prototype = new Super(); 

應與new一起使用運營商:

var subInstance = new Sub(); 

功能的應用程序或「構造鏈」:

function Super() { 
    this.member1 = 'superMember1'; 
    this.member2 = 'superMember2'; 
} 


function Sub() { 
    Super.apply(this, arguments); 
    this.member3 = 'subMember3'; 
} 

這種方法也應與new運營商使用:

var subInstance = new Sub(); 

與第一例子不同的是,當我們applySuper構造函數在Sub內的this對象,它在Super上直接在新實例上添加分配給this的屬性,例如,subInstance直接包含member1member2的屬性(subInstance.hasOwnProperty('member1') == true;)。

在第一個示例中,這些屬性是通過原型鏈達到的,它們存在於內部的[[Prototype]]對象上。

寄生繼承或電源的構造函數:

function createSuper() { 
    var obj = { 
    member1: 'superMember1', 
    member2: 'superMember2' 
    }; 

    return obj; 
} 

function createSub() { 
    var obj = createSuper(); 
    obj.member3 = 'subMember3'; 
    return obj; 
} 

這種方法是基於基本的「對象增廣」,你並不需要使用new操作,正如你所看到的,this關鍵字不參與。

var subInstance = createSub(); 

ECMAScript 5th Ed。 Object.create方法:

// Check if native implementation available 
if (typeof Object.create !== 'function') { 
    Object.create = function (o) { 
    function F() {} // empty constructor 
    F.prototype = o; // set base object as prototype 
    return new F(); // return empty object with right [[Prototype]] 
    }; 
} 

var superInstance = { 
    member1: 'superMember1', 
    member2: 'superMember2' 
}; 

var subInstance = Object.create(superInstance); 
subInstance.member3 = 'subMember3'; 

上述方法是通過Crockford提出了一種原型繼承技術。

對象實例從其他對象實例繼承,就是這樣。

這種技術可以比簡單的「對象增強」更好,因爲繼承屬性不通過所有的新對象實例複製,由於對象被設置爲[[Prototype]]擴展目的,在上面的示例subInstance僅包含member3屬性。

+3

不使用實例進行繼承 - 使用ES5 Object.create()或定製的clone()函數(例如http://mercurial.intuxication.org/hg/js-hacks/raw-file/tip/clone.js)直接從原型對象繼承;請參閱http://stackoverflow.com/questions/1404559/what-will-be-a-good-minimalistic-javascript-inheritance-method/1404697#1404697以獲得解釋的意見 – Christoph 2010-01-21 09:00:12

+0

謝謝@Christoph,我正要提到'Object.create'方法:) – CMS 2010-01-21 15:18:45

+1

這是不正確的繼承,因爲你將在Sub的原型上擁有Super的實例成員。 因此,所有Sub的實例將共享相同的'member1'變量,這根本不可取。當然,他們可以重寫它,但這沒有意義。 https://github.com/dotnetwise/Javascript-FastClass是一個更好的糖解決方案。 – Adaptabi 2014-04-18 07:52:02

0

由於JavaScript中沒有類,因此不能從JavaScript中的類繼承。

+7

如果你從字面上看,這是不正確的...... – cchamberlain 2013-10-06 00:59:18

7

那麼,在JavaScript中沒有「類繼承」,只有「原型繼承」。所以你不要製作「卡車」班,然後將其標記爲「汽車」的一個子類。相反,你製作一個對象「傑克」,並說它使用「約翰」作爲原型。如果約翰知道,「4 + 4」是多少,那麼傑克也知道。

我建議你閱讀道格拉斯克羅克福德有關原型繼承的文章:http://javascript.crockford.com/prototypal.html他還展示瞭如何使JavaScript與其他OO語言具有「相似」的繼承關係,並解釋說這實際上意味着以某種方式打破javaScript並不意味着被使用。

+0

我們假設傑克的原型是約翰。在運行期間,我向John添加了一個屬性/行爲。我會從傑克那裏得到那些財產/行爲嗎? – 2015-05-07 02:41:44

+0

你當然會。例如,這就是人們通常將「trim()」方法添加到所有字符串對象(它不是內置的)的方式。請參閱此處的示例:https://developer.mozilla.org/en-US/docs/Web/ JavaScript/Reference/Global_Objects/String/Trim – naivists 2015-05-07 06:02:35

1

您可以使用.inheritWith.fastClasslibrary。它比大多數流行的庫更快,有時甚至比本地版本更快。

使用非常簡單:

function Super() { 
    this.member1 = "superMember";//instance member 
}.define({ //define methods on Super's prototype 
    method1: function() { console.log('super'); } //prototype member 
}.defineStatic({ //define static methods directly on Super function 
    staticMethod1: function() { console.log('static method on Super'); } 
}); 

var Sub = Super.inheritWith(function(base, baseCtor) { 
    return { 
     constructor: function() {//the Sub constructor that will be returned to variable Sub 
     this.member3 = 'subMember3'; //instance member on Sub 
     baseCtor.apply(this, arguments);//call base construcor and passing all incoming arguments 
     }, 
     method1: function() { 
     console.log('sub'); 
     base.method1.apply(this, arguments); //call the base class' method1 function 
     } 
} 

使用

var s = new Sub(); 
s.method1(); //prints: 
//sub 
//super 
6

,我覺得這句話是最有啓發性:

從本質上說,一個JavaScript 「下課」只是一個Function對象,它服務於是一個構造函數加上一個附加的原型對象。(Source: Guru Katz

我喜歡使用構造,而不是對象,所以我偏愛「僞經典的繼承」方法described here by CMS。下面是多重繼承與原型鏈的示例:

// Lifeform "Class" (Constructor function, No prototype) 
function Lifeform() { 
    this.isLifeform = true; 
} 

// Animal "Class" (Constructor function + prototype for inheritance) 
function Animal() { 
    this.isAnimal = true; 
} 
Animal.prototype = new Lifeform(); 

// Mammal "Class" (Constructor function + prototype for inheritance) 
function Mammal() { 
    this.isMammal = true; 
} 
Mammal.prototype = new Animal(); 

// Cat "Class" (Constructor function + prototype for inheritance) 
function Cat (species) { 
    this.isCat = true; 
    this.species = species 
} 
Cat.prototype = new Mammal(); 

// Make an instance object of the Cat "Class" 
var tiger = new Cat("tiger"); 

console.log(tiger); 
// The console outputs a Cat object with all the properties from all "classes" 

console.log(tiger.isCat, tiger.isMammal, tiger.isAnimal, tiger.isLifeform); 
// Outputs: true true true true 

// You can see that all of these "is" properties are available in this object 
// We can check to see which properties are really part of the instance object 
console.log("tiger hasOwnProperty: " 
    ,tiger.hasOwnProperty("isLifeform") // false 
    ,tiger.hasOwnProperty("isAnimal") // false 
    ,tiger.hasOwnProperty("isMammal") // false 
    ,tiger.hasOwnProperty("isCat")  // true 
); 

// New properties can be added to the prototypes of any 
// of the "classes" above and they will be usable by the instance 
Lifeform.prototype.A = 1; 
Animal.prototype.B  = 2; 
Mammal.prototype.C  = 3; 
Cat.prototype.D   = 4; 

console.log(tiger.A, tiger.B, tiger.C, tiger.D); 
// Console outputs: 1 2 3 4 

// Look at the instance object again 
console.log(tiger); 
// You'll see it now has the "D" property 
// The others are accessible but not visible (console issue?) 
// In the Chrome console you should be able to drill down the __proto__ chain 
// You can also look down the proto chain with Object.getPrototypeOf 
// (Equivalent to tiger.__proto__) 
console.log(Object.getPrototypeOf(tiger)); // Mammal 
console.log(Object.getPrototypeOf(Object.getPrototypeOf(tiger))); // Animal 
// Etc. to get to Lifeform 

這裏是another good resource from MDN,這裏是a jsfiddle so you can try it out

0
function Person(attr){ 
    this.name = (attr && attr.name)? attr.name : undefined; 
    this.birthYear = (attr && attr.birthYear)? attr.birthYear : undefined; 

    this.printName = function(){ 
    console.log(this.name); 
    } 
    this.printBirthYear = function(){ 
    console.log(this.birthYear); 
    } 
    this.print = function(){ 
    console.log(this.name + '(' +this.birthYear+ ')'); 
    } 
} 

function PersonExt(attr){ 
    Person.call(this, attr); 

    this.print = function(){ 
    console.log(this.name+ '-' + this.birthYear); 
    } 
    this.newPrint = function(){ 
    console.log('New method'); 
    } 
} 
PersonExt.prototype = new Person(); 

// Init object and call methods 
var p = new Person({name: 'Mr. A', birthYear: 2007}); 
// Parent method 
p.print() // Mr. A(2007) 
p.printName() // Mr. A 

var pExt = new PersonExt({name: 'Mr. A', birthYear: 2007}); 
// Overwriten method 
pExt.print() // Mr. A-2007 
// Extended method 
pExt.newPrint() // New method 
// Parent method 
pExt.printName() // Mr. A 
1

在閱讀了很多文章後,我想出了這個解決方案(jsfiddle here)。大多數時候我並不需要一些更復雜的

var Class = function(definition) { 
    var base = definition.extend || null; 
    var construct = definition.construct || definition.extend || function() {}; 

    var newClass = function() { 
     this._base_ = base;   
     construct.apply(this, arguments); 
    } 

    if (definition.name) 
     newClass._name_ = definition.name; 

    if (definition.extend) { 
     var f = function() {}  
     f.prototype = definition.extend.prototype;  
     newClass.prototype = new f(); 
     newClass.prototype.constructor = newClass; 
     newClass._extend_ = definition.extend;  
     newClass._base_ = definition.extend.prototype;   
    } 

    if (definition.statics) 
     for (var n in definition.statics) newClass[n] = definition.statics[n];   

    if (definition.members) 
     for (var n in definition.members) newClass.prototype[n] = definition.members[n];  

    return newClass; 
} 


var Animal = Class({ 

    construct: function() {   
    }, 

    members: { 

     speak: function() { 
      console.log("nuf said");       
     }, 

     isA: function() {   
      return "animal";   
     }   
    } 
}); 


var Dog = Class({ extend: Animal, 

    construct: function(name) { 
     this._base_();   
     this.name = name; 
    }, 

    statics: { 
     Home: "House", 
     Food: "Meat", 
     Speak: "Barks" 
    }, 

    members: { 
     name: "", 

     speak: function() { 
      console.log("ouaf !");   
     }, 

     isA: function(advice) { 
      return advice + " dog -> " + Dog._base_.isA.call(this);   
     }   
    } 
}); 


var Yorkshire = Class({ extend: Dog, 

    construct: function(name,gender) { 
     this._base_(name);  
     this.gender = gender; 
    }, 

    members: { 
     speak: function() { 
      console.log("ouin !");   
     }, 

     isA: function(advice) {   
      return "yorkshire -> " + Yorkshire._base_.isA.call(this,advice);  
     }   
    } 
}); 


var Bulldog = function() { return _class_ = Class({ extend: Dog, 

    construct: function(name) { 
     this._base_(name);  
    }, 

    members: { 
     speak: function() { 
      console.log("OUAF !");   
     }, 

     isA: function(advice) {   
      return "bulldog -> " + _class_._base_.isA.call(this,advice);  
     }   
    } 
})}(); 


var animal = new Animal("Maciste"); 
console.log(animal.isA()); 
animal.speak(); 

var dog = new Dog("Sultan"); 
console.log(dog.isA("good")); 
dog.speak(); 

var yorkshire = new Yorkshire("Golgoth","Male"); 
console.log(yorkshire.isA("bad")); 
yorkshire.speak(); 

var bulldog = new Bulldog("Mike"); 
console.log(bulldog.isA("nice")); 
bulldog.speak(); 
4

Javascript繼承是從Java和PHP有點不同,因爲它並沒有真正上課。相反,它具有提供方法和成員變量的原型對象。您可以鏈接這些原型以提供對象繼承。我在研究這個問題時發現的最常見的模式在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);

就個人而言,我覺得繼承在Javascript尷尬,但這是我找到的最佳版本。

1

由於CMS的答案,並順藤摸瓜與原型和的Object.create並沒有什麼過了一段時間,我能想出使用申請我的繼承一個整潔的解決方案如下所示:

var myNamespace = myNamespace || (function() { 
    return { 

     BaseClass: function(){ 
      this.someBaseProperty = "someBaseProperty"; 
      this.someProperty = "BaseClass"; 
      this.someFunc = null; 
     }, 

     DerivedClass:function(someFunc){ 
      myNamespace.BaseClass.apply(this, arguments); 
      this.someFunc = someFunc; 
      this.someProperty = "DerivedClass"; 
     }, 

     MoreDerivedClass:function(someFunc){ 
      myNamespace.DerivedClass.apply(this, arguments); 
      this.someFunc = someFunc; 
      this.someProperty = "MoreDerivedClass"; 
     } 
    }; 
})(); 
26

對於那些誰在2015年或之後

隨着最新版本的ECMAScript標準(ES6)的達到這個頁面,你可以使用去keywork extends

注意,CLAS s的定義不是經常的object因此,類成員之間沒有逗號。 要創建一個類的實例,您必須使用new關鍵字。從基類繼承,使用extends

class Vehicle { 
    constructor(name) { 
     this.name = name; 
     this.kind = 'vehicle'; 
    } 
    getName() { 
     return this.name; 
    } 
} 

// Create an instance 
var myVehicle = new Vehicle('rocky'); 
myVehicle.getName(); // => 'rocky' 

從基類繼承,使用extends

class Car extends Vehicle { 
    constructor(name) { 
     super(name); 
     this.kind = 'car' 
    } 
} 

var myCar = new Car('bumpy'); 

myCar.getName(); // => 'bumpy' 
myCar instanceof Car; // => true 
myCar instanceof Vehicle; // => true 

從派生類,你可以使用超級任何構造函數或方法來訪問其基類:

  • 調用父類的構造函數,使用super().
  • 要呼叫另一個成員,請使用super.getName()

還有更多使用類。如果你想深入探究這個問題,我推薦Dr. Axel Rauschmayer的「Classes in ECMAScript 6」。*

source

+1

在引擎蓋下,'class'和'extends'是原型鏈的(超有用)語法糖:http://stackoverflow.com/a/23877420/895245 – 2016-11-20 12:21:06

+0

只爲你信息'instance.name'這裏'mycar.name'將返回類的名字。這是ES6和ESnext的默認行爲。這裏爲mycar.name將返回'車輛' – Shiljo 2017-08-08 13:16:38

0
function Base() { 
    this.doSomething = function() { 
    } 
} 

function Sub() { 
    Base.call(this); 
} 

var sub = new Sub(); 
sub.doSomething(); 
+0

請不要只發布代碼,解釋它做了什麼以及它如何回答這個問題。 – 2017-06-28 11:40:51