在图书馆学习红宝书的一天(二)· 慢慢看原型、原型链就看懂了~
前言
大家好,這里是@IT·平頭哥聯(lián)盟,我是團(tuán)寵閃光少女——粉刷醬。
要怎么描述編程是個(gè)多幸福的工作呢?
我們很多人都想著如果能一輩子編程,那真是太好了。
而現(xiàn)實(shí)生活中,對(duì)未來(lái)的擔(dān)憂和焦慮常常困擾著我們。我想,如果要努力維持現(xiàn)有的幸福的話,還是應(yīng)該不停地學(xué)習(xí)。于是今天又去圖書(shū)館學(xué)習(xí)了一天,深入了解了js中的原型、原型鏈,現(xiàn)在跟大家分享一下~
正式開(kāi)講
原型和原型鏈大概是畢業(yè)時(shí)候面試的噩夢(mèng)了,感覺(jué)怎么也理解不了,怎么也不會(huì)。后來(lái)靜下心來(lái)想想,其實(shí)只是那時(shí)候在大學(xué)里實(shí)踐的太少,以至于畢業(yè)時(shí)候要學(xué)習(xí)太多的實(shí)踐知識(shí),html、css、js還有各種框架,而靜不下心來(lái)細(xì)細(xì)理解基礎(chǔ)理論罷了。那現(xiàn)在,我們一起靜下心來(lái),好好理解一下javascript中的面向?qū)ο竽切┦聝喊?#xff5e;
先了解一下面向?qū)ο蟮囊饬x。
一切事物皆對(duì)象,通過(guò)面向?qū)ο蟮姆绞?#xff0c;將現(xiàn)實(shí)世界的事物抽象成對(duì)象,現(xiàn)實(shí)世界中的關(guān)系抽象成類、繼承,幫助人們實(shí)現(xiàn)對(duì)現(xiàn)實(shí)世界的抽象與數(shù)字建模。通過(guò)面向?qū)ο蟮姆椒?#xff0c;更利于用人理解的方式對(duì)復(fù)雜系統(tǒng)進(jìn)行分析、設(shè)計(jì)與編程。同時(shí),面向?qū)ο竽苡行岣呔幊痰男?#xff0c;通過(guò)封裝技術(shù),消息機(jī)制可以像搭積木的一樣快速開(kāi)發(fā)出一個(gè)全新的系統(tǒng)。面向?qū)ο笫侵敢环N程序設(shè)計(jì)范型,同時(shí)也是一種程序開(kāi)發(fā)的方法。對(duì)象指的是類的集合。它將對(duì)象作為程序的基本單元,將程序和數(shù)據(jù)封裝其中,以提高軟件的重用性、靈活性和擴(kuò)展性。
1.1面向?qū)ο?/h4>
現(xiàn)在假設(shè)我是一個(gè)捏泥人的女?huà)z,我每天的kpi是捏二百個(gè)泥人~~
var person = {name: "Nicholas",age: 29,job: "Software Engineer",sayName: function(){alert(this.name);} }; 復(fù)制代碼用對(duì)象字面量賦值方式捏了一個(gè),挺簡(jiǎn)單的嘛,但是還得捏199個(gè),有點(diǎn)累哇,巧了,我不是會(huì)編程的嘛,工廠模式走起來(lái),先做一個(gè)小工(zuo)廠(fang) 生產(chǎn)小泥人,那我就只需要把小泥人信息輸進(jìn)去,就可以得到小泥人~~如下:
function createPerson(name, age, job){var o = new Object();o.name = name;o.age = age;o.job = job;o.sayName = function(){alert(this.name);};return o; } var person1 = createPerson("Nicholas", 29, "Software Engineer"); var person2 = createPerson("Greg", 27, "Doctor"); 復(fù)制代碼這樣我每天就節(jié)省了大量的時(shí)間,干點(diǎn)啥好呢,沒(méi)事做呀~ 繼續(xù)研究下我這個(gè)工廠,雖然解決了創(chuàng)建多個(gè)相似對(duì)象的問(wèn)題,但是沒(méi)有跟上時(shí)代面向?qū)ο蟮某绷?#xff0c;實(shí)在不高級(jí),用構(gòu)造函數(shù)的方式改寫一下。
function Person(name, age, job){this.name = name;this.age = age;this.job = job;this.sayName = function(){alert(this.name);}; }var person1 = new Person("Nicholas", 29, "Software Engineer"); var person2 = new Person("Greg", 27, "Doctor"); 復(fù)制代碼跟工廠函數(shù)代碼相比,有以下幾點(diǎn)區(qū)別:
1、沒(méi)有顯式地創(chuàng)建對(duì)象;2、直接將屬性和方法賦給了 this 對(duì)象; 3、沒(méi)有return語(yǔ)句。 復(fù)制代碼但是實(shí)際上用構(gòu)造函數(shù)創(chuàng)建實(shí)例時(shí),必須使用new操作符。以這種方式調(diào)用構(gòu)造函數(shù)實(shí)際上會(huì)經(jīng)歷以下4個(gè)步驟:
(1) 創(chuàng)建一個(gè)新對(duì)象;(2) 將構(gòu)造函數(shù)的作用域賦給新對(duì)象(因此 this 就指向了這個(gè)新對(duì)象);(3) 執(zhí)行構(gòu)造函數(shù)中的代碼(為這個(gè)新對(duì)象添加屬性);(4) 返回新對(duì)象。 復(fù)制代碼構(gòu)造函數(shù)創(chuàng)建的實(shí)例person1和person2分別保存著Person的一個(gè)不同的實(shí)例。這兩個(gè)對(duì)象都有一個(gè)constructor(構(gòu)造函數(shù))屬性,該屬性指向Person。這就意味著創(chuàng)建的實(shí)例可以標(biāo)識(shí)為一種特定的類型。這就有了面向?qū)ο蟮母拍盍恕?/p>
但是這里還是存在一個(gè)問(wèn)題需要優(yōu)化的問(wèn)題,每一個(gè)實(shí)例都創(chuàng)建了一個(gè)新的sayName的方法,而創(chuàng)建多個(gè)完成同樣任務(wù)的Function實(shí)例是不必要,這里我們可以定義一個(gè)全局函數(shù)sayName,然后在構(gòu)造函數(shù)中使用this.sayName指向這個(gè)全局函數(shù)。這樣問(wèn)題是解決了,但是破壞了面向?qū)ο蟮姆庋b的特征。
所以下面我們需要了解一下js中的原型模式。
1.2 原型模式
js中,我們創(chuàng)建的每個(gè)函數(shù)都有一個(gè)prototype(原型)屬性,這個(gè)屬性是一個(gè)指針,指向一個(gè)對(duì)象。而這個(gè)對(duì)象就包含著在實(shí)例中共享的屬性和方法。這就完美解決了我們上述的這些問(wèn)題。給個(gè)栗子:
function Person(){ } Person.prototype.name = "Nicholas"; Person.prototype.age = 29; Person.prototype.job = "Software Engineer"; Person.prototype.sayName = function(){alert(this.name); }; var person1 = new Person(); person1.sayName(); //"Nicholas" var person2 = new Person(); person2.sayName(); //"Nicholas" alert(person1.sayName == person2.sayName); //true 復(fù)制代碼這里Person.prototype指向的就被稱為原型對(duì)象,默認(rèn)情況下,所有原型對(duì)象都會(huì)自動(dòng)獲得一個(gè)constructor(構(gòu)造函數(shù))屬性,指向這個(gè)prototype所在的函數(shù)。也就是Person.prototype.constructor==Person。
構(gòu)造函數(shù)創(chuàng)建一個(gè)新實(shí)例后,該實(shí)例的內(nèi)部將包含一個(gè)指針(內(nèi)部屬性),指向構(gòu)造函數(shù)的原型對(duì)象。ECMA-262第5版中管這個(gè)指針叫[[Prototype]]。雖然在腳本中 沒(méi)有標(biāo)準(zhǔn)的方式訪問(wèn)[[Prototype]],但 Firefox、Safari和Chrome在每個(gè)對(duì)象上都支持一個(gè)屬性__proto__,指向構(gòu)造函數(shù)的原型對(duì)象。
當(dāng)代碼讀取某個(gè)實(shí)例的某個(gè)屬性時(shí),首先從對(duì)象實(shí)例本身搜索,如果在實(shí)例中找到了該屬性,則返回該屬性的值;如果沒(méi)有找到,則繼續(xù)搜索該實(shí)例的原型對(duì)象,如果在原型對(duì)象中找到了這個(gè)屬性,則返回該屬性的值。
接下來(lái)介紹幾個(gè)方法:
isPrototypeOf()方法判斷實(shí)例與原型之間的關(guān)系。
alert(Person.prototype.isPrototypeOf(person1)); //true alert(Person.prototype.isPrototypeOf(person2)); //true 復(fù)制代碼Object.getPrototypeOf(),在所有支持的實(shí)現(xiàn)中,這個(gè)方法返回[[Prototype]]的值,即實(shí)例對(duì)應(yīng)的原型對(duì)象的值。
alert(Object.getPrototypeOf(person1) == Person.prototype); //true 復(fù)制代碼hasOwnProperty()方法屬性存在于對(duì)象實(shí)例中時(shí),才會(huì)返回 true。
in操作符會(huì)在通過(guò)對(duì)象實(shí)例能夠訪問(wèn)給定屬性時(shí)返回true,無(wú)論該屬性存在于實(shí)例中還是原型中。
hasPrototypeProperty()方法實(shí)例中具有某屬性時(shí)為false,實(shí)例中不具有原型中具有時(shí)為true.
function Person(){ } Person.prototype.name = "Nicholas"; Person.prototype.age = 29; Person.prototype.job = "Software Engineer"; Person.prototype.sayName = function(){alert(this.name); }; var person = new Person(); alert("name" in person); //true alert(person.hasOwnProperty("name")); //false alert(hasPrototypeProperty(person, "name")); //true person.name = "Greg"; alert("name" in person); //true alert(person.hasOwnProperty("name")); //true alert(hasPrototypeProperty(person, "name")); //false 復(fù)制代碼for-in 循環(huán)時(shí),返回的是所有能夠通過(guò)對(duì)象訪問(wèn)的、可枚舉的(enumerated)屬性,其中 既包括存在于實(shí)例中的屬性,也包括存在于原型中的屬性。
Object.keys()方法取得對(duì)象上所有可枚舉的實(shí)例屬性.
Object.getOwnPropertyNames()得到所有實(shí)例屬性,無(wú)論它是否可枚舉.
1.3 原型語(yǔ)法和特性
可以用對(duì)象字面量方法來(lái)重寫整個(gè)原型對(duì)象。
function Person(){ } Person.prototype = {name : "Nicholas",age : 29,job: "Software Engineer",sayName : function () {alert(this.name);} }; 復(fù)制代碼但此時(shí)constructor屬性不再指向Person了,可以重設(shè) constructor 屬性。如下:
function Person(){ } Person.prototype = {constructor : Person,name : "Nicholas", 7 age : 29,job: "Software Engineer",sayName : function () {alert(this.name);} }; 復(fù)制代碼這種方式重設(shè) constructor 屬性會(huì)導(dǎo)致它的[[Enumerable]]特性被設(shè)置為true。默認(rèn)情況下,原生的constructor屬性是不可枚舉的。所以采用以下方式定義。
Object.defineProperty(Person.prototype, "constructor", {enumerable: false,value: Person }); 復(fù)制代碼原型的動(dòng)態(tài)性:由于在原型中查找值的過(guò)程是一次搜索,因此我們對(duì)原型對(duì)象所做的任何修改都能夠立即從實(shí)例上反映出來(lái)——即使是先創(chuàng)建了實(shí)例后修改原型也照樣如此。
var friend = new Person(); Person.prototype.sayHi = function(){alert("hi"); }; friend.sayHi(); //"hi"(沒(méi)有問(wèn)題!) 復(fù)制代碼我們知道,調(diào)用構(gòu)造函數(shù)時(shí)會(huì)為實(shí)例添加一個(gè)指向最初原型的 [[Prototype]]指針,所以重寫原型對(duì)象切斷了現(xiàn)有原型與任何之前已經(jīng)存在的對(duì)象實(shí)例之間的聯(lián)系;它們引用的仍然是最初的原型。
function Person(){ } var friend = new Person(); Person.prototype = {constructor: Person,name : "Nicholas",age : 29,job : "Software Engineer",sayName : function () {alert(this.name);} }; friend.sayName(); //error 復(fù)制代碼我們也可以給原生對(duì)象的原型,定義新方法。例如:
String.prototype.startsWith = function (text) {return this.indexOf(text) == 0; }; var msg = "Hello world!"; alert(msg.startsWith("Hello")); //true 復(fù)制代碼原型中所有屬性是被很多實(shí)例共享的,包含引用類型值的屬性來(lái)說(shuō)就會(huì)有些問(wèn)題。
function Person(){ } Person.prototype = {constructor: Person,name : "Nicholas",age : 29,job : "Software Engineer",friends : ["Shelby", "Court"],sayName : function () {alert(this.name); } }; var person1 = new Person(); var person2 = new Person(); person1.friends.push("Van"); alert(person1.friends); //"Shelby,Court,Van" alert(person2.friends); //"Shelby,Court,Van" alert(person1.friends === person2.friends); //true 復(fù)制代碼1.4 組合使用
組合使用構(gòu)造函數(shù)模式和原型模式
function Person(name, age, job){ this.name = name; 3 this.age = age; this.job = job; this.friends = ["Shelby", "Court"];2} Person.prototype = {constructor : Person,sayName : function(){alert(this.name);} } var person1 = new Person("Nicholas", 29, "Software Engineer"); var person2 = new Person("Greg", 27, "Doctor"); person1.friends.push("Van"); alert(person1.friends); //"Shelby,Count,Van" alert(person2.friends); //"Shelby,Count" alert(person1.friends === person2.friends);//false alert(person1.sayName === person2.sayName);//true 復(fù)制代碼動(dòng)態(tài)原型模式
function Person(name, age, job){//屬性this.name = name; this.age = age; this.job = job;//方法if (typeof this.sayName != "function"){Person.prototype.sayName = function(){alert(this.name);}; } } var friend = new Person("Nicholas", 29, "Software Engineer"); friend.sayName(); 復(fù)制代碼1.4 原型鏈
假如我們讓原型對(duì)象等于另一個(gè)類型的實(shí)例,則此原型對(duì)象將包含一個(gè)指向另一個(gè)原型的指針,相應(yīng)地,另一個(gè)原型中也包含著一個(gè)指向另一個(gè)構(gòu)造函數(shù)的指針。假如另一個(gè)原型又是另一個(gè)類型的實(shí)例,那么上述關(guān)系依然成立,如此層層遞進(jìn),就構(gòu)成了實(shí)例與原型的鏈條。這就是所謂原型鏈的基本概念。
function SuperType(){this.property = true; } SuperType.prototype.getSuperValue = function(){return this.property; }; function SubType(){this.subproperty = false; }//繼承了 SuperType,原來(lái)存在于SuperType的實(shí)例中的所有屬性和方法,現(xiàn)在也存在于SubType.prototype SubType.prototype = new SuperType(); var instance = new SubType(); alert(instance.getSuperValue());//true 復(fù)制代碼要注意instance.constructor現(xiàn)在指向的是SuperType,這是因?yàn)樵瓉?lái)SubType.prototype中的 constructor被重寫了的緣故。
通過(guò)實(shí)現(xiàn)原型鏈,本質(zhì)上擴(kuò)展了原型搜索機(jī)制。當(dāng)以讀取模式訪問(wèn)一個(gè)實(shí)例屬性時(shí),首先會(huì)在實(shí)例中搜索該屬性。如果沒(méi)有找到該屬性,則會(huì)繼續(xù)搜索實(shí)例的原型。在通過(guò)原型鏈實(shí)現(xiàn)繼承的情況下,搜索過(guò)程就得以沿著原型鏈繼續(xù)向上。就拿上面的例子來(lái)說(shuō),調(diào)用 instance.getSuperValue()會(huì)經(jīng)歷三個(gè)搜索步驟:1)搜索實(shí)例;2)搜索SubType.prototype; 3)搜索 SuperType.prototype,最后一步才會(huì)找到該方法。)在找不到屬性或方法的情況下,搜索過(guò)程總是要一環(huán)一環(huán)地前行到原型鏈末端才會(huì)停下來(lái)。
所有引用類型默認(rèn)都繼承了Object,而這個(gè)繼承也是通過(guò)原型鏈實(shí)現(xiàn)的。大家要記住,所有函數(shù)的默認(rèn)原型都是 Object 的實(shí)例,因此默認(rèn)原型都會(huì)包含一個(gè)內(nèi)部指針,指向Object.prototype。這也正是所有自定義類型都會(huì)繼 toString()、 valueOf()等默認(rèn)方法的根本原因。
以上~
一起學(xué)習(xí)喲~~ 比心~~
peace&love
轉(zhuǎn)載于:https://juejin.im/post/5bf3e3a96fb9a049f069e008
總結(jié)
以上是生活随笔為你收集整理的在图书馆学习红宝书的一天(二)· 慢慢看原型、原型链就看懂了~的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 社区电商平台小区乐获GGV领投超1亿美元
- 下一篇: 通过SSH克隆远程仓库(GitLab)到