【JS基礎】對象繼承的定義與實現

簡介

類的概念,自己在javascript的語言上是不存在的, 但因爲最近人們使用ES6語法,TS語言上都會有的 class extends 繼承的概念, 下面咱們須要使用原生js, 結合原型鏈,實現類的 繼承,多態

ES5實現繼承

  1. 原型繼承
  2. 借用構造函數繼承
  3. mixin 複製繼承
  4. 寄生繼承

原型繼承方式

原型繼承, 主要利用對象的原型鏈 __proto__, 每個對象都擁有__proto__, 它指向的是構造函數的prototype 原型對象.javascript

一個對象的屬性或函數的尋找會經歷如下幾個步驟。
以定義 var o = {};, 執行 var toString = o.toString 爲例.前端

  1. 執行 var tmp = o,做爲臨時引用 (爲了描述使用)
  2. 嘗試檢查 tmp 是否自定義toString(),若是存在自定義屬性則當即執行。若是當前對象無定義該屬性, 進入第3步
  3. 嘗試檢查 tmp 是否使用 Object.defineProperty 定義toString 的屬性描述, 若是存在定義,則直接引用,若是不存在則進入第4步
  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);

優勢:編程

  1. 能夠使用 instanceof 檢測是不是某一個父類
  2. 原型鏈實現方式

缺點:app

  1. 沒法借用父類構造函數

借用構造函數函數

// 模擬調用父類函數
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

優勢:工具

  1. 可以借用父類構造函數
  2. 具有鏈式調用函數

缺點:this

  1. 子類構造函數須要調用父類構造函數

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

優勢:

  1. 實現簡單,只須要進行復制屬性和方法

缺點:

  1. 處理對象都爲對象, 沒有處理構造函數
  2. 沒法實現子類調用父類的場景

寄生繼承

寄生繼承屬於重寫, 新增父類建立的對象的屬性, 返回擴展的對象

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

優勢:

  1. 結合原型屬性和實例屬性實現方案

缺點:

  1. 沒法共享屬性, 每個新的對象都建立新的實例屬性和方法

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

ES6 的 class extends

說完ES5的實現方式,咱們來聊聊ES6自帶的語法。 classclass extends

ES6裏的類

參照其它語言,如 java, 類中存在靜態屬性, 靜態方法, 實例屬性,實例方法.

聲明一個類

class Rectangle {
  // 類的構造函數
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
}
let rect = new Reactangle(320, 240);
console.log(rect.height, rect.width);

注意: 類不存在聲明提早的概念,必須先定義後使用

使用 extends 建立子類

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的classclass extends 也是使用的原型鏈方式實現繼承關係。 super 是一個關鍵詞, 其實是指向父類的prototype, 在constructor 使用super(), 能夠調用父類的構造函數, 使用super.method() 能夠調用父類的原型方法
原型屬性採用ES5的defineProperty定義屬性描述來實現。

小結

目前JS的使用場景愈來愈廣, 面向對象編程的使用也愈來愈多, 前端已經Node.js都有須要用到類與繼承。 同時這也是多年來不變的前端JS考題。

相關知識推薦

相關文章
相關標籤/搜索