原型 , 構(gòu)造模式 , 原型模式 ,組合模式
1.理解原型
在JS中,只要聲明一個函數(shù)A , 就會對應(yīng)的產(chǎn)生一個對象 , 這個對象就叫原型對象
試試看
function A() {
}
console.log(A.prototype);
console.log(A.prototype.constructor);
返回值如下:
定義了一個函數(shù) A , 沒有添加任何的屬性和方法 , A 的兩未定義個屬性 卻可以返回兩個值?
prototype
屬性返回了一個對象 , constructor
屬性返回了函數(shù)A
prototype
是 A 里面的屬性 , 指向一個對象,這個對象就叫原型對象 ,
而這個原型對象里 默認(rèn)情況 儲存著 constructor
屬性和從Object繼承而來做盅,這里暫且不表;
constructor
是一個屬性 , 存在原型對象里 , 指向了這個函數(shù)
比如倆人吵架 , 都用手指頭指著對方罵;
而這兩個屬性就是手指頭,構(gòu)造函數(shù)和原型對象就是吵架的人;
手指頭是人的一部分,就像這兩個屬性存在對象的內(nèi)部是一樣的
2.與原型相關(guān)的屬性和方法
constructor
屬性
存在于原型對象中,指向構(gòu)造函數(shù);
如果重寫原型對象則會導(dǎo)致constructor
屬性不再指向構(gòu)造函數(shù);
如果需要constructor
依舊指向構(gòu)造函數(shù),可以在重寫的時候加上以下代碼
Person.prototype = {
constructor : Person //讓constructor重新指向Person函數(shù)
}
[[prototype]]
屬性
用構(gòu)造函數(shù)創(chuàng)建一個實例對象后,這個對象中會有一個不可訪問的屬性[[prototype]]
,這個屬性就指向了構(gòu)造函數(shù)的原型對象
Chrome瀏覽器和火狐瀏覽器提供這個對這個屬性的訪問
使用__proto__
方法可以訪問到原型對象(左右各兩個下劃線),這個屬性存在于被構(gòu)造函數(shù)創(chuàng)建的實例對象中;一般不建議使用
hasOwnProperty()
方法
判斷一個屬性是否來自對象自身 ;
返回 true
表示為對象自身屬性;返回false
可以判斷出存在原型中或?qū)傩圆淮嬖?所以要如何確定一個屬性存在原型里呢?
in
操作符
in
操作符會從對象的本身開始 , 查找是否有對應(yīng)的屬性 , 在對象自身中沒有找到 , 就會沿著原型鏈開始查找 , 直到找到返回true
,反之,則返回false
3. 兩個模型--構(gòu)造函數(shù)模型 和 原型模型
3.1構(gòu)造函數(shù)模型
理論上任何函數(shù)都可以作為構(gòu)造函數(shù),但是一般約定,構(gòu)造函數(shù)的函數(shù)名以大寫字母開頭
比如:
構(gòu)造函數(shù) :
function Person(){ }
當(dāng)把一個函數(shù)作為構(gòu)造函數(shù),并利用new創(chuàng)建的一個新的實例對象
利用構(gòu)造函數(shù)創(chuàng)建實例 : var p1 = new Person( );
3.1.1構(gòu)造過程
相關(guān):
實例與構(gòu)造函數(shù)的關(guān)系 :
實例對象p1被構(gòu)造函數(shù)Person創(chuàng)建后,P1和Person是沒有任何的關(guān)系了;
實例與原型 :
- 實例對象中有一個[[prototype]]屬性指向原型對象;
- 使用new Person()創(chuàng)建多個對象,則多個對象都會同時指向原型對象缓窜。
- 可以手動給這個原型對象添加屬性和方法,那么p1,p2,p3...都會共享這些屬性和方法
- 屬性和方法的查找會從實例開始 , 沿著原型鏈查找 , 直到找到屬性或方法;
- 所以給實例對象添加和原型中同名的屬性 , 會優(yōu)先訪問實例中的屬性
- 通過實例只能訪問到原型的屬性或方法,不能修改;
//這段代碼將會拋出錯誤
function Person(name,age) {
}
Person.prototype.sex = "男"
let p1 = new Person();
p1.prototype.sex = "女";
console.log(p1.sex);
3.1.2構(gòu)造函數(shù)的優(yōu)勢
可以傳入?yún)?shù),適用于 每個實例都有的同名但是值不相同的屬性
function Person(name,age) {
this.name = name;
this.age = age;
this.speakName = function (){
console.log(this.name)
}
}
let p1 = new Person("shark",3);
let p2 = new Person("dd",4)
console.log(p1.name,p1);
console.log(p1.age)
//輸出結(jié)果
shark 3
dd 4
內(nèi)存模型:
目前來說,每個實例都有了自己的name和age , 但是新的問題也隨之而來了
3.1.3構(gòu)造函數(shù)的缺陷
觀察內(nèi)存模型 , 就會發(fā)現(xiàn)每個實例中都有一個相同的方法 , 浪費了內(nèi)存
這不是很OK , 不是我們想要的結(jié)果,所以一種新的模型站了出來 - -原型模型
3.2原型模型
3.2.1原型模式的優(yōu)勢
針對以上的問題 , 對代碼做出了以下修改
function Person(name,age) {
this.name = name;
this.age = age;
}
Person.prototype.speakName = function (){
console.log(this.name)
}
let p1 = new Person("shark",3);
let p2 = new Person("dd",4)
console.log(p1.name,p1);
console.log(p1.age)
利用原型模式的優(yōu)勢 ---共享所有的方法
可以讓我們共享speakName
這個方法;
這正好解決了構(gòu)造函數(shù)的缺陷 , 每個實例都有自己的名字 , 方法在不浪費內(nèi)存和性能的情況下共用;
3.14 原型模式的缺陷
那假如使用原型模式添加屬性呢?
function Person() {
}
Person.prototype.country = "China";
Person.prototype.name = "小李";
var p1 = new Person();
var p2 = new Person();
場景:
現(xiàn)在有假如兩個中國人p1,p2 ,他們的國籍一樣,使用原型模式,使得兩個人的國籍一致
結(jié)合上一個例子說明了:
原型中適合存儲大家共有的屬性和方法;
不過原型也存在缺陷 :
名字沒有辦法做到每個人都獨一無二 , 不過在之前提到的構(gòu)造函數(shù)模型中 , 正好解決了給不同的實例的相同name
賦不同值的情況;
3.4 總結(jié)
原型模式適合封裝方法和共享的屬性,構(gòu)造方法模式適合封裝值不同的屬性
如果把這兩個模式結(jié)合起來,就有了組合模式;
4.組合使用兩種模型
組合構(gòu)造是基于兩種模式互補的一種新的構(gòu)造方法;
總結(jié)一下兩種模式的優(yōu)缺點:
優(yōu)勢 | 缺陷 | |
---|---|---|
構(gòu)造函數(shù)模式 | 屬性在對象中都獨有一份 | 對于方法來說,沒有必要一人一份 |
原型模式 | 方法可以共享 | 一般屬性的值是不同的,不適合共享 |
不難看出,兩者的正好是互補的,所以組合起來使用是最佳的方法;
4.1 組合模式
//在構(gòu)造方法內(nèi)部封裝屬性
function Person(name, age) {
this.name = name;
this.age = age;
}
//在原型對象內(nèi)封裝方法
Person.prototype.eat = function (food) {
alert(this.name + "愛吃" + food);
}
Person.prototype.play = function (playName) {
alert(this.name + "愛玩" + playName);
}
var p1 = new Person("李四", 20);
var p2 = new Person("張三", 30);
p1.eat("蘋果");
p2.eat("香蕉");
p1.play("志玲");
p2.play("鳳姐");
雖然完美解決了種模式的缺陷,但是還不夠完美
因為 , 代碼還不夠優(yōu)雅
4.2 動態(tài)原型模式
動態(tài)原型模式把所有的屬性和方法都封裝在構(gòu)造方法中搬卒,而僅僅在需要的時候才去在構(gòu)造方法中初始化原型钮糖,又保持了同時使用構(gòu)造函數(shù)和原型的優(yōu)點。
<script type="text/javascript">
//構(gòu)造方法內(nèi)部封裝屬性
function Person(name, age) {
//每個對象都添加自己的屬性
this.name = name;
this.age = age;
/*
判斷this.eat這個屬性是不是function圆存,如果不是function則證明是第一次創(chuàng)建對象,
則把這個funcion添加到原型中仇哆。
如果是function辽剧,則代表原型中已經(jīng)有了這個方法,則不需要再添加税产。
perfect怕轿!完美解決了性能和代碼的封裝問題偷崩。
*/
if(typeof this.eat !== "function"){
Person.prototype.eat = function () {
alert(this.name + " 在吃");
}
}
}
var p1 = new Person("志玲", 40);
p1.eat();
</script>
看起來優(yōu)美多了..但是還是差一絲優(yōu)美...
4.3 優(yōu)化
function Person(opt) {
this._init(opt)
}
//方法和屬性都被封裝到了一起,但是屬性實際上還是屬于構(gòu)造模式創(chuàng)建的
//體現(xiàn)了封裝性,但是重寫Prototype帶來了一個小問題
Person.prototype = {
//新的原型對象不存在constructor屬性,故補齊
constructor:Person
//初始化屬性
_init: function (opt) {
this.name = opt.name;
this.age = opt.age;
},
eat: function () {
return "名字:" + this.name
},
howOld: function () {
return "年齡:" + this.age
},
};
var p1 = new Person({
name: "李四",
age: 99
});
前端新人一枚 , 歡迎批評指正~