javascript
实现JavaScript继承
66套java從入門到精通實戰課程分享
使用TypeScript或者ES2015+標準中的extends關鍵字是很容易實現繼承的,但這不是本文的重點。JS使用了基于原型(prototype-based)的繼承方式,extends只是語法糖,本文重點在于不使用extends來自己實現繼承,以進一步理解JS中的繼承,實際工作中肯定還是要優先考慮使用extends關鍵字的。
原型 & 原型鏈
原型用于對象屬性的查找。畫出下面代碼中的原型鏈圖示:
class Person {private _name: string;constructor(name: string) {this._name = name;}get getName(): string {return this._name;} }let person = new Person("xfh");?
?
圖中,__proto__表示實例的原型對象,prototype表示構造函數的原型對象。不再推薦使用__proto__,將來可能會被廢棄,可使用Object.getPrototypeOf()來獲取對象的原型。
關于原型/鏈,記住以下幾點:
-
原型鏈的終點是null,從這個角度,可以將null看作所有Object的基類
-
實例的原型對象和它構造函數的原型對象是同一個對象(比較拗口)
-
所有的函數(包括構造函數及Function自身)都是Function的實例
-
函數是普通的對象,只是具備了可調用(callable)功能 ,想到了Python中的類裝飾器,也是具備了可調用功能的普通類
-
所有的對象終歸是Object的實例,即Object位于所有對象的原型鏈上
?
typeof操作符與instanceof`關鍵字的區別如下:
Keep in mind the only valuable purpose of?typeof?operator usage is checking the Data Type. If we wish to check any Structural Type derived from Object it is pointless to use?typeof?for that, as we will always receive?"object". The indeed proper way to check what sort of Object we are using is?instanceof?keyword. But even in that case there might be misconceptions.
實現繼承
JS中對象成員分為三類:實例、靜態、原型。實例成員綁定到具體實例上(通常是this上),靜態成員綁定到構造函數上,原型成員就存在原型對象上:
/*** 從基類繼承成員* @param child 子類構造函數或實例* @param basae 基類構造函數或實例*/ function inheritMembers(child, base){let ignorePropertyNames = ["name", "caller", "prototype", "__proto__", "length", "arguments"];let propertyNames = Object.getOwnPropertyNames(base);for (let propertyName of propertyNames) {if (ignorePropertyNames.includes(propertyName)) {continue;}let descriptor = Object.getOwnpropertyDescriptor(base, propertyName);if (!descriptor) {continue;}Object.defineProperty(child, propertyName, descriptor);} } /*** 從基類繼承原型及靜態成員* @param thisCtor 子類構造函數* @param baseCtor 基類構造函數 */ function inheritSharedMembers(thisCtor, baseCtor) {if (typeof thisCtor !== "function" || typeof baseCtor !== "function") {throw TypeError("參數必須是函數: thisCtor, baseCtor");}//繼承原型成員thisCtor.prototype = Object.create(baseCtor.prototype);thisCtor.prototype.constructor = thisCtor;//繼承靜態成員inheritMembers(thisCtor, baseCtor); } /*** 調用子類及父類構造函數創建子類實例,并繼承父類實例成員(這也是調用父類構造函數的原因)* @param thisInstance 子類實例* @param baseInstance 父類實例*/ function createInstance(thisInstance, baseInstance) {inheritMember(thisInstance, baseInstance);return thisInstance; }//構造函數 function Animal(tag) {// 實例屬性this.tag = tag; }//靜態方法,需通過構造函數來調用 Animal.bark = function () {console.log("static func, this= " + this + ", typeof this=" + typeof this); }; //原型方法,需要通過實例來調用 Animal.prototype.getInfo = function() {console.log("property func, tag:" + this.tag); };function Dog(name = null) {this.name = name ?? "default"; } //添加子類原型方法 Dog.prototype.dogBark = function () {console.log("dog bark"); }; //繼承父類原型及靜態成員 inheritSharedMembers(Dog, Animal);var animal = new Animal("animal"); Animal.bark(); // TypeError: animal.bark is not a function // animal.bark(); animal.getInfo(); // property getinfo not exist on type 'typeof Animal' // Animal.getInfo();let dog = createInstance(new Dog("dog"), new Animal("dog"));dog.getInfo(); dog.dogBark(); Dog.bark(); console.log(dog.name);最后使用v4.1.3版本的TS,編譯為ES5版本的JS,看看TS背后是如何實現繼承的:
class Person {name: string;age: number;constructor(name: string, age: number) {//只能在構造函數中使用this關鍵字this.name = name;this.age = age;}//靜態方法中調用本類中的另一個靜態方法時,可以使用this.methodName的形式//在外部調用時只能類名.方法名的形式,所以此時方法內部,this是指向構造函數的//即,this.methodName等價于類名.方法名static static_method() {// 這里this指向Person類,typeof this=function// 可以看出class Person本質上是構造函數,class只是語法糖console.log(`static method, this=${this}, typeof this=${typeof this}`);} }// 使用extends繼承 class Chinese extends Person {constructor(name: string, age: number) {//必須調用父類構造函數,且需要在子類構造函數使用this關鍵字之前調用,否則會產生錯誤://A 'super' call must be the first statement in the constructor when a class contains initialized properties or has parameter properties.super(name, age);}sayHello() {console.log(`I'm ${this.name}, I'm ${this.age} years old.`)} }let cn = new Chinese('xfh', 26);cn.sayHello(); Chinese.static_method();編譯后代碼如下:
"use strict"; var __extends = (this && this.__extends) || (function() {var extendStatics = function(d, b) {extendStatics = Object.setPrototypeOf ||({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };return extendStatics(d, b);};return function (d, b) {extendStatics(d, b);function __() { this.constructor = d; }d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());}; }); var Person = /** @class */ (function () {function Person(name, age) {//只能在構造函數中使用this關鍵字this.name = name;this.age = age;}// 靜態方法中調用本類中的另一個靜態方法時,可以使用this.methodName的形式// 在外部調用時只能類名.方法名的形式,所以此時方法內部,this是指向構造函數的// 即, this.methodName等價于類名.方法名.Person.static_method = function () {//這里this指向Person類,typeof this=function//可以看出class Person本質上是構造函數,class只是語法糖console.log("static method, this=" + this+ ", typeof this=" + typeof this);};return Person; }()); // 使用extends繼承 var Chinese = /** @class */ (function (_super) {__extends(Chinese, _super);function Chinese(name, age) {//必須調用父類構造函數,且需要在子類構造函數使用this關鍵字之前調用,否則會產生錯誤:// A 'super' call must be the first statement in the constructor when a class contains initialized properties or has parameter properties.return _super.call(this, name, age) || this;}Chinese.prototype.sayHello = function () {console.log("I'm " + this.name + ", I'm " + this.age + " years old.");};return Chinese; }(Person)); var cn = new Chinese('xfh', 26); cn.sayHello(); Chinese.static_method();總結
以上是生活随笔為你收集整理的实现JavaScript继承的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: C/C++还能混合编程
- 下一篇: JS破解专题|光汇云油登录算法