引言
本文主要介紹Javascript創建對象的幾種方式:單體模式、工廠模式、構造函數模式和原型鏈模式
一、單體模式
對象數據類型的作用:把描述同一個事物(同一個對象)的屬性和方法放在一個內存空間下,起到了分組的作用。這樣不同事物之間的屬性即使屬性名相同,相互也不會發生衝突。我們把這種分組編寫代碼的模式叫做"單體模式”,在下面例子中,我們把person1或person2也叫做"命名空間”。
var person1={ name:"尹華芝", age:18 }; var person2={ name:"吳曉波", age:28 }
二、工廠模式
單體模式雖然解決了分組的作用,但是不能實現批量的生產,屬於手工作業模式。接下來我們介紹的“工廠模式”,就可以實現對象創建批量生產。
那什麼是“工廠模式”呢?簡單來說,就是把實現同一件事情的相同的代碼放到一個函數中,以後如果在想實現這個功能,不需要從新的編寫這些代碼來了,只需要執行當前的函數即可,即函數的封裝。函數封裝的好處----“低耦合高內聚”,即減少頁面中的冗餘代碼,提高代碼的重複利用率!
function createJsPerson(name,age){ var obj=new Object(); obj.name=name; obj.age=age; obj.writeJs=function (){ console.log("my name is"+ this.name +'i can write JS'); }; return obj; } var p1=createJsPerson("王小波",48); p1.writeJs(); var p2=createJsPerson("尹華芝",18); p2.writeJs()
三、構造函數模式
使用工廠方法創建的對象,使用的構造函數都是Object,所創建的對象都是Object這個類型,就導致我們無法區分出多種不同類型的對象。構造函數模式的目的就是爲了創建一個自定義類,並且創建這個類的實例。構造函數模式中擁有了類和實例的概念,並且實例和實例之間是相互獨立的,即實例識別。
構造函數就是一個普通的函數,創建方式和普通函數沒有區別,不同的是構造函數習慣上首字母大寫。另外就是調用方式的不同,普通函數是直接調用,而構造函數需要使用new關鍵字來調用。
構造函數的執行流程:
1.立刻創建一個新的對象
2.將新建的對象設置爲函數中this
3.逐行執行函數中的代碼
4.將新建的對象作爲返回值返回
function Person(name, age, gender) { this.name = name this.age = age this.gender = gender this.sayName = function () { alert(this.name); } } var per = new Person("孫悟空", 18, "男"); function Dog(name, age, gender) { this.name = name this.age = age this.gender = gender } var dog = new Dog("旺財", 4, "雄") console.log(per);//當我們直接在頁面中打印一個對象時,事件上是輸出的對象的toString()方法的返回值 console.log(dog);
image
使用instanceof可以檢查一個對象是否是一個類的實例。所有的對象都是Object的後代,所以任何對象和Object左instanceof檢查時都會返回true。
console.log(per instanceof Person);//true console.log(dog instanceof Person);//false console.log(dog instanceof Object);//true
注意點
per1和per2都是Person這個類的實例,所以都擁有sayName這個方法,但是不同實例之間的方法是不一樣的。在類中給實例增加的屬性(this.xxx=xxx)屬於當前實例的私有的屬性,實例和實例之間是單獨的個體,所以私有的屬性之間是不相等的。
var per1 = new Person("孫悟空",18,"男"); var per2 = new Person("豬八戒",28,"男"); per1.sayName(); per2.sayName(); console.log(per1.sayName === per2.sayName);//false
每創建一個Person構造函數,在Person構造函數中,爲每一個對象都添加了一個sayName方法,也就是說構造函數每執行一次就會創建一個新的sayName方法。這樣就導致了構造函數執行一次就會創建一個新的方法,執行10000次就會創建10000個新的方法,而10000個方法都是一摸一樣的,這是完全沒有必要,完全可以使所有的對象共享同一個方法。
function Person(name, age , gender){ this.name = name; this.age = age; this.gender = gender; this.sayName = fun; } function fun(){ alert("Hello大家好,我是:"+this.name); };
將函數定義在全局作用域這種方法,雖然會提升性能,但污染了全局作用域的命名空間,而且定義在全局作用域中也很不安全。那麼有沒有更好的辦法呢?
四、原型鏈模式
我們所創建的每一個函數,解析器都會向函數中添加一個屬性prototype
,這個屬性對應着一個對象,這個對象就是我們所謂的原型對象。如果函數作爲普通函數調用prototype
沒有任何作用。當函數以構造函數的形式調用時,它所創建的對象中都會有一個隱含的屬性,指向該構造函數的原型對象,我們可以通過__proto__
來訪問該屬性。
function MyClass() {} var mc = new MyClass() console.log(mc.__proto__ === MyClass.prototype)//true
原型對象就相當於一個公共的區域,所有同一個類的實例都可以訪問到這個原型對象,我們可以將對象中共有的內容,統一設置到原型對象中。當我們訪問對象的一個屬性或方法時,它會先在對象自身中尋找,如果有則直接使用,如果沒有則會去原型對象中尋找,如果找到則直接使用。如果沒有則去原型的原型中尋找,直到找到Object對象的原型,Object對象的原型沒有原型,如果在Object原型中依然沒有找到,則返回undefined。
function MyClass() {} MyClass.prototype.a = 123; MyClass.prototype.sayHello = function () { alert("hello"); }; var mc = new MyClass() console.log(mc.a)//123 console.log("a" in mc);//true //使用in檢查對象中是否含有某個屬性時,如果對象中沒有但是原型中有,也會返回true console.log(mc.hasOwnProperty("age"));//false //使用對象的hasOwnProperty()來檢查對象自身中是否含有該屬性 console.log(mc.__proto__.hasOwnProperty("hasOwnProperty"));//false console.log(mc.__proto__.__proto__.hasOwnProperty("hasOwnProperty"));//true console.log(mc.__proto__.__proto__.__proto__);//null
以後我們創建構造函數時,可以將這些對象共有的屬性和方法,統一添加到構造函數的原型對象中,這樣不用分別爲每一個對象添加,也不會影響到全局作用域,就可以使每個對象都具有這些屬性和方法了。
function Person(name, age , gender){ this.name = name; this.age = age; this.gender = gender; } Person.prototype.sayName = function () { alert("Hello大家好,我是:" + this.name); }