javascript
javascript 本地对象和内置对象_JavaScript 的面向对象
圖片來源于 DigitalOcean
1. 什么是類
在說 JavaScript 的面向?qū)ο蟮膶崿F(xiàn)方法之前,我們先來看面向?qū)ο缶幊痰囊粋€核心概念——類(class)。類是對擁有同樣屬性(property)和行為的一系列對象(object)的抽象。?這里說的“行為”,在基于類的面向?qū)ο蟮恼Z言中通常叫做類的方法(method)。而在 JavaScript 里,函數(shù)也是“一等公民”,可以被直接賦值給一個變量或一個對象的屬性,因此在本文后續(xù)的討論中,把“行為”也歸入“屬性”的范疇。
2. JavaScript 對“類”的實現(xiàn)
JavaScript 一開始是被設(shè)計成在網(wǎng)頁上對表單進(jìn)行校驗或者對網(wǎng)頁上的元素進(jìn)行操縱的一種腳本語言,沒有像 C++ 和 Java 那樣用?class、private、protected?等關(guān)鍵字來定義類的語法。JavaScript 采用的是一種更簡單的實現(xiàn)方式:既然類就是擁有同樣屬性的一系列對象,那么只要通過一種方式能使某一些對象擁有同樣的屬性就行了。
JavaScript 規(guī)定每一個對象都可以有一個原型([[prototype]]?內(nèi)部屬性)。(在實現(xiàn) ECMAScript 5.1 規(guī)范以前,除了?Object.prototype?以外的對象都必須有一個原型。)每個對象都“共享”其原型的屬性:在訪問一個對象的屬性時,如果該對象本身沒有這個屬性,則 JavaScript 會繼續(xù)試圖訪問其原型的屬性。這樣,就可以通過指定一些對象的原型來使這些對象都擁有同樣的屬性。從而我們可以這樣認(rèn)為,在 JavaScript 中,以同一個對象為原型的對象就是屬于同一個類的對象。
2.1 JavaScript 中對象的原型的指定方式
那么 JavaScript 中的對象與其原型是怎樣被關(guān)聯(lián)起來的呢?或者說,JavaScript 中的對象的原型是怎樣被指定的呢?
2.1.1 new 操作符
JavaScript 有一個 new 操作符(operator),它基于一個函數(shù)來創(chuàng)建對象。這個用 new 操作符創(chuàng)建出來的對象的原型就是 new 操作符后面的函數(shù)(稱為“構(gòu)造函數(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 對象。關(guān)于 Object.create 方法的實現(xiàn)細(xì)節(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)?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 的類,它的構(gòu)造函數(shù)接受一個字符串的名稱,還一個方法 introduceSelf 會輸出自己的名字。
//?----====?類定義開始?====----function?Person(name)?{
????this.name?=?name;
}
Person.prototype.introduceSelf?=?function?()?{
????console.log("My?name?is?"?+?this.name);
};
//?----====?類定義結(jié)束?====----
//?下面實例化一個?Person?類的對象
var?someone?=?new?Person("Tom");
//?此時?someone?的原型為?Person.prototype
someone.introduceSelf();?//?輸出?My?name?is?Tom
如果轉(zhuǎn)換為 ECMAScript 6 引入的類聲明(class declaration)語法,則上述 Person 類的定義等同于:
class?Person?{????constructor(name)?{
????????this.name?=?name;
????}
????introduceSelf()?{
????????console.log("My?name?is?"?+?this.name);
????}
}
2.3 對“構(gòu)造函數(shù)”的再思考
在上面的例子中,假如我們不通過?Person.prototype?來定義 introduceSelf 方法,而是在構(gòu)造函數(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 構(gòu)造函數(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?屬性被覆蓋,我們?nèi)匀豢梢酝ㄟ^?`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?屬性是不同的對象,分別占用不同的內(nèi)存空間。
//?因此這種方法會造成內(nèi)存空間的浪費。
delete?a.introduceSelf;
a.introduceSelf();?//?會拋?TypeError
b.introduceSelf?=?function?()?{
????console.log("I?am?a?pig");
};
//?此時?b?的行為已經(jīng)與?Person2?類規(guī)定的脫節(jié),對象?a?和對象?b?看起來已經(jīng)不像是同一個類的對象了
但是這種方法也不是一無是處。例如我們需要利用閉包來實現(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?屬性已經(jīng)被封裝起來,在?Person?構(gòu)造函數(shù)以外的地方無法訪問
//?name?相當(dāng)于?Person?類的一個私有(private)成員屬性
3. JavaScript 的類繼承
類的繼承實際上只需要實現(xiàn):
1. 子類的對象擁有父類定義的所有成員屬性;
2. 子類的任何一個構(gòu)造函數(shù)都必須在開頭調(diào)用父類的構(gòu)造函數(shù)。
實現(xiàn)第 2 點的方式比較直觀。而怎樣實現(xiàn)第 1 點呢?其實我們只需要讓子類的構(gòu)造函數(shù)的 prototype 屬性?(子類的實例對象的原型)?的原型是父類的構(gòu)造函數(shù)的 prototype 屬性?(父類的實例對象的原型),簡而言之就是:把父類實例的原型作為子類實例的原型的原型。這樣在訪問子類的實例對象的屬性時,JavaScript 會沿著原型鏈找到子類規(guī)定的成員屬性,再找到父類規(guī)定的成員屬性。而且子類可在子類構(gòu)造函數(shù)的 prototype 屬性中重載(override)父類的成員屬性。
3.1 代碼示例
下面給出一個代碼示例,定義一個 ChinesePerson 類繼承上文中定義的 Person 類:
function?ChinesePerson(name)?{????Person.apply(this,?name);?//?調(diào)用父類的構(gòu)造函數(shù)
}
ChinesePerson.prototype.greet?=?function?(other)?{
????console.log(other?+?"你好");
};
Object.setPrototypeOf(ChinesePerson.prototype,?Person.prototype);?//?將?Person.prototype?設(shè)為?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?的重載并不會影響父類的實例對象
文中的兔紙圖片由作者提供,
推薦閱讀:
前端項目框架搭建隨筆 --- Webpack 踩坑記
freeCodeCamp 高級算法
[譯文] 如何在 JavaScript 中更好地使用數(shù)組
與50位技術(shù)專家面對面20年技術(shù)見證,附贈技術(shù)全景圖總結(jié)
以上是生活随笔為你收集整理的javascript 本地对象和内置对象_JavaScript 的面向对象的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python中系列的含义_python中
- 下一篇: java epoll select_Ja