[TOC]javascript
這篇文章中的內容會比較的多,並且在基礎中是數據相對比較複雜的基礎,主要是講到了 JS 這門語言中如何實現繼承、多態,以及什麼狀況如何定義 私有屬性、方法,共有屬性、方法,被保護的屬性和方法。明確的定義了 JS 中的訪問邊界問題,以及最終實現的原理是什麼。接下來,讓咱們仔細瞅瞅這部分吧~前端
首先在 ES5 中是沒有類的概念的,咱們通常是經過構造函數中來實現類。下面就舉個例子。 另外咱們再複習下咱們在 JS 中的常常會提到的問題,原型以及原型鏈java
JS 中經過
__prpto__
的橋樑實現原型鏈, 也叫作實現繼承。 JS 中經過prototype
的屬性複製本身的模版對象(也能夠叫作被複制的對象)git
上例子以前,咱們來看一張圖 ( 來自 juejin,侵刪)github
造物主無中生有,從 null 製造了一個 No.1 對象( 神 ),這個 No.1 對象以爲本身太孤獨,就 copy 了一份本身,咱們叫她 Object ,同時 No1 對象 但願 Object 能夠爲本身幹活,而後 Object 就學會了 new 這個技能,new 一下,同時加入各類屬性,就能夠 瞬間讓這個世界豐富多彩了起來,後來,豐富多彩的世界也物以羣分了,而後就出現了 String、Number、Boolean、Array、Date... 等等類型(demo1),而後造物主又發現,typescript
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 啊啊啊,簡直要瘋掉了,到底這些都是一些啥啊。。。不要着急,下面咱們從新來認識一下這三個貨,出現的緣由以及分別表明着什麼 測試瀏覽器
1、首先,咱們總最容易理解的
constructor
(構造器)來理解app
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 屬性
複製代碼
這裏咱們有一張圖函數
同時,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
對象。
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
複製代碼
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
屬性具體在哪裏?
constructor
屬性,那會是在哪裏呢?constructor
屬性都是指向 構造函數(Person)constructor
想必也會佔用大量的內存,並且根本沒有必要constructor
做爲一個共享數據,放在 prototype
對象中,節約內存。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、總結
實現一個類的話上面的案例基本上簡單的呈現了下:
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
複製代碼
// 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;
複製代碼
__proto__
指向的是 Person.prototype 恰好,咱們在 Person.prototype 新增了一個 work 方法,因此 p 能夠經過 __proto__
原型鏈找到 work 方法執行成功...
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 中實現繼承、類以及原理