原型prototype是javascript中極其重要的概念之一肺素,但也是比較容易引起混淆的地方走趋。我們需要花費(fèi)一些時(shí)間和精力好好理解原型的概念,這對于我們學(xué)習(xí)javascript是必須的汁雷。
原型的概念
真正理解什么是原型是學(xué)習(xí)原型理論的關(guān)鍵介时。很多人在此產(chǎn)生了混淆,沒有真正理解蒜埋,自然后續(xù)疑惑更多淫痰。
首先,我們明確原型是一個(gè)對象整份,其次待错,最重要的是,
** Every function has a prototype property and it contains an object **
這句話就是說烈评,每個(gè)函數(shù)都有一個(gè)屬性叫做原型火俄,這個(gè)屬性指向一個(gè)對象。
也就是說讲冠,原型是函數(shù)對象的屬性瓜客,不是所有對象的屬性,對象經(jīng)過構(gòu)造函數(shù)new出來竿开,那么這個(gè)new出來的對象的構(gòu)造函數(shù)有一個(gè)屬性叫原型谱仪。明確這一點(diǎn)很重要。
** The prototype property is a property that is available to you as soon as you define the function. Its initial value is an "empty" object.
**
每次你定義一個(gè)函數(shù)的時(shí)候否彩,這個(gè)函數(shù)的原型屬性也就被定義出來了疯攒,也就可以使用了,如果不對它進(jìn)行顯示賦值的話列荔,那么它的初始值就是一個(gè)空的對象Object卸例。
所以称杨,綜上我們知道我們討論原型的時(shí)候,都是基于函數(shù)的筷转,有了一個(gè)函數(shù)對象,就有了原型悬而。切記這一點(diǎn)呜舒,討論原型,不能脫離了函數(shù)笨奠,它是原型真正歸屬的地方袭蝗,** 原型只是函數(shù)的一個(gè)屬性 **!
function foo(a,b) {
return a+b;
}
foo.prototype
foo.constructor
chrome控制臺測試結(jié)果
我們可以看到函數(shù)foo的原型是空對象Object般婆,所有函數(shù)的構(gòu)造函數(shù)都是Function到腥。
使用原型給對象添加方法和屬性
不使用原型,使用構(gòu)造函數(shù)給對象添加屬性和方法的是通過this蔚袍,像下面這樣乡范。
function Gadget(name, color) {
this.name = name;
this.color = color;
this.whatAreYou = function() {
return 'I am ' + this.color + ' ' + this.name;
}
}
Gadget是一個(gè)構(gòu)造函數(shù),作為一個(gè)函數(shù)啤咽,它有一個(gè)屬性晋辆,這個(gè)屬性是原型,它指向一個(gè)對象宇整,目前我們沒有設(shè)置這個(gè)屬性瓶佳,所以它是一個(gè)空的對象。
** Adding methods and properties to the prototype property of the constructor
function is another way to add functionality to the objects this constructor produces **
當(dāng)我們有了原型之后鳞青,我們可以給構(gòu)造函數(shù)的原型對象添加屬性和方法來霸饲。
像下面這樣
Gadget.prototype.price = 100;
Gadget.prototype.rating = 3;
Gadget.prototype.getInfo = function() {
return 'Rating: ' + this.rating +', price: ' + this.price;
}
給原型添加了屬性和方法后,原型所指的對象也會更新
使用原型對象的屬性和方法
我們使用原型的對象和方法不會在直接在構(gòu)造函數(shù)上使用臂拓,而是通過構(gòu)造函數(shù)new出一個(gè)對象厚脉,那么new出來的對象就會有構(gòu)造函數(shù)原型里的屬性和方法。
這里很容易造成誤解埃儿,我們需要強(qiáng)調(diào)newtoy這個(gè)new出來的對象是沒有原型的器仗,原型只是函數(shù)對象的一個(gè)屬性,newtoy是通過構(gòu)造函數(shù)new出來的對象童番,所以他不是函數(shù)對象精钮,也沒有prototype屬性,我們在chrome的控制臺里自然也無法訪問他的prototype屬性剃斧。
但我們可以通過構(gòu)造函數(shù)訪問轨香。
我們知道每個(gè)對象都有constructor屬性,newtoy的constructor屬性就指向Gadget幼东,那么我們通過constructor可以訪問到prototype臂容。
到這里科雳,我們對為什么要通過constructor.protptype訪問屬性應(yīng)該清楚了。(筆者第一次接觸原型就沒看懂這個(gè))脓杉,切記糟秘,原型是函數(shù)對象的屬性,只有函數(shù)對象才有原型就容易理解了球散。
原型的實(shí)時(shí)性
這里特別需要提出尿赚,原型是實(shí)時(shí)的,意思就是原型對象的屬性和方法會實(shí)時(shí)更新蕉堰。其實(shí)很好理解凌净,javascript中對象是通過引用傳遞的,原型對象只有一份屋讶,不是new出一個(gè)對象就復(fù)制一份冰寻,所以我們對原型的操作和更新,會影響到所有的對象皿渗。這就是原型對象的實(shí)時(shí)性斩芭。
自身屬性與原型屬性
這里涉及到j(luò)avascript是如何搜索屬性和方法的,javascript會先在對象的自身屬性里尋找羹奉,如果找到了就輸出秒旋,如果在自身屬性里沒有找到,那么接著到構(gòu)造函數(shù)的原型屬性里去找诀拭,如果找到了就輸出迁筛,如果沒找到,就null耕挨。
所以细卧,如果碰到了自身屬性和原型屬性里有同名屬性,那么根據(jù)javascript尋找屬性的過程筒占,顯然贪庙,如果我們直接訪問的話,會得到自身屬性里面的值翰苫。
我們加下來做一個(gè)小實(shí)驗(yàn)止邮,尋找toString方法是誰的屬性,一步步尋找
通過實(shí)驗(yàn)我們可以發(fā)現(xiàn)奏窑,原來toString方法是object的原型對象的方法导披。
isPrototypeOf()
Object的原型里還有這樣一個(gè)方法isPrototypeOf(),這個(gè)方法可以返回一個(gè)特定的對象是不是另一個(gè)對象的原型,實(shí)際這里不準(zhǔn)確埃唯,因?yàn)槲覀冎乐挥泻瘮?shù)對象有原型屬性撩匕,普通對象通過構(gòu)造函數(shù)new出來,自動繼承了構(gòu)造的函數(shù)原型的屬性方法墨叛。但這個(gè)方法是可以直接判斷止毕,而不需要先取出constructor對象再訪問prototype模蜡。看下面的例子:
function Human(name) {
this.name = name;
}
var monkey = {
hair:true,
feeds:'banana',
}
Human.prototype = monkey;
var chi = new Human('chi');
我們知道chi這個(gè)對象是沒有原型屬性的扁凛,它有的是他的構(gòu)造函數(shù)的原型屬性monkey忍疾。但isPrototypeOf直接判斷,實(shí)際上是省略了獲取構(gòu)造函數(shù)的過程令漂,搞清楚這里面的區(qū)別膝昆。
object還有一個(gè)getPrototypeOf方法,基本用法和isPrototype一樣叠必,參考下面的代碼:
神秘的proto鏈接
我們之前訪問對象的原型,都要先取得構(gòu)造函數(shù)然后訪問prototype
chi.constructor.prototype;
newtoy.constructor.prototype;
這樣是不是特別別扭妹窖,所以各個(gè)瀏覽器一般都會給出一個(gè)proto屬性纬朝,前后分別有雙下劃線,對象的這個(gè)屬性可以直接訪問到構(gòu)造函數(shù)的原型骄呼。這就很方便了共苛。所以proto與prototype是有很大區(qū)別的。區(qū)別就在此蜓萄。proto是實(shí)例對象用來直接訪問構(gòu)造函數(shù)的屬性隅茎,prototype是函數(shù)對象的原型屬性。
chi.constructor.prototype == chi.__proto__
顯然現(xiàn)在已經(jīng)很容易弄清楚了proto和prototype的區(qū)別了嫉沽。
原型的陷阱
原型在使用的時(shí)候有一個(gè)陷阱:
** 在我們完全替換掉原型對象的時(shí)候辟犀,原型會失去實(shí)時(shí)性,同時(shí)原型的構(gòu)造函數(shù)屬性不可靠绸硕,不是理論上應(yīng)該的值堂竟。**
這個(gè)陷進(jìn)說的是什么呢?好像不太明白
舉個(gè)例子我們就懂了
function Dog() {
this.tail = true;
}
var benji = new Dog();
var rusty = new Dog();
Dog.prototype.say = function () {
return 'Woof!';
};
我們進(jìn)行測試:
直到這里一切都是正常的
接下來我們將原型對象整個(gè)替換掉
Dog.prototype = {
paws: 4,
hair: true
};
通過測試我們發(fā)現(xiàn)玻佩,我們沒法訪問剛剛更新的原型對象出嘹,卻能訪問之前的原型對象,這說明沒有實(shí)現(xiàn)實(shí)時(shí)性咬崔。
我們繼續(xù)測試
我們發(fā)現(xiàn)這時(shí)新建的對象可以訪問更新后的原型税稼,但是構(gòu)造方法又不對了,本來constructor屬性應(yīng)該指向dog垮斯,結(jié)果卻指向了Object郎仆。這就是javascript中的原型陷阱。
我們很容易解決這個(gè)問題甚脉,只要在更新原型對象后面丸升,重新指定構(gòu)造函數(shù)即可。
Dog.prototype.constructor = Dog;
這樣所有就按正常的運(yùn)行了
** 所以我們切記在替換掉原型對象之后牺氨,切記重新設(shè)置constructor.prototype **
小結(jié)
我們大概介紹了原型中容易混淆的問題狡耻,主要有以下幾方面:
- 所有函數(shù)都有一個(gè)屬性prototype墩剖,這就是我們指的原型,他的初始值是一個(gè)空的對象
- 你可以原型對象添加屬性和方法夷狰,甚至直接用另一個(gè)對象替換他
- 當(dāng)你用構(gòu)造函數(shù)new出一個(gè)對象之后岭皂,這個(gè)對象可以訪問構(gòu)造函數(shù)的原型對象的屬性和方法
- 對象的自身屬性搜索的優(yōu)先級比原型的屬性要高
- proto屬性的神秘連接及其同prototype的區(qū)別
- prototype使用中的陷阱