其實原型我看了有一段時間了,也沒有看很長時間,就是找一個下午的時間聽聽課,然後就感覺很懵,似懂非懂的,接着就自己想想,然後就還是懵。平時翻翻技術類的公衆號或者CSDN上的文章也會涉及到原型的內容,加上前天我又看一一遍講解,看的多了慢慢的就更熟悉更瞭解了。
今天,下定決心要總結總結寫寫原型了。內容多少會有點問題,諒解諒解,畢竟是菜鳥的小白爲了記錄自己的學習過程。
構造函數和實例對象
構造函數也是一個函數,它與普通函數的區別就在於構造函數名首字母要大寫。通過構造函數創建對象的過程叫對象的實例化,創建的對象是實例對象。Object構造函數,是系統提供好的構造函數,我們自定義的構造函數也是構造函數。
var obj2 = new Object();
function Person(uname, uage) {
this.uname = uname;
this.uage = uage;
}
Person("xz", 18);
構造函數中的屬性和方法,分爲兩種:實例成員和靜態成員。一般構造函數中都是實例成員,靜態成員的添加方法是直接在構造函數上面添加的。
上面的uname和uage都是實例成員,靜態成員:Person.sex = “male”,這裏的sex就是靜態成員。
實例成員:構造函數內部通過this添加的屬性和方法
靜態成員:構造函數本身添加的屬性和方法
創建對象
再談談創建對象的幾種方式吧。
一般是三種方式:對象字面量創建、Object創建、自定義構造函數創建。其實總的來說是兩種方式:對象字面量創建和通過構造函數創建。
// 1. 字面量創建對象
var obj1 = {
name: "lyf",
age: 18
}
// 2. 通過 new Object
var obj2 = new Object();
obj2.name = "zly";
obj2.age = 17;
// 3. 通過自定義構造函數
function Person(uname, uage) {
this.uname = uname;
this.uage = uage;
}
// 通過new創建出來的對象,是構造函數實例化出來的對象 —— 對象的實例化
var obj3 = new Person("xz", 18);
new關鍵的過程
- 先創建一個空對象
- 把空對象賦值給this
- 把屬性和方法都掛在this上面
- 最後把this對象返回
所以這就是在創建對象時,不需要返回值的原因。
原型
上面說完了構造函數和實例對象,接下來我們就來介紹介紹原型。
每個函數都有一個prototype屬性叫原型,這個屬性是一個對象,所以叫原型對象。
每個對象都有一個__proto__屬性,叫對象的原型,它指向構造該對象的構造函數的原型對象。
自定義構造函數:
// 只要是函數,就有原型對象
function Person(uname, uage) {
this.uname = uname;
this.uage = uage;
}
// 構造函數也是函數, 所以構造函數也有自己的原型對象
var obj = new Person("xz", 18);
// 通過prototype訪問函數的原型對象
console.log(Person.prototype);
這就是原型對象。可以看到,原型對象裏有constructor和__proto__兩個屬性。
上面說了,只要是對象就有__proto__ 屬性,指向構造該對象的構造函數的原型對象。
__proto__是對象的原型,這裏__proto__指向創建Person.prototype對象的構造函數的原型對象 —— Object.prototype。
constructor是構造函數,指向創建該對象的函數。簡單來說,就是記錄誰創建了這個對象。這裏constructor指向創建Person.prototype對象的函數 —— Person。Person創建了Person.prototype原型對象,所以constructor指向Person。
// 通過prototype訪問函數的原型對象
console.log(Person.prototype); // Person的原型對象
console.log(Person.prototype.__proto__); // Person原型對象的原型
// 原型對象裏有一個constructor屬性,該屬性指向創建它(原型對象)的函數
console.log(Person.prototype.constructor);
console.log(Person.prototype.__proto__ === Object.prototype);
第一個輸出的是Person構造函數的原型對象:Person.prototype。
第二個輸出的是Person構造函數的原型對象的原型:Object.prototype。Object是頂級對象,任何一個對象都是Object的實例化對象。
第三個輸出的是Person構造函數的原型對象的構造函數:Person。constructor屬性是指向創建該對象的構造函數。
第四個輸出的是true。
看了構造函數的原型,現在再來看下實例對象的原型
var obj = new Person("xz", 18);
// 構造函數實例化的對象
console.log(obj);
// 實例化對象有一個原型屬性:__ptoro__
// 對象的__proto__ 屬性 指向構造函數的原型對象
console.log(obj.__proto__);
// 對象的原型 === 構造函數的原型對象
console.log(obj.__proto__ === Person.prototype);
第一個輸出的是obj實例對象自己,對象除了有構造函數定義的屬性外,還有有__ptoto__屬性。
第二個輸出的是obj的原型 —— Person構造函數。因爲obj是通過Person構造函數實例化出來的,所以obj的原型是Person構造函數d的原型對象。
第三個輸出的是true。因爲obj的原型是Person構造函數原型對象,Person構造函數的原型對象是Person.prototype,所以輸出true。
來一張obj、Person、Person.prototype的圖,就明白了
這個圖再完善一下,加上Object就更清晰了。我們都知道,所有的對象到最後都會返回Object,Object對象是頂級對象。
這樣的話,是不是有人還想試試Object.prototype.proto?那就試試唄。
返回null,這就解釋了爲什麼Object是頂級對象,再往上找就沒有了。
原型鏈
講完原型,終於可以說原型鏈了。接下來原型鏈就在原型的基礎上就稍稍的簡單了一點。
看圖看圖。
沒錯,就是看這個圖,有沒有看到對象的__proto__,用__proto__連接起來的就是原型鏈。當訪問到對象本身沒有的屬性或者方法時,此時就會順着原型鏈進行查找,一直找到Object,prototype,如果找到的話就調用返回,如果沒有找到就會返回undefined。
還是這個例子。
function Person(uname, uage) {
this.uname = uname;
this.uage = uage;
}
// 構造函數也是函數, 所以構造函數也有自己的原型對象
var obj = new Person("xz", 18);
假如現在訪問sex屬性。肯定返回undefined,整個原型鏈上都沒有sex屬性。
console.log(obj.sex);
我在Perso.prototype上添加sex屬性後,再去訪問。此時就可以訪問到了,obj對象本身沒有這個屬性,但是obj的原型Peroson.prototype上有,obj通過__proto__屬性訪問Peroson.prototype得到sex屬性,最後返回。
Person.prototype.sex = "male";
同理,不在Peroson.prototype上添加sex屬性,在Object.prototype上添加。同樣的obj依然可以訪問到,通過原型鏈中__proto__屬性。
當obj對象、obj對象的原型以及Object原型對象都有sex屬性時,obj對象會返回哪個值?
Person.prototype.sex = "male";
Object.prototype.sex = "女";
function Person(uname, uage, sex) {
this.uname = uname;
this.uage = uage;
this.sex = usex;
}
// 構造函數也是函數, 所以構造函數也有自己的原型對象
var obj = new Person("xz", 18, "男");
console.log(obj.sex);
返回的是obj對象自己的屬性值。
需要注意的是,先從自己找,自己沒有就找自己的原型,原型沒有就再往上以及原型找,就這樣一層一層的找。
JavaScript查找機制:就近原則。
補充
這種形式是在原型對象上追加屬性
Person.prototype.sex = "male";
這種形式是改寫了原型對象,也就是說,這個把系統提供的Person.prototype給覆蓋了。再訪問Person.prototype時只有sex這一個屬性了。
Person.prototype = {
sex: "male"
}
覆蓋的問題要謹慎。
emmm,結束了結束了。原型和原型鏈暫時就瞭解了這些哈。