日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

JavaScript 的面向對象

 西北望msm66g9f 2020-05-06

圖片來源于 DigitalOcean

1. 什么是類

在說 JavaScript 的面向對象的實現(xiàn)方法之前,我們先來看面向對象編程的一個核心概念——類(class)。類是對擁有同樣屬性(property)和行為的一系列對象(object)的抽象。 這里說的“行為”,在基于類的面向對象的語言中通常叫做類的方法(method)。而在 JavaScript 里,函數(shù)也是“一等公民”,可以被直接賦值給一個變量或一個對象的屬性,因此在本文后續(xù)的討論中,把“行為”也歸入“屬性”的范疇。

2. JavaScript 對“類”的實現(xiàn)

JavaScript 一開始是被設計成在網(wǎng)頁上對表單進行校驗或者對網(wǎng)頁上的元素進行操縱的一種腳本語言,沒有像 C++ 和 Java 那樣用 class、privateprotected 等關鍵字來定義類的語法。JavaScript 采用的是一種更簡單的實現(xiàn)方式:既然類就是擁有同樣屬性的一系列對象,那么只要通過一種方式能使某一些對象擁有同樣的屬性就行了。

JavaScript 規(guī)定每一個對象都可以有一個原型([[prototype]] 內部屬性)。(在實現(xiàn) ECMAScript 5.1 規(guī)范以前,除了 Object.prototype 以外的對象都必須有一個原型。)每個對象都“共享”其原型的屬性:在訪問一個對象的屬性時,如果該對象本身沒有這個屬性,則 JavaScript 會繼續(xù)試圖訪問其原型的屬性。這樣,就可以通過指定一些對象的原型來使這些對象都擁有同樣的屬性。從而我們可以這樣認為,在 JavaScript 中,以同一個對象為原型的對象就是屬于同一個類的對象。

2.1 JavaScript 中對象的原型的指定方式

那么 JavaScript 中的對象與其原型是怎樣被關聯(lián)起來的呢?或者說,JavaScript 中的對象的原型是怎樣被指定的呢?

2.1.1 new 操作符

JavaScript 有一個 new 操作符(operator),它基于一個函數(shù)來創(chuàng)建對象。這個用 new 操作符創(chuàng)建出來的對象的原型就是 new 操作符后面的函數(shù)(稱為“構造函數(shù)”)的 prototype 屬性。例如:

var a = {'aa'1};
function B() {}
B.prototype = a;
var b = new B();

此時 b 對象的原型就是 a 對象。我在另一篇文章中介紹了 new 操作符的具體實現(xiàn)邏輯,供大家參考。

2.1.2 Object.create 方法

Object.create 方法直接以給定的對象作為原型創(chuàng)建對象。一個代碼例子:

var a = {'aa'1};
var b = Object.create(a);

此時 b 對象的原型就是 a 對象。關于 Object.create 方法的實現(xiàn)細節(jié),大家可參考我的這篇文章。

2.1.3 Object.setPrototypeOf 方法

new 操作符和 Object.create 方法都是在創(chuàng)建一個對象的同時就指定其原型。而 Object.setPrototypeOf 方法則是指定一個已被創(chuàng)建的對象的原型。代碼例子:

var a = {'aa'1};
var b = Object.create(a);
// 此時 b 的原型是 a
var c = {'cc'2};
Object.setPrototypeOf(b, c);
// 此時 b 的原型變?yōu)?nbsp;c 了

2.1.4 隱式指定

數(shù)字、布爾值、字符串、數(shù)組和函數(shù)在 JavaScript 中也是對象,而它們的原型是被 JavaScript 隱式指定的:

1. 數(shù)字(例如 1、1.1、NaN、Infinity)的原型是 Number.prototype;

2. 布爾值(true 和 false)的原型為 Boolean.prototype

3. 字符串(例如 ''、'abc')的原型為 String.prototype;

4. 函數(shù)(例如 function () {}、function (a) { return a + '1'; }) 的原型為 Function.prototype

5. 數(shù)組(如 []、[1, '2'])的原型是 Array.prototype;

6. 用花括號直接定義的對象(如 {}{'a': 1})的原型是 Object.prototype。

2.2 JavaScript 中定義類的代碼示例

下面給出定義一個類的一段 JavaScript 代碼的示例。它定義一個名為 Person 的類,它的構造函數(shù)接受一個字符串的名稱,還一個方法 introduceSelf 會輸出自己的名字。

// ----==== 類定義開始 ====----
function Person(name{
    this.name = name;
}
Person.prototype.introduceSelf = function () {
    console.log('My name is ' + this.name);
};
// ----==== 類定義結束 ====----
// 下面實例化一個 Person 類的對象
var someone = new Person('Tom');
// 此時 someone 的原型為 Person.prototype
someone.introduceSelf(); // 輸出 My name is Tom

如果轉換為 ECMAScript 6 引入的類聲明(class declaration)語法,則上述 Person 類的定義等同于:

class Person {
    constructor(name) {
        this.name = name;
    }
    introduceSelf() {
        console.log('My name is ' + this.name);
    }
}

2.3 對“構造函數(shù)”的再思考

在上面的例子中,假如我們不通過 Person.prototype 來定義 introduceSelf 方法,而是在構造函數(shù)中給對象指定一個 introduceSelf 屬性:

function Person(name{
    this.name = name;
    this.introduceSelf = function () {
        console.log('My name is ' + this.name);
    };
}
var someone = new Person('Tom');
someone.introduceSelf(); // 也會輸出 My name is Tom

雖然這種方法中,通過 Person 構造函數(shù) new 出來的對象也都有 introduceSelf 屬性,但這里 introduceSelf 變成了 someone 自身的一個屬性而不是 Person 類的共有的屬性:

function Person1(name{
    this.name = name;
}
Person1.prototype.introduceSelf = function () {
    console.log('My name is ' + this.name);
};
var a = new Person1('Tom');
var b = new Person1('Jerry');
console.log(a.introduceSelf === b.introduceSelf); // 輸出 true
delete a.introduceSelf;
a.introduceSelf(); // 仍然會輸出 My name is Tom,因為 introduceSelf 不是 a 自身的屬性,不會被 delete 刪除
b.introduceSelf = function () {
    console.log('I am a pig');
};
Person1.prototype.introduceSelf.call(b); // 輸出 My name is Jerry
// 即使 b 的 introduceSelf 屬性被覆蓋,我們仍然可以通過 `Person1.prototype` 來讓 b 執(zhí)行 Person1 類規(guī)定的行為。
function Person2(name{
    this.name = name;
    this.introduceSelf = function () {
        console.log('My name is ' + this.name);
    };
}
a = new Person2('Tom');
b = new Person2('Jerry');
console.log(a.introduceSelf === b.introduceSelf); // 輸出 false
// a 的 introduceSelf 屬性與 b 的 introduceSelf 屬性是不同的對象,分別占用不同的內存空間。
// 因此這種方法會造成內存空間的浪費。
delete a.introduceSelf;
a.introduceSelf(); // 會拋 TypeError
b.introduceSelf = function () {
    console.log('I am a pig');
};
// 此時 b 的行為已經與 Person2 類規(guī)定的脫節(jié),對象 a 和對象 b 看起來已經不像是同一個類的對象了

但是這種方法也不是一無是處。例如我們需要利用閉包來實現(xiàn)對 name 屬性的封裝時:

function Person(name{
    this.introduceSelf = function () {
        console.log('My name is ' + name);
    };
}
var someone = new Person('Tom');
someone.name = 'Jerry';
someone.introduceSelf(); // 輸出 My name is Tom
// introduceSelf 實際用到的 name 屬性已經被封裝起來,在 Person 構造函數(shù)以外的地方無法訪問
// name 相當于 Person 類的一個私有(private)成員屬性

3. JavaScript 的類繼承

類的繼承實際上只需要實現(xiàn):

1. 子類的對象擁有父類定義的所有成員屬性;

2. 子類的任何一個構造函數(shù)都必須在開頭調用父類的構造函數(shù)。

實現(xiàn)第 2 點的方式比較直觀。而怎樣實現(xiàn)第 1 點呢?其實我們只需要讓子類的構造函數(shù)的 prototype 屬性 (子類的實例對象的原型) 的原型是父類的構造函數(shù)的 prototype 屬性 (父類的實例對象的原型),簡而言之就是:把父類實例的原型作為子類實例的原型的原型。這樣在訪問子類的實例對象的屬性時,JavaScript 會沿著原型鏈找到子類規(guī)定的成員屬性,再找到父類規(guī)定的成員屬性。而且子類可在子類構造函數(shù)的 prototype 屬性中重載(override)父類的成員屬性

3.1 代碼示例

下面給出一個代碼示例,定義一個 ChinesePerson 類繼承上文中定義的 Person 類:

function ChinesePerson(name{
    Person.apply(this, name); // 調用父類的構造函數(shù)
}
ChinesePerson.prototype.greet = function (other{
    console.log(other + '你好');
};
Object.setPrototypeOf(ChinesePerson.prototype, Person.prototype); // 將 Person.prototype 設為 ChinesePerson.prototype 的原型

var someone = new ChinesePerson('張三');
someone.introduceSelf(); // 輸出“My name is 張三”
someone.greet('李四'); // 輸出“李四你好”

上述定義 ChinesePerson 類的代碼改用 ECMAScript 6 的類聲明語法的話,就變成:

class ChinesePerson extends Person {
    constructor(name) {
        super(name);
    }

    greet(other) {
        console.log(other + '你好');
    }
}

3.1.1 重載父類成員屬性的代碼示例

你會不會覺得上面代碼示例中,introduceSelf 輸出半英文半中文挺別扭的?那我們讓 ChinesePerson 類重載 introduceSelf 方法就好了:

ChinesePerson.prototype.introduceSelf = function () {
    console.log('我叫' + this.name);
};
var someone = new ChinesePerson('張三');
someone.introduceSelf(); // 輸出“我叫張三”

var other = new Person('Ba Wang');
other.introduceSelf(); // 輸出 My name is Ba Wang
// ChinesePerson 的重載并不會影響父類的實例對象

    本站是提供個人知識管理的網(wǎng)絡存儲空間,所有內容均由用戶發(fā)布,不代表本站觀點。請注意甄別內容中的聯(lián)系方式、誘導購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權內容,請點擊一鍵舉報。
    轉藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多