1. 程式人生 > >js面向對象編程——創建對象

js面向對象編程——創建對象

分享 語法檢查 這就是 編程 spa fine 全局 his 定義

JavaScript對每個創建的對象都會設置一個原型,指向它的原型對象。

當我們用obj.xxx訪問一個對象的屬性時,JavaScript引擎先在當前對象上查找該屬性,如果沒有找到,就到其原型對象上找,如果還沒有找到,就一直上溯到Object.prototype對象,最後,如果還沒有找到,就只能返回undefined。

例如,創建一個Array對象:

var arr = [1,2,3];

其原型鏈是:

arr ----> Array.prototype ----> Object.prototype ----> null

Array.prototype定義了indexOf()、shift()等方法,因此你可以在所有的Array對象上直接調用這些方法。

當我們創建一個函數時:

     function foo() {
            return 0;
        }

函數也是一個對象,它的原型鏈是:

foo———>Function.prototype——>Object.prototype——>null

由於Function.prototype定義了apply()等方法,因此,所有函數都可以調用apply()方法。

很容易想到,如果原型鏈很長,那麽訪問一個對象的屬性就會因為花更多的時間查找而變得更慢,因此要註意不要把原型鏈搞得太長。

構造函數

除了直接用{……}創建一個對象外,JavaScript還可以用一種構造函數的方法來創建對象,它的用法是,先定義一個構造函數:

    function Student(name) {
            this.name = name;
            this.hello = function(){
                alert(‘hello‘+this.name+‘!‘);
            }
        }

這確實是一個普通函數,但是在JavaScript中,可以用關鍵字new來調用這個函數,並返回一個對象

      var xiaoming = new Student(‘小明‘);
        xiaoming.name;//‘小明‘
        xiaoming.hello();//
Hello,小明!

註意,如果不寫new,這就是個普通函數,它返回undefined,但是,如果寫了new,它就變成了一個構造函數,它綁定的this指向新創建的對象,並默認返回this,也就是說,不需要在最後寫return this;

新創建的xiaoming的原型鏈是:

xiaoming——>Student.prototype——>Object.prototype——>null

也就是說,xiaoming的原型指向函數Student的原型,如果你又創建了xiaohong、xiaojun,那麽這些對象的原型與xiaoming是一樣的。

用newStudent()創建的對象還從原型上獲得了一個constructor屬性,它指向函數Student本身:

      xiaoming.constructor === Student.prototype.constructor;//true
        Student.prototype.constructor === Student;//true
        Object.getPrototypeOf(xiaoming) === Student.prototype;//true
        xiaoming.instanceof Student;//true 

技術分享

紅色箭頭是原型鏈,註意,Student.protorype指向的對象就是xiaoming、xiaohong的原型對象,這個原型對象自己還有個屬性constructor,指向Student函數本身。

另外,函數Student恰好有個屬性prototype指向xiaoming、xiaohong的原型對象,但是xiaoming、xiaohong這些對象可沒有prototype這個屬性,不過可以用_proto_這個非標磚用法來查看。

現在我們就認為xiaoming、xiaohong這些對象“繼承”自Student。

有個小問題:

      xiaoming.name;//‘小明‘
        xiaohong.name;//‘小紅‘
        xiaoming.hello;//function:Student.hello()
        xiaoming.hello;//function:Student.hello()
        xiaoming.hello === xiaohong.hello;//false

xiaoming和xiaohong各自的name不同,這是對的,否則我們無法區分誰是誰了。

xiaoming和xiaohong各自的hello是一個函數,但它們是兩個不同的函數,雖然函數名稱和代碼都是相同的!

如果我們通過new Student()創建了很多對象,這些對象的hello函數實際上只需要共享同一個函數就可以了,這樣可以節省很多內存。

要讓創建的對象共享一個hello函數,根據對象的屬性查找原則,我們只要把hello函數移動到xiaoming、xiaohong這些對象的共同原型上就可以了,也就是Student.prototype:

技術分享

修改代碼:

   function Student(name) {
            this.name = name;
        }

        Student.prototype.hello = function () {
            alert(‘Hello,‘+this.name+‘!‘);
        }

用new創建基於原型的JavaScript對象,以上。

忘記寫new怎麽辦?

如果一個函數被定義為用於創建對象的構造函數,但是調用時忘記了寫new怎麽辦?

在strict模式下,this.name=name將報錯,因為this綁定為undefined,在非strict模式下,this.name=name不報錯,因為this綁定為window,於是無意間創建了全局變量name,並且返回undefined,這個結果更糟糕。

所以調用構造函數千萬不要忘記寫new。為了區分普通函數和構造函數,按照約定,構造函數首字母應該大寫,而普通函數首字母應該小寫,這樣,一些語法檢查工具如jslint將可以幫你檢測到漏寫的new。

最後我們可以編寫一個createStudent()函數,在內部封裝所有的new操作,一個常用的編程模式像這樣:

     function Student(props){
            this.name = props.name ||‘匿名‘;//默認值為‘匿名‘
            this.grade = props.grade || 1;//默認值為1
        }

        Student.prototype.hello = function(){
            alert(‘Hello,‘+this.name+‘!‘);
        };

        function createStudent(props){
            return new Student(props || {})
        }

這個createStudent()函數有幾個巨大的有點:一是不需要用new來調用,二是參數非常靈活,可以不傳,也可以這麽傳:

    var xiaoming = createStudent({
            name:‘小明‘
        });

        xiaoming.grade;//1

如果創建的對象有很多屬性,我們只需要傳遞需要的某些屬性,剩下的屬性可以用默認值。由於參數是一個Object,我們無需記憶參數的順序,如果恰好從JSON拿到了一個對象,就可以直接創建出xiaoming。

js面向對象編程——創建對象