js的建立物件的方法
第一種:工廠模式
根據接收引數返回,包含引數的物件
優點:解決建立多個物件的問題
缺點:沒法判斷物件的型別
function createPerson(name,age,job){ var o = new Object(); o.name = name; o.age = age; o.job = job; o.sayName = function(){ alert(this.name); } return o; }
第二種:建構函式模式
function Person(name,age,job){ this.name = name; this.age = age; this.job = job; this.sayName = function(){ alert(this.name); } } let p1= new Person('bzw',20,'stu'); console.log(p1 instanceof Person);//true console.log(p1.hasOwnProperty('name'));//true
建構函式模式與工廠模式區別:
1.沒有顯式的建立函式
2.直接將屬性和方法賦值給this物件
3.沒有return語句
建構函式定義:任何函式,只要可以通過new操作符來呼叫,那它就可以作為建構函式
以這種方式呼叫建構函式會經理以下幾個步驟:
1.建立新物件
2.將作用域賦值給新物件
3.執行建構函式的程式碼
4.返回新物件
缺點:每個方法都要在例項化的物件上面重新建立一遍
第三種:原型模式(重點)
想要知道原型模式,必須得知道什麼叫做原型,每個函式都有一個prototype(原型)屬性,這個屬性是一個指標,指向一個物件,這個物件包括所有例項可以共享的屬性和方法。
此外還需要知道什麼叫做原型物件,還是那句話,只要建立一個函式,就會有一個prototype(原型)屬性,而這個prototype恰恰指向這個函式的原型物件,這個原型物件包括所有例項可以共享的屬性和方法。
每建立一個例項,這個例項會有一個[[prototype]]指標,專門指向建構函式的原型物件,每個原型物件又會有一個constuctor屬性,它是一個指向prototype屬性所在函式的指標
function Person1(){ } Person1.prototype.name = 'bzw'; Person1.prototype.age = 20; Person1.prototype.job = 'stud'; console.log(Person.prototype);//{constructor: ƒ} console.log(Person.prototype.constructor);//指向Person函式 let p2 = new Person1(); let p3 = new Person1(); console.log(Person1.prototype.isPrototypeOf(p2));//true console.log(Person1.prototype.isPrototypeOf(p3));//true console.log(Object.getPrototypeOf(p2) === Person1.prototype);//true console.log(p2.hasOwnProperty('name'));//false
in與hasOwnProperty的區別:
in會在例項化物件裡面找有沒有這個屬性,如果沒有就去它的原型裡面去找,hasOwnProperty只會在例項化物件裡去找有沒有這個屬性
console.log('name' in p2);//true console.log(p2.hasOwnProperty('name'));//false
我們可以寫個函式專門用來判斷某個物件只有原型裡面有某個屬性
function hasPrototypeProperty(obj,key){ return !obj.hasOwnProperty(key) && (key in obj); } console.log(hasPrototypeProperty(p2,'name'));//true p2.name = 10; console.log(hasPrototypeProperty(p2,'name'));//false
給原型新增方法每次都需要寫一個Person.prototype這樣寫太麻煩,我們可以下面這樣寫
function Person2(){ } Person2.prototype = { name:'bzw', age:20, job:'stu' }
這樣寫挺方便但是,有個缺點就是原型物件的constructor會指向Object,這不是我們想看到的
console.log(Person2.prototype.constructor);//Object()
console.log(Object.keys(Person2.prototype))//["name", "age", "job"]
我們可以指定它的constructor,這樣就更改過來了,
Person2.prototype = { constructor:Person2,//constructor的enumerable預設為false不可遍歷,但是這種方法會讓它變為true,即可以遍歷 name:'bzw', age:20, job:'stu' }
這裡可能大家有個問題,為什麼這樣要指定constructor了,因為我們前面說過,一個建構函式的constructor需要指向prototype屬性的所在的函式
我們可以列印一下的Person2.prototype.constructor
console.log(Person2.prototype.constructor);//person2()
結果如下:
或者可以用下圖來表達上述的關係
當然這樣寫還是有點弊端
//Object.keys()會獲取物件中所有可以遍歷的屬性 console.log(Object.keys(Person2.prototype))//["constructor", "name", "age", "job"]
constructor竟然可以被遍歷出來,這是不對的,我們發現這個constructor可以遍歷,那麼上面的方法就不是特別好,我們可以用下下面的方法,即指定了Person2的constructor指向問題,又解決constructor可以遍歷的問題,可謂一舉兩得
Object.defineProperty(Person2.prototype,'constructor',{ enumerable:false, value:Person2 }); console.log(Object.keys(Person2.prototype))//["name", "age", "job"] console.log(Person2.prototype.constructor);//Person2();
原型的動態性
我們可以利用原型的動態性給原型新增屬性和方法
function Person3(){ } let friend = new Person3(); Person3.prototype.a = 1; console.log(friend.a);//1 即使例項已經建立好了, 但是當我給原型新增屬性的時候,例項還是原型後面新增的方法 Person3.prototype = { constructor:Person3, name:'bzw' } //例項化物件裡面會有一個指標指向原型,而不是指向建構函式,這裡把原型修改為另一個物件,就等於切斷了最初原型與建構函式之間的關係 console.log(friend instanceof Person3)//false console.log(friend.name)//undefined
下圖可以反映上述的情況
原型模式的缺點
function Person4(){ } Person4.prototype={ constructor:Person4, name:[4], } let p4 = new Person4(); console.log(p4.name);//[4] p4.name.push(5); let p5 = new Person4(); console.log(p4.name,p5.name);//[4, 5],[4, 5]
我們發現例項竟然可以對更改原型的值,這個不太好,於是就有其他的物件建立方法
第四種:組合建構函式和原型模式
function Person5(name,age,job){ this.name = name; this.age = age; this.job = job; this.friend = ['tom','jerry']; } Person5.prototype={ constructor:Person5, sayName:function(){ console.log(this.name); } } let p6 = new Person5('bzw',20,'stu'); let p7 = new Person5('bzw1',19,'stu1'); p6.friend.push('bob'); console.log(p6.friend);//["tom", "jerry", "bob"] console.log(p7.friend);//["tom", "jerry"]
第五種:動態原型
function Person6(name,age,job){ this.name = name; this.age = age; this.job = job; this.friend = []; if(typeof this.sayName != 'function'){ Person6.prototype.sayName = function(){ return this.name; } } } let p8 = new Person6('bzw2',20,'stu'); p8.friend.push('v'); console.log(p8.sayName())//bzw2 let p9 = new Person6('bzw3',21,'stu'); console.log(p8.friend,p9.friend);//["v"] []
使用動態原型不能使用物件字面量來重寫原型,否則會切斷建構函式與原先原型的關係,也會切斷例項與新原型的關係
第六種:寄生建構函式模式(和工廠模式很像)
特點:返回的物件與建構函式或者建構函式的原型屬性之間沒有關係,也就是說,建構函式返回的物件與在建構函式在外部建立的物件沒有什麼不同
function Person7(name,age,job){ let o = new Object(); o.name = name; o.age = age; o.job = job; o.sayName = function(){ return this.name; } return o; } let p10 = new Person7('bzw1',20,'stu'); console.log(p10.sayName());//bzw1
第七種:穩妥建構函式模式(適合於某些安全執行環境下,這些安全環境會禁用this和new)
function Person8(name,age,job){ let o = new Object(); //定義私有變數和函式 let sur = '姓名:'; let sum = function(){ return sur+name; } o.sayName = function(){ return sum(); } return o; } let p11 = Person8('bzw'); console.log(p11.sayName(),sur);//姓名:bzw,sur is not defined