JavaScript學習總結(五)原型和原型鏈詳解

贊助我以寫出更好的文章,give me a cup of coffee?javascript


私有變量和函數

在函數內部定義的變量和函數,若是不對外提供接口,外部是沒法訪問到的,也就是該函數的私有的變量和函數。前端

<script type="text/javascript">
    function Box(){
        var color = "blue";//私有變量
        var fn = function() //私有函數
        {
            
        }
    }
</script>

這樣在函數對象Box外部沒法訪問變量colorfn,他們就變成私有的了:java

var obj = new Box();
    alert(obj.color);//彈出 undefined
    alert(obj.fn);//同上

靜態變量和函數

當定義一個函數後經過點號 「.」爲其添加的屬性和函數,經過對象自己仍然能夠訪問獲得,可是其實例卻訪問不到,這樣的變量和函數分別被稱爲靜態變量靜態函數git

<script type="text/javascript">
    function Obj(){};

    Obj.num = 72;//靜態變量
    Obj.fn = function()  //靜態函數
    {
        
    }  
    
    alert(Obj.num);//72
    alert(typeof Obj.fn)//function
    
    var t = new Obj();
    alert(t.name);//undefined
    alert(typeof t.fn);//undefined
</script>

實例變量和函數

在面向對象編程中除了一些庫函數咱們仍是但願在對象定義的時候同時定義一些屬性和方法,實例化後能夠訪問,js也能作到這樣github

<script type="text/javascript">
          function Box(){
                this.a=[]; //實例變量
                this.fn=function(){ //實例方法
                    
                }
            }
            
            console.log(typeof Box.a); //undefined
            console.log(typeof Box.fn); //undefined
            
            var box=new Box();
            console.log(typeof box.a); //object
            console.log(typeof box.fn); //function
</script>

爲實例變量和方法添加新的方法和屬性面試

<script type="text/javascript">
function Box(){
                this.a=[]; //實例變量
                this.fn=function(){ //實例方法
                    
                }
            }
            
            var box1=new Box();
            box1.a.push(1);
            box1.fn={};
            console.log(box1.a); //[1]
            console.log(typeof box1.fn); //object

            var box2=new Box();
            console.log(box2.a); //[]
            console.log(typeof box2.fn); //function
</script>

box1中修改了afn,而在box2中沒有改變,因爲數組和函數都是對象,是引用類型,這就說明box1中的屬性和方法與box2中的屬性與方法雖然同名但卻不是一個引用,而是對Box對象定義的屬性和方法的一個複製。編程

這個對屬性來講沒有什麼問題,可是對於方法來講問題就很大了,由於方法都是在作徹底同樣的功能,可是卻又兩份複製,若是一個函數對象有上千和實例方法,那麼它的每一個實例都要保持一份上千個方法的複製,這顯然是不科學的,這可腫麼辦呢,prototype應運而生。segmentfault

基本概念

咱們建立的每一個函數都有一個prototype屬性,這個屬性是一個指針,指向一個對象,這個對象的用途是包含能夠由特定類型的全部實例共享的屬性和方法。那麼,prototype就是經過調用構造函數而建立的那個對象實例的原型對象。數組

使用原型的好處是可讓對象實例共享它所包含的屬性和方法。也就是說,沒必要在構造函數中添加定義對象信息,而是能夠直接將這些信息添加到原型中。使用構造函數的主要問題就是每一個方法都要在每一個實例中建立一遍。瀏覽器

JavaScript中,一共有兩種類型的值,原始值和對象值。每一個對象都有一個內部屬性 prototype ,咱們一般稱之爲原型。原型的值能夠是一個對象,也能夠是null。若是它的值是一個對象,則這個對象也必定有本身的原型。這樣就造成了一條線性的鏈,咱們稱之爲原型鏈

含義

函數能夠用來做爲構造函數來使用。另外只有函數纔有prototype屬性而且能夠訪問到,可是對象實例不具備該屬性,只有一個內部的不可訪問的__proto__屬性。__proto__是對象中一個指向相關原型的神祕連接。按照標準,__proto__是不對外公開的,也就是說是個私有屬性,可是Firefox的引擎將他暴露了出來成爲了一個共有的屬性,咱們能夠對外訪問和設置。

<script type="text/javascript">
    var Browser = function(){};
    Browser.prototype.run = function(){
        alert("I'm Gecko,a kernel of firefox");
    }
    
    var Bro = new Browser();
    Bro.run();
</script>

當咱們調用Bro.run()方法時,因爲Bro中沒有這個方法,因此,他就會去他的__proto__中去找,也就是Browser.prototype,因此最終執行了該run()方法。(在這裏,函數首字母大寫的都表明構造函數,以用來區分普通函數)

當調用構造函數建立一個實例的時候,實例內部將包含一個內部指針(__proto__)指向構造函數的prototype,這個鏈接存在於實例和構造函數的prototype之間,而不是實例與構造函數之間。

<script type="text/javascript">
function Person(name){                             //構造函數
                this.name=name;
            }
            
            Person.prototype.printName=function() //原型對象
            {
                alert(this.name);
            }
            
            var person1=new Person('Byron');//實例化對象
            console.log(person1.__proto__);//Person
            console.log(person1.constructor);//本身試試看會是什麼吧
            console.log(Person.prototype);//指向原型對象Person
            var person2=new Person('Frank');
</script>

Person的實例person1中包含了name屬性,同時自動生成一個__proto__屬性,該屬性指向Person的prototype,能夠訪問到prototype內定義的printName方法,大概就是這個樣子的:

27224146-a78177522b814194a4eaaa840e1b4aea.png
 每一個JavaScript函數都有prototype屬性,這個屬性引用了一個對象,這個對象就是原型對象。原型對象初始化的時候是空的,咱們能夠在裏面自定義任何屬性和方法,這些方法和屬性都將被該構造函數所建立的對象繼承。

那麼,如今問題來了。構造函數、實例和原型對象三者之間有什麼關係呢?

構造函數、實例和原型對象的區別

實例就是經過構造函數建立的。實例一創造出來就具備constructor屬性(指向構造函數)和__proto__屬性(指向原型對象),

構造函數中有一個prototype屬性,這個屬性是一個指針,指向它的原型對象。

原型對象內部也有一個指針(constructor屬性)指向構造函數:Person.prototype.constructor = Person;

實例能夠訪問原型對象上定義的屬性和方法。

在這裏person1和person2就是實例,prototype是他們的原型對象。

再舉個栗子:

<script type="text/javascript">
    function Animal(name)   //積累構造函數
    {
        this.name = name;//設置對象屬性
    }
    
    Animal.prototype.behavior = function() //給基類構造函數的prototype添加behavior方法
    {  
        alert("this is a "+this.name);
    }
    
    var Dog = new Animal("dog");//建立Dog對象
    var Cat = new Animal("cat");//建立Cat對象
    
    Dog.behavior();//經過Dog對象直接調用behavior方法
    Cat.behavior();//output "this is a cat"
    
    alert(Dog.behavior==Cat.behavior);//output true;
</script>

能夠從程序運行結果看出,構造函數prototype上定義的方法確實能夠經過對象直接調用到,並且代碼是共享的。(能夠試一下將Animal.prototype.behavior 中的prototype屬性去掉,看看還能不能運行。)在這裏,prototype屬性指向Animal對象。

數組對象實例

再看個數組對象的實例。當咱們建立出array1這個對象的時候,array1實際在Javascript引擎中的對象模型以下:

var array1 = [1,2,3];

圖片描述

array1對象具備一個length屬性值爲3,可是咱們能夠經過以下的方法來爲array1增長元素:

array1.push(4);

push這個方法來自於array1的__proto__成員指向對象的一個方法(Array.prototye.push())。正是由於全部的數組對象(經過[]來建立的)都包含有一個指向同一個具備push,reverse等方法對象(Array.prototype)的__proto__成員,才使得這些數組對象可使用push,reverse等方法。

函數對象實例

function Base() {  
    this.id = "base" 
}

圖片描述

var obj = new Base();

這樣代碼的結果是什麼,咱們在Javascript引擎中看到的對象模型是:

圖片描述

new操做符具體幹了什麼呢?其實很簡單,就幹了三件事情。

var obj  = {};  
obj.__proto__ = Base.prototype;  
Base.call(obj);

原型鏈

原型鏈:當從一個對象那裏調取屬性或方法時,若是該對象自身不存在這樣的屬性或方法,就會去本身關聯的prototype對象那裏尋找,若是prototype沒有,就會去prototype關聯的前輩prototype那裏尋找,若是再沒有則繼續查找Prototype.Prototype引用的對象,依次類推,直到Prototype.….Prototype爲undefined(Object的Prototype就是undefined)從而造成了所謂的「原型鏈」。

<script type="text/javascript">
    function Shape(){
        this.name = "shape";
        this.toString = function(){
            return this.name;
        }
    }
    function TwoShape(){
        this.name = "2 shape";
    }
    function Triangle(side,height){
        this.name = "Triangle";
        this.side = side;
        this.height = height;
        this.getArea = function(){
            return this.side*this.height/2;
        }
    }
    
    TwoShape.prototype = new Shape();
    Triangle.prototype = new TwoShape();
</script>

這裏,用構造器Shape()新建了一個實體,而後用它去覆蓋該對象的原型。

<script type="text/javascript">
    function Shape(){
        this.name = "shape";
        this.toString = function(){
            return this.name;
        }
    }
    function TwoShape(){
        this.name = "2 shape";
    }
    function Triangle(side,height){
        this.name = "Triangle";
        this.side = side;
        this.height = height;
        this.getArea = function(){
            return this.side*this.height/2;
        }
    }
    
    TwoShape.prototype = new Shape();
    Triangle.prototype = new TwoShape();
    
    TwoShape.prototype.constructor = TwoShape;
    Triangle.prototype.constructor = Triangle;
    
    var my = new Triangle(5,10);
    my.getArea();
    my.toString();//Triangle
    my.constructor;//Triangle(side,height)
</script>

原型繼承

原型繼承:
在原型鏈的末端,就是Object構造函數prototype屬性指向的那個原型對象。這個原型對象是全部對象的祖先,這個老祖宗實現了諸如toString等全部對象天生就該具備的方法。其餘內置構造函數,如Function,Boolean,String,DateRegExp等的prototype都是從這個老祖宗傳承下來的,但他們各自又定義了自身的屬性和方法,從而他們的子孫就表現出各自宗族的那些特徵。

ECMAScript中,實現繼承的方法就是依靠原型鏈實現的。

<script type="text/javascript">
    function Father(){             //被繼承的函數叫作超類型(父類,基類)
    this.name = "Jack";
}

function Son(){          //繼承的函數叫作子類型(子類,派生類)
    this.age = 10;
}
//經過原型鏈繼承,賦值給子類型的原型屬性
//new Father()會將father構造裏的信息和原型裏的信息都交給Son

Son.prototype = new Father();//Son繼承了Father,經過原型,造成鏈條

var son = new Son();
alert(son.name);//彈出 Jack
</script>

原型鏈的問題:原型鏈雖然很強大,能夠用它來實現繼承,但它也存在一些問題。其中最主要的問題來自包含引用類型的值原型。包含引用類型的原型屬性會被全部實例共享;而這也正是爲何要在構造函數中,而不是在原型對象中定義屬性的緣由。在經過原型來實現繼承時,原型實際上回變成另外一個類型的實例。因而,原先的實例屬性也就變成了原型的屬性。

在建立子類型的實例時,不能向超類型的構造函數中傳遞參數。實際上,應該說是沒有辦法在不影響全部對象實例的狀況下,給超類型的構造函數傳遞參數。再加上剛剛討論的因爲原型中包含引用類型值所帶來的問題,實踐中不多會單獨使用原型鏈。

再舉個栗子:

<script type="text/javascript">
    function Person(name)
    {
        this.name = name;//設置對象屬性
    };
    
    Person.prototype.company = "Microsoft";//設置原型的屬性
    Person.prototype.SayHello = function() //原型的方法
    {  
        alert("Hello,I'm "+ this.name+ " of " + this.company);
    };
    
    var BillGates = new Person("BillGates");//建立person對象
    BillGates.SayHello();//繼承了原型的內容,輸出"Hello,I'm BillGates of Microsoft"
    
    var Jobs = new Person("Jobs");
    Jobs.company = "Apple";//設置本身的company屬性,掩蓋了原型的company屬性
    Jobs.SayHello = function()
    {
        alert("Hi,"+this.name + " like " + this.company);
    };
    Jobs.SayHello();//本身覆蓋的屬性和方法,輸出"Hi,Jobs like Apple"
    BillGates.SayHello();//Jobs的覆蓋沒有影響原型,BillGates仍是照樣輸出
</script>

__ptoto__屬性

__ptoto__屬性(IE瀏覽器不支持)是實例指向原型對象的一個指針,它的做用就是指向構造函數的原型屬性constructor,經過這兩個屬性,就能夠訪問原型裏的屬性和方法了。

Javascript中的對象實例本質上是由一系列的屬性組成的,在這些屬性中,有一個內部的不可見的特殊屬性——__proto__,該屬性的值指向該對象實例的原型,一個對象實例只擁有一個惟一的原型。

<script type="text/javascript">
    function Box(){        //大寫,表明構造函數
        Box.prototype.name = "trigkit4";//原型屬性
        Box.prototype.age = "21";
        Box.prototype.run = function()//原型方法
        {  
            return this.name + this.age + 'studying';
        }
    }
    
    var box1 = new Box();
    var box2 = new Box();
    alert(box1.constructor);//構造屬性,能夠獲取構造函數自己,
                            //做用是被原型指針定位,而後獲得構造函數自己
</script>

__proto__屬性和prototype屬性的區別

prototypefunction對象中專有的屬性。
__proto__是普通對象的隱式屬性,在new的時候,會指向prototype所指的對象;
__ptoto__其實是某個實體對象的屬性,而prototype則是屬於構造函數的屬性。__ptoto__只能在學習或調試的環境下使用。

原型模式的執行流程

1.先查找構造函數實例裏的屬性或方法,若是有,就當即返回。
2.若是構造函數的實例沒有,就去它的原型對象裏找,若是有,就當即返回

原型對象的

<script type="text/javascript">
    function Box(){        //大寫,表明構造函數
        Box.prototype.name = "trigkit4";//原型屬性
        Box.prototype.age = "21";
        Box.prototype.run = function()//原型方法
        {  
            return this.name + this.age + 'studying';
        }
    }
    
    var box1 = new Box();
    alert(box1.name);//trigkit4,原型裏的值
    box1.name = "Lee";
    alert(box1.name);//Lee,就進原則
    
    var box2 = new Box();
    alert(box2.name);//trigkit4,原型的值,沒有被box1修改
</script>

構造函數的

<script type="text/javascript">
    function Box(){                   
        this.name = "Bill";
    }
    
    Box.prototype.name = "trigkit4";//原型屬性
    Box.prototype.age = "21";
    Box.prototype.run = function()//原型方法
    {  
            return this.name + this.age + 'studying';
    }

    var box1 = new Box();
    alert(box1.name);//Bill,原型裏的值
    box1.name = "Lee";
    alert(box1.name);//Lee,就進原則
</script>

綜上,整理一下:

<script type="text/javascript">
    function Person(){};
    
    Person.prototype.name = "trigkit4";
    Person.prototype.say = function(){
        alert("Hi");
    }
    
    var p1 = new Person();//prototype是p1和p2的原型對象
    var p2 = new Person();//p2爲實例化對象,其內部有一個__proto__屬性,指向Person的prototype
    
    console.log(p1.prototype);//undefined,這個屬性是一個對象,訪問不到
    console.log(Person.prototype);//Person
    console.log(Person.prototype.constructor);//原型對象內部也有一個指針(constructor屬性)指向構造函數
    console.log(p1.__proto__);//這個屬性是一個指針指向prototype原型對象
    p1.say();//實例能夠訪問到在原型對象上定義的屬性和方法
    
</script>

構造函數.prototype = 原型對象
原型對象.constructor = 構造函數(模板)
原型對象.isPrototypeof(實例對象)   判斷實例對象的原型 是否是當前對象

工廠模式

function createObject(name,age){
    var obj = new Object();
    obj.name = name;
    obj.age = age;
    return obj;
}

工廠模式解決了實例化對象大量重複的問題,但還有一個問題,那就是根本沒法搞清楚他們究竟是哪一個對象的實例。
使用構造函數的方法,既解決了重複實例化的問題,又解決了對象識別的問題。

使用構造函數的方法和工廠模式的不一樣之處在於:

1.構造函數方法沒有顯示的建立對象(new Object());
 2.直接將屬性和方法賦值給this對象
 3.沒有return 語句

當使用了構造函數,而且new 構造函數(),那麼就在後臺執行了new Object()
函數體內的this表明了new Object()出來的對象

1.判斷屬性是在構造函數的實例裏,仍是在原型裏,可使用`hasOwnProperty()`函數
2.字面量建立的方式使用constructor屬性不會指向實例,而會指向Object,構造函數建立的方式則相反
爲何指向Object?由於Box.prototype = {};這種寫法其實就是建立了一個新對象。
而每建立一個函數,就會同時建立它的prototype,這個對象也會自動獲取constructor屬性
3.若是是實例方法,不一樣的實例化,他們的方法地址是不同的,是惟一的
4.若是是原型方法,那麼他們的地址的共享的

原型擴展:詳解js閉包

相關文章
相關標籤/搜索