1.對象的概念
首先樹立一個概念:萬物皆對象。JavaScript 中的所有事物都是對象,包括字符串、數(shù)值、數(shù)組、函數(shù)等等。
js中的對象分為內建對象和自定義對象。內建對象是指String、Date、Array等js中內置定義的對象類型。除內建對象外的其他對象為自定義對象。
對象,是帶有屬性和方法的特殊數(shù)據(jù)類型。
2.面向對象
使用對象時,只關注對象提供的功能,不關注其內部細節(jié)。比如電腦——有鼠標、鍵盤,我們只需要知道怎么使用鼠標,敲打鍵盤即可,不必知道為何點擊鼠標可以選中、敲打鍵盤是如何輸入文字以及屏幕是如何顯示文字的??傊覀儧]必要知道其具體工作細節(jié),只需知道如何使用其提供的功能即可,這就是面向對象。
JavaScript的面向對象編程和大多數(shù)其他語言如Java、C#的面向對象編程都不太一樣。如果你熟悉Java或C#,很好,你一定明白面向對象的兩個基本概念:
類:類是對象的類型模板,例如,定義Student類來表示學生,類本身是一種類型,Student表示學生類型,但不表示任何具體的某個學生;
實例:實例是根據(jù)類創(chuàng)建的對象,例如,根據(jù)Student類可以創(chuàng)建出xiaoming、xiaohong、xiaojun等多個實例,每個實例表示一個具體的學生,他們全都屬于Student類型。
所以,類和實例是大多數(shù)面向對象編程語言的基本概念。
不過,在JavaScript中,這個概念需要改一改。JavaScript不區(qū)分類和實例的概念,而是通過原型(prototype)來實現(xiàn)面向對象編程。有關原型的概念和原理下文會提到。
3.對象的基本構成
訪問名稱為ObjectName的對象的屬性:
ObjectName.propertyName
訪問名稱為ObjectName的對象的方法并直接調用該方法:
ObjectName.methodName()
4.對象的創(chuàng)建
原型是指當我們想要創(chuàng)建xiaoming這個具體的學生時,我們并沒有一個Student類型可用。那怎么辦?恰好有這么一個現(xiàn)成的對象:
var robot = {? ?
????name:'Robot',
? ? height:1.6,? ?
????run:function(){
????????console.log(this.name +' is running...');? ?
????}
};
我們看這個robot對象有名字,有身高,還會跑,有點像小明,干脆就根據(jù)它來“創(chuàng)建”小明得了!
于是我們把它改名為Student,然后創(chuàng)建出xiaoming:
var Student = {? ?
????name:'Robot',? ?
????height:1.2,? ?
????run:function()? {
????????console.log(this.name +' is running...');? ?
????}
};
var xiaoming = { name: '小明' };
xiaoming.__proto__ = Student;? //指向原型對象
注意最后一行代碼把xiaoming的原型指向了對象Student,看上去xiaoming仿佛是從Student繼承下來的:
xiaoming.name;? //'小明'
xiaoming.run();? //小明 is running...
xiaoming有自己的name屬性,但并沒有定義run()方法。不過,由于小明是從Student繼承而來,只要Student有run()方法,xiaoming也可以調用。
JavaScript的原型鏈和Java的Class區(qū)別就在于它沒有“Class”的概念,所有對象都是實例,所謂繼承關系不過是把一個對象的原型指向另一個對象而已。
(1) 創(chuàng)建對象的原理
JavaScript對每個創(chuàng)建的對象都會設置一個原型,指向它的原型對象。
當我們用ObjectName.propertyName訪問一個對象的屬性時,JavaScript引擎先在當前對象上查找該屬性,如果沒有找到,就到其原型對象上找,如果還沒有找到,就一直上溯到Object.prototype對象,最后,如果還沒有找到,就只能返回undefined。
現(xiàn)在我們開始創(chuàng)建一個對象:
var arr = [1,2,3];
其原型鏈如下所示:
arr => Array.prototype => Object.prototype => null
在這里,數(shù)組arr是JavaScript的內建對象Array的一個實例,其原型鏈指向Array的prototype。Array本身為一種特殊的對象(即key值為0,1,2,3...的鍵值對),因此Array的原型鏈指向Object的prototype。原型鏈的終點指向null。
由上例啟發(fā),我們再看一下前面提到的xiaoming與Stutent的關系:

上圖中,小明作為一個Student的實例(可以理解為xiaoming是學生這個大類下的一個個體),含有一個名為__proto__的原型鏈指針(標準寫法為[[prototype]],許多瀏覽器中為便于區(qū)分顯示為__proto__),該指針指向當前對象的原型的構造器(constructor)。
大多數(shù)情況下,__proto__可以理解為“構造器函數(shù)的原型”,即:__proto__ === constructor.prototype? (通過Object.create()創(chuàng)建的對象不適于此等式)
有些同學可能會有疑問,prototype屬性與__proto__的區(qū)別。兩者的概念上可以這樣理解:prototype屬性是函數(shù)才擁有的屬性,而每一個對象都有一個指向其原型的__proto__屬性,其__proto__屬性在一般情況下指向其原型的constructor(即對象的__proto__指向本對象的構造器)。


(2) 對象的原型鏈的理解
JavaScript的原型及原型鏈概念的原理,類似于數(shù)據(jù)結構中的鏈表結構:
鏈表是一種物理存儲單元上非連續(xù)、非順序的存儲結構,數(shù)據(jù)元素的邏輯順序是通過鏈表中的指針鏈接次序實現(xiàn)的。鏈表由一系列結點(鏈表中每一個元素稱為結點)組成,結點可以在運行時動態(tài)生成。
相比數(shù)組而言,鏈表在內存中不需要連續(xù)的區(qū)域,只需要每一個節(jié)點都能夠記錄下一個節(jié)點的內存地址,通過引用進行查找,這樣的特點也就造就了鏈表增刪操作時間消耗很小,而查找遍歷時間消耗很大的特點。
鏈表的兩種模式(單向鏈表、雙向鏈表)如下圖:

簡單了解了鏈表結構后,我們現(xiàn)在可以來了解一下JavaScript中的原型鏈與鏈表的區(qū)別了:

(3) ES6中的Class的理解
前文提到JS中沒有類的概念,是針對ES5而言。在2015年推出的ES6語法中,JS提供了更接近傳統(tǒng)語言的寫法,引入了 class(類)這個概念,作為對象的模板。通過class關鍵字,可以定義類。
傳統(tǒng)的ES5中,定義類似“類”的概念,使用的方法是定義一個構造函數(shù),通過繼承的方式實現(xiàn)“類”的功能:
// 先定義一個函數(shù),強行叫它構造函數(shù),構造函數(shù)常規(guī)建議首字母大寫:
function Student(name, age) {?
????this.name = name;? ?// 構造函數(shù)的屬性都定義在函數(shù)內部
????this.age = age;? ?// this指向實例對象
?}??
// 構造函數(shù)的方法都定義在構造函數(shù)的原型上?
Student.prototype.showHisInfo = function () {?
????return? '(' + this.name + ', ' + this.age + ')';?
};?
// new 一個對象,就OK了
var xiaoming = new Student('小明', 18);?
當使用class時,結構如下:?
class Student{
????constructor(name,age) {? // 構造函數(shù),為類的一部分
????????// 定義將來實例的屬性
????????this.name =?name;?
????????this.age = age;? ?
????}
????// 給類添加方法
????showHisInfo() {
????????return this.name + ','+this.age;? ?// 取到上面兩個屬性值
????}
}
let xiaoming = new Student('小明', 18);
參考文獻:
廖雪峰--面向對象編程:https://www.liaoxuefeng.com/wiki/1022910821149312/1023022126220448
數(shù)據(jù)結構——淺談鏈表:https://blog.csdn.net/weixin_41582192/article/details/81181077