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