類的概念,自己在javascript的語言上是不存在的, 但因爲最近人們使用ES6語法,TS語言上都會有的
class extends
繼承的概念, 下面咱們須要使用原生js, 結合原型鏈,實現類的 繼承,多態
原型繼承, 主要利用對象的原型鏈 __proto__
, 每個對象都擁有__proto__
, 它指向的是構造函數的prototype
原型對象.javascript
一個對象的屬性或函數的尋找會經歷如下幾個步驟。
以定義 var o = {};
, 執行 var toString = o.toString
爲例.前端
var tmp = o
,做爲臨時引用 (爲了描述使用)tmp
是否自定義toString()
,若是存在自定義屬性則當即執行。若是當前對象無定義該屬性, 進入第3步tmp
是否使用 Object.defineProperty
定義toString
的屬性描述, 若是存在定義,則直接引用,若是不存在則進入第4步tmp
是否存在 __proto__
,若是存在,則將tmp = o.__proto__
, 執行第2步; 若是不存在,則返回 undefined
, 屬性查找結束;具體案例java
function Animal () { throw new Error('抽象類, 不容許直接實例化'); } Animal.prototype.voice = function () { console.log('the ' + this.name + ' sound'); } function Dog () { this.name = 'dog'; } Dog.prototype = Object.create(Animal.prototype); // 顯示指向 Dog.prototype.constructor = Dog; var dog = new Dog(); dog.voice(); // the dog sound console.log(dog instanceof Dog); console.log(dog instanceof Animal); // 隱世指向 Animal.prototype.constructor console.log(dog.__proto__.constructor === Animal);
優勢:編程
缺點:app
借用構造函數函數
// 模擬調用父類函數 Object.prototype.super = function (proto, name) { var args = Array.from(arguments).slice(1); var proto = proto.__proto__; while (proto && null == proto[name]) { proto = proto[name]; } if (proto && typeof proto[name] === 'function') { return proto[name].apply(this, args); } console.warn('the instance have not super ' + name + ' function'); }; function Animal (name) { this.name = name; } Animal.prototype.voice = function () { console.log(this.name + ' sound'); }; function Dog (name, type) { Animal.apply(this, [name]); this.type = type; } Dog.prototype = Object.create(Animal.prototype); Dog.prototype.constructor = Animal; Dog.prototype.voice = function () { console.log('the dog type is ' + this.type); this.super(Animal.prototype, 'voice'); }; var dog = new Dog('二哈', '哈士奇'); dog.voice(); // the dog type is 哈士奇 // 二哈 sound
優勢:工具
缺點:this
mixin複製繼承prototype
依靠對象拷貝屬性的方式, 給予一個源對象未有的屬性賦值code
function mixin(source, target) { for (var name in target) { if (!source[name]) { source[name] = target[name]; } } return source; } var Animal = { name: 'animal', voice: function () { console.log('the name is ' , this.name); console.log('voice~~'); } }; var Cat = mixin({ name: 'cat', sound: function () { return this.voice(); } }, Animal); var helloKitty = mixin({ name: 'hello keitty' }, Cat); helloKitty.sound();
優勢:
缺點:
寄生繼承
寄生繼承屬於重寫, 新增父類建立的對象的屬性, 返回擴展的對象
function Animal() { this.speed = 10; } Animal.prototype.run = function () { console.log('speed is ' + this.speed); } function Cat () { var animal = new Animal(); var runFn = animal.run; animal.speed = 20; animal.run = function () { console.log('the cat will run'); runFn.apply(this, arguments); }; return animal; } var cat = new Cat(); console.log(cat instanceof Cat);
優勢:
缺點:
Object.create
Object.create
是ES5定義的方法, 相比於字面量對象,構造函數對象, 又一種新的建立對象的方式。
var prototype = {foo: 1}; var o = Object.create(prototype); console.log(o.foo); // 1 o.foo = 100; console.log(o.foo); // 100 delete o.foo; console.log(o.foo); // 1 console.log(o.__proto__ === prototype); // true
從上面能夠看見, Object.create
傳入一個對象,同時會返回一個新的對象,而這個新的對象的__proto__
指向傳入對象
Object.create(null)
返回一個無原型鏈的空對象, 對象的全部屬性均爲實例屬性
Object.create 的 polyfill
// 簡化版 polyfill Object.create = Object.create || function (proto) { function F() {} F.prototype = proto; return new F(); };
說完ES5的實現方式,咱們來聊聊ES6自帶的語法。 class
與 class extends
參照其它語言,如 java
, 類中存在靜態屬性, 靜態方法, 實例屬性,實例方法.
聲明一個類
class Rectangle { // 類的構造函數 constructor(height, width) { this.height = height; this.width = width; } } let rect = new Reactangle(320, 240); console.log(rect.height, rect.width);
注意: 類不存在聲明提早的概念,必須先定義後使用
class Animal { constructor(name) { // 實例屬性 this.name = name; } // 原型屬性描述 get fullname () { console.log(this.name); } // 原型方法 speak() { console.log(this.name + ' makes a noise.'); } } // 靜態屬性 Animal.name = 'Animal'; class Dog extends Animal { construcotr(name) { // 調用父類構造函數 super(name); } speak() { console.log(this.name + ' barks.'); } // 靜態方法只適合建立工具函數 // 返回 undefined static eat() { return this; } } var d = new Dog('Mitzie'); // 'Mitzie barks.' d.speak();
實際上ES6的class
與 class extends
也是使用的原型鏈方式實現繼承關係。 super
是一個關鍵詞, 其實是指向父類的prototype
, 在constructor
使用super()
, 能夠調用父類的構造函數, 使用super.method()
能夠調用父類的原型方法
。
原型屬性採用ES5的defineProperty
定義屬性描述來實現。
目前JS的使用場景愈來愈廣, 面向對象編程的使用也愈來愈多, 前端已經Node.js都有須要用到類與繼承。 同時這也是多年來不變的前端JS考題。