在這一章中介紹的 class 類,但願同窗們能夠在上一章節中 複習下構造函數、原型、原型鏈等基礎知識javascript
一、先來舉個例子:前端
class Persons { name: any; age: number | undefined; constructor(name: string, age: number) { this.name = name; this.age = age; } getName(): void { console.log(`${this.name}今年已經${this.age}歲了`); } } let p11 = new Persons("za", 123); console.log(p11.getName()); // za今年已經123歲了
var Persons = /** @class */ (function() { function Persons(name, age) { this.name = name; this.age = age; } Persons.prototype.getName = function() { console.log( this.name + "\u4ECA\u5E74\u5DF2\u7ECF" + this.age + "\u5C81\u4E86" ); }; return Persons; })(); var p11 = new Persons("za", 123); console.log(p11.getName()); // za今年已經123歲了
二、這裏和咱們使用 Es6 中的 class 有一些差異java
// javascript 中 class 的定義 class An { constructor(name) { this.name = name; } getName() { console.log(this.name); } } var a = new An("zz"); a.getName(); // zz
三、差別在於,咱們須要去定義 constructor 構造函數中傳入的數據參數的類型git
class Animal { name: string | undefined; food: string; constructor(name: string, food: string) { this.name = name; this.food = food; } eat() { console.log(`${this.name}吃${this.food}`); } } class Cat extends Animal { constructor(name: string, food: string) { super(name, food); } jump() { console.log(`${this.name}正在跳`); } } let xiaohhua = new Cat("xiaohua", "貓糧"); console.log(xiaohhua.eat()); // xiaohua吃貓糧 console.log(xiaohhua.jump()); // xiaohua正在跳
這裏和 ES6 中的 class 繼承內容基本上沒什麼出入es6
這裏的修飾符是對類中對 屬性和方法的類型的定義github
不定義的類心的話,默認就是 public 類型typescript
class Animals { public name: string | undefined; constructor(name: string) { this.name = name; } eat() { console.log(`${this.name}哇`); } }
轉換成 es5 代碼app
"use strict"; var Animals = /** @class */ (function() { function Animals(name) { this.name = name; } Animals.prototype.eat = function() { console.log(this.name + "\u54C7"); }; return Animals; })(); // 和沒定義以前同樣
當成員被標記成 private 時,它就不能在聲明它的類的外部訪問函數
class Animal2 { private name: string | undefined; constructor(name: string) { this.name = name; } eat() { console.log(`${this.name}哇`); } } var a = new Animal2("private"); a.name = "123"; // 報錯,name 屬性只能在 Animal2 內部使用 new Animal2("private").name = "432"; // 報錯: 屬性「name」爲私有屬性,只能在類「Animal2」中訪問。
當成員被標記成 protected 時,它就不能在聲明它的類的外部訪問,可是該類的子類能夠訪問學習
class Person2 { protected name: string; constructor(name: string) { this.name = name; } } class exPerson extends Person2 { public age: number; constructor(age: number, name: string) { super(name); this.age = age; this.name = name; } public getInfo() { console.log(`${this.name}哈哈哈哈${this.age}`); } } let ps = new exPerson(123, "za"); // 派生類能夠繼承 protected 屬性,可是 ps.name = "zz"; // 報錯 外部沒法直接訪問 console.log(ps); // { name: 'za', age: 123 }
構造函數也可以被 設置成 protected 屬性
class Person22 { protected name: string; protected constructor(name: string) { this.name = name; } } class exPerson2 extends Person2 { public age: number; constructor(age: number, name: string) { super(name); this.age = age; this.name = name; } public getInfo() { console.log(`${this.name}哈哈哈哈${this.age}`); } } let exp = new exPerson2(21, "exp-name"); let per22 = new Person22("zs"); // 報錯 類「Person22」的構造函數是受保護的,僅可在類聲明中訪問
使用 readonly 關鍵字將屬性設置爲只讀的。 只讀屬性必須在聲明時或構造函數裏被初始化
class octPers { readonly name: string; readonly age: number = 8; constructor(name: string, age: number) { this.name = name; this.age = age; } } let ns = new octPers("zz", 123); console.log("---1", ns); ns.age = 456; // 報錯 Cannot assign to 'age' because it is a read-only property. console.log("---2", ns); // 這裏會執行什麼內容呢?
這裏所謂的靜態方法,其實就是將方法直接定義在了 構造函數對象上,只有構造函數自己才能去使用它,任何其餘都沒法使用(包括它的 派生類)
class staticPerson { public name: string; public age: number = 8; constructor(name: string, age: number) { this.name = name; this.age = age; } static getName1() { console.log("---static-getName---", this); } protected getName(): void { console.log("---protected-getName---", this); } } let ress = new staticPerson("zzs", 123); console.log("---instancing getName", staticPerson.getName1()); // 屬性「getName」受保護,只能在類「staticPerson」及其子類中訪問。
這裏面其實更多的是 JS 的繼承與多態,咱們以 ES5 和 ES6 分別對繼承和多態進行對比
這裏咱們想一想繼承,究竟是繼承什麼?如何繼承?爲何要繼承?
類的方式,其核心在於將 子類的 prototype 指向了 父類的實例,這樣的話,子類的實例的
__proto__
指向子類的prototype
, 然而 子類的prototype
被賦予了 父類的實例。咱們製做一個簡單的圖,來講明一下這裏如何實現的繼承。
var SuperClass = function(name) { var id = 1; this.name = name; this.work = function() { console.log(this.name + 'in SuperClass'); }; }; SuperClass.prototype.getSuperName = function() { return this.name; }; var SubClass = function() { this.getSubName = function() { console.log('this is subname'); }; }; SubClass.prototype = new SuperClass('superClass'); var sub = new SubClass(); // 這樣有缺點麼? 固然有,下面咱們來經過例子來講明一下
這種繼承的方式的缺點、
var SuperClass = function(name) { var id = 1; this.name = name; this.todo = [1, 2, 3, 4]; this.work = function() { console.log(this.name + 'in SuperClass'); }; }; SuperClass.prototype.getSuperName = function() { return this.name; }; var SubClass = function() { this.getSubName = function() { console.log('this is subname'); }; }; SubClass.prototype = new SuperClass('superClass'); var sub = new SubClass(); sub.todo.push('subClass name'); var sub2 = new SubClass(); console.log(sub2.todo); // [ 1, 2, 3, 4, 'subClass name'] // 這裏是缺陷一,父類屬性會被實例子類修改、污染 console.log(sub.name); //superClass console.log(sub2.name); //superClass // 子類的實例只能有一個name,這很顯然也是不夠靈活的,這裏就是缺陷二
這裏由於子類實例對象1,對於父類共有屬性進行了修改,致使子類實例對象2 的對應屬性受到了污染。那有沒有什麼辦法能夠避免這種污染呢?固然是有的,後面咱們會介紹到的。
// 聲明父類 function Animal(color) { this.name = 'animal'; this.type = ['pig', 'cat']; this.color = color; } // 添加原型方法 Animal.prototype.eat = function(food) { console.log(food); }; // 聲明子類 function Dog() { Animal.apply(this, arguments); // 這一步的操做就是改變 Animal 方法的上下文,而後讓 Dog 也具有了 父類構造函數內的屬性和方法 } var dog1 = new Dog('blue'); // dog1.color -> blue var dog2 = new Dog('red'); // dog2.color -> red dog1.type.push('haha'); console.log(dog2.type); // [ 'pig', 'cat' ]
我沒看到 dog1 修改了繼承自父類的屬性 type ,可是 dog2 的 type 屬性併爲被影響到。緣由就是咱們實例化的時候,建立的實例對象的指針指向的位置是不一樣的,因此對應的
__proto__
指向的是 不一樣的子類構造函數的prototype
。可能會比較繞口,可是本質就是 new 操做生成了2個不一樣的對象,各自有各自的原型屬性,互不干擾。
可是上面也有一個缺陷就是,子類沒辦法繼承到父類原型上的方法和屬性
那聰明的前端開發者們,就想到了 集合前2者的優點,進行了 組合式繼承。
// 聲明父類 function Animal(color) { this.name = 'animal'; this.type = ['pig', 'cat']; this.color = color; } // 添加原型方法 Animal.prototype.eat = function(food) { console.log(food); }; // 聲明子類 function Dog() { Animal.apply(this, arguments); // 這一步的操做就是改變 Animal 方法的上下文,而後讓 Dog 也具有了 // 父類構造函數內的屬性和方法 } Dog.prototype = new Animal('Animal Color'); var dog1 = new Dog(); console.log((dog1.color = 'dog1.name')); var dog2 = new Dog(); console.log(dog2.color); // undefined 這裏爲何 dog2.color 是 undefined 而不是 'dog1.name' 呢? 由於,咱們子類的構造函數,已經繼承了 父類的構造函數內部的屬性和方法,而後,在實例咱們 子類的時候,子類的實例對象就會有先從自己的對象中去尋找 color 屬性。 當找到對應屬性的時候,不管是否有值,都會優先返回 實例化對象自己的屬性,而再也不須要從原型鏈中查找對應屬性。
這裏咱們想一想繼承,究竟是繼承什麼?如何繼承?爲何要繼承?
class Animal { constructor(name) { this.name = name; } eat(food) { console.log(`${this.name}吃${food}`); } } class Dog extends Animal { constructor(name) { super(name); this.name = name; } run() { console.log('小狗泡泡跑'); } } let dog1 = new Dog('小狗'); let dog2 = new Dog('小花'); console.log(dog1.name); // 小狗 console.log(dog2.name); // 小花 dog1.__proto__ === Dog.prototype // true Dog.__proto__ === Animal // true 這裏 Dog 的 __proto__ 指向的是 Animal 這個類 由於 Animal 這個類中的 constructor 就是原來的構造函數, 其中剩下的方法、屬性都是 prototype 上的公共方法與屬性。是能夠被子類繼承
這裏全篇文章又總結了下 JS 中繼承的原理以及一些咱們平時可能忽略的問題,這裏就至關於在 學習 ts 以前,帶着你們再一塊兒複習一下。好了,本篇文章就先到這裏了。
GitHub 地址:(歡迎 star 、歡迎推薦 : )
《前端之路》 - TypeScript(四)class 篇