1. 程式人生 > >javascript深入瞭解(面向物件)

javascript深入瞭解(面向物件)

目標:以執行效率最高的方式實現javascript的面向物件模式。

實現目標方式:從最基礎的實現開始一步一步優化到最後的實現方式。請執行或者讀懂每一段程式碼。

一,建立物件:

var people1 = new Object();//只有物件上才能新增屬性
    people1.name = 'fanlizhi';
    people1.sayName = function(){alert(people1.name);}
    var people2 = new Object();
    people2.name = '小明';
    people2.sayName = function(){alert(people2.name);}
                                                                                                          
    people1.sayName();//fanlizhi
    people2.sayName();//小明

缺點:最先想到的方式,例項都需要重新給屬性如果n個例項,程式碼量大。

二,工廠模式:

function createPerson(name){
        var o = new Object();
        o.name = name;
        o.sayName = function(){
            alert(this.name);
        }
        return o;
    }
    var people1 = createPerson('fanlizhi');
    var people2 = createPerson('小明');

    people2.sayName();//小明

進步:較上面相比有統一的入口方法createPerson並且n個例項程式碼量明顯減少。

缺點:不能解決物件識別的問題,我不知道people1和people2都是屬於什麼物件的。

三,建構函式模式:

function Person(name){
        this.name = name;
        this.sayName = function(){
            alert(this.name);
        }
    }
    var people1 = new Person('fanlizhi');
    var people2 = new Person('小明');
                                                              
    people1.sayName();//fanlizhi
    people2.sayName();//小明
                                                   
    //constructor:標識物件型別的
    alert(people1.constructor == Person);alert(people2.constructor == Person);//true
    //instanceof:檢測物件型別是否屬於XXX
    alert(people1 instanceof Object);alert(people1 instanceof Person);alert(people2 instanceof Object);alert(people2 instanceof Person);//true

進步:更加靠近面嚮物件語言形式了, 用到new關鍵字和“類名”(方法名Person)開頭大寫。直接將this指向Person而且不需要return物件。

缺點:因為每次例項化的時候都會執行Person方法,所以每個例項不僅有自己獨有的name同時,也將有自己獨有的sayName方法(不是同一個方法的引用)。如下面的例子:

alert(people1.sayName == people2.sayName); //false

因此浪費了資源,我們只需要呼叫同一個方法就行了而不像現在呼叫的是兩個sayName方法。

四;提出建構函式中的方法:

function Person(name){
        this.name = name;
        this.sayName = sayName;
    }
    function sayName(){
        alert(this.name);
    }   
    var people1 = new Person('fanlizhi');
    var people2 = new Person('小明');
                                            
    alert(people1.sayName == people2.sayName);//true

進步:people1和people2中的方法終於是引用的同一個方法了。

缺點:大家都知道,sayName是window的一個方法可以用window.sayName()呼叫,給window新增方法很亂,所以封裝性不好。

五,原型模式:

首先我需要說明一下什麼是prototype,要不然在程式碼裡面會顯得突兀,prototype:當例項物件的時候,物件會自動生成一個prototype屬性,他是一個指標指向Person原型,我們可以直接理解為Person.prtotype = Person原型 = 可以共享的屬性和方法的一個載體。

function Person(name){
     this.name = name;
}
Person.prototype.sayName = function(){
     alert(this.name);
}
var people1 = new Person('fanlizhi');
var people2 = new Person('小明');
             
alert(people1.sayName == people2.sayName);//true

進步:由於上一種方式缺點在於指標指的是window,這裡用prototype指向了Person,更乾淨了建構函式中傳值的name可以例項化name為不同的物件,所以放到Person下(即:建構函式下),而sayName只需要呼叫公共的方法不需要每個物件有自己單獨的例項,所以放到prototype中。

我們已經走完實現過程了,這還早,下面才是重頭戲,我們一起來分析一下上面的程式碼。

深入理解原型:

先看一個例子:這是我用firebug檢視people2中所含有的屬性,還是原型模式中的程式碼。

__proto__:當例項化物件people1的時候直譯器內部的一個關聯關係。換句話說就是people1與Person.prototype有一個連線__proto__,isPrototypeOf就是判斷這個連線的方法(後面會給出例子)。我們還有個連結不知道大家忘記沒有,就是constructor,constructor其實就是物件例項化的時候people1與Person的關係,people1.constructor指向Person,其實這個constructor是來自於People.prototype.constructor,這個例項化時候內部直譯器自動加上的預設屬性。

alert(Person.prototype.isPrototypeOf(people1));//true

注意:我要強調一點就是__proto__是例項和Person.prototype之間的關係,而constructor是例項和Person之間的關係。

下面我們聊一下關於優先順序的問題,先看程式碼:

function Person(name){
        this.name = name;
    }
    Person.prototype.sayName = function(){
        alert(this.name);
    }   
    var people1 = new Person('fanlizhi');
    people1.sayName = function(){
        alert(this.name+' hello');
    }
    people1.sayName();//fanlizhi hello
    delete people1.sayName;
    people1.sayName();//fanlizhi
從這裡我們發現一個訪問優先順序的問題:當我們第一次呼叫people1.sayName()時他先找例項people1中是否含有sayName()如果有就呼叫並返回當我們刪除了people1中的sayName的時候,再次呼叫people1.sayName(),這次發現例項people1中沒有sayName(),所以他就找原型裡面是否有當發現原型裡面有的時候就呼叫原型裡面的sayName()。在這裡我得講兩個方法hasOwnProperty() 和 in 這個有助於我們對優先順序的瞭解,先看例子:
function Person(){
    }
    Person.prototype.name = 'fanlizhi';
    Person.prototype.sayName = function(){
        alert(this.name);
    }   
    var people1 = new Person();
    alert(people1.hasOwnProperty("name"));//false
    alert("name" in people1);//true
    people1.name = '小明';
    alert(people1.hasOwnProperty("name"));//true
    alert("name" in people1);//true

看例子基本就懂了,hasOwnProperty 從名字來看“有自己獨有的屬性”第一次呼叫的時候為false,因為name不是獨有的而是公共的後來通過people1.name = '小明'; 賦值給他一個獨有的name,所以後來為true, 而in操作符,只要能訪問到people1.name,不論是獨有的還是公共的都返回true。

我們經常會看到這樣的例子:
function Person(){
    }
    Person.prototype = {
        name:'fanlizhi',
        sayName:function(){
            alert(this.name);
        }
    }
我們用一段程式碼來輔助我們對上面方式的分析:
function Person(){
    }
    var people1 = new Person();
    alert(people1.constructor);//Person()
    Person.prototype = {
        name:'fanlizhi',
        sayName:function(){
            alert(this.name);
        }
    }
    var people2 = new Person();
    alert(people2.constructor);//Object()
    people1.sayName();//error
    people2.sayName();//fanlizhi

當我們例項化people1的時候,預設建立的constructor會取到prototype的指向,是隸屬於Person()的所以alert(people1.constructor);//Person();當我們例項化people2的時候,也會取到prototype的指向,但是這次我們是重寫了Person.prototype所以列印的是Object(),至於為什麼,可以理解成var  o = {} ,這是一個Object申明的過程。people1.sayName();//error : 由於例項化的時候使用的是預設的prototype,後來改寫了Person.prototype所以__proto__被破壞找不到Person.prototype和例項關聯,所以error,而people2則是在例項化的時候就呼叫的是後來重寫的Person.prototype,並建立了關聯所以呼叫時候能根據連線找到屬性,而沒有報錯。

以上便是我對面向物件的理解,通過文字和程式碼傳達給大家,希望大家多多提問,一起來討論。

最後有個思考題:

function Person(){
    }
    Person.prototype = {
                constructor:Person,
        name:'fanlizhi',
        sayName:function(){
            alert(this.name);
        }
    }
為什麼要在原型中加入constructor:Person呢?