深入理解 JavaScript 中的 class
深入理解 JavaScript 中的 class(類)
學習內容:
1、 掌握ES6中建立類和物件的方式 2、 掌握ES6中類的繼承 3、 掌握ES6中靜態方法的使用和呼叫 4、 理解ES6中例項化物件的原理一、ES5:function
1、建構函式
封裝、繼承、多型是大多OOP語言都支援的特性,而JavaScript在ES5中沒有提出真正意義上的類、繼承的概念。它通過函式首字母大寫的方式告知開發者這是一個建構函式或者類(有些人會將它理解為類),但從嚴格意義上來講,它是建構函式。這對於初學者來說,看起來是非常腦溢血的。比如下方code:
// 建構函式
function Person(name, age) {
// 例項屬性
this.name = name;
this.age = age;
// 例項方法
this.speak = function() {
console.log(`你好,我的名字叫${this.name},今年${this.age}歲了。`)
}
}
// 例項化物件,並傳入引數
let father = new Person("張三", 20);
// 呼叫例項屬性
console.log(father.name);
// 呼叫例項方法
father.speak();
如同上方code所示,我們通過function
關鍵字+首字母大寫的方式告知開發者,這不是普通的函式,而是一個特殊的函式—建構函式。如果首字母小寫則將變成一個普通函式,意義就立馬改變了。並且它們的呼叫方式也不同,普通函式就是以函式的方式呼叫函式名();
,而建構函式可以以這樣的方式呼叫,同時也可以使用new
關鍵字來例項化物件。
2、靜態方法
在大多數OOP語言中,靜態基本上都是為了共享資料,以減少例項物件所造成的記憶體消耗。但是ES5中並沒有提出相對明確的靜態方法。但是,我們可以通過原型物件來實現靜態方法。比如下方code:
// 建構函式
function Father(name) {
// 例項屬性
this.name = name;
// 例項方法
this.run = function() {
console.log(`我叫${this.name},我會跑!`)
}
}
// 靜態屬性
Father.age = 20;
// 給Father建構函式的原型中新增方法
Father.prototype = {
// 重新指向建構函式
constructor: Father,
// 原型方法
speak: function() {
console.log("我會說話!");
},
run: function() {
console.log("我會跑!");
}
};
// 例項化物件
var father = new Father("張三");
//呼叫靜態屬性
consloe.log(Father.age);
// 呼叫原型方法
father.speak();
在上述code中,我們通過給建構函式的原型以物件的賦值,並將指標重新指向建構函式的方式來給建構函式新增靜態方法,而靜態屬性我們使用建構函式名.屬性名=屬性值;
的方式實現,呼叫的時候使用建構函式名.屬性名;
的方式呼叫。
3、繼承
在大多數OOP語言中,繼承基本上都是為了減少code冗餘,提高code的可讀性和靈活性以及可維護性而存在的。但是JavaScript在ES5中並沒有提出相對明確的繼承概念。但是在ES5中,我們可以通過原型鏈和一些方法來實現真正意義上的繼承。比如下方code:
// 基類建構函式
function Father(name, age) {
// 例項屬性定義在建構函式內部
this.name = name;
this.age = age;
// 判斷原型中的該屬性是否是函式型別的,也就是判斷原型中是否有該方法
if ((typeof Father.prototype.speak) !== "function") {
// 方法定義在原型中
Father.prototype.speak = function() {
console.log(`我叫${this.name},今年${this.age}歲了`)
}
}
}
// 派生類建構函式
function Son(name, age, sex) {
// 呼叫基類的建構函式,相當於把基類中的屬性新增到未來的派生來例項物件中
// 將Father基類中的this指向改變為son的例項,並將基類所需要的引數傳遞
Father.call(this, name, age);
// 例項物件屬性
this.sex = sex;
}
// 修改派生類的原型,這樣就可以繼承基類原型中的方法
Son.prototype = new Father();
// 例項化son,並將基類所需要的引數一併傳遞
var son = new Son("小明", 20, "男");
console.log(son.name);
console.log(son.age)
son.speak();
在上述code中,我們可以看出派生類通過Son.prototype = new Father();
更改派生類原型為基類例項的方式來繼承基類中的屬性和方法,而通過Father.call(this, name, age);
修改this指向來呼叫基類的例項屬性和方法,將其變成派生類例項的屬性和方法,假如例項物件中有與原型同名的屬性和方法則預設替換。在ES5中它是以這樣的原理來實現繼承的。
但是在實際開發中,以這樣的方式實現靜態、繼承確實是有些麻煩,於是乎JavaScript在ES6中正式提出了class(類)、constructor(建構函式)、extends(繼承)、static(靜態)的概念。
二、ES6:class
2015年6月JavaScript正式釋出ES6版本,並且最重要的特點之一就是,引入了class
的概念,使全球js開發者收益,使得js開發者終於告別了,直接使用原型物件模仿物件導向中的類和類繼承的時代。
但是js中並沒有一個真正的class
原始型別,class
僅僅只是對原型物件運用的語法糖。所以,只有理解如何使用原型物件實現類和類繼承,才能真正地用好class
。那麼先上code讓大家體驗一下:
class Person {
// 建構函式
constructor(age) {
this.age = age;
};
// 靜態方法
static speak = function(age) {
console.log(`我的名字叫${Person.uName},今年${age}歲了`)
};
}
// 靜態屬性
Person.uName = "你好";
// 例項化物件
var father = new Person(20);
// 呼叫屬性和方法
console.log(Person.uName);
Person.speak(father.age);
// 試圖修改靜態屬性,但是不可以
Person.name = "小明";
console.log(Person.uName);
我們看到出現了一個個新的關鍵字,關鍵字class
用於宣告一個類,關鍵字constructor
用於宣告建構函式,關鍵字static
用於宣告靜態方法,而靜態屬性則於ES5中一樣,同樣只能寫在類的外邊。並且靜態屬性和方法不能被例項物件所呼叫,只能被類所呼叫。
ES6的語法很簡單,並且也很容易理解。但是在例項化的背後,究竟是什麼在起作用呢?
三、class
例項化背後的原理
使用class
的語法,讓開發者告別了使用prototype
原型物件模仿物件導向的時代。但是,class
並不是ES6引入的全新概念,它的原理依舊是原型物件和原型鏈。
我們前邊不是學習了測試資料型別的知識嘛,那麼我們是不是就可以使用typeof
來檢測類的型別,請看下方code:
// 宣告一個類
class Person {
// 建構函式
constructor(age) {
this.age = age;
};
// 靜態方法
static speak = function(age) {
console.log(`我的名字叫${Person.uName},今年${age}歲了`)
};
}
// 靜態屬性
Person.uName = "你好";
// 例項化物件
var father = new Person(20);
// 測試Person的型別
console.log(typeof Person);
通過typeof
資料型別測試,我們驚奇的發現,class
並非什麼全新的資料型別,它實際上只是function
函式型別。
為了能夠更加直觀的瞭解Person
類,我們可以將它的例項物件在控制檯上輸出,結果如下:
我們發現Person例項物件的屬性並不多,除去內建屬性外,大部分屬性根據名字都能明白它的作用。其中需要我們關注的兩個屬性是prototype原型物件
和__proto__例項物件的原型
。相信大家肯定會問:不是ES5中的原型和原型鏈嘛?沒錯,這就是原型物件。也就是說使用class建立物件的方式就是隱式的使用原型物件和原型鏈。
分析:
1. ES6中`宣告類和建構函式`其實就等於ES5中使用`function`宣告的建構函式
2. 而靜態方法和靜態屬性其實就是在原型物件中設定的屬性和方法
3. 在例項化物件的時候,瀏覽器自動建立了一個物件(原型物件),並且將該物件所屬類的建構函式與原型物件繫結,或者說原型物件記錄了建構函式的引用並指向了建構函式,而建構函式的`prototype`屬性指向了原型物件
4. 當通過例項屬性呼叫靜態屬性和方法的時候,例項屬性的`__proto__`屬性又指向了原型物件
5. 那麼這無疑是ES5中建構函式、例項物件與原型物件的三角關係
**溫馨提示:**如果有看不懂的朋友,請看博主上兩篇博文,詳細講解了原型物件和基與原型物件的繼承。
四、繼承
在傳統物件導向中(ES5物件導向),類是可以繼承類的,這樣派生類就可以使用基類中的屬性和方法,來達到code複用的目的。
當然,ES6中也可以實現繼承。ES6中明確提出了類的繼承語法,即使用extends
關鍵字,並使用super()
方法呼叫基類的建構函式。其實super()
類似於call()
中的借調繼承。請看下方code:
// 基類
class Anima {
constructor(name, age) {
this.name = name;
this.age = age;
}
say() {
console.log(`我的名字叫${this.name},今年${this.age}歲了`);
}
}
// 派生類繼承基類
class Dog extends Anima {
constructor(name, age, sex) {
// 呼叫基類的構造
super(name, age)
// 例項成員
this.sex = sex;
// 靜態成員
Dog.color = "紅色";
// 靜態方法
Dog.run = function() {
console.log("我想睡覺");
}
}
// 方法重寫
say() {
console.log(`我的名字叫${this.name},今年${this.age}歲了,顏色是${Dog.color}`);
}
}
var dog = new Dog("大黃", 18, "男");
dog.nm = "哈哈"
dog.say()
Dog.run();
console.log(dog);
console.log(Dog);
實際上ES6中的繼承與ES5中的繼承原理是一樣的,其本質都是基與原型物件和原型鏈的繼承。
五、物件常用方法
Object.key(物件)
:獲取當前物件中的屬性名,並返回一個包含所有屬性名的陣列。Object.defineProperty()
:該方法用於設定、修改、新增物件中的屬性
Object.defineProperty(物件,修改或新增的屬性名,{
value:修改或新增的屬性的值,
writable:true/false,//如果值為false 不允許修改這個屬性值
enumerable: false,//enumerable 如果值為false 則不允許遍歷
configurable: false //configurable 如果為false 則不允許刪除這個屬性 屬性是否可以被刪除或是否可以再次修改特性
})
體驗:
// 基類
class Anima {
constructor(name, age) {
this.name = name;
this.age = age;
}
say() {
console.log(`我的名字叫${this.name},今年${this.age}歲了`);
}
}
// 派生類繼承基類
class Dog extends Anima {
constructor(name, age, sex) {
// 呼叫基類的構造
super(name, age);
// 例項成員
this.sex = sex;
// 靜態成員
Dog.color = "紅色";
// 靜態方法
Dog.run = function() {
console.log("我想睡覺");
}
}
// 方法重寫
say() {
console.log(`我的名字叫${this.name},今年${this.age}歲了,顏色是${Dog.color}`);
}
}
// 例項化dog物件
var dog = new Dog("大黃", 18, "男");
// 設定dog物件中的sex屬性
Object.defineProperty(dog, "sex", {
value: "小明",
writable: false,
enumerable: false,
configurable: false
});
// 試圖修改,但是並不能修改
dog.sex = "女";
// 列印的還是 小明
console.log(dog.sex);
// 以陣列的形式獲取物件中所有物件的屬性
// 注意:由於我們上邊設定sex屬性不可遍歷,所以無法通過key()方法獲取到sex屬性,因為key()方法的原理是通過遍歷查詢的
console.log(Object.keys(dog));
六、總結
雖然ES5中,class(類)的繼承和靜態方法要比ES5中的語法簡單,好理解。但是js中並沒有一個真正的class
原始型別,class
僅僅只是對原型物件運用的語法糖。
簡單來說,js中不管是ES5還是ES6中的繼承和靜態方法的實現都是基與原型物件和原型鏈實現的。所以,只有理解ES5中如何使用原型物件實現類和類繼承,才能真正地用好ES6中的class
。希望大家不要因為ES6的語法簡單而忽略ES5中的知識。
不管是ES6也好,ES7也罷,基本上都是基與ES5來封裝實現的,所以希望各位博友能夠先學好ES5。同時也希望大家能夠廣集意見,如果博主有說的不對的地方還請給位博友糾正。當然如果還有不懂的地方,博主也會給大家詳細解釋。最後感謝各位博友的支援,以及感謝CSDN能夠提高這樣好的一個平臺,謝謝!
相關文章
- 深入理解Swift中的Class和StructSwiftStruct
- 深入理解JS中的物件(三):class 的工作原理JS物件
- 深入理解JavaScript中的箭頭JavaScript
- 深入理解JavaScript中的精度丟失JavaScript
- 深入理解JavaScript中的WeakMap和WeakSetJavaScript
- 深入理解Javascript中的隱式呼叫JavaScript
- 深入理解JavaScript中的類繼承JavaScript繼承
- 深入理解Swift中static和class關鍵字Swift
- 前端-JavaScript中的class前端JavaScript
- JavaScript物件導向—深入ES6的classJavaScript物件
- JavaScript 和 TypeScript 中的 classJavaScriptTypeScript
- 深入理解JavaScript原型JavaScript原型
- 深入理解JavaScript物件JavaScript物件
- 破解class檔案的第一步:深入理解JAVA Class檔案Java
- 如何理解es6中的class,以及class中的constructor函式Struct函式
- 深入理解JVM(五)Class類的檔案結構JVM
- 深入理解Javascript之PromiseJavaScriptPromise
- 深入理解javascript系列(十一):thisJavaScript
- javascript深入理解系列文章JavaScript
- 深入理解JavaScript作用域JavaScript
- 深入理解Javascript之ModuleJavaScript
- 理解 JavaScript 中的 thisJavaScript
- 深入理解 Javascript 之 作用域JavaScript
- 深入理解Javascript之Execution ContextJavaScriptContext
- 深入理解JavaScript之Event LoopJavaScriptOOP
- 理解 JavaScript 中的原型JavaScript原型
- JS中this的深入理解JS
- 深入理解Js中的thisJS
- java基礎:深入理解Class物件與反射機制Java物件反射
- 深入理解 JavaScript 中的型別和型別判斷問題JavaScript型別
- 深入理解javascript系列(十六):深入高階函式JavaScript函式
- 再理解es6 中的 class super extends
- 深入理解Javascript之Callstack&EventLoopJavaScriptOOP
- 深入理解 JavaScript 回撥函式JavaScript函式
- JavaScript深入理解系列:call與applyJavaScriptAPP
- JavaScript中class繼承超乎你的想象《一》JavaScript繼承
- 深入理解JavaScirpt中的this(轉)Java
- 深入理解Java中的AQSJavaAQS