ES6 類(Class)基本用法和靜態屬性+方法詳解


原文地址:http://blog.csdn.net/pcaxb/article/details/53759637

ES6 類(Class)基本用法和靜態屬性+方法詳解

JavaScript語言的傳統方法是通過構造函數,定義並生成新對象,prototype 屬性使您有能力向對象添加屬性和方法。下面是通過傳統的方式創建和使用對象的案例:

<span style="font-size:18px;">//Person.js  
function Person(x,y){  
    this.x = x;  
    this.y = y;  
}  
  
Person.prototype.toString = function (){  
    return (this.x + "的年齡是" +this.y+"歲");  
}  
export {Person};  
//index.js  
import {Person} from './Person';  
let person = new Person('張三',12);  
console.log(person.toString());</span>

1.Class的基本用法
ES6引入了Class(類)這個概念,作為對象的模板,通過class關鍵字,可以定義類。基本上,ES6的class可以看作只是一個語法糖,它的絕大部分功能,ES5都可以做到,新的class寫法只是讓對象原型的寫法更加清晰、更像面向對象編程的語法而已。上面的代碼用ES6的“類”改寫,就是下面這樣。

<span style="font-size:18px;">//Person.js  
class Person{  
    // 構造  
    constructor(x,y){  
        this.x = x;  
        this.y = y;  
    }  
  
  
    toString(){  
        return (this.x + "的年齡是" +this.y+"歲");  
    }  
}  
export {Person};  
//index.js  
import {Person} from './Person';  
let person = new Person('張三',12);  
console.log(person.toString());</span>  

上面代碼定義了一個“類”,可以看到里面有一個constructor方法,這就是構造方法,而this關鍵字則代表實例對象。也就是說,ES5的構造函數Person,對應ES6的Person類的構造方法。Person類除了構造方法,還定義了一個toString方法。注意,定義“類”的方法的時候,前面不需要加上function這個關鍵字,直接把函數定義放進去了就可以了。另外,方法之間不需要逗號分隔,加了會報錯。
ES6的類,完全可以看作構造函數的另一種寫法。

<span style="font-size:18px;">//Person.js  
console.log(typeof Person);//function  
console.log(Person === Person.prototype.constructor);//true</span>  

上面代碼表明,類的數據類型就是函數,類本身就指向構造函數。

<span style="font-size:18px;">//Person.js  
console.log(Person.prototype);//輸出的是一個對象</span>  

構造函數的prototype屬性,在ES6的“類”上面繼續存在。事實上,類的所有方法都定義在類的prototype屬性上面,通過以下方式可是覆蓋類中的方法,當然定義類的時候也可以通過這個方式添加方法。

<span style="font-size:18px;">//index.js  
Person.prototype = {  
    getName(){  
        return '張三';  
    },  
    getAge(){  
        return '12';  
    }  
};</span>  

在類的實例上面調用方法,其實就是調用原型上的方法

<span style="font-size:18px;">//index.js  
console.log(person.constructor === Person.prototype.constructor);//true</span>  

Object.assign方法可以給對象Person動態的增加方法,而Person.prototype = {}是覆蓋對象的方法,或者在初始化的時候添加方法。

<span style="font-size:18px;">//index.js  
Object.assign(Person.prototype,{  
    getWidth(){  
        console.log('12');  
    },  
    getHeight(){  
        console.log('24');  
    }  
});  
console.log(Person.prototype);</span>  

toString方法是Person類內部定義的方法,ES6中它是不可枚舉的,這一點與ES5的行為不一致,ES5是可以枚舉的。

<span style="font-size:18px;">//index.js  
//ES5  
console.log(Object.keys(Person.prototype));//["toString", "getWidth", "getHeight"]  
console.log(Object.getOwnPropertyNames(Person.prototype));//["constructor", "toString", "getWidth", "getHeight"]  
  
//ES6  
console.log(Object.keys(Person.prototype));//["getWidth", "getHeight"]  
console.log(Object.getOwnPropertyNames(Person.prototype));//["constructor", "toString", "getWidth", "getHeight"]</span>  

Object.keys(obj),返回一個數組,數組里是該obj可被枚舉的所有屬性。Object.getOwnPropertyNames(obj),返回一個數組,數組里是該obj上所有的實例屬性。

 

在ES6中,類的屬性名可以使用表達式,具體實現方式如下

<span style="font-size:18px;">//Article.js  
let methodName = "getTitle";  
export default class Article{  
    [methodName](){  
        console.log('輸出文章的標題1');  
    }  
}  
//index.js  
import Article from './Article';  
//console.log(Article.prototype);  
let article = new Article();  
article.getTitle()</span>  

constructor方法是類的構造函數是默認方法,通過new命令生成對象實例時,自動調用該方法。一個類必須有constructor方法,如果沒有顯式定義,一個默認的constructor方法會被添加。所以即使你沒有添加構造函數,也是有默認的構造函數的。一般constructor方法默認返回實例對象this,但是也可以指定constructor方法返回一個全新的對象,讓返回的實例對象不是該類的實例。

<span style="font-size:18px;">//ConstructorStu.js  
import Article from './Article';  
export default class ConstructorStu{  
    // 構造  
    constructor() {  
        console.log('constructor');  
        return new Article();  
    }  
}  
//index.js  
import ConstructorStu from './ConstructorStu';  
console.log('==111==');  
console.log(new ConstructorStu() instanceof ConstructorStu);//false  
console.log('==222==');  
let cons =  new ConstructorStu();  
console.log('==333==');  
cons.constructor();  
console.log('==444==');  
運行結果  
==111==  
constructor  
false  
==222==  
constructor  
==333==  
==444==</span>

說明:類的構造函數,不使用new是沒法調用的,即使你使用實例對象去調用也是不行的,這是它跟普通構造函數的一個主要區別。
實例的屬性除非顯式定義在其本身(即定義在this對象上),否則都是定義在原型上(即定義在class上)。hasOwnProperty()函數用於指示一個對象自身(不包括原型鏈)是否具有指定名稱的屬性。如果有,返回true,否則返回false。

<span style="font-size:18px;">//Person.js  
class Person{  
    // 構造  
    constructor(x,y){  
        this.x = x;  
        this.y = y;  
    }  
  
    toString(){  
        return (this.x + "的年齡是" +this.y+"歲");  
    }  
}  
  
let person = new Person('lis',8);  
console.log(person.toString());  
console.log(person.hasOwnProperty('x'));//true  
console.log(person.hasOwnProperty('y'));//true  
console.log(person.hasOwnProperty('toString'));//false  
console.log(person.__proto__.hasOwnProperty('toString'));//true</span>  

說明:上面結果說明對象上有x,y屬性,但是沒有toString屬性。也就是說x,y是定義在this對象上,toString定義在類上。

<span style="font-size:18px;">let person1 = new Person('張三',12);  
let person2 = new Person('李四',13);  
console.log(person1.__proto__ === person2.__proto__);//true</span> 

類的所有實例共享一個原型對象,person1和person2都是Person的實例,它們的原型都是Person.prototype,所以__proto__屬性是相等的。這也意味着,可以通過實例的__proto__屬性為Class添加方法。

<span style="font-size:18px;">let person1 = new Person('張三',12);  
let person2 = new Person('李四',13);  
person1.__proto__.getH = function (){  
    return "Height";  
};  
console.log(person1.getH());  
console.log(person2.getH());</span>  

上面代碼在person1的原型上添加了一個getH方法,由於person1的原型就是person2的原型,因此person2也可以調用這個方法。而且,此后新建的實例person3也可以調用這個方法。這意味着,使用實例的__proto__屬性改寫原型,必須相當謹慎,不推薦使用,因為這會改變Class的原始定義,影響到所有實例。
__proto__參考資料:點擊打開鏈接

class不存在變量提升,需要先定義再使用,因為ES6不會把類的聲明提升到代碼頭部,但是ES5就不一樣,ES5存在變量提升,可以先使用,然后再定義。

<span style="font-size:18px;">//正確  
new A();  
function A(){  
  
}//ES5可以先使用再定義,存在變量提升  
//錯誤  
new B();  
class B{  
  
}//B is not a constructor  
//ES6不能先使用再定義,不存在變量提升</span>  

這個類的名字是Expression而不是Expre,Expre只在Class的內部代碼可用,指代當前類。

<span style="font-size:18px;">//Expression.js  
const Expression = class Expre{  
  
    static getAge(){  
        return '12';  
    }  
  
    getClassName(){  
        return " ClassName1= " +Expre.name + " ClassName2= " +Expression.name;  
    }  
  
};  
  
let exp = new Expression();  
//let exp = new Expre();錯誤  
//bundle.js:7935 Uncaught ReferenceError: Expre is not defined  
console.log(exp.getClassName());//ClassName1= Expre ClassName2= Expre  
//console.log(Expre.getAge());錯誤  
//bundle.js:7935 Uncaught ReferenceError: Expre is not defined  
console.log(Expression.getAge());</span>  

說明:Expre.name和Expression.name返回的都是Expre,返回的都是當前。
如果類的內部沒用到的話,可以省略Expre,也就是可以寫成下面的形式

<span style="font-size:18px;">//Expression.js  
const MyExpre = class{  
  
   getClassName(){  
        return MyExpre.name;  
    }  
  
};  
  
let myExpre = new MyExpre();  
console.log(myExpre.getClassName());//MyExpre</span>  

說明:如果省略了class后面的那個名字Expre,MyExpre.name返回的就是MyExpre,如果沒有省略MyExpre.name返回就是class后面的那個名字Expre。
采用Class表達式,可以寫出立即執行的Class

<span style="font-size:18px;">//Expression.js  
let person = new class{  
  
    // 構造  
    constructor(props) {  
        this.props = props;  
    }  
  
    getProps(){  
        return this.props;  
    }  
  
}('構造函數的參數');  
  
console.log(person.getProps());//構造函數的參數</span>  

私有方法是常見需求,但ES6不提供,只能通過變通方法模擬實現。一種做法是在命名上加以區別,在方法前面加上_(下划線),表示這是一個只限於內部使用的私有方法。但是,這種命名是不保險的,在類的外部,還是可以調用到這個方法。另一種方法就是索性將私有方法移出模塊,因為模塊內部的所有方法都是對外可見的。

<span style="font-size:18px;">//PrivateMethod.js  
export default class PrivateMethod{  
  
    // 構造  
    constructor() {  
    }  
  
    getName(){  
        priName();  
    }  
}  
  
function priName(){  
    console.log('私有方法測試');  
}  
//index.js  
import PriMe from './PrivateMethod';  
let prime = new PriMe();  
prime.getName();</span>

說明:通過這種方式還可以定義私有屬性,同理。還有一種方法是利用Symbol值的唯一性,將私有方法的名字命名為一個Symbol值,不過這種方式稍微麻煩點。
類的方法內部如果含有this,它默認指向類的實例。getName方法中的this,默認指向ThisStu類的實例。但是,如果將這個方法提取出來單獨使用,this會指向該方法運行時所在的環境,因為找不到name方法而導致報錯。

<span style="font-size:18px;">//ThisStu.js  
class ThisStu{  
  
    getName(){  
        return this.name();  
    }  
  
    name(){  
        return '王五';  
    }  
  
}  
export {ThisStu};  
  
//index.js  
import {ThisStu} from './ThisStu';  
let thisStu = new ThisStu();  
console.log(thisStu.getName());  
const {getName} = thisStu;  
getName();  
//Cannot read property 'name' of undefined</span>  

一個比較簡單的解決方法是,在構造方法中綁定this,這樣就不會找不到name方法了。修改ThisStu.js文件如下:

<span style="font-size:18px;">//ThisStu.js  
class ThisStu{  
    // 構造  
    constructor() {  
        this.getName = this.getName.bind(this);  
    }  
  
    getName(){  
        return this.name();  
    }  
  
    name(){  
        return '王五';  
    }  
  
}  
export {ThisStu};</span>  

使用構造函數,直接給當前實例的getName賦值,修改修改ThisStu.js文件的構造函數如下:

<span style="font-size:18px;">// 構造  
constructor() {  
    //this.getName = this.getName.bind(this);  
    this.getName = ()=>{  
        return this.name();  
    }  
}  
</span>  

2.Class的靜態方法
類相當於實例的原型,所有在類中定義的方法,都會被實例繼承。如果在一個方法前,加上static關鍵字,就表示該方法不會被實例繼承,而是直接通過類來調用,這就稱為“靜態方法”。

<span style="font-size:18px;">//StaticMethod.js  
//定義靜態方法  
static getAge(){  
 return '獲取Age的靜態方法';  
}  
//通過類名直接調用  
console.log(StaticMethod.getAge());</span>  

如果StaticMethod繼承StaticMethodParent,StaticMethodParent的靜態方法,可以被StaticMethod繼承。

<span style="font-size:18px;">//StaticMethodParent.js  
export default class StaticMethodParent{  
    static getCommon(){  
        return '父類的靜態方法';  
    }  
}  
//通過子類直接調用  
console.log(StaticMethod.getCommon());  
如果StaticMethod繼承StaticMethodParent,StaticMethodParent的靜態方法,可以在StaticMethod中用super調用。  
//定義靜態方法  
static getAge(){  
    //子類可以調用父類的靜態方法  
    console.log(super.getCommon());  
    return '獲取Age的靜態方法';  
}</span>  

說明:靜態方法只能在靜態方法中調用,不能再實例方法中調用。

3.Class靜態屬性和實例屬性
靜態屬性指的是Class本身的屬性,即Class.propname,而不是定義在實例對象(this)上的屬性。ES6使用靜態屬性和實例屬性:

<span style="font-size:18px;">//StaticMethod.js  
//定義靜態屬性  
StaticMethod.firstName = 'pca';  
console.log(StaticMethod.firstName);  
//定義實例屬性  
//ES6實例屬性只能在constructor構造函數中定義  
constructor() {  
    super();  
    this.width = '40cm';  
}  
getWidth(){  
    return this.width;//使用的時候需要加上this  
}  
//為了可讀性的目的,對於那些在constructor里面已經定義的實例屬性,新寫法允許直接列出。  
width;</span>  

說明:目前ES6,只有這種寫法可行,因為ES6明確規定,Class內部只有靜態方法,沒有靜態屬性。

ES7有一個靜態屬性的提案,目前Babel轉碼器支持。安裝babel-preset-stage-0 包含了0-3的stage,可根據需要添加,不同的stage封裝了不同的插件,官方推薦是使用stage-1
安裝命令(根據自己的需求調整):

<span style="font-size:18px;">npm install --save babel-preset-stage-0</span>  

ES7使用靜態屬性和實例屬性:

<span style="font-size:18px;">//StaticMethod.js  
//ES7提案 定義靜態屬性  
static lastName = 'pcaca';  
//ES7定義實例屬性  
height = '150cm';</span> 

說明:ES7和ES6的靜態屬性和實例屬性只是定義不一樣,調用的方式是一樣的

Class的靜態方法/Class靜態屬性和實例屬性的整個案例:

<span style="font-size:18px;">//StaticMethodParent.js  
export default class StaticMethodParent{  
    static getCommon(){  
        return '父類的靜態方法';  
    }  
}  
  
//StaticMethod.js  
import StaticMethodParent from './StaticMethodParent'  
  
//定義靜態屬性和靜態方法  
class StaticMethod extends StaticMethodParent{  
    //因為ES6明確規定,Class內部只有靜態方法,沒有靜態屬性,所以ES6在類中定義靜態屬性都是錯誤的。  
    //static lastName = 'pcaca';ES6錯誤  
  
    //ES7提案 定義靜態屬性  
    //安裝babel-preset-stage-0 包含了0-3的stage,可根據需要添加,  
    //不同的stage封裝了不同的插件,官方推薦是使用stage-1  
    static lastName = 'pcaca';  
  
    //ES7定義實例屬性  
    height = '150cm';  
  
    getHeight(){  
        return this.height;//ES7的使用也要加上this  
    }  
  
    //ES6實例屬性只能在constructor構造函數中定義  
    constructor() {  
        super();  
        this.width = '40cm';  
    }  
  
    //為了可讀性的目的,對於那些在constructor里面已經定義的實例屬性,新寫法允許直接列出。  
    width;  
  
    getWidth(){  
        return this.width;//使用的時候需要加上this  
    }  
  
    //定義靜態方法  
    static getAge(){  
        //子類可以調用父類的靜態方法  
        console.log(super.getCommon());  
        return '獲取Age的靜態方法';  
    }  
};  
  
//定義靜態屬性  
StaticMethod.firstName = 'pca';  
  
export {StaticMethod};  
  
//index.js  
import {StaticMethod} from './StaticMethod';  
console.log(StaticMethod.getAge());  
console.log(StaticMethod.getCommon());  
console.log(StaticMethod.firstName);  
console.log(StaticMethod.lastName);  
let staticMethod = new StaticMethod();  
console.log(staticMethod.height);  
console.log(staticMethod.getHeight());  
console.log(staticMethod.width);  
console.log(staticMethod.getWidth());  
//staticMethod.getAge();//bundle.js:7906 Uncaught TypeError: staticMethod.getAge is not a function</span>  

參考資料:點擊打開鏈接


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM