ES6-class關鍵字與繼承

class關鍵字

js語言中,生成實例對象的傳統方法就是通過構造函數:

function Student(name, number) {
	this.name = name
	this.number = number
}
Student.prototype.sayHi = function() {
	console.log('姓名 ' + this.name + ', 學號' + this.number)
}

var xialuo = new Student('夏洛', 100)
console.log(xialuo.name) // 夏洛
console.log(xialuo.number) // 100
xialuo.sayHi() // 姓名 夏洛 , 學號 100

ES6提供了class(類)這個概念,作爲對象的模板。通過class關鍵字,可以定義類
基本上,ES6的class可以看做知識一個語法糖,它的絕大部分功能,ES5都可以看到,新的class寫法讓對象原型的寫法更加清晰,上面的代碼用class改寫:

class Student {
  constructor(name, number) {
    this.name = name
    this.number = number
  }
  sayHi() {
    console.log(`姓名 ${this.name} , 學號 ${this.number}`)
  }
}

const xialuo = new Student('夏洛', 100)
console.log(xialuo.name) // 夏洛
console.log(xialuo.number) // 100
xialuo.sayHi() // 姓名 夏洛 , 學號 100

class定義了一個Student類,他裏面有constructor方法,這就是構造方法;而this關鍵字則代表實例對象
也就是說,ES5的構造函數Student,對應ES6的Student類的構造方法; Student類除了構造方法,還定義了一個sayHi方法,定義類的方法的時候,方法之間不需要逗號分隔
構造函數的prototype屬性,在ES6的類上繼續存在,實際上,類的所有方法都定義在類的prototype屬性上面;

constructor方法

constructor方法是類的默認方法,通過new 命令生成對象實例時,自動調用該方法,一個類必須有constructor方法,如果沒有顯示定義,一個空的constructor方法會被默認添加

constructor方法默認返回實例對象(即this)

class Student{}

// 等同於 定義了一個空的類Student,JavaScript 引擎會自動爲它添加一個空的constructor方法。

class Student{
	constructor() {}
}

類的實例對象

生成的實例對象的寫法,與ES一樣都是使用new命令,實例的屬性除非顯示定義在其本身(即定義在this對象上),否則都是定義在原型上

class Student {
  constructor(name, number) {
    this.name = name
    this.number = number
  }
  sayHi() {
    console.log(`姓名 ${this.name} , 學號 ${this.number}`)
  }
}
const xialuo = new Student('夏洛', 100)
console.log(xialuo.name) // 夏洛
console.log(xialuo.number) // 100
xialuo.sayHi() // 姓名 夏洛 , 學號 100

xialuo.hasOwnProperty('name') // true
xialuo.hasOwnProperty('number') // true
xialuo.hasOwnProperty('sayHi') // true

name和number都是實例對象Student 自身的屬性(因爲定義在this變量上),所以hasOwnProperty方法返回true,而sayHi是原型對象的屬性(因爲定義在Student 類上),所以hasOwnProperty()方法返回false,這些都是ES5的行爲保持一致;

不存在變量提升

這個和ES5完全不一樣

new Student() // Cannot access 'Student' before initialization
class Student{}

Foo類使用在前,定義在後,這樣會報錯,因爲 ES6 不會把類的聲明提升到代碼頭部。這種規定的原因與下文要提到的繼承有關,必須保證子類在父類之後定義。

Class 的取值函數(getter)和存值函數(setter)

與 ES5 一樣,在“類”的內部可以使用get和set關鍵字,對某個屬性設置存值函數和取值函數,攔截該屬性的存取行爲。

class Student {
  constructor(name, number) {
    this.name = name;
    this.number = number;
  }
  get like() {
    return "like";
  }
  set like(value) {
    console.log(`set ${value}`);
  }
  sayHi() {
    console.log(`姓名 ${this.name} , 學號 ${this.number}`);
  }
}
const xialuo = new Student("夏洛", 100);
xialuo.like; // 'like'
xialuo.like = "math"; // 'set math'  'math'

like屬性有對應的存值函數和取值函數,因此賦值和讀取行爲都被自定義了。

class 的靜態方法

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

ES6 明確規定,Class 內部只有靜態方法,沒有靜態屬性。

class Student{
	static sayHi(){
		console.log('hi')
	}
}
Student.sayHi() // 'hi'
const xialuo = new Studeng()
xialuo.sayHi() // xialuo.sayHi is not a function

Student類的sayHi方法前有static關鍵字,表明該方法是一個靜態方法,可以直接在Foo類上調用(Student.sayHi()),而不是在Student類的實例上調用。如果在實例上調用靜態方法,會拋出一個錯誤,表示不存在該方法。
注意,如果靜態方法包含this關鍵字,這個this指的是類,而不是實例。

父類的靜態方法,可以被子類繼承。

class People{
  static sayHi() {
    return 'hi';
  }
}

class Student extends People{
}

Student .sayHi() // 'hi'

繼承

Class可以通過extends關鍵字實現繼承,這比ES5的通過修改原型鏈實現繼承,要清晰和方便很多;

通過extends關鍵字,子類繼承了父類的所有屬性和方法

// 父類
class People {
  constructor(name) {
    this.name = name
  }
  eat() {
    console.log(`${this.name} eat something`)
  }
}

// 子類
class Student extends People {
  constructor(name, number) {
    super(name) // 調用父類People的構造函數constructor(name)
    this.number = number
  }
  sayHi() {
    console.log(`姓名 ${this.name} , 學號 ${this.number}`)
  }
}

const xialuo = new Student('夏洛', 100)
console.log(xialuo.name) // 夏洛
console.log(xialuo.number) // 100
xialuo.sayHi() // 姓名 夏洛 , 學號 100

super 關鍵字

super這個關鍵字,既可以當作函數使用,也可以當作對象使用,

第一情況是:super當作函數調用時,代表父類的構造函數,ES6要求,子類的構造函數必須執行一個super函數;

第二種情況,super作爲對象時,在普通方法中,指向父類的原型對象,在靜態方法中,指向父類;ES6 規定,通過super調用父類的方法時,super會綁定子類的this

子類必須在constructor方法中調用super方法,否則新建實例時會報錯,這是因爲子類沒有自己的this對象,而是繼承父類的this對象,然後對其進行加工,如果不調用super方法,子類就得不到this對象;

super雖然代表了父類Father的構造函數,但是返回的是子類Student的實例,即super內部的this指向的是People ,因此super()在這裏相當於People .constructor.call(this);
而且作爲函數時,super()只能用在子類的構造函數中,在其他地方會報錯;

// 父類
class People {
  constructor(name) {
    this.name = name
  }
  eat() {
    console.log(`${this.name} eat something`)
  }
}

// 子類
class Student extends People {
  constructor(name, number) {
    super(name) // 調用父類People的構造函數constructor(name)
    this.number = number
  }
  sayHi() {
    console.log(`姓名 ${this.name} , 學號 ${this.number}`)
  }
}

const xialuo = new Student('夏洛', 100)
console.log(xialuo.name) // 夏洛
console.log(xialuo.number) // 100
xialuo.sayHi() // 姓名 夏洛 , 學號 100

// 子類 
class Teacher extends People {
  
  constructor(name, subject) {
    super(name) // 調用父類People的構造函數
    this.subject = subject
  }
  teach () {
    console.log(`${this.name} 教授 ${this.subject}`)
  }
}
const wanglaoshi = new Teacher('王老師', '語文')
console.log(wanglaoshi.name) // 王老師
console.log(wanglaoshi.subject) // 語文
wanglaoshi.teach() // 王老師 教授 語文

類的prototype屬性和proto屬性

  • 大多數瀏覽器的ES5實現之中,每一個對象都有__proto__屬性,指向對應的構造函數的prototype屬性
  • 每個class都有顯示原型prototype
  • 每個實例都有隱式原型__proto__
  • 實例的__proto__指向對應class的prototype

基於原型的執行規則

  • 實例獲取屬性xialuo.name或執行方法xialuo.sayHi()時
  • 先有自身屬性和方法中尋找
  • 如果找不到會去__proto__中查找,最終可以去到Object.prototype

class的原型本質

  • class只是ES6語法規範,由ECMA委員會發布
  • ECMA只規定語法規則,即我們代碼的書寫規範,不規定如何實現
    在這裏插入圖片描述
// 父類
class People {
  constructor(name) {
    this.name = name
  }
  eat() {
    console.log(`${this.name} eat something`)
  }
}

// 子類
class Student extends People {
  constructor(name, number) {
    super(name) // 調用父類People的構造函數constructor(name)
    this.number = number
  }
  sayHi() {
    console.log(`姓名 ${this.name} , 學號 ${this.number}`)
  }
}

const xialuo = new Student('夏洛', 100)
console.log(xialuo.name) // 夏洛
console.log(xialuo.number) // 100
xialuo.sayHi() // 姓名 夏洛 , 學號 100

xialuo.__proto__ === Student.prototype // true
Student.prototype.__proto__ === People.prototype// true
People.prototype.__proto__ === Object.prototype // true
xialuo instanceof Student // true
xialuo instanceof People // true
xialuo instanceof Object // true

通過class手寫一個簡易的jQuery,考慮插件和擴展性

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>jQuery-test</title>
  </head>
  <body>
    <p>這是個p標籤1</p>
    <p>這是個p標籤2</p>
    <p>這是個p標籤3</p>
    <script>
      class jQuery {
        constructor(selector) {
          const result = document.querySelectorAll(selector);
          const length = result.length;
          for (let i = 0; i < length; i++) {
            this[i] = result[i];
          }
          this.length = length;
          this.selector = selector;
        }
        get(index) {
          return this[index];
        }
        each(fn) {
          for (let i = 0; i < this.length; i++) {
            const elem = this[i];
            fn(elem);
          }
        }
        on(type, fn) {
          return this.each((elem) => {
            elem.addEventListener(type, fn, false);
          });
        }
      }
      // const $p = new jQuery('p')
      // $p.get(1)
      // $p.each((elem) => console.log(elem.nodeName))
      // $p.on('click', () => alert('clicked'))

      // 插件
      jQuery.prototype.dialog = function (info) {
        alert(info);
      };
      // const $p = new jQuery('p')
      // $p.dialog('dialog')

      // "造輪子"
      class myJQuery extends jQuery {
        constructor(selector) {
          super(selector);
        }
        // 擴展自己的方法
        addClass(className) {
          this.each((elem) => elem.classList.add(className));
        }
        style(data) {
          // 相關操作
        }
      }
      // const $p = new myJQuery('p')
      // $p.addClass('test')
    </script>
  </body>
</html>

謝謝你閱讀到了最後
期待你,點贊、評論、交流

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