關於JS的prototype詳解

JavaScript面向對象 構造函數和原型鏈javascript

 

首先,咱們要先了解一下類的概念,JavaScript 自己是一種面向對象的語言,它所涉及的元素根據其屬性的不一樣都依附於某一個特定的類。咱們所常見的類包括:數組變量(Array)、邏輯變量(Boolean)、日期變量(Date)、結構變量(Function)、數值變量(Number)、對象變量(Object)、字符串變量(String) 等,而相關的類的方法,也是程序員常常用到的(在這裏要區分一下類的注意和屬性發方法),例如數組的push方法、日期的get系列方法、字符串的split方法等等,
可是在實際的編程過程當中不知道有沒有感受到現有方法的不足?prototype 方法應運而生!下面,將經過實例由淺入深講解 prototype 的具體使用方法:html

一、最簡單的例子,瞭解 prototype:java

(1) Number.add(num):做用,數字相加程序員

實現方法:Number.prototype.add = function(num){return(this+num);}
試驗:alert((3).add(15)) -> 顯示 18正則表達式


(2) Boolean.rev(): 做用,布爾變量取反編程

實現方法:Boolean.prototype.rev = function(){return(!this);}
試驗:alert((true).rev()) -> 顯示 false數組

是否是很簡單?這一節僅僅是告訴讀者又這麼一種方法,這種方法是這樣運用的。ide

二、已有方法的實現和加強,初識 prototype:函數

(1) Array.push(new_element)
  做用:在數組末尾加入一個新的元素
  實現方法:post

 

複製代碼代碼以下:

  Array.prototype.push = function(new_element){
         this[this.length]=new_element;
         return this.length;
     }

 

  讓咱們進一步來加強他,讓他能夠一次增長多個元素!
  實現方法:

 

複製代碼代碼以下:

  Array.prototype.pushPro = function() {
         var currentLength = this.length;
         for (var i = 0; i < arguments.length; i++) {
             this[currentLength + i] = arguments[i];
         }
         return this.length;
     }

 

  應該不難看懂吧?以此類推,你能夠考慮一下如何經過加強 Array.pop 來實現刪除任意位置,任意多個元素(具體代碼就再也不細說了)

(2) String.length
  做用:這其實是 String 類的一個屬性,可是因爲 JavaScript 將全角、半角均視爲是一個字符,在一些實際運用中可能會形成必定的問題,如今咱們經過 prototype 來彌補這部不足。
  實現方法:

 

複製代碼代碼以下:

  String.prototype.cnLength = function(){
         var arr=this.match(/[^\x00-\xff]/ig);
         return this.length+(arr==null?0:arr.length);
     }

 

  試驗:alert("EaseWe空間Spaces".cnLength()) -> 顯示 16
  這裏用到了一些正則表達式的方法和全角字符的編碼原理,因爲屬於另兩個比較大的類別,本文不加說明,請參考相關材料。

三、新功能的實現,深刻 prototype:在實際編程中所用到的確定不僅是已有方法的加強,更多的實行的功能的要求,下面我就舉兩個用 prototype 解決實際問題的例子:

(1) String.left()
  問題:用過 vb 的應該都知道left函數,從字符串左邊取 n 個字符,可是不足是將全角、半角均視爲是一個字符,形成在中英文混排的版面中不能截取等長的字符串
  做用:從字符串左邊截取 n 個字符,並支持全角半角字符的區分
  實現方法:

 

複製代碼代碼以下:

  String.prototype.left = function(num,mode){
         if(!/\d+/.test(num))return(this);
         var str = this.substr(0,num);
         if(!mode) return str;
         var n = str.Tlength() - str.length;
         num = num - parseInt(n/2);
         return this.substr(0,num);
     }

 

  試驗:
     alert("EaseWe空間Spaces".left(8)) -> 顯示 EaseWe空間
     alert("EaseWe空間Spaces".left(8,true)) -> 顯示 EaseWe空
  本方法用到了上面所提到的String.Tlength()方法,自定義方法之間也能組合出一些不錯的新方法呀!

(2) Date.DayDiff()
  做用:計算出兩個日期型變量的間隔時間(年、月、日、周)
  實現方法:

 

複製代碼代碼以下:

  Date.prototype.DayDiff = function(cDate,mode){
         try{
             cDate.getYear();
         }catch(e){
             return(0);
         }
         var base =60*60*24*1000;
         var result = Math.abs(this - cDate);
         switch(mode){
             case "y":
                 result/=base*365;
                 break;
             case "m":
                 result/=base*365/12;
                 break;
             case "w":
                 result/=base*7;
                 break;
             default:
                 result/=base;
                 break;
         }
         return(Math.floor(result));
     }

 

  試驗:alert((new Date()).DayDiff((new Date(2002,0,1)))) -> 顯示 329
     alert((new Date()).DayDiff((new Date(2002,0,1)),"m")) -> 顯示 10
  固然,也能夠進一步擴充,得出響應的小時、分鐘,甚至是秒。

(3) Number.fact()
  做用:某一數字的階乘
  實現方法:

 

複製代碼代碼以下:

  Number.prototype.fact=function(){
         var num = Math.floor(this);
         if(num<0)return NaN;
         if(num==0 || num==1)
             return 1;
         else
             return (num*(num-1).fact());
     }

 

  試驗:alert((4).fact()) -> 顯示 24
  這個方法主要是說明了遞歸的方法在 prototype 方法中也是可行的!



JavaScript可以實現的面向對象的特徵有:
·公有屬性(public field)
·公有方法(public Method)
·私有屬性(private field)
·私有方法(private field)
·方法重載(method overload)
·構造函數(constructor)
·事件(event)
·單一繼承(single inherit)
·子類重寫父類的屬性或方法(override)
·靜態屬性或方法(static member)


例子一(JavaScript中容許添加行爲的類型):能夠在類型上使用proptotype來爲類型添加行爲。這些行爲只能在類型的實例上體現。 JS中容許的類型有Array, Boolean, Date, Enumerator, Error, Function, Number, Object, RegExp, String

複製代碼代碼以下:

<script type="text/javascript">   
Object.prototype.Property = 1;   
Object.prototype.Method = function ()   
{   
    alert(1);   
}   

var obj = new Object();   
alert(obj.Property);   
obj.Method();   
</script>   

<script type="text/javascript">
Object.prototype.Property = 1;
Object.prototype.Method = function (){ alert(1);} 
var obj = new Object();
alert(obj.Property);
obj.Method();
</script>


例子二(prototype使用的限制):在實例上不能使用prototype,不然發生編譯錯誤

複製代碼代碼以下:

<script type="text/javascript">   
var obj = new Object();   
obj.prototype.Property = 1; //Error  
//Error  
obj.prototype.Method = function()   
{   
    alert(1);   
}   
</script>   

<script type="text/javascript">var obj = new Object();obj.prototype.Property = 1; //Error//Errorobj.prototype.Method = function(){ alert(1);}</script>


例子三(如何定義類型上的靜態成員):能夠爲類型定義「靜態」的屬性和方法,直接在類型上調用便可

複製代碼代碼以下:

<script type="text/javascript">   
Object.Property = 1;   
Object.Method = function()   
{   
    alert(1);   
}   

alert(Object.Property);   
Object.Method();   
</script>   

<script type="text/javascript">Object.Property = 1;Object.Method = function(){ alert(1);} alert(Object.Property);Object.Method();</script>


例子五():這個例子演示了一般的在JavaScript中定義一個類型的方法

複製代碼代碼以下:

<script type="text/javascript">   
function Aclass()   
{   
this.Property = 1;   
this.Method = function()   
{   
    alert(1);   
}   
}   
var obj = new Aclass();   
alert(obj.Property);   
obj.Method();   
</script>  
<script type="text/javascript">function Aclass(){this.Property = 1;this.Method = function(){ alert(1);}}var obj = new Aclass();alert(obj.Property);obj.Method();</script>


例子六(JavaScript中容許添加行爲的類型):能夠在外部使用prototype爲自定義的類型添加屬性和方法。

複製代碼代碼以下:

<script type="text/javascript">   
function Aclass()   
{   
this.Property = 1;   
this.Method = function()   
{   
    alert(1);   
}   
}   
Aclass.prototype.Property2 = 2;   
Aclass.prototype.Method2 = function  
{   
    alert(2);   
}   
var obj = new Aclass();   
alert(obj.Property2);   
obj.Method2();   
</script>   

<script type="text/javascript">function Aclass(){this.Property = 1;this.Method = function(){ alert(1);}}Aclass.prototype.Property2 = 2;Aclass.prototype.Method2 = function{ alert(2);}var obj = new Aclass();alert(obj.Property2);obj.Method2();</script>


例子八():能夠在對象上改變屬性。(這個是確定的)也能夠在對象上改變方法。(和廣泛的面向對象的概念不一樣)

複製代碼代碼以下:

<script type="text/javascript">   
function Aclass()   
{   
this.Property = 1;   
this.Method = function()   
{   
    alert(1);   
}   
}   
var obj = new Aclass();   
obj.Property = 2;   
obj.Method = function()   
{   
    alert(2);   
}   
alert(obj.Property);   
obj.Method();   
</script>  
<script type="text/javascript">function Aclass(){this.Property = 1;this.Method = function(){ alert(1);}}var obj = new Aclass();obj.Property = 2;obj.Method = function(){ alert(2);}alert(obj.Property);obj.Method();</script>


例子九():能夠在對象上增長屬性或方法

複製代碼代碼以下:

<script type="text/javascript">   
function Aclass()   
{   
this.Property = 1;   
this.Method = function()   
{   
    alert(1);   
}   
}   
var obj = new Aclass();   
obj.Property = 2;   
obj.Method = function()   
{   
    alert(2);   
}   
alert(obj.Property);   
obj.Method();   
</script>   

<script type="text/javascript">function Aclass(){this.Property = 1;this.Method = function(){ alert(1);}}var obj = new Aclass();obj.Property = 2;obj.Method = function(){ alert(2);}alert(obj.Property);obj.Method();</script>


例子十(如何讓一個類型繼承於另外一個類型):這個例子說明了一個類型如何從另外一個類型繼承。

複製代碼代碼以下:

<script type="text/javascript">   
function AClass()   
{   
       this.Property = 1;   
       this.Method = function()   
       {   
              alert(1);   
       }   
}   

function AClass2()   
{   
       this.Property2 = 2;   
       this.Method2 = function()   
       {   
              alert(2);   
       }   
}   
AClass2.prototype = new AClass();   

var obj = new AClass2();   
alert(obj.Property);   
obj.Method();   
alert(obj.Property2);   
obj.Method2();   
</script>   

<script type="text/javascript">function AClass(){ this.Property = 1; this.Method = function() { alert(1); }} function AClass2(){ this.Property2 = 2; this.Method2 = function() { alert(2); }}AClass2.prototype = new AClass(); var obj = new AClass2();alert(obj.Property);obj.Method();alert(obj.Property2);obj.Method2();</script>


 例子十一(如何在子類中從新定義父類的成員):這個例子說明了子類如何重寫父類的屬性或方法。

複製代碼代碼以下:

<script type="text/javascript">   
function AClass()   
{   
       this.Property = 1;   
       this.Method = function()   
       {   
              alert(1);   
       }   
}   

function AClass2()   
{   
       this.Property2 = 2;   
       this.Method2 = function()   
       {   
              alert(2);   
       }   
}   
AClass2.prototype = new AClass();   
AClass2.prototype.Property = 3;   
AClass2.prototype.Method = function()   
{   
       alert(4);   
}   
var obj = new AClass2();   
alert(obj.Property);   
obj.Method();   
</script>   

1.構造函數的簡單介紹

  2.構造函數的缺點

  3.prototype屬性的做用

  4.原型鏈(prototype chain)

  5.constructor屬性

    5.1:constructor屬性的做用

  6.instanceof運算符

 

1.構造函數的簡單介紹

  在個人一篇Javascript 中構造函數與new命令的密切關係文章中,詳細了介紹了構造函數的概念和特色,new命令的原理和用法等,若是對於構造函數不熟悉的同窗,能夠前往細細品味。如下作一個簡單的回顧。

  所謂構造函數,就是提供了一個生成對象的模板並描述對象的基本結構的函數。一個構造函數,能夠生成多個對象,每一個對象都有相同的結構。總的來講,構造函數就是對象的模板,對象就是構造函數的實例。

  構造函數的特色有:

    a:構造函數的函數名首字母必須大寫。

    b:內部使用this對象,來指向將要生成的對象實例。

    c:使用new操做符來調用構造函數,並返回對象實例。

  看一個最簡單的一個例子。

複製代碼
1     function Person(){
2         this.name = 'keith';
3     }
4 
5     var boy = new Person();
6     console.log(boy.name);    //'keith'
複製代碼

 

 

2.構造函數的缺點

  全部的實例對象均可以繼承構造函數中的屬性和方法。可是,同一個對象實例之間,沒法共享屬性。

複製代碼
 1     function Person(name,height){
 2         this.name=name;
 3         this.height=height;
 4         this.hobby=function(){
 5             return 'watching movies';
 6         }
 7     }
 8 
 9     var boy=new Person('keith',180);
10     var girl=new Person('rascal',153);
11 
12     console.log(boy.name);    //'keith'
13     console.log(girl.name);    //'rascal'
14     console.log(boy.hobby===girl.hobby);  //false
複製代碼

  上面代碼中,一個構造函數Person生成了兩個對象實例boy和girl,而且有兩個屬性和一個方法。可是,它們的hobby方法是不同的。也就是說,每當你使用new來調用構造函數放回一個對象實例的時候,都會建立一個hobby方法。這既沒有必要,又浪費資源,由於全部hobby方法都是童顏的行爲,徹底能夠被兩個對象實例共享。

  因此,構造函數的缺點就是:同一個構造函數的對象實例之間沒法共享屬性或方法。

 

3.prototype屬性的做用

  爲了解決構造函數的對象實例之間沒法共享屬性的缺點,js提供了prototype屬性。

  js中每一個數據類型都是對象(除了null和undefined),而每一個對象都繼承自另一個對象,後者稱爲「原型」(prototype)對象,只有null除外,它沒有本身的原型對象。

  原型對象上的全部屬性和方法,都會被對象實例所共享

  經過構造函數生成對象實例時,會將對象實例的原型指向構造函數的prototype屬性。每個構造函數都有一個prototype屬性,這個屬性就是對象實例的原型對象。

複製代碼
 1     function Person(name,height){
 2         this.name=name;
 3         this.height=height;
 4     }
 5 
 6     Person.prototype.hobby=function(){
 7         return 'watching movies';
 8     }
 9 
10     var boy=new Person('keith',180);
11     var girl=new Person('rascal',153);
12 
13     console.log(boy.name);    //'keith'
14     console.log(girl.name);    //'rascal'
15     console.log(boy.hobby===girl.hobby);  //true
複製代碼

  上面代碼中,若是將hobby方法放在原型對象上,那麼兩個實例對象都共享着同一個方法。我但願你們都能理解的是,對於構造函數來講,prototype是做爲構造函數的屬性;對於對象實例來講,prototype是對象實例的原型對象。因此prototype便是屬性,又是對象。

  原型對象的屬性不是對象實例的屬性。對象實例的屬性是繼承自構造函數定義的屬性,由於構造函數內部有一個this關鍵字來指向將要生成的對象實例。對象實例的屬性,其實就是構造函數內部定義的屬性。只要修改原型對象上的屬性和方法,變更就會馬上體如今全部對象實例上。

複製代碼
1     Person.prototype.hobby=function(){
2         return 'swimming';
3     }
4     console.log(boy.hobby===girl.hobby);  //true
5     console.log(boy.hobby());    //'swimming'
6     console.log(girl.hobby());    //'swimming'
複製代碼

  上面代碼中,當修改了原型對象的hobby方法以後,兩個對象實例都發生了變化。這是由於對象實例實際上是沒有hobby方法,都是讀取原型對象的hobby方法。也就是說,當某個對象實例沒有該屬性和方法時,就會到原型對象上去查找。若是實例對象自身有某個屬性或方法,就不會去原型對象上查找。

1     boy.hobby=function(){
2         return 'play basketball';
3     }
4     console.log(boy.hobby());    //'play basketball'
5     console.log(girl.hobby());    //'swimming'

  上面代碼中,boy對象實例的hobby方法修改時,就不會在繼承原型對象上的hobby方法了。不過girl仍然會繼承原型對象的方法。

  總結一下:

  a:原型對象的做用,就是定義全部對象實例所共享的屬性和方法。

  b:prototype,對於構造函數來講,它是一個屬性;對於對象實例來講,它是一個原型對象。

  

4.原型鏈(prototype chains)

  對象的屬性和方法,有多是定義在自身,也有多是定義在它的原型對象。因爲原型對象自己對於對象實例來講也是對象,它也有本身的原型,因此造成了一條原型鏈(prototype chain)。好比,a對象是b對象的原型,b對象是c對象的原型,以此類推。全部一切的對象的原型頂端,都是Object.prototype,即Object構造函數的prototype屬性指向的那個對象。

  固然,Object.prototype對象也有本身的原型對象,那就是沒有任何屬性和方法的null對象,而null對象沒有本身的原型。

1     console.log(Object.getPrototypeOf(Object.prototype));    //null
2     console.log(Person.prototype.isPrototypeOf(boy))    //true

  原型鏈(prototype chain)的特色有:

    a:讀取對象的某個屬性時,JavaScript引擎先尋找對象自己的屬性,若是找不到,就到它的原型去找,若是仍是找不到,就到原型的原型去找。若是直到最頂層的Object.prototype仍是找不到,則返回undefined

    b:若是對象自身和它的原型,都定義了一個同名屬性,那麼優先讀取對象自身的屬性,這叫作「覆蓋」(overiding)。

    c:一級級向上在原型鏈尋找某個屬性,對性能是有影響的。所尋找的屬性在越上層的原型對象,對性能的影響越大。若是尋找某個不存在的屬性,將會遍歷整個原型鏈。

  看概念可能比較晦澀,咱們來看一個例子。可是理解了概念真的很重要。

1     var arr=[1,2,3];
2     console.log(arr.length);    //3
3     console.log(arr.valueOf())    //[1,2,3]
4     console.log(arr.join('|'))    //1|2|3    

  上面代碼中,定了一個數組arr,數組裏面有三個元素。咱們並無給數組添加任何屬性和方法,但是卻在調用length,join(),valueOf()時,卻不會報錯。

  length屬性是繼承自Array.prototype的,屬於原型對象上的一個屬性。join方法也是繼承自Array.prototype的,屬於原型對象上的一個方法。這兩個方法是全部數組所共享的。當實例對象上沒有這個length屬性時,就會去原型對象查找。

  valueOf方法是繼承自Object.prototype的。首先,arr數組是沒有valueOf方法的,因此就到原型對象Array.prototype查找。而後,發現Array.prototype對象上沒有valueOf方法。最後,再到它的原型對象Object.prototype查找。

  來看看Array.prototype對象和Object.prototype對象分別有什麼屬性和方法。

複製代碼
1     console.log(Object.getOwnPropertyNames(Array.prototype))
2 //["length", "toSource", "toString", "toLocaleString", "join", "reverse", "sort", "push", "pop", "shift", "unshift", "splice", "concat", "slice", "lastIndexOf", "indexOf", "forEach", "map", "filter", "reduce", "reduceRight", "some", "every", "find", "findIndex", "copyWithin", "fill", "entries", "keys", "values", "includes", "constructor", "$set", "$remove"]
3     console.log(Object.getOwnPropertyNames(Object.prototype))
4 // ["toSource", "toString", "toLocaleString", "valueOf", "watch", "unwatch", "hasOwnProperty", "isPrototypeOf", "propertyIsEnumerable", "__defineGetter__", "__defineSetter__", "__lookupGetter__", "__lookupSetter__", "__proto__", "constructor"]
複製代碼

   我相信,你們看到這裏,對prototype仍是似懂非懂的。這很正常,畢竟是js中比較重要又比較抽象的概念,不可能那麼快就掌握,再啃多幾篇,說不定掌握其精髓。在某乎上,有一個活生生的實例,可能也是你們會遇到的問題。能夠看看 js構造函數和原型對象

 

5.constructor屬性

  prototype對象有一個constructor屬性,默認指向prototype對象所在的構造函數。

1     function A(){};
2     console.log(A.prototype.constructor===A)    //true

  要注意的是,prototype是構造函數的屬性,而constructor則是構造函數的prototype屬性所指向的那個對象,也就是原型對象的屬性。注意不要混淆。

1     console.log(A.hasOwnProperty('prototype'));    //true
2     console.log(A.prototype.hasOwnProperty('constructor')); //true

  因爲constructor屬性是定義在原型(prototype)對象上面,意味着能夠被全部實例對象繼承。

1     function A(){};
2     var a=new A();
3 
4     console.log(a.constructor);    //A()
5     console.log(a.constructor===A.prototype.constructor);//true

  上面代碼中,a是構造函數A的實例對象,可是a自身沒有contructor屬性,該屬性實際上是讀取原型鏈上面的A.prototype.constructor屬性。

  5.1:constructor屬性的做用

    a:分辨原型對象到底屬於哪一個構造函數

1     function A(){};
2     var a=new A();
3 
4     console.log(a.constructor===A)    //true
5     console.log(a.constructor===Array)    //false

    上面代碼表示,使用constructor屬性,肯定實例對象a的構造函數是A,而不是Array

    b:從實例新建另外一個實例

1     function A() {};
2     var a = new A();
3     var b = new a.constructor();
4     console.log(b instanceof A);    //true

    上面代碼中,a是構造函數A的實例對象,能夠從a.constructor間接調用構造函數。

    c:調用自身的構造函數成爲可能

1     A.prototype.hello = function() {
2         return new this.constructor();
3     }

    d:提供了一種從構造函數繼承另一種構造函數的模式

複製代碼
1     function Father() {}
2 
3     function Son() {
4         Son.height.constructor.call(this);
5     }
6 
7     Son.height = new Father();
複製代碼

    上面代碼中,FatherSon都是構造函數,在Son內部的this上調用Father,就會造成Son繼承Father的效果。

    e:因爲constructor屬性是一種原型對象和構造函數的關係,因此在修改原型對象的時候,必定要注意constructor的指向問題。

    解決方法有兩種,要麼將constructor屬性指向原來的構造函數,要麼只在原型對象上添加屬性和方法,避免instanceof失真。

 

6.instanceof運算符

  instanceof運算符返回一個布爾值,表示指定對象是否爲某個構造函數的實例。

1     function A() {};
2     var a = new A();
3     console.log(a instanceof A);    //true

  由於instanceof對整個原型鏈上的對象都有效,因此同一個實例對象,可能會對多個構造函數都返回true。

1     function A() {};
2     var a = new A();
3     console.log(a instanceof A);    //true
4     console.log(a instanceof Object);    //true

  注意,instanceof對象只能用於複雜數據類型(數組,對象等),不能用於簡單數據類型(布爾值,數字,字符串等)。

複製代碼
1     var x = [1];
2     var o = {};
3     var b = true;
4     var c = 'string';
5     console.log(x instanceof Array);    //true
6     console.log(o instanceof Object);    //true
7     console.log(b instanceof Boolean);    //false
8     console.log(c instanceof String);    //false
複製代碼

  此外,null和undefined都不是對象,因此instanceof 老是返回false。

1     console.log(null instanceof Object);    //false
2     console.log(undefined instanceof Object);    //false

  利用instanceof運算符,還能夠巧妙地解決,調用構造函數時,忘了加new命令的問題。

複製代碼
1     function Keith(name,height) {
2         if (! this instanceof Keith) {
3             return new Keith(name,height);
4         }
5         this.name = name;
6         this.height = height;
7     }
複製代碼

  上面代碼中,使用了instanceof運算符來判斷函數體內的this關鍵字是否指向構造函數Keith的實例,若是不是,就代表忘記加new命令,此時構造函數會返回一個對象實例,避免出現意想不到的結果。

 

 

    由於限於篇幅的緣由,暫時介紹到這裏。

  我會在下次的分享中談談原型(prototype)對象的一些原生方法,如Object.getPrototypeOf(),Object.setPrototypeOf()等,而且介紹獲取原生對象方法的比較。

相關文章
相關標籤/搜索