作者:極客小俊 一個專注于web技術的80后
我不用拼過聰明人,我只需要拼過那些懶人 我就一定會超越大部分人!
CSDN@極客小俊,原創文章, B站技術分享
B站視頻 : ?? Bilibili.com ??
個人博客: ?? cnblogs.com ??
首先我們了解一下普通物件 與 函式物件
我們在學習原型之前首先了解一下javascript當中的 普通物件 和 函式物件,
如圖1

- 普通物件:
- 最普通的物件:具有_ _ proto_ _這個屬性它(指向其原型鏈),注意: 普通的物件是沒有prototype這個屬性的! 如果你呼叫必然回傳undefined
- 原型物件: 我們會在下面繼續說明!
javascript中哪些情況屬于普通物件 如下代碼:
//普通物件
function Test() {
}
var obj1 = new Test();
var obj2 = new Object();
var obj3 = {};
console.log(typeof obj1); //Object
console.log(typeof obj2); //Object
console.log(typeof obj3); //Object
-
函式物件:
-
凡是用Function()創建的都是函式物件, 比如:自定義函式, 事件函式 系統的Object、Array、Date、String、RegEx 以上都屬于 函式物件
==小提示:== 以上的都是Function的實體物件, 那么也只有實體才會有_ _ proto_ _ 這個屬性
-
? 注意: 這里的Function是比較特殊的函式物件, 因為Function.prototype本身它應該指向是原型物件, 但Function的prototype卻是函式物件 上圖已經有說明!
javascript中哪些情況屬于函式物件 如下代碼:
//函式物件
function F1(){
}
var F2 = function(){
}
var F3 = function(a,b){
}
window.onload=function () {
var div1=document.getElementById('div1');
div1.onclick=function () {
alert(1);
}
console.log(typeof div1.onclick); //function
}
console.log(typeof F1); //function
console.log(typeof F2); //function
console.log(typeof F3); //function
console.log(typeof Object); //function
console.log(typeof Array); //function
console.log(typeof String); //function
console.log(typeof Date); //function
console.log(typeof RegEx); //function
console.log(typeof Function); //function
函式物件都是Function的實體物件
就如同Array是通過Function創建出來的,
因為Array是Function的實體,是實體就會有._ _ proto_ _ 這個屬性, 從上面的流程圖上看 一個函式物件的_ _ proto_ _屬性值是: ? () { [native code] }
而特殊的Function.prototype的值也是一個: ? () { [native code] }
所以我們可以推斷出一個條件: 函式物件._ _ proto_ _ === Function.prototype 是成立的! 回傳true
由此引出下面判斷條件:
Array._ _proto_ _ == Function.prototype //true
String._ _proto_ _ == Function.prototype //true
RegExp._ _proto_ _== Function.prototype //true
Date._ _proto_ _ == Function.prototype //true
? () { [native code] } 是什么?
native code 的意思是它是程式自帶的,是二進制編譯的無法顯示出來代碼, native code是本地代碼, 這里我們就簡單的解釋一下即可!
以上內容作為學習原型之前的鋪墊了解即可!! 接下來我們就慢慢的拆解圖1的細節內容!!
引出兩個問題
問題1 性能方面
如果創建一個物件 在記憶體的堆區中就會開辟一個空間來保存物件,如果每個物件里面有相同的方法也會被創建出來
這樣就存在一個問題,就是公共的方法或者屬性會存在記憶體當中n份.. , 大量的占用了記憶體開銷!
如圖2
每一個物件都生成了同樣的say()方法, 這種代碼中如果每個物件都有公共一樣的方法 就顯得很占據記憶體空間!

上圖的代碼如下
function Person(name,age){
this.name=name;
this,age=age;
this.say=function () {
console('輸出結果');
}
}
var obj1=new Person();
var obj2=new Person();
var obj3=new Person();
var obj4=new Person();
var obj5=new Person();
問題2 屬性 方法 不能共享!
有時候我們希望一個方法能夠被多個相同物件型別都可以公共的進行使用!
例如: 定義一個陣列私有方法, 而另外一個陣列物件是不能訪問這個私有方法的
代碼案例:
var arr=[5,5,10];
//arr陣列物件的sum方法
arr.sum=function () {
var result=0;
for(var i=0;i<this.length;i++){
result+=this[i];
}
return result;
}
console.log(arr.sum()); //結果: 20
var arr2=[10,10,10];
console.log(arr2.sum()); //結果: Uncaught TypeError: arr2.sum is not a function
解答: 這里報錯是因為 arr2 這個陣列物件 根本就不存在sum() 這個方法, 它是屬于arr陣列物件私有的一個方法!
所以有時候我們希望一個方法能夠被多個相同物件型別都可以公共的來使用!
以上兩個問題 , 問題1是性能優化不足,問題2是私有方法不能被相同型別的物件呼叫 , 所以解決上述問題方法或屬性不能共享的辦法 就要用到: 原型 [提高性能] 也就是通常說的: 原型模式
接下來我們就來探討一下原型是什么!
原型物件是什么!
根據圖1 函式物件都有一個prototype屬性指向的是一個原型物件, 那么我們可以推出以下概念:
每創建一個函式都會有一個prototype屬性,這個屬性是一個指標,指向一個物件(原型物件)
原型物件,也就是(建構式.prototype), 當中含有一個constructor 屬性 這個屬性(指向的就是當前原型物件的建構式)
如下圖:

原型物件作用:是包含特定型別的所有實體共享的屬性和方法, 就是說你把屬性和方法定義在原型物件里面之后,那么這個型別的實體就都會共享這些屬性和方法!
原型物件優點: 就是可以讓所有實體物件共享它所包含的屬性和方法,
原型物件的語法基礎
要特定型別的所有實體都共享的屬性和方法, 就要把它們定義在原型物件的下面!
原型: 要使用prototype這個關鍵字, 要寫在建構式的下面:
//語法如下
建構式名.prototype.屬性=值;
建構式名.prototype.方法=function(){
..代碼段..
}
如下圖: 所以用: 建構式名.prototype 你就可以把屬性和方法定義在原型物件當中

案例代碼:
function createPerson(name,age) {
this.name=name;
this,age=age;
}
createPerson.prototype.say=function () {
console.log('我的名字叫'+this.name);
}
var a=new createPerson('張三','33');
var b=new createPerson('李四','55');
var c=new createPerson('王武','66');
a.say();
b.say();
c.say();
為了方便理解 我畫了一張圖, 以上代碼的流程圖分析如下圖:

特殊的Function.prototype
Function.prototype是個例外,為什么說它是一個例外呢? 按道理來說它這里獲取出來的應該是一個原型物件,但卻是一個函式物件,
作為一個函式物件,它又沒有prototype屬性, 從圖1中就可以看出來這個道理!
你用Function.prototype 獲取出來是一個: ? () { [native code] } 這個東西是什么上面已經解釋過了!
如下圖:

原型知識點
為了節省記憶體,我們把物件的方法都放在原型里面,為什么呢?
因為改寫物件下面公用的方法或者屬性、讓公用的方法或者屬性在記憶體中只存在一份
在我們通過new實體化物件的時候,在記憶體中,會自動拷貝建構式中的所有屬性和方法,用來給實體物件賦值,而不管我們實體化多少次,原型里面的屬性和方法只生成一次,所以會節省記憶體,
普通定義方式與原型定義的優先級高低
如下代碼:
function createPerson(name,age) {
this.name=name;
this,age=age;
}
createPerson.prototype.say=function () {
console.log('我的名字叫'+this.name);
}
var a=new createPerson('張三','33');
var b=new createPerson('李四','55');
var c=new createPerson('王武','66');
a.say();
b.say();
c.say();
//普通定義的優先級高于原型prototype
c.say=function(){
console.log('輸出ok');
}
c.say();
所以以上的普通定義的方式要比原型定義的方式的優先級高!,但這并不是把原型覆寫了 只是優先呼叫普通定義的方法
如圖:

原型的中的 _ _ proto_ _
首先回顧一下, 實體化new的時候,系統會在記憶體中創建了一個空的物件,就像是這樣 var p = {} , 拷貝建構式中的屬性和方法到空物件中,
重點的是: 每個實體化物件都會有一個 _ _ proto_ _ 屬性, 這個屬性是自動生成的, _ _ proto_ _ 屬性指向類的原型物件,
建構式、實體化物件、原型物件的之間的關系
先來看一段代碼案例
function createPerson(name,age) {
this.name=name;
this,age=age;
}
createPerson.prototype.say=function () {
console.log('我的名字叫'+this.name);
}
var obj=new createPerson('張三','33');
console.log(obj.__proto__); //實體化obj的__proto__屬性 可以獲取到原型物件
console.log(createPerson.prototype); //建構式的prototype屬性 也可以獲取到原型物件
console.log(obj.__proto__.constructor); //原型物件中的constructor又可以獲取到建構式
建構式、實體化物件、原型物件的基本關系圖分析

當然你也可以通過 實體化物件的_ _ proto_ _屬性 和 建構式的prototype屬性進行比較可以驗證結果, 我們可以通過列印來驗證
console.log(實體化物件._ _proto_ _ === 建構式.prototype); //true
定義在實體和定義在原型下的區別總結:
先看一段代碼案例:
function Test(){
}
//定義屬性
Test.prototype.name = "張三";
Test.prototype.age = 33;
//定義方法
Test.prototype.getAge = function(){
return this.age;
}
var t1 = new Test();
var t2 = new Test();
var t3 = new Test();
t3.name = "李四";
console.log(t1.name); // 張三 來自原型
console.log(t2.name); // 張三 來自原型
console.log(t3.name); // 李四 來自實體
//列印實體看下圖結果
console.log(t1);
console.log(t2);
console.log(t3);

以上圖就解釋了為什么定義在原型中 屬性和方法是公用的, 而單獨定義在實體中是屬于獨立的屬性和方法不共有!
所以 我們也推斷出 在實體中定義屬性和方法 會覆寫 或者說 會實作呼叫實體中定義的屬性和方法 如果沒有才會到原型中去尋找! 這里其實就是我們一會要講到的原型鏈!
_ _ proto_ _與 prototype的詳細認識
1.所有的參考型別,比如陣列、物件、都有一個_ _ proto_ _屬性(也叫隱式原型,它來指向自己的原型物件)
重點再次提醒: 所有的物件參考 都有_ _ proto_ _ 這個屬性! 記住了!
通過下面的測驗我們不難發現,其中它們賦值的參考物件中 列印出來看到都有一個 _ _ proto_ _的屬性 都是指向自己的原型物件
代碼如下:
function createPerson(name,age) {
this.name=name;
this,age=age;
}
createPerson.prototype.say=function () {
console.log('我的名字叫'+this.name);
}
var obj=new createPerson('測驗','33');
//物件參考列印
console.log(obj);
var arr=[1,2,3];
//陣列參考的列印
console.log(arr);
var arr2=new Array(2,2,2);
//陣列參考的列印
console.log(arr2);
**2.再一次重點注意: 所有參考型別,它的_ _ proto_ _屬性指向這個參考本身的原型物件 而建構式的prototype屬性的值也就是指向的原型物件 **
所以在各自相應參考型別的_ _ proto_ _屬性 和 建構式的 prototype屬性 彼此它們兩個是相等的! 上面的圖中也可以表明這一點!
_ _ proto_ _屬性 和 建構式的 prototype屬性比較, 案例代碼如下:
//案例1
function createPerson(name,age) {
this.name=name;
this,age=age;
}
createPerson.prototype.say=function () {
console.log('我的名字叫'+this.name);
}
var obj=new createPerson('張三','33');
console.log(obj.__proto__); //列印出obj物件參考的原型物件
console.log(createPerson.prototype); //列印出createPerson建構式的原型物件
console.log(obj.__proto__===createPerson.prototype); //而且它們是相等的,指向同一個原型物件
//案例2
var arr=new Array(2,2,2);
console.log(arr.__proto__);
console.log(Array.prototype);
console.log(arr.__proto__ === Array.prototype);
3.所有的建構式 或者 普通函式都有一個prototype屬性 (這也叫顯式原型,它也指向自己的原型物件),
案例代碼:
//普通函式
function Test() {
}
//列印普通函式的prototype屬性
console.log(Test.prototype);
//建構式
function createPerson(name,age) {
this.name=name;
this,age=age;
}
createPerson.prototype.say=function () {
console.log('我的名字叫'+this.name);
}
console.log(createPerson.prototype);
圖解如下:

_ _proto _ _和 prototype區別
prototype是每個函式都會具備的一個屬性,它是一個指標,指向原型物件,只有普通函式或 建構式才有,
_ _ proto_ _屬性 是主流瀏覽器上在除null物件以外的每個參考物件上都支持存在的一個屬性,它能夠指向當前該參考物件的:原型物件
其實_ _ proto_ _ 就是用來將參考物件與原型相連的屬性
小結: 一個只有函式才有的屬性(prototype),一個是參考物件才有的屬性(_ _ proto_ _ ),
注意: 你用一個函式去呼叫屬性(_ _ proto_ _ ), 會得到一個: ? () { [native code] }
原型中批量添加屬性與方法
使用prototype這個關鍵字, 要批量的把屬性和方法寫入原型 就在建構式的下面寫一個JSON格式 如下代碼, 這樣比單一的一個個寫方便!
//語法
建構式.prototype={
屬性名: 值,
方法名:function(){
//方法體... 這里的this是什么要看執行的時候誰呼叫了這個函式
},
方法名:function(){
//方法體... 這里的this是什么要看執行的時候誰呼叫了這個函式
}
}
案例代碼:
createPerson.prototype={
aaa:123,
// prototype物件里面又有其他的屬性
showName:function(){
//this是什么要看執行的時候誰呼叫了這個函式
console.log("我的名字叫:"+this.name);
},
showAge:function(){
//this是什么要看執行的時候誰呼叫了這個函式
console.log("我的年齡是:"+this.age);
}
}
function createPerson(name,age) {
this.name=name;
this.age=age;
}
var obj= new createPerson('張三','33');
console.log(obj);
obj.showName();
obj.showAge();
console.log(obj.aaa);
以上原型代碼 圖解

原型注意事項
重點注意
如果是自定義建構式,并且使用{ }這種方式批量的在prototype中定義屬性和方法, 會改變原型中constructor對建構式的指向!
也就是說使用{ }這種方式批量在prototype中定義屬性和方法, 那么constructor的指向就是一個函式物件
測驗代碼如下
function createPerson(name,age) {
this.name=name;
this,age=age;
}
/* createPerson.prototype.say=function(){
console.log('我的名字叫'+this.name);
}*/
createPerson.prototype={
say:function () {
console.log('我的名字叫'+this.name);
}
}
var obj=new createPerson('張三','33');
console.log(obj.__proto__);
在控制臺輸出會看到 _ _proto _ _的值, constructor這個屬性就沒有了!

console.log(createPerson.prototype.constructor); //回傳 ? Object() { [native code] }
原型基本小結
- 讓相同方法在記憶體中存在一份
- 原型定義方式要比普通定義方式的優先級要低
- 在專案當中公共相同的屬性和方法可以加載在原型上
原型鏈 核心原理
首先這里要提出一點的是 在JS中實作繼承主要是依靠原型鏈來實作! ,所以我們才需要學習原型鏈的原理!
原型鏈核心概念
原型鏈: 當試圖呼叫或想得到一個物件實體中的屬性 或 方法時,如果這個物件本身不存在這個屬性 或 方法 也就是說建構式中沒有定義你想要的屬性或方法,那么就會通過建構式的prototype屬性到原型中去 尋找這個屬性 或者 方法 (也就是它的建構式的’prototype’屬性會到原型物件中去尋找) , 如果有就回傳,如果沒有就會到頂層的Object去找 , 如果有就回傳, 如果還是找不到就回傳undefined!
原型鏈流程圖

上圖可以用以下例子來說明:當建構式 Test 存在 getName 這個方法時,就不用到建構式的原型當中去找 getName 這個方法;反之,就到建構式的原型當中去找 getName 這個方法;如果建構式的原型中也不存在 getName 這個方法時,就要到頂層物件的原型中去找 getName 這個方法,
上圖測驗案例代碼如下:
function Test(name){
this.name=name;
this.getName=function(){
return this.name+"我在建構式中";
}
}
Test.prototype.getName=function(){
return this.name+"我在原型物件中";
}
Object.prototype.getName=function(){
return this.name+"我在頂層物件中";
}
var t1=new Test('小紅');
console.log(t1.getName());
原型鏈案例代碼2 如下:
Array.prototype.aaa=123; //把這個自定義屬性定義到原型物件下
var arr=new Array(1,2,3);
console.log(arr.aaa);
console.log(Array.prototype);
var arr2=['重慶','上海','北京'];
console.log(arr2.aaa);
如下圖:

小結: arr 和 arr2都能夠找到aaa這個屬性, 并且這個屬性是陣列原型物件下的屬性,是公共的
只要是陣列就可以呼叫這個屬性, 同理方法也是一樣,
所以創建很多很多個相同型別物件的時候, 創建出來的每一個物件,如果里面都有一些公共的方法,這樣就會占用很多的資源,
而通過原型來實作的話,只需要在建構式里面給屬性賦值,而把方法寫在prototype屬性,當然屬性也是可以寫在原型當中的,
這樣每個物件參考都可以使用prototype屬性里面的方法 或 屬性,并且節省了不少的資源
這就是我們為什么要使用原型的原因!
原型鏈總體結構圖解小結
當試圖呼叫或想得到一個物件實體中的屬性 或 方法時,如果這個物件本身不存在這個屬性 或 方法 那么就會通過建構式的prototype屬性到原型中去 尋找這個屬性 或者 方法 如果有就回傳,如果沒有就會到頂層的Object去找 , 如果有就回傳, 如果還是找不到就回傳undefined!
但又因為建構式中的prototype屬性值本身又是一個物件(原型物件), 所以這里它也有一個_ _ proto_ _ 屬性 , 就可以向上繼續獲取_ _ _ proto_ _屬性 , 那么這里獲取出來的就是頂層的Object物件
如下圖:

小結:
當obj呼叫test()方法,JS發現Fn中沒有這個方法,于是它就去Fn.prototype中去找,發現還是沒有這個方法,然后就去Object.prototype中去找,找到了,就呼叫Object.prototype中的test()方法,
這就是原型鏈查找,而則一層一層的鏈接的關系就是:原型鏈,
obj能夠呼叫Object.prototype中的方法正是因為存在原型鏈關系的機制!
另外,在使用原型的時候,一般推薦將需要擴展的方法寫在 建構式.prototype屬性中,
而不要寫在: 建構式.prototype._ _ proto _ _中, 因為這里也就是Object頂層物件,定義到這里的屬性和方法 所有JS物件都可以呼叫,在這里面定義多了就會影響整體性能,所以不建議定義到這里!


"點贊" "評論" "收藏"轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/188907.html
標籤:其他
上一篇:js之資料型別(1)
下一篇:HTML常用小知識
