class 基本語法

1.簡介

JavaScript 語言中,生成實(shí)例對象的傳統(tǒng)方法是通過構(gòu)造函數(shù)。下面是一個例子。


1.jpg

上面這種寫法跟傳統(tǒng)的面向?qū)ο笳Z言(比如 C++ 和 Java)差異很大,很容易讓新學(xué)習(xí)這門語言的程序員感到困惑。

ES6 提供了更接近傳統(tǒng)語言的寫法,引入了 Class(類)這個概念,作為對象的模板。通過class關(guān)鍵字,可以定義類。

基本上,ES6 的class可以看作只是一個語法糖,它的絕大部分功能,ES5 都可以做到,新的class寫法只是讓對象原型的寫法更加清晰、更像面向?qū)ο缶幊痰恼Z法而已。上面的代碼用 ES6 的class改寫,就是下面這樣。

2.jpg

上面代碼定義了一個“類”,可以看到里面有一個constructor方法,這就是構(gòu)造方法,而 this關(guān)鍵字則代表實(shí)例對象。也就是說,ES5 的構(gòu)造函數(shù)Person,對應(yīng) ES6 的Person類的構(gòu)造方法。

Person類除了構(gòu)造方法,還定義了一個toString方法。注意,定義“類”的方法的時候,前面不需要加上function這個關(guān)鍵字,直接把函數(shù)定義放進(jìn)去了就可以了。另外,方法之間不需要逗號分隔,加了會報(bào)錯。

上面代碼表明,類的數(shù)據(jù)類型就是函數(shù),類本身就指向構(gòu)造函數(shù)。

使用的時候,也是直接對類使用new命令,跟構(gòu)造函數(shù)的用法完全一致。

構(gòu)造函數(shù)的prototype屬性,在 ES6 的“類”上面繼續(xù)存在。事實(shí)上,類的所有方法都定義在類的prototype屬性上面。

在類的實(shí)例上面調(diào)用方法,其實(shí)就是調(diào)用原型上的方法。

prototype對象的constructor屬性,直接指向“類”的本身,這與 ES5 的行為是一致的。

1.jpg

另外,類的內(nèi)部所有定義的方法,都是不可枚舉的(non-enumerable)。

1.jpg

上面代碼中,toString方法是Person類內(nèi)部定義的方法,它是不可枚舉的。這一點(diǎn)與 ES5 的行為不一致。

1.jpg

上面代碼采用 ES5 的寫法,toString方法就是可枚舉的。




2.嚴(yán)格模式

類和模塊的內(nèi)部,默認(rèn)就是嚴(yán)格模式,所以不需要使用use strict指定運(yùn)行模式。只要你的代碼寫在類或模塊之中,就只有嚴(yán)格模式可用。

考慮到未來所有的代碼,其實(shí)都是運(yùn)行在模塊之中,所以 ES6 實(shí)際上把整個語言升級到了嚴(yán)格模式。



3.constructor 方法

constructor方法是類的默認(rèn)方法,通過new命令生成對象實(shí)例時,自動調(diào)用該方法。

// 定義類

class Person{
    constructor(name,age){
        this.name=name;
        this.age=age;
        console.log(2213)   // 2213
    }
}

let p1=new Person('Andy',31)

可以看到控制臺打印了2213, 證明通過new命令生成實(shí)例對象時,自動調(diào)用constructor方法

另外,類里面的this指代的都是實(shí)例對象,請看下面這個例子

// 定義類

class Person{
    constructor(name,age){
        this.name=name;
        this.age=age;
    }

    toString(){
        console.log(this) 
    }
}

let p1=new Person('Andy',31)   //  Person {name: "Andy", age: 31}


p1.toString()


一個類必須有constructor方法,如果沒有顯式定義,一個空的constructor方法會被默認(rèn)添加。

1.jpg

上面代碼中,定義了一個空的類Person,JavaScript 引擎會自動為它添加一個空的constructor方法。

constructor方法默認(rèn)返回實(shí)例對象(即this),完全可以指定返回另外一個對象。

1.jpg

上面代碼中,constructor函數(shù)返回一個全新的對象,結(jié)果導(dǎo)致實(shí)例對象不是Foo類的實(shí)例。

類必須使用new調(diào)用,否則會報(bào)錯。這是它跟普通構(gòu)造函數(shù)的一個主要區(qū)別,后者不用new也可以執(zhí)行。



4.類的實(shí)例對象

生成類的實(shí)例對象的寫法,與 ES5 完全一樣,也是使用new命令。

需要注意的是,需要加上new,如果忘記加上new,像函數(shù)那樣調(diào)用Class,將會報(bào)錯。

1.jpg

與 ES5 一樣,實(shí)例的屬性除非顯式定義在其本身(即定義在this對象上),否則都是定義在原型上(即定義在class上)。

toString

上面代碼中,定義了一個 Person類,constructor中的sexagenew出來的實(shí)例對象的屬性,打印 Person.hasOwnProperty('sex')結(jié)果是false, 打印 p1.hasOwnProperty('sex')結(jié)果是true 。 這也證明了 constructor中的this指代的是實(shí)例對象。




與 ES5 一樣,類的所有實(shí)例共享一個原型對象。

1.jpg

上面代碼中,p1和p2都是Person的實(shí)例,它們的原型都是Person.prototype,所以 _proto_屬性是相等的。

這也意味著,可以通過實(shí)例的_proto_屬性為“類”添加方法。

1.jpg

上面代碼在p1的原型上添加了一個printName方法,由于p1的原型就是p2的原型,
因此p2也可以調(diào)用這個方法。而且,此后新建的實(shí)例p3也可以調(diào)用這個方法。
這意味著,使用實(shí)例的_proto
屬性改寫原型,必須相當(dāng)謹(jǐn)慎,
不推薦使用,因?yàn)檫@會改變“類”的原始定義,影響到所有實(shí)例。





5.class表達(dá)式

與函數(shù)一樣,類也可以使用表達(dá)式的形式定義。

const MyClass = class Me {
  getClassName() {
    return Me.name;
  }
};

let inst = new MyClass();
inst.getClassName() // Me
Me.name // ReferenceError: Me is not defined

上面代碼使用表達(dá)式定義了一個類。

需要注意的是,這個類的名字是MyClass而不是Me,Me只在 Class 的內(nèi)部代碼可用,指代當(dāng)前類。




6.不存在變量提升

類不存在變量提升(hoist),這一點(diǎn)與 ES5 完全不同

new Foo(); // ReferenceError
class Foo {}

上面代碼中,F(xiàn)oo類使用在前,定義在后,這樣會報(bào)錯,因?yàn)?ES6 不會把類的聲明提升到代碼頭部。這種規(guī)定的原因與下文要提到的繼承有關(guān),必須保證子類在父類之后定義。

{
  let Foo = class {};
  class Bar extends Foo {
  }
}

上面的代碼不會報(bào)錯,因?yàn)锽ar繼承Foo的時候,F(xiàn)oo已經(jīng)有定義了。但是,如果存在class的提升,上面代碼就會報(bào)錯,因?yàn)閏lass會被提升到代碼頭部,而let命令是不提升的,所以導(dǎo)致Bar繼承Foo的時候,F(xiàn)oo還沒有定義。


7.私有方法和私有屬性

私有方法是常見需求,但 ES6 不提供,只能通過變通方法模擬實(shí)現(xiàn)。





8.this的指向

類的方法內(nèi)部如果含有this,它默認(rèn)指向類的實(shí)例。但是,必須非常小心,一旦單獨(dú)使用該方法,很可能報(bào)錯。

1.jpg

上面的例子說明,在單獨(dú)執(zhí)行printName這個函數(shù)的時候,由于this的指向問題,函數(shù)中的代碼 this.print()當(dāng)前上下文環(huán)境中找不到print這個函數(shù),所以報(bào)錯。

一個比較簡單的解決方法是,在構(gòu)造方法中綁定this,這樣就不會找不到print方法了。

請看下面這個更為復(fù)雜的情況。

2.jpg

這個例子中,在class Logger的constructor中我們使用了bind方法,把printName這個方法的執(zhí)行上下文環(huán)境綁定到了class Logger的實(shí)例上。

bind方法是新創(chuàng)建一個函數(shù),然后把它的上下文綁定到bind()括號中的參數(shù)上,然后將它返回。

所以,bind后函數(shù)不會執(zhí)行,而只是返回一個改變了上下文的函數(shù)副本,而call和apply是直接執(zhí)行函數(shù)。

后面的代碼中,我們new了一個 Logger的實(shí)例,打印出來。
發(fā)現(xiàn)實(shí)例中有userName passwords 和 printName三個屬性,前面兩個是key value屬性,最后的一個 printName是Logger實(shí)例的一個方法。

通過var {printName}=logger把printName單獨(dú)拿出來,打印printName發(fā)現(xiàn)它是一個函數(shù),它是實(shí)例logger的一個OwenProperty,其原型proto指向Function的prototype屬性。

注意到printName._proto與實(shí)例 logger._proto顯然是不等的。

它與Function的原型才相等!

前面提到,我們使用bind方法,將printName的上下文環(huán)境this改變成了class Logger的實(shí)例對象上,所以直接調(diào)用 printName方法可行了,它會打印出hello lv的信息。


關(guān)于bind()函數(shù)

1.jpg

這個例子中,我們在constructor中使用了bind方法,將實(shí)例對象logger的上下文環(huán)境this綁定到了一個新的對象上面,新的對象上面也有print方法

我們把實(shí)例對象logger的print方法賦值給printName方法,單獨(dú)調(diào)用printName方法時,發(fā)現(xiàn)打印的都是 敵法師,新的對象里面的name,sex屬性值。




9.name 屬性

由于本質(zhì)上,ES6 的類只是 ES5 的構(gòu)造函數(shù)的一層包裝,所以函數(shù)的許多特性都被Class繼承,包括name屬性。

class Point {}
Point.name // "Point"

前面有提到過。

Person.hasOwnProperty('name');打印的是true,是因?yàn)榍珊暇褪沁@個原因。實(shí)際上打印 Person.hasOwnProperty('age');打印的就是false





10.Class 的取值函數(shù)(getter)和存值函數(shù)(setter)

與 ES5 一樣,在“類”的內(nèi)部可以使用get和set關(guān)鍵字,對某個屬性設(shè)置存值函數(shù)和取值函數(shù),攔截該屬性的存取行為。

1.jpg

上面代碼中,prop屬性有對應(yīng)的存值函數(shù)和取值函數(shù),因此賦值和讀取行為都被自定義了。

存值函數(shù)和取值函數(shù)是設(shè)置在屬性的 Descriptor 對象上的。

12.class的靜態(tài)方法

類相當(dāng)于實(shí)例的原型,所有在類中定義的方法,都會被實(shí)例繼承。如果在一個方法前,加上static關(guān)鍵字,就表示該方法不會被實(shí)例繼承,而是直接通過類來調(diào)用,這就稱為“靜態(tài)方法”。

1.jpg

上面代碼中,Person類有個靜態(tài)方法hello(),可以在Person類上調(diào)用,但是不能在Person的實(shí)例上調(diào)用。

嘗試 p.hello()是報(bào)錯的。

注意,如果靜態(tài)方法包含this關(guān)鍵字,這個this指的是類,而不是實(shí)例。

1.jpg

上面代碼中,靜態(tài)方法bar調(diào)用了this.baz,這里的this指的是Person類,而不是Person的實(shí)例,等同于調(diào)用Person.baz。

另外,從這個例子還可以看出,靜態(tài)方法可以與非靜態(tài)方法重名。



父類的靜態(tài)方法,可以被子類繼承。


靜態(tài)方法也是可以從super對象上調(diào)用的。

1.jpg





13.class的靜態(tài)屬性和實(shí)例屬性

靜態(tài)屬性指的是 Class 本身的屬性,即Class.propName,而不是定義在實(shí)例對象(this)上的屬性。

// 定義類

class Person{
    
}

Person.prop=1;

console.log(Person.prop);  //  1

上面的寫法為Person類定義了一個靜態(tài)屬性prop。

目前,只有這種寫法可行,因?yàn)?ES6 明確規(guī)定,Class 內(nèi)部只有靜態(tài)方法,沒有靜態(tài)屬性。

// 以下兩種寫法都無效
class Person {
  // 寫法一
  prop: 2

  // 寫法二
  static prop: 2
}

Person.prop // undefined
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • class的基本用法 概述 JavaScript語言的傳統(tǒng)方法是通過構(gòu)造函數(shù),定義并生成新對象。下面是一個例子: ...
    呼呼哥閱讀 4,210評論 3 11
  • 1、引言 JavaScript是一門基于原型繼承的語法,ES5中我們實(shí)現(xiàn)面向?qū)ο髽?gòu)造“父類”的寫法一般通過構(gòu)造函數(shù)...
    七_(dá)五閱讀 226評論 0 0
  • ??面向?qū)ο螅∣bject-Oriented,OO)的語言有一個標(biāo)志,那就是它們都有類的概念,而通過類可以創(chuàng)建任意...
    霜天曉閱讀 2,265評論 0 6
  • 講解員的講解中,我們了解到東區(qū)自來水廠的水源是黃河的地表水,流經(jīng)兩條主干道來到自來水廠,首先經(jīng)過曝氣池除去水中高含...
    emmmmmm哦閱讀 178評論 0 0
  • 親愛的孩子! 此時此刻,我們每個人都面對選擇!都在檢視著自己!我們?nèi)绾稳ミx擇?我們?yōu)槭裁醋鰶Q定?我們怎樣的選擇造就...
    論劍閱讀 326評論 0 0

友情鏈接更多精彩內(nèi)容