天天看點

javascript對象模型

javascript對象模型

文章目錄

  • ​​javascript對象模型​​
  • ​​定義類​​
  • ​​1.字面式聲明方式(也稱為字面值建立對象。)​​
  • ​​2.ES6之前----構造器構造類​​
  • ​​3.ES6中的class關鍵字​​
  • ​​this的不同作用域​​
  • ​​高階對象、高階類、或稱Mixin模式​​
  • ​​繼承實作​​
  • ​​高階對象實作​​

JavaScript是一種基于原型(Prototype)的面向對象語言,而不是基于類的面向對象語言。

C++、Java有類Class和執行個體Instance的概念,類是一類事物的抽象,而執行個體則是類的實體。

  • JS是基于原型的語言,它隻有原型對象的概念。
  1. 原型對象就是一個模闆,新的對象從這個模闆建構進而擷取最初的 屬性。任何對象在運作時可以動态的增加屬性。而且,任何一個對象都可以作為另一個對象的原型,這樣後者就可 以共享前者的屬性。

定義類

1.字面式聲明方式(也稱為字面值建立對象。)

  • js1.2開始支援
  • 文法:
var obj = {
    property_1:value_1,
    property_2:value_2,
    ...,
    "property n":value_n
}      
  • 示例:
var obj = {
    x:1,
    1:'abc',
    'y':'123'
}

for (let s in obj){
    console.log(s,typeof(s),obj[s])
}      
javascript對象模型

2.ES6之前----構造器構造類

  1. 定義一個函數(構造器)對象,函數名首字母大寫
  2. 使用this定義屬性
  3. 使用new和構造器建立一個新對象
  4. 如果繼承,需要調用繼承對象的call方法,第一個參數傳入this來實作繼承。
//定義類,構造器
function Point(x,y){
    this.x = x;
    this.y = y;
    this.show = ()=>{console.log(this,this.x,this.y)}
    console.log('Point~~~~~~')
}

console.log(Point);
p1 = new Point(1,2);
console.log(p1)
console.log('--------------------');
//繼承
function Point3D(x,y,z){
    Point.call(this,x,y); //"繼承Point,需要調用Point的call方法"
    this.z = z;
    console.log("Point3D~~~~~~~~~~~~~~");
}

console.log(Point3D)
p2 = new Point3D(14,15,16);
console.log(p2);
p2.show()      
javascript對象模型
  • new建構一個新的通用對象,new操作符會将新對象的this值傳遞給Point3D構造函數,函數為這個對象建立z屬性。
  • 使用new後可以得到一個對象,使用這個對象的this來調用構造器。
  • 使用Point3D對象的this來執行Point的構造器,是以使用call方法,傳入子類的this。
  • 最後完成構造後,将對象指派給p2
  • 注意:如果不使用new關鍵字,就是一次普通的函數調用,this不代表執行個體。

3.ES6中的class關鍵字

從ES6開始,新提供了class關鍵字,使得建立對象更加簡單、清晰。

  1. 類定義使用class關鍵字。建立的本質上還是函數,是一個特别的函數
  2. 一個類隻能擁有一個名為constructor的構造器方法。如果沒有顯式的定義一個構造方法,則會添加一個預設的constuctor方法。
  3. 繼承使用extends關鍵字
  4. 一個構造器可以使用super關鍵字來調用一個父類的構造函數
  5. 類沒有私有屬性
  6. 注意:js中沒有多繼承。
class Point{
    constructor(x,y){
        this.x = x;
        this.y = y;
        console.log("Point被初始化")
    }

    show(){ //show方法
        console.log(this,this.x,this.y)
    }
}

let p1 = new Point(10,11);
p1.show()

// 繼承
class Point3D extends Point{
    constructor(x,y,z){
        super(x,y);
        this.z = z;
        console.log("Point3D被初始化")
    }
}

let p2 = new Point3D(20,21,22)
p2.show()      
javascript對象模型
  • 繼承中方法的重寫
  1. 子類中直接重寫父類的方法。
  2. 如果需要使用父類的方法,使用super.method()的方式調用
class Point{
    constructor(x,y){
        this.x = x;
        this.y = y;
        console.log("Point被初始化")
    }

    show(){ //show方法
        console.log(this,this.x,this.y)
    }
}

let p1 = new Point(10,11);
p1.show()

// 繼承
class Point3D extends Point{
    constructor(x,y,z){
        super(x,y);
        this.z = z;
        console.log("Point3D被初始化")
    }

    show(){ // 重寫
        console.log(this,this.x,this.y,this.z)
    }
}

let p2 = new Point3D(20,21,22)
p2.show()      
javascript對象模型
  • 對象中屬性和方法的通路順序
  1. 在對象中屬性的通路順序高于方法的通路順序。
  2. 如果一個對象中定義了個和方法相同的屬性名,并且該屬性指向一個函數對象,那麼該屬性的通路會優先與對象中方法的通路
  3. 注意:類在執行個體化時,會先為示例對象添加方法,在添加屬性。是以會導緻屬性覆寫方法。即屬性的通路順序優于方法的通路順序。
  4. 父類、子類使用同一種方式類定義屬性或者方法,子類覆寫父類。
  5. 通路同名屬性或方法時,優先使用屬性。
class Point{
    constructor(x,y){
        this.x = x;
        this.y = y;
        this.show = ()=> console.log('Point~~~~show',this) //會将類中的方法替換
        console.log("Point被初始化")
    }

    show(){ //show方法
        console.log(this,this.x,this.y)
    }
}

let p1 = new Point(10,11);
p1.show()

// 繼承
class Point3D extends Point{
    constructor(x,y,z){
        super(x,y);
        this.z = z;
        console.log("Point3D被初始化")
    }

    //無法實作對屬性的重寫,需要重寫必須重新定義屬性
    show(){ // 重寫
        console.log(this,this.x,this.y,this.z)
    }
}

let p2 = new Point3D(20,21,22)
p2.show() //是屬性的通路
console.log(p1) //實質上是替換了類中的方法
console.log(p2) //實質上是替換了類中的方法      
javascript對象模型
  • 靜态屬性
  1. 靜态屬性目前還沒有得到很好的支援。
  • 靜态方法
  1. 在方法名上加上static,就是靜态方法了。類似于python中的類變量。
  2. 靜态方法中的this是類本身對象。而不是類執行個體的對象。
  3. 可以通過類名直接通路類中的靜态方法
  4. 執行個體對象通路靜态方法需要通過constructor屬性通路類中的靜态方法。
class Add{
    constructor(x,y){
        this.x = x;
        this.y = y;
    }

    static print(){
        console.log(this,typeof this,'--------------')
    }
}

add = new Add(3,4);
console.log(Add,'=========');
Add.print();
// add.print(); // 執行個體不能直接通路靜态方法,和C++,Java一緻
add.constructor.print();//可以通過constructor通路靜态方法      
javascript對象模型

this的不同作用域

  • js和C++,Java一樣有this,但是js的表現是不通的。因為C++,Java是靜态編譯型語言,this是編譯期綁定,而js是動态語言,運作期綁定。
  • 在js中函數執行時,會開啟新的執行上下文環境ExecutionContext。this屬性是什麼需要看函數是怎麼調用的了。
  1. 普通函數調用方式,this指向全局對象。全局對象是nodejs的global或者浏覽器中的window。
  2. js檔案根中的this。初始是一個空對象。即object.
  3. 通過執行個體對象調用執行個體方法,執行個體方法中的this指代的是該執行個體對象。
  4. call和apply方法調用,會傳遞一個this對象給調用的方法。
  • 全局中的this不是global對象
a = 10;
var b = 100;
let c = 20;
const d = 30;
function add(){};

console.log(this);
console.log(this == global,this === global);
console.log(global);      
javascript對象模型

]

  • 方法中的this就是global對象
a = 10;
var b = 100;
let c = 20;
const d = 30;
function add(){
    console.log('-------------');
    console.log(this == global,this === global);
    console.log(this)
};

add();      
javascript對象模型
  • 執行個體對象中的this是對象的執行個體本身
var school = {
    name :'xdd',
    getNameFunc : function (){ //由于是類對象的屬性方法,即方法中的this就是類對象,而不是global
        console.log(this == global,this === global); // false,false
        console.log(this,this.name);
        console.log("---------------")
        return function(...arrs){ // 沒有使用new關鍵字,即隻是一個普通的函數
            console.log(this === global); //傳回的是一個函數,而不是執行個體對象的函數。即this激素global
            console.log(this.name);
            console.log(arrs)
        }
    }
}

console.log(school);
const fun = school.getNameFunc();
fun();
console.log('-------------使用call方法注入this對象-------------')
fun.call(school,1,2,3,4) //call方法傳參可以直接傳參
console.log('-------------使用apply方法注入this對象-------------')
fun.apply(school,[1,2,3,4]) // apply方法傳參需要使用數組
console.log('-------------使用bind方法為方法綁定this對象,傳回函數執行-------------')
fun.bind(school)(1,2,3,4)      
javascript對象模型

school對象中函數傳回的方法中的this不是執行個體本身。它已經不是C++,java的指向執行個體本身了。this的問題,這是曆史遺留問題,為了相容,新版本保留了。而我們使用時,有時候需要明确的讓this必須是我們期望的對象。有如下解決方法

  1. 顯示傳入this對象,即在方法中接受一個that對象,讓調用者傳入this。
var school = {
    name :'xdd',
    getNameFunc : function (){ //由于是類對象的屬性方法,即方法中的this就是類對象,而不是global
        console.log(this == global,this === global); // false,false
        console.log(this,this.name);
        console.log("---------------")
        return function(that){ // 沒有使用new關鍵字,即隻是一個普通的函數
            console.log(this === global); //傳回的是一個函數,而不是執行個體對象的函數。即this激素global
            console.log(that.name);
        }
    }
}
school.getNameFunc()(school)      
  1. ES3(ES-262第三版)引入了apply、call方法(上面例子已經展現了)
  • apply、call方法都是函數對象的方法,第一參數都是傳入對象引入的。
  • apply傳入其它參數需要數組。
  • call傳入其它參數需要使用可變參數收集。
  1. ES5引入了bind方法
  • bind方法來設定函數的this值。會傳回一個綁定了的函數。
  • bind方法是為函數先綁定this,調用時直接使用。
  1. ES6引入支援this的箭頭函數
  • ES6新技術,就不需要相容this問題。
var school = {
    name :'xdd',
    getNameFunc : function (){ //由于是類對象的屬性方法,即方法中的this就是類對象,而不是global
        console.log(this == global,this === global); // false,false
        console.log(this,this.name);
        console.log("---------------")
        return (...args)=>{ // 沒有使用new關鍵字,即隻是一個普通的函數
            console.log(this === global); //傳回的是一個函數,而不是執行個體對象的函數。即this激素global
            console.log(this.name);
            console.log(args)
        }
    }
}
school.getNameFunc()(1,2,3,4)      
  • 以上解決this問題的方法,bind方法最常用。

高階對象、高階類、或稱Mixin模式

Mixin模式,混合模式。這是一種不用繼承就可以複用的技術。主要還是為了解決多重繼承的問題。多繼承的繼承路徑問題。

  • js是基于對象的,類和對象都是對象模闆。
  • 混合mixin,指的是将一個對象的全部或者部分拷貝到另外一個對象上去。其實就是屬性了。
  • 可以将多個類或對象混合成一個類或對象。

繼承實作

class Serialization{
    constructor(){
        console.log("Serialization constructor---------");
        if (typeof(this.stringify) !== 'function'){ //檢測順序是否是一個方法
            throw new ReferenceError('should define stringify.')
        }
    }
}

class Point extends Serialization{
    constructor(x,y){
        console.log('Point Constructor--------------');
        super();//調用父類的構造器
        this.x = x;
        this.y = y;
    }
}

// s = new Serialization(); // 構造Serialization失敗。
// p = new Point(4,5);// 構造類對象時,調用父類構造器執行也會失敗。      
  • 上面例子中父類構造器函數中,要求具有數學是stringify的序列化函數,如果沒有則抛出異常。
  • 完善代碼如下:
class Serialization{
    constructor(){
        console.log("Serialization constructor---------");
        if (typeof(this.stringify) !== 'function'){ //檢測順序是否是一個方法
            throw new ReferenceError('should define stringify.')
        }
    }
}

class Point extends Serialization{
    constructor(x,y){
        console.log('Point Constructor--------------');
        super();//調用父類的構造器
        this.x = x;
        this.y = y;
    }

    stringify(){
        return `<Point x=${this.x}, y=${this.y}>`;
    }
}

class Point3D extends Point{
    constructor(x,y,z){
        super(x,y);
        this.z = z;
    }

    stringify(){
        return `<Point x=${this.x},y=${this.y},z=${this.z}`;
    }
}

// s = new Serialization(); // 構造Serialization失敗。
p = new Point(4,5);// 構造類對象時,調用父類構造器執行也會失敗。
console.log(p.stringify())
p3d = new Point3D(9,8,7);
console.log(p3d.stringify())      

高階對象實作

  • 将類的繼承構造成箭頭函數
//Mixi類屬性檢測,檢查子類是否有特定屬性
const Serialization = Sup => class extends Sup{
    constructor(...args){
        console.log("Serialization constructor---------");
        super(...args)
        if (typeof(this.stringify) !== 'function'){ //檢測順序是否是一個方法
            throw new ReferenceError('should define stringify.')
        }
    }
}

// 頂級父類
class Point{
    constructor(x,y){
        console.log('Point Constructor--------------');
        this.x = x;
        this.y = y;
    }
}

// 強制限制了Point3D方法必須重寫stringif方法
class Point3D extends Serialization(Point){
    constructor(x,y,z){
        super(x,y);
        this.z = z;
    }

    stringify(){
        return `<Point x=${this.x},y=${this.y},z=${this.z} >`;
    }
}

p3d = new Point3D(9,8,7);
console.log(p3d.stringify())      
  • 注意:
  1. Serialization(Point)這個一步實際上是一個匿名箭頭函數調用,傳回了一個新的類型。Point3D繼承自這個新的匿名類型,增強了功能。
  2. React架構大量使用了這種Mixin技術。