ES6之什麼是class?

傳統的 javascript 中只有對象,沒有類的概念。它是基於原型的面嚮對象語言。 原型對象特點就是將自身的屬性共享給新對象。這樣的寫法相對於其它傳統面向 對象語言來講,很有一種獨樹一幟的感腳!非常容易讓人困惑!

首先大家要明白,爲什麼要出現 class ?

首先回顧我們的 JavaScript 編程思想的發展史。 從 JS 誕生之時,剛開始做的就是面向過程的編程,把一個問題給解釋清楚了, 幾行 js 就可以搞定。隨着 js 的發展以及瀏覽器對於 js 執行速度越來越高。我們 對於 js 實現的功能越來越多,伴隨代碼量也會越來越多,我們仍然使用面向過 程式的編程方案,就會有問題。 我們製作打怪獸遊戲:

攻擊 function attack()

逃跑 function escape()

加血 function resume()

會關注這三個方法的具體實現,並且反覆調用這些方法完成遊戲。這是面向過程 的思路。 當我們增加打怪獸的使用者時,單純這幾個方法不足以完成多人遊戲。 我們需要更高級的思想面向對象,儘管 javascript 不具備面向對象的特徵(繼承, 封裝,多態)。但是我們可以採用類似的這種思想的方式去改寫這樣的需求。

function BraveMan() {
}
BraveMan.prototype.attack = function () {}
Braveman.prototype.escape = function () {}
BraveMan.prototype.resume = function() {}

基於我們原來過程的基礎之上,我們封裝這樣的構造函數,用於產生可以多次執 行這樣過程的對象。這樣的話我們的打怪獸,不單純如何打怪獸(面向過程), 而是變成了 誰能打怪獸(類似面向對象的思想),這裏的勇者就是我們想要的 對象,可以多次實例化。這也是這種思想給我們帶來的好處,模塊化,可擴展等 好處。

在我們的日常 codeing 中,很多大的項目當中都需要使用這種類似面向對象的思 想去進行編程,在 Javascript 中不存在面向對象,我們採用的類似面向對象的過 程叫,基於原型編程,下面是工作中的存在的代碼(音樂播放器中的兩個模塊):

// ControlIndex 模塊
function ControlIndex(len) {
    // this 指向 Control 對象
    // Control 上掛載兩個屬性一個爲 index 初始值爲 0
    // len 爲當前數據長度
    this.index = 0;
    this.len = len;
}

// 將方法掛在對象得原型上
ControlIndex.prototype = {
    prev: function () {
        return this.getIndex(-1);
    },
    next: function () {
        return this.getIndex(1);
    },
    getIndex: function (val) {
        var index = this.index;
        var len = this.len;
        var curIndex = (index + val + len) % len;
        this.index = curIndex;
        return curIndex;
    }
}

// AudioManager 模塊
function AudioManager(){
    // 創建一個音頻 audio 對象
    this.audio = new Audio();
    // 默認音樂狀態暫停
    this.status = 'pause';
}
AudioManager.prototype = {
    play:function(){
        this.audio.play();
        this.status = 'play';
    },
    pause:function(){
        this.audio.pause();
        this.status = 'pause';
    },
    setAudioSource:function(src){
        this.audio.src = src;
        // 重新加載音頻元素
        this.audio.load();
    }
}

在這裏使用的基於原型編程的一個例子,將一個項目中的不同模塊分解,每個模 塊使用這種方式進行編程。複用性更好,同時分工明確,不單單是 A 方法做完 B 方法做,而是統一的交給管理對象去執行這些方法。

對於 Javascript 的函數來說,我們的函數是由很多不完善的地方,首先我們通過 function 聲明的函數,可以聲明,普通方法,構造函數,單純是這兩個函數我們 是沒有犯法區分的,在之前我們默認採用大頭峯式寫法,表明構造函數,但是必 須每個遵守纔可以,而且容易出問題,當把構造函數當成普通函數來執行,會產 生全局變量,還可能會報錯。

基於上面的種種現象: 我們需要類似的面向對象的思想進行編程。 我們的原始的 function 聲明的構造函數,約束性,安全性不夠,不足以支撐這 種思想。

所以在新的語法規範當中 ECMAScript6 中引入了 class,基於原有 function 的方式 的語法糖,讓我們使用起來,更方便,更安全,目的性更強。

而在 ES6 中引入了 Class(類)這個概念,通過 class 關鍵字可以定義類。該關鍵 字的出現使得其在對象寫法上更加清晰,更像是一種面向對象的語言。ES6 的寫 法就會是這個樣子:

class Person{//定義了一個名字爲 Person 的類
    constructor(name,age){//constructor 是一個構造方法,用來接收參數
        this.name = name;//this 代表的是實例對象
        this.age=age;
    }
    say(){//這是一個類的方法,注意千萬不要加上 function
        return "我的名字叫" + this.name+"今年"+this.age+"歲了";
    }
}
var obj=new Person("yinchengnuo",18);
console.log(obj.say());//我的名字叫 yinchengnuo今年 18 歲了

但是要注意的是:

1.在類中聲明方法的時候,千萬不要給該方法加上 function 關鍵字

2.方法之間不要用逗號分隔,否則會報錯

通過以下代碼可以看出類實質上就是一個函數。類自身指向的就是構造函數。所 以可以認爲 ES6 中的類其實就是構造函數的另外一種寫法!

console.log(typeof Person);//function
console.log(Person===Person.prototype.constructor);//true

以下代碼說明構造函數的 prototype 屬性,在 ES6 的類中依然存在着。

console.log(Person.prototype);//輸出的結果是一個對象

實際上類的所有方法都定義在類的 prototype 屬性上。 一起來證明一下:

Person.prototype.say=function(){//定義與類中相同名字的方法。成功實現了覆蓋!
 return "我是來證明的,你叫" + this.name+"今年"+this.age+"歲了";
}
var obj=new Person("yinchengnuo",23);
console.log(obj.say());//我是來證明的,你叫 yinchengnuo今年 23 歲了

當然也可以通過 prototype 屬性對類添加方法。如下:

Person.prototype.laodeng=function(){
    return "我是通過 prototype 新增加的方法,名字叫 yyy";
}
var obj=new Person("ycn",18);
console.log(obj.laodeng());//我是通過 prototype 新增加的方法,名字叫yyy

還可以通過 Object.assign 方法來爲對象動態增加方法:

Object.assign(Person.prototype,{
    getName:function(){
        return this.name;
    },
    getAge:function(){
        return this.age;
    }
})
var obj=new Person("ycn",1);
console.log(obj.getName());//ycn
console.log(obj.getAge());//1

constructor 方法是類的構造函數的默認方法,通過 new 命令生成對象實例時, 自動調用該方法。

class Box{
    constructor(){
        console.log("啦啦啦,今天天氣好晴朗");//當實例化對象時該行代碼會執行。
    }
}
var obj=new Box();

constructor 方法如果沒有顯式定義,會隱式生成一個 constructor 方法。所以即 使你沒有添加構造函數,構造函數也是存在的。constructor 方法默認返回實例對 象 this,但是也可以指定 constructor 方法返回一個全新的對象,讓返回的實例對 象不是該類的實例。

class Desk{
    constructor(){
        this.xixi="666";
    }
}
class Box{
    constructor(){
        return new Desk();// 這裏沒有用 this,直接返回一個全新的對象
    }
}
var obj=new Box();
console.log(obj.xixi);//666

constructor 中定義的屬性可以稱爲實例屬性(即定義在 this 對象上),constructor 外聲明的屬性都是定義在原型上的,可以稱爲原型屬性(即定義在 class 上)。 hasOwnProperty()函數用於判斷屬性是否是實例屬性。其結果是一個布爾值, true 說明是實例屬性,false 說明不是實例屬性。in 操作符會在通過對象能夠訪 問給定屬性時返回 true,無論該屬性存在於實例中還是原型中。

class Box{
    constructor(num1,num2){
        this.num1 = num1;
        this.num2=num2;
    }
    sum(){
        return num1+num2;
    }
}
var box=new Box(12,88);
console.log(box.hasOwnProperty("num1"));//true
console.log(box.hasOwnProperty("num2"));//true
console.log(box.hasOwnProperty("sum"));//false
console.log("num1" in box);//true
console.log("num2" in box);//true
console.log("sum" in box);//true
console.log("say" in box);//false

類的所有實例共享一個原型對象,它們的原型都是 Person.prototype,所以 proto 屬性是相等的。

class Box{
    constructor(num1,num2){
        this.num1 = num1;
        this.num2=num2;
    }
    sum(){
        return num1+num2;
    }
}
//box1 與 box2 都是 Box 的實例。它們的__proto__都指向 Box 的 prototype
var box1=new Box(12,88);
var box2=new Box(40,60);
console.log(box1.__proto__===box2.__proto__);//true

由此,也可以通過 proto 來爲類增加方法。使用實例的 proto 屬性改寫原型,會 改變 Class 的原始定義,影響到所有實例,所以不推薦使用!

class Box{
    constructor(num1,num2){
        this.num1 = num1;
        this.num2=num2;
    }
    sum(){
        return num1+num2;
    }
}
var box1=new Box(12,88);
var box2=new Box(40,60);
box1.__proto__.sub=function(){
    return this.num2-this.num1;
}
console.log(box1.sub());//76
console.log(box2.sub());//20

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

//ES5 可以先使用再定義,存在變量提升
new A();
function A(){
}
//ES6 不能先使用再定義,不存在變量提升 會報錯
new B();//B is not defined
class B{
}

這是我們對 ES6 中 class(類)的概念的瞭解,既然提出了類,這個類又是怎麼實現的呢?

在這首先要了解一下類的繼承:有三種屬性,公有屬性,私有屬性, 靜態屬性(Es7)/靜 態類(Es6)

繼承公有屬性:

function Parent(){
    this.name = 'parent';
}
new Parent();//this 指向當前實例
Parent() //this 指向 window
function Child(){
    this.age = 9;
    Parent.call(this);//相當於 this.name = 'parent' //繼承父類屬性
}

繼承父類屬性:

function Parent(){
    this.name = 'parent';
}
Parent.prototype.eat = function(){
    console.log('eat')
}
function Child(){
    this.age = 9;
    Parent.call(this);//相當於 this.name = 'parent' //繼承父類屬性
}
Child.prototype.smoking = function(){
    console.log('smoking')
}

Child.prototype = Parent.prototype;//這個不叫繼承
//因爲這樣如果改變 Child.prototype 加屬性,Parent.prototype 的實例也會有這個屬性,,
此時這兩者屬於兄弟關係
Child.prototype._proto_ = Parent.prototype // 方法一
//object.create
Child.prototype = object.create(Parent.prototype); // 常用,方法二    
function create(parentPrototype,props){
    function Fn(){}
    Fn.prototype = parentPrototype;
    let fn = new Fn();
    for(let key in props){
        Object.defineProperty(fn,key,{
            ...props[key],
            enumerable:true
        });
    }
    return fn();
}
Child.prototype = create(Parent.prototype,{
    constructor: {
        value:Child
    }
})

繼承公有屬性和私有屬性:

之前的繼承都是原型鏈的繼承,聖盃模式 在 Class 中 的繼承,有什麼不一樣了呢, 在 ES5 中真正的繼承應該是什麼樣呢:

function Animal (weight, name) {
    this.name = name
    this.age = 0
    this.weight = 10
}
Animal.prototype.eat = function () {
    console.log('animal eat')
}
Animal.prototype.drink = function () {
    console.log('a d')
}
function Person(name, weight) {
    Animal.call(this, weight, name)
}
Person.prototype = Object.create(Animal.prototype)
Person.prototype.eat = function () {
    console.log('Person eat')
}
 var p = new Person('dxm', 70) 

這裏有兩點,要注意的地方,首先是

1 父類構造函數 爲什麼要 call

2 原型需要重寫。 在 class 中繼承就變的簡單了許多,同比上面的例子通過 class 來實現

class Animal {
    constructor(name) {
        this.name = name
    }
    eat() {
        console.log('a e')
    }
    drink() {
        console.log('a d')
    }
}
class Person extends Animal {
    constructor(name) {
        super(name)
    }
    eat() {
        console.log('p e')
    }
}

在這裏實現之後,多了兩個大家不認識的次, extends super extends 後面跟的就是我們要繼承的內容

super 有些注意點 子類也叫派生類,必須在 constructor 中調用 super 函數,要不無法使用 this, 準確的說子 類是沒有 this 的 就算是繼承之後什麼也不寫,默認的也會填上去

class Person extends Animal {
    constructor(...arg) {
        super(...arg)    
    }
}

在調用 super 之前不可以使用 this 回報錯 super 可以作爲對象使用,指向的是 父類的原型 super 調用父類方法時,會綁定子類的 this。

類只能 new:

class Parent{
    //私有屬性
    constructor(){
        this.name = 'parent',
        this.age = '40'
    }
    //公有屬性,原型上的方法
    eat(){
        console.log('eat')
    }
    //靜態方法/屬性 es6/es7
    //屬於類上的方法 Child.a()
    static b(){
        return 2
    }
}
new Parent();
class Child extends Parent{ //繼承父親的私有和公有
    //私有屬性
    constructor(){
        super() // 相當於 Parent.call(this)
        this.name = 'child'
    }
    //公有屬性,原型上的方法
    smoking(){
        console.log('smoking')
    }
    //靜態方法/屬性 es6/es7
    //屬於類上的方法 Child.a()
    static a(){
        return 1
    }
}
let child = new Child();
console.log(child.name,child.age,child.eat(),child.smoking,Child.b())
//類可以繼承公有,私有和靜態
//父類的構造函數中返回類一個引用類型,會把這個引用類型作爲子類的 this

我們首先寫一個創建類的函數:

//檢測實例是不是 new 出來的
function _classCallCheck(instance,constructor){
    if(!(instance instanceof constructor)){
        throw new Error('Class constructor Child cannot be invoked without new')
    }
}

//constructor 構造函數
//prprotoPropertys 構造函數原型
//staticPropertys 靜態方法的描述
function definePropertys(target,arr){
    for(let i=0;i<arr.length;i++){
        Object.defineProperty(target,arr[i].key,{
            ...arr[i],
            configurable : true,
            enumerable : true,
            writable:true
        })
    }
}
function _createClass(constructor,protoPropertys,staticPropertys){
    if(protoPropertys.length > 0){
        definePropertys(constructor.prototype,protoPropertys)
    }
    if(staticPropertys.length > 0){
        definePropertys(constructor,staticPropertys)
    }
}
let Parent = function(){
    //寫邏輯
    function P(){
        _classCallCheck(this,P)
        this.name = 'parent';
        //return {}
    }
    _createClass(P,//屬性描述器
        [
            {
                key: 'eat',
                value: function () {
                    console.log('喫')
                }
            }
        ],
        [
            {
                key:'b',
                value:function () {
                    return 2;
                }
            }
        ]
    )
    return P;
}()
let p = new Parent();
console.log(p.eat())

這樣我們的類就創建完成了

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章