JS 語法之--物件型別(構造器,class,this),高階物件(高階類,Minix模式)
1、JS 物件型別
JavaScript 是一種基於原型(prototype)的面嚮物件語言,而不是基於類的面嚮物件語言
C++, Java 有類Class 和例項Instance 的概念,類是一類事物 的抽象,而例項則是類的實體。
JS是基於原型的語言,它只有原型物件的概念,原型物件就是一個模板,新的物件從這個模板構建從而獲取最初的屬性,任何物件在執行的時候可以動態的增加屬性,而且,任何一個物件都可以作為另一個物件的原型,這樣後者就可以共享前者的屬性。
2、定義類
字面式宣告方式
這種方法也稱為 字面值建立物件。JS 1.2 開始支援
ES6之前--構造器
1、應以一個函式(構造器)物件,函式名首字母大寫
2、使用this 的定義屬性
3、使用new 和構造器建立一個新物件
測試:
1 // 定義類,構造器 2 function Point(x, y) { 3 this.x = x; 4 this.y = y; 5 this.show = () => {console.log(this, this.x, this.y)} 6 console.log('======================') 7 } 8 9 //繼承 10 function Point3D(a, b, z) { 11 Point.call(this,a,b) 12 this.z = z; 13 console.log('print3d') 14 } 15 16 console.log(Point3D) 17 p2 = new Point3D(1,2,3) 18 console.log('******************') 19 console.log(p2) 20 console.log('******************') 21 p2.show()
結果:
1 Info: Start process (19:52:17) 2 [Function: Point3D] 3 ====================== 4 print3d 5 ****************** 6 Point3D { x: 1, y: 2, show: [Function], z: 3 } 7 ****************** 8 Point3D { x: 1, y: 2, show: [Function], z: 3 } 1 2 9 Info: End process (19:52:17)
new 構建一個新的通用物件,new操作符會將新物件的this 值傳遞給Point3D 構造器函式,函式為這個物件建立 z 屬性
從上句話知道,new 後得到一個物件,使用這個物件的this 來呼叫構造器,那麼如何執行‘基類’的構造方法
使用Point3D 物件的this來執行Point 的構造器,所以使用call 方法,傳入子類的this
最終,構造完成後,將物件賦值給 p2
注意:如果不使用new 關鍵字,就是一個普通函式的函式呼叫,this不代表例項。
ES6 中的class
從ES 6開始,新提供了class 關鍵字,使得建立物件更加簡單,清晰。
-
- 類定義使用了class 關鍵字,建立的本質上海市函式,是一個特殊的函式
- 一個類只能擁有一個名為constructor 的構造方法,如果沒有顯式的定義一個構造方法,則會新增一個預設的constructor 方法
- 繼承使用extends 關鍵字
- 一個構造器可以使用super 關鍵字來呼叫一個父類的建構函式
- 類沒有私有屬性。
測試:
1 // 基類定義 2 class Point { 3 constructor(x, y) /*構造器 */{ 4 this.name = x 5 this.age = y 6 } 7 show() /* 方法 */ { 8 console.log(this.name, this.age) 9 } 10 } 11 12 13 let p1 = new Point('tom', 12) 14 p1.show() 15 console.log(p1) // 可以看到物件的屬性 16 17 //繼承 18 class Point3D extends Point { 19 constructor (a,b,c) { 20 console.log('---------------------------') 21 super(a, b) 22 this.password = c 23 } 24 } 25 26 let p2 = new Point3D('jack',18,'123456') 27 p2.show() 28 console.log(p2)
結果:
1 Info: Start process (20:11:33) 2 tom 12 3 Point { name: 'tom', age: 12 } 4 --------------------------- 5 jack 18 6 Point3D { name: 'jack', age: 18, password: '123456' } 7 Info: End process (20:11:33)
重寫方法:
子類Point3D 的show方法,需要重寫
子類中直接重寫父類的方法即可
如果需要使用父類的方法,使用super.method() 的方式呼叫
使用箭頭函式重寫上面的方法
1 // 基類定義 2 class Point { 3 constructor(x, y) /*構造器 */{ 4 this.name = x 5 this.age = y 6 // this.show = function () {console.log(this.name, this.age)} 7 this.show = () => console.log(this.name, this.age) 8 } 9 10 } 11 12 13 let p1 = new Point('tom', 12) 14 console.log(p1.show()) 15 console.log(p1) // 可以看到物件的屬性 16 17 //繼承 18 class Point3D extends Point { 19 constructor (a,b,c) { 20 console.log('---------------------------') 21 super(a, b) 22 this.password = c 23 // this.show = function () {console.log(this, this.name, this.age, this.password)} 24 this.show = () => console.log(this, this.name, this.age, this.password) 25 } 26 } 27 28 let p2 = new Point3D('jack',18,'123456') 29 p2.show() 30 console.log(p2)
結果:從執行結果看,箭頭函式也支援子類的覆蓋
1 Info: Start process (20:24:52) 2 tom 12 3 undefined 4 Point { name: 'tom', age: 12, show: [Function] } 5 --------------------------- 6 Point3D { name: 'jack', age: 18, show: [Function], password: '123456' } 'jack' 18 '123456' 7 Point3D { name: 'jack', age: 18, show: [Function], password: '123456' } 8 Info: End process (20:24:53)
測試:shou方法的呼叫次序
1 // 基類定義 2 class Point { 3 constructor(x, y) /*構造器 */{ 4 this.name = x 5 this.age = y 6 // this.show = function () {console.log(this.name, this.age)} 7 // this.show = () => console.log(this.name, this.age,'===1') 8 } 9 show() {console.log(this.name, this.age,'===2')} 10 11 12 } 13 14 let p1 = new Point('tom', 12) 15 console.log(p1.show()) 16 console.log(p1) // 可以看到物件的屬性 17 18 //繼承 19 class Point3D extends Point { 20 constructor (a,b,c) { 21 console.log('---------------------------') 22 super(a, b) 23 this.password = c 24 // this.show = function () {console.log(this, this.name, this.age, this.password)} 25 // this.show = () => console.log(this, this.name, this.age, this.password,'===3') 26 } 27 // show(){console.log(this, this.name, this.age, this.password,'===4')} 28 29 } 30 31 let p2 = new Point3D('jack',18,'123456') 32 p2.show() 33 console.log(p2)
通過測試,p2.show() 先呼叫自己類的物件 show,再 到父類的物件的show,在自己所屬類的show在到父類的show
靜態屬性:
靜態屬性目前還沒有很好的支援
靜態方法;
在方法名前加上 static ,就是靜態方法
1 class Add{ 2 constructor(x, y) { 3 this.x = x; 4 this.y =y; 5 } 6 static point() { 7 console.log(this.x) 8 } 9 } 10 11 add = new Add(20,40) 12 console.log(Add) 13 Add.point() 14 //add.point() 例項不能直接訪問驚天方法,java 和C++ 一樣 15 add.constructor.point();// 物件通過 constructor 訪問靜態方法
結果:
[Function: Add]
undefined
undefined
靜態方法中的this 是Add類, 不是Add的例項
注:靜態的概念不同於python的靜態
this 的坑
雖然JS 和C++ java 一樣有this,但是JS 的變現是不同的
原因在於c++ java 是靜態編譯語言,this是編譯期間繫結,而js 是動態,執行期繫結
* 普通函式呼叫, this就是全域性物件測試
前提:
函式執行的時候,會開啟新的執行上下文環境ExecutionContext
建立this 屬性,但是this 是什麼 就要看函式怎麼呼叫。
-
- myFunction(1,2,3)普通函式呼叫方式,this 指向全域性物件 ,全域性物件是node.js的global或者瀏覽器中的window
- myObject.myFunction(1,2,3) 物件方法的呼叫方式,this指向包含該方法的物件
- call 和apply 方法呼叫,要看第一個引數是誰
分析上例:
解決方式 1:顯式傳入
通過主動傳入物件,這樣就避開了 this 問題
解決方式2: ES3(ES-262第三版),引入了apply,call方法
apply,call方法都是函式物件的方法,第一引數都是傳入物件引入的
apply 傳其他引數需要使用陣列
call 傳其他引數需要使用可變引數收集
解決方式 3 ES5引入了bind方法
解決方式 4:ES6 支援this 箭頭函式
ES6 新技術,就不需要相容this 問題
ES6 新的定義方式如下;
最常用的就是bind()
7、高階物件,高階類,或稱為Mixin模式
Mixin模式。混合模式,這是一種不用繼承 就可以 複用的技術,主要還是為了解決多重繼承的問題,
多繼承的繼承路徑是個問題
JS 是基於物件,類,和物件都是物件模板
混合Mixin 指的是,將一個物件的全部或者部分拷貝到另一個物件上去,其實就是屬性了
可以將多個類或者物件混合成一個類或物件
繼承的實現:
先看一個繼承實現的例子:
父類建構函式中,要求具有屬性 stringify 的序列化函式,如果沒有則丟擲異常
1 class Serillization { 2 constructor(){ 3 console.log('serialization constructor ------') 4 if (typeof(this.stringify) !== 'function') { 5 throw new ReferenceError('should define stringify') 6 } 7 } 8 } 9 10 class Point extends Serillization { 11 constructor(x, y) { 12 console.log('point constructor -------') 13 super();// 繼承必須呼叫 super() 14 this.x = x 15 this.y = y 16 } 17 stringify() { 18 return `<Point x=${this.x}, y=${this.y}>` 19 } 20 } 21 22 class Point3D extends Point{ 23 constructor(x, y, z){ 24 super(x, y) 25 this.z = z 26 } 27 stringify (){ 28 return `<Point x=${this.x}, y=${this.y}, z=${this.z}>` 29 } 30 } 31 32 p = new Point(4,5) 33 console.log(p.stringify()) 34 p3d = new Point3D(7,8,9) 35 console.log(p3d.stringify())
結果:
1 Info: Start process (21:53:17) 2 point constructor ------- 3 serialization constructor ------ 4 <Point x=4, y=5> 5 point constructor ------- 6 serialization constructor ------ 7 <Point x=7, y=8, z=9> 8 Info: End process (21:53:18)
高階物件的實現:
將類的繼承構建成箭頭函式
1 // 普通的繼承 2 class A extends Object{}; // 大寫O 3 console.log(A) 4 5 // 匿名類 6 const A1 = class { 7 constructor(x) { 8 this.x = x 9 } 10 } 11 12 console.log(A1) 13 console.log(new A1(100).x) 14 15 console.log('--------------------------------------------') 16 17 // 匿名類繼承 18 const B = class extends Object { // 這裡返回的是一個類 19 constructor(){ 20 super() 21 console.log('B constructor') 22 } 23 } 24 console.log(B) 25 b = new B() 26 console.log(b) // B {} B 是? 27 28 29 console.log('--------------------------------------------') 30 const C = Sup => class extends Sup { // 這裡返回的是一個函式 31 constructor(){ 32 super() 33 console.log('C constructor') 34 } 35 } 36 37 38 39 cls = new C(Object) // 不可以new 因為返回的是一個普通函式 40 41 42 obj2 = new (C(Object))() 43 44 cls = C(Object) // 返回來,呼叫一次,才能用// 返回一個類 45 obj = new cls() 46 console.log(obj) 47 48 // cls = C(A) 49 // console.log(cls) 50 // c = new cls() 51 // console.log(c) 52 53 // c1 = new (C(Object))()
改造上面序列化的例子:
1 const Serialication = Sup => class extends Sup{ 2 constructor(...args) { 3 console.log('seroalization ------') 4 super(...args) 5 if (typeof(this.stringify) !== 'function') { 6 throw new ReferenceError('should define stringify') 7 } 8 } 9 } 10 11 class Point { 12 constructor(x, y) { 13 console.log('point constructor -------') 14 this.x = x 15 this.y = y 16 } 17 } 18 19 class Point3D extends Serialication(Point){ 20 constructor(x, y, z){ 21 super(x, y) // super 是 Serialication(Point) 包裝過的新型別 22 this.z = z 23 } 24 stringify (){ 25 return `<Point x=${this.x}, y=${this.y}, z=${this.z}>` 26 } 27 } 28 29 let p3d = new Point3D(70,80,90) 30 console.log(p3d.stringify())程式碼