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>
謝謝你閱讀到了最後
期待你,點贊、評論、交流