造函數其實和普通函數本質上並無區別,唯一的區別有兩個:
函數首字母大寫,這個區別只是約定俗成的,便於區分。你實在要小寫定義構造函數也完全沒問題,所以這個區別可以忽略。
構造函數的調用需要用new操作符,而普通函數的調用又分很多種,但是都不會用到new操作符。所以,構造函數和普通函數的區別就在這個new操作符里,現在讓我們來好好研究一下這個new操作符。
用new操作符創建對象時發生的事情:
(1)創建一個新對象;
(2)將構造函數的作用域賦給新對象(因此this就指向了這個新對象);
(3)執行構造函數中的代碼(為這個新對象添加屬性和方法);
(4)返回新對象;
通過new操作符后跟構造函數的方式創建的對象(實例),這個對象有一個constructor(構造函數)屬性,該屬性指向構造函數Person。
創建的對象,毫無疑問是Person的實例,同時也是Object的實例;所有對象皆繼承自Object。
構造函數是定義在Global對象中的,在瀏覽器中,即為window對象。
注意:原本的構造函數是window對象的方法,如果不用new操作符而直接調用,那么構造函數的執行對象就 是window,即this指向了window。現在用new操作符后,this就指向了新生成的對象。理解這一步至關重要。
執行構造函數中的代碼,看代碼:
function Person(){ this.name = "Tiny Colder"; var age = 22; window.age = 22; } var p = new Person(); alert(p.name)//Tiny Colder; alert(p.age)//undefined; alert(window.age)//22;
當用new操作符創建對象時,先創建了一個對象實例,然后執行代碼。所以還在糾結,什么時候構造函數定義的屬性會繼承給實例對象的,都可以這么來看:
var p = new Object(); p.name = "Tiny Colder";
這是普通的創建對象,然后給對象添加屬性的方法。如果每創建一個對象,都需要這么幾行代碼,無疑是糟糕的。這個需求就正好跟這一點對應:new操作符,自動執行構造函數里的代碼。如此我們便可以省掉添加屬性時重復冗余的代碼。那么這些屬性時如何添加到新生成的對象里的呢?
function Person(){ this.name = "Tiny Colder"; return {}; } var p = new Person(); alert(p.name)//undefined;
一個對象就這么被創建出來了。
實際上,
var p = new Person();
和
var p = new Object();
Person.apply(p);
是一樣的效果。
構造函數也是函數
任何函數,只要通過new操作符來調用,那么它就可以作為構造函數;任何函數,如果不通過new操作符來調用,那它與普通函數並無區別。
(1)當做構造函數調用
var person = new Person("CC",23);
(2)當做普通函數使用
Person("CC",23); //添加到window對象 console.log(window.name); //"CC" console.log(window.age); //23
(3)在另一個對象的作用域中調用
var person = new Object(); Person.call(person,"CC",23); console.log(person.name); //"CC" console.log(person.age); //23
構造函數的問題
使用構造函數的主要問題,就是每個方法都要在每個實例上重新創建一次。
var person1 = new Person("CC",23); var person2 = new Person("VV",32);
兩個實例都有sayName()方法,但是兩個方法不是同一Function的實例,也就是說,兩個實例上的同名函數是不相等的。
console.log(person1.sayName == person2.sayName); //false