《前端之路》- TypeScript (三) ES5 中實現繼承、類以及原理

這篇文章中的內容會比較的多,並且在基礎中是數據相對比較複雜的基礎,主要是講到了 JS 這門語言中如何實現繼承、多態,以及什麼狀況如何定義 私有屬性、方法,共有屬性、方法,被保護的屬性和方法。明確的定義了 JS 中的訪問邊界問題,以及最終實現的原理是什麼。接下來,讓咱們仔細瞅瞅這部分吧~javascript

1、先講講 ES5 中構造函數(類)靜態方法和多態

首先在 ES5 中是沒有類的概念的,咱們通常是經過構造函數中來實現類。下面就舉個例子。
另外咱們再複習下咱們在 JS 中的常常會提到的問題,原型以及原型鏈前端

1-1 JS 中原型以及原型鏈

JS 中經過 __prpto__ 的橋樑實現原型鏈, 也叫作實現繼承。
JS 中經過 prototype 的屬性複製本身的模版對象(也能夠叫作被複制的對象)java

例子一

上例子以前,咱們來看一張圖 ( 來自 juejin,侵刪)git

造物主無中生有,從 null 製造了一個 No.1 對象( 神 ),這個 No.1 對象以爲本身太孤獨,就 copy 了一份本身,咱們叫她 Object ,同時 No1 對象 但願 Object 能夠爲本身幹活,而後 Object 就學會了 new 這個技能,new 一下,同時加入各類屬性,就能夠 瞬間讓這個世界豐富多彩了起來,後來,豐富多彩的世界也物以羣分了,而後就出現了 String、Number、Boolean、Array、Date... 等等類型(demo1),而後造物主又發現,github

String.constructor;
// ƒ String() { [native code] }
Number.constructor;
// ƒ Number() { [native code] }
Array.constructor;
// ƒ Array() { [native code] }
Object.constructor;
// ƒ Object() { [native code] }
Function.constructor;
// ƒ Function() { [native code] }

// demo1
String.prototype;
// String {"", constructor: ƒ, anchor: ƒ, big: ƒ, blink: ƒ, …}
Number.prototype;
// Number {0, constructor: ƒ, toExponential: ƒ, toFixed: ƒ, toPrecision: ƒ, …}
Array.prototype;
// [constructor: ƒ, concat: ƒ, copyWithin: ƒ, fill: ƒ, find: ƒ, …]
Function.prototype;
// ƒ () { [native code] }
Object.prototype;
// {constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, …}

其實到這裏的時候,其實不少同窗,已經開始完全蒙圈了,這是啥啊? 怎麼一會 constructor 一會 prototype 還有一個 proto 啊啊啊,簡直要瘋掉了,到底這些都是一些啥啊。。。不要着急,下面咱們從新來認識一下這三個貨,出現的緣由以及分別表明着什麼
測試typescript

1-2 JS 中原型以及原型鏈中,咱們常見的 constructor、prototype、proto 這三者之間的關係

1、首先,咱們總最容易理解的 constructor(構造器)來理解瀏覽器

var F = function() {
  this.name = "F-構造函數";
};

var f1 = new F();
var f2 = new F();

console.log(F.constructor); // ƒ Function() { [native code] } 是瀏覽器自帶的原生方法 Function
console.log(f1.constructor); // ƒ () {this.name = 'F-構造函數';} 是構造函數 F 自己
console.log(f2.constructor); // ƒ () {this.name = 'F-構造函數';} 是構造函數 F 自己

// 這個時候你們其實對於 constructor 屬性有必定的瞭解了,對象、函數都有 constructor 屬性

這裏咱們有一張圖app

同時,JS 原生自帶的一些方法和 上文中 咱們定義的 Person 類也很是相似,惟一的區別就是,Person 是用戶本身定義的, 生自帶的方法是官方指定的。函數

JS 語言中自帶的原生方法 String、Number、Array、Boolean、Function、Object、Date 等,都是 Function 的實例化對象
String、Number、Array、Boolean、Function、Object、Date 等 的 constructor 都指向 Function
(Function 的構造函數也指向 Function,不要疑惑,雖然毫無道理,可是就是這麼發生了)學習


2、接下來咱們再來看看 prototype 是怎麼樣產生的, JS 的語言設計,爲何須要 prototype 對象。

  • 仍是上面的例子:只不過咱們分別給 f1 和 f2 添加一個 say 方法,而後咱們去對比這 2 個方法的差別
var F = function() {
  this.name = "F-構造函數";
};

var f1 = new F();
var f2 = new F();

f1.say = function() {
  console.log("say hello");
};
f2.say = function() {
  console.log("say hello");
};
console.log(f1.say === f2.say); // false
  • 咱們發現並不相等,由於經過構造函數實例化生成的對象的指針都分別指向不一樣的棧(也能夠理解爲內存)(這裏不太明白的化,建議看下《你不知道的 JavaScript》)
  • 咱們去對比這 2 個不一樣對象上的相同名稱的方法,確定是不同的
  • 那若是我實例化幾千幾萬個對象,都包含這個方法的化,那內存豈不是要爆了
  • 因此基於節約內存的出發點,咱們是否能夠建立一個 實例話對象均可以訪問的公共對象,這個時候 prototype 就應運而生了

基於上面的例子咱們再修改下:

var F = function() {
  this.name = "F-構造函數";
};

F.prototype.say = function() {
  console.log("say hello");
};

var f1 = new F();
var f2 = new F();

f1.say(); // say hello
f2.say(); // say hello
console.log(f1.say === f2.say); // true

因此 prototype 對象的出現,達到了 共享、公用的效果。節約了內存。同時 prototype 對象用於放某同一類型實例的共享屬性和方法,實質上是爲了內存着想。
這裏咱們再放一張圖


3、constructor 屬性具體在哪裏?

  • 以這裏爲例子,咱們打印出來 f1 的時候,並無在對象的一級目錄中找到 constructor 屬性,那會是在哪裏呢?
  • 按照上面的這張圖,咱們會發現,每一個實例化對象的 constructor 屬性都是指向 構造函數(Person)
  • 那若是咱們實例化幾千幾萬個對象呢? 每一個實例化對象的 constructor 想必也會佔用大量的內存,並且根本沒有必要
  • 因此這個時候神奇的事情發生了,咱們把 每一個實例化對象的 constructor 做爲一個共享數據,放在 prototype 對象中,節約內存。
  • 這個時候就會又有下面的圖了

  • 這個時候咱們確定會思考一個問題就是: 咱們直接經過 f1.constructor 訪問到的 構造屬性 是經過什麼方式來訪問到的呢?
  • 另一個問題: 若是咱們修改了 f1.constructor 這個值,咱們是否是就根本沒有辦法訪問到 實例化對象的構造函數了?
var F = function() {
  this.name = "F-構造函數";
};

F.prototype.say = function() {
  console.log("say hello");
};

var f1 = new F();
var f2 = new F();

f1.constructor = function() {
  this.name = "匿名構造函數";
};

console.log(f1.constructor == f2.constructor); // fasle 這個時候 f1 對象就沒辦法找到本身的構造函數了,

// 由於咱們給 f1 實例化對象新增了一個 constructor 屬性,這個時候,JS 就會優先返回這個值,而不是真正的 構造函數對象,聰明的 JS 確定不會讓這種事情發生的,對麼。下面就該咱們的 __proto__  出場啦

4、__proto__ 的出現 目的: 讓實例找到本身的實例

  • 對,下面就是咱們要來講到了 __proto__
  • 核心能力: 任何實例化對象的 __proto__ 屬性都指向其 構造函數的 prototype (咱們能夠把 prototype 理解成一個 能夠抽離成成千上萬實例化對象都具有的 公共屬性的集合 其中包括了:constructor 屬性、以及使用者定義在 prototype 上的屬性或者方法 )
  • 廢話很少說了,咱們先上圖

  • 而後咱們就得出來一個結論
f1.__proto__ === F.prototype; // true

5、總結

  • 這裏咱們針對 JS 中原型已經原型鏈又進一步的複習鞏固了一下,其實還有不少類容是能夠深挖的,由於這裏是 ts 篇,咱們就暫時先寫到這裏,後續咱們能夠在留言區進行進一步的討論

1-2 JS 中經過構造函數來實現 類

實現一個類的話上面的案例基本上簡單的呈現了下:

function Person(name) {
  this.name = name;
  this.run = function() {
    console.log(this.name + "跑步");
  };
}

Person.prototype.age = 12;
Person.prototype.work = function() {
  console.log(this.name + "寫代碼");
};

Person.weight = "70kg";
Person.eat = function() {
  console.log("在吃飯");
};

var p = new Person("zhangsan");

p.run(); // zhangsan跑步
p.work(); // zhangsan寫代碼
p.eat(); // Uncaught TypeError: p.eat is not a function
  • 這裏咱們就針對,上面出現的錯誤和正確的狀況分析一下:
  • 一、爲何執行 p.run() 成功了,這裏簡單過一下 new 的操做
// new 操做背後的真相
function New(name) {
  this.name = name;
}

// 1、建立一個新的對象
var o = {};

// 2、須要認祖歸宗,須要知道本身是被哪一個構造函數實例化生成的
o.__proto__ = New.prototype;

// 3、須要拿到 祖上傳給你的傳家寶
New.apply(o, arguments); // arguments 爲傳入的參數, 經過執行構造函數,巧妙的將構造函數中 this 的上下文轉換成了 新生成的 o 對象的上下文,讓其也擁有了構建函數內部的屬性和方法

// 4、最後返回 o
return o;
  • 二、爲何執行 p.run() 成功了咧,由於 new 的過程當中 實例化對象 p 中已經繼承了構造函數 Person 內的屬性和方法因此成功了
  • 三、爲何執行 p.work() 成功了? 由於 p 的 __proto__ 指向的是 Person.prototype 恰好,咱們在 Person.prototype 新增了一個 work 方法,因此 p 能夠經過 __proto__ 原型鏈找到 work 方法執行成功
  • 四、爲何 p.eat() 報錯了? 咱們看看 eat 方法咱們是如何定義的:
...
 Person.eat = function() { console.log('在吃飯') }
 p2 =  new Person('lisi')
// 由於 eat 這個靜態方法是掛載在構造函數這個對象上的,而咱們的 new 操做是繼承了 構造函數內部的方法和屬性,
// 因此在繼承父類私有屬性的時候沒有找到,那還有 原型鏈上的呢?一樣,new 操做是將 `__proto__` 指向了 Person.prototype 而這個對象中也沒有這個方法,因此就報錯了
 // 那若是 p2 想訪問,有辦法麼? 有的
 p2.constructor.eat()  // 在吃飯
 // 同時 p2.constructor.eat() === Person.eat() === Person.prototype.constructor.eat()
 // 可是這種訪問的方式,沒辦法和對象的上下文結合起來,也沒有多大的做用,因此咱們每每在咱們平常的開發中用到的比較少。

總結

對,沒錯。這一章 都是基礎知識,那麼基於這個基礎上,咱們下一章節會正式來進入 typescript 中 class 的學習中來
包括了 TypeScript 中的類,類的定義、方法屬性的定義和類的修飾符等,敬請期待~

GitHub 地址:(歡迎 star 、歡迎推薦 : )
《前端之路》 - TypeScript(三)ES5 中實現繼承、類以及原理

相關文章
相關標籤/搜索