亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

ES6+ Class 前置知識

1. 前言

在早期的 JavaScript 中是沒類的概念的,如果想要實現類的功能需要通過構造函數來創建,使用 prototype 來實現類的繼承。對于一些高級語言如 C++、Java、python 等,都是有類的概念的,而且在這些語言中類是非常重要的。而 JavaScript 由于歷史原因在設計最初就沒有想要引入類的概念,隨著 JavaScript 越來越多地應用到大型項目中,JavaScript 的短板就顯現了。雖然,可以使用原型等方式來解決,但是還是存在各種各樣的問題。

要學習 ES6 中的 class 首先要了解 ES5 中的構造函數,主要了解在構造函數中是如何實現類和繼承的,了解了這些知識帶你有助于后面我們更深入的理解 ES6 中的 class。

2. 構造函數

2.1 基本用法

我們知道在 ES5 中如果想創建一個實例,是通過構造函來實現的。下面我們創建一個動物的類:

function Animal(type) {
  this.type = type || '鳥類';
}
Animal.prototype.eat = function() {
  console.log('鳥類吃蟲子!')
};

var animal = new Animal();

上面的代碼就是使用構造函數來創建一個類,這里的構造函數首字母需要大寫,這是約定俗成的,不需要解釋記住就行。然后使用 new 的方式來實例化一個實例。

了解構造函數后,我們要明確地知道創建的實例有兩種屬性,一種是自己的,一種是公用的?針對上面的代碼中 type 和 eat 哪個是自己的那個是公用的呢?一般來說綁定在 this 上的是自有屬性,因為在實例化一個對象后 this 是指向這個實例的;而公共屬性一般認為是 prototype 上的。另外,我們可以使用 hasOwnProperty 來判斷是否是自身的屬性。

console.log(animal.hasOwnProperty('type'));		// true
console.log(animal.hasOwnProperty('eat'));		// false

為什么要知道屬性是否是自己的呢?如果能想明白這個那么就會對類的繼承有個深入的理解。下面我們來看兩段代碼:

var animal1 = new Animal();
var animal2 = new Animal();

console.log(animal1.type);	// 鳥類
console.log(animal2.type);	// 鳥類
animal1.type = '家禽';
console.log(animal1.type);	// 家禽
console.log(animal2.type);	// 鳥類

console.log(animal1.eat());	// 鳥類吃蟲子!
console.log(animal2.eat());	// 鳥類吃蟲子!
animal1.__proto__.eat = function() {
  console.log('家禽吃糧食!')
}
console.log(animal1.eat());	// 家禽吃糧食!
console.log(animal2.eat());	// 家禽吃糧食!

上面的代碼中我們可以看出當我們對 animal1 屬性 type 修改后不會影響 animal2 的 type 屬性,但是我們可以通過 animal1 的原型鏈對原型上的 eat 方法進行修改后,這時 animal2 上的 eat 方法也被修改了。這說明在實例上修改自有屬性不會影響其他實例上的屬性,但是,對非自有屬性進行修改時就會影響其他屬性的方法。主要這樣會存在一個隱患,實例可以修改類的方法,從而影響到其他繼承這個類的實例。在這樣的情況下我們要想實現一個完美的繼承就需要考慮很多的東西了。

2.2 __proto__prototype 、 constructor

在說構造函數繼承之前我們需要明確幾個概念: __proto__prototype 、 constructor 這三個都是構造函數中的概念,中文的意思可以理解為 __proto__(原型鏈) 、 prototype(原型) 、 constructor(構造方法)。它們在 class 上也是存在的。想要了解它們之間的關系,我們先看下面的幾段代碼:

var animal = new Animal();

animal.__proto__ === Animal.prototype;	// true
animal.__proto__.hasOwnProperty('eat');	// true

animal.constructor === animal.__proto__.constructor;	// true

通過上面的關系對比可以使用示意圖的方式更容易理解。

圖片描述

通過上面的代碼和示意圖我們知道,原型是構造函數上的屬性,實例可以通過自身的原型鏈查找到,并且可以修改屬性。

2.3 繼承

了解了 __proto__ 、 prototypeconstructor 三者的關系那么我們就要來學習一下構造函數的繼承了,上面我們定義了一個動物的構造函數,但是我們不能直接去 new 一個實例,因為 new 出來的實例沒有任何意義,是一個動物實例,沒有具體指向。這時我們需要創建一個子類來繼承它。這時可以對 Animal 類做個限制:

function Animal(type) {
  if (new.target === Animal) {
    throw new Error('Animal 類不能被 new,只能被繼承!')
  }
  this.type = type || '鳥類';
}
Animal.prototype.eat = function() {
  console.log('鳥類吃蟲子!')
};

var animal = new Animal();
//VM260:3 Uncaught Error: Animal 類不能被 new,只能被繼承!

既然不能被 new 那要怎么去繼承呢?雖然不能被 new 但是我們可以去執行這個構造函數啊,比較它本質還是一個函數。執行構造函數時 this 的指向就不是當前的實例了,所以還需要對 this 進行綁定。我們定義一個子類:Owl(貓頭鷹)

function Owl() {
  Animal.call(this);
}
var owl = new Owl();

通過使用 call 方法在 Owl 內部綁定 this,這樣實例就繼承了 Animal 上 this 的屬性了。但是在 Animal 的原型中還有關于 Animal 類的方法,這些方法怎么繼承呢?

首先要明確的是不能使用 Owl.prototype = Animal.prototype 這樣的方式去繼承,上面也說了這會使我們對子類原型修改的方法會作用到其他子類中去。那么怎么可以實現這一繼承呢?這時就需要原型鏈出場了,我們可以使用 Owl 原型上的原型鏈指向 Animal 的原型,實例 owl 根據鏈的查找方式是可以繼承 Animal 的原型上的方法的。

function Owl() {
  Animal.call(this);
}
Owl.prototype.__proto__ = Animal.prototype;

var owl = new Owl();
owl.eat();	// 鳥類吃蟲子!

通過原型鏈的方式還是比較麻煩的,也不優雅,ES6 提供了 setPrototypeOf() 方法可以實現相同的效果:

// Owl.prototype.__proto__ = Animal.prototype;
Owl.setPrototypeOf(Owl.prototype, Animal.prototype);

這樣在子類 Owl 的原型上增加方法不會影響父類,這樣也算是比較好的方式解決了子類的繼承。

3. 小結

本節沒有去學習 class 的使用,而是復習了在 ES5 中是怎么定義類的存在的,使用的是構造函數的方式來定義一個類。在類的實際應用中繼承是最為關鍵的,通過對如何實現構造函數中的繼承,復習了原型、原型鏈和構造方法。在構造函數的繼承中,子類不能直接去 new 一個父類,因為這樣沒有意義。所以我們通過在子類中執行構造函數并綁定子類的this繼承了父類的屬性,再通過子類原型的原型鏈繼承了父類原型上的屬性。通過本節的學習我們更加深刻地理解構造函數在 JavaScript 中扮演什么樣的角色,繼而 ES6 提出了 “真正“ 意義上的類,其實本質還是通過原型的方式,下一節我們將具體學習 ES6 的 class。