好傢伙,本篇為《JS高階程式設計》第八章「物件、類與物件導向程式設計」學習筆記
工廠模式是另外一種關注物件建立概念的建立模式。
它的領域中同其它模式的不同之處在於它並沒有明確要求我們使用一個構造器。
取而代之,一個工廠能提供一個建立物件的公共介面,我們可以在其中指定我們希望被建立的工廠物件的型別。
function createPerson(name,age,job){
let person =new Object();
person.name= name;
person.age =age;
person.job =job;
person.getName = function(){
console.log(this.name);
}
return person;
}
let person_1 = createPerson("panghu","20","student")
person_1.getName();
console.log(person_1);
(看上去沒什麼問題,但怎麼總覺得怪怪的)
let person =new Object();
/...
...
...
../
return person;
前面幾章提到過,ECMAScript中的建構函式是用於建立特定型別物件的。
像Object和Array這樣的原生建構函式,執行時可以直接在執行環境中使用。
當然也可以自定義建構函式,以函數的形式為自己的物件型別定義屬性和方法。
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.getName = function () {
console.log(this.name);
};
}
let person_1 = new Person("panghu", "20", "student");
person_1.getName();
console.log(person_1);
在這個例子中,Person()建構函式代替了 createPerson()工廠函數。
實際上,Person()內部的程式碼跟createPerson()基本是一樣的,只是有如下區別。
□沒有顯式地建立物件。
□屬性和方法直接賦值給了this。
□沒有return。
另外,要注意函數名Person的首字母大寫了。
按照慣例,建構函式名稱的首字母都是要大寫的,非建構函式則以小寫字母開頭。
這是從物件導向程式語言那裡借鑑的( 是的,非常好的借鑑 ),有助於在ECMAScript中區分建構函式和普通函數。
畢竟ECMAScript的建構函式就是能建立物件的函數。
要建立Person的範例,應使用new操作符。以這種方式呼叫建構函式會執行如下操作。
(1)在記憶體中建立一個新物件。
(2)這個新物件內部的[[Prototype]]特性被賦值為建構函式的prototype屬性。
(3)建構函式內部的this被賦值為這個新物件(即this指向新物件)。
(4)執行建構函式內部的程式碼(給新物件新增屬性)。
(5)如果建構函式返回非空物件,則返回該物件;否則,返回剛建立的新物件。
2.1.建構函式也是函數
建構函式與普通函數唯一的區別就是呼叫方式不同。除此之外,建構函式也是函數。
並沒有把某個函數定義為建構函式的特殊語法。
任何函數只要使用new操作符呼叫就是建構函式,而不使用new操作符呼叫的函數就是普通函數。
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.getName = function () {
console.log(this.name);
};
}
let person_1 = new Person("panghu", "20", "student");
person_1.getName();
//作為函數呼叫,新增到window物件
Person("xiaofu","20","student")
window.getName();
此處如果將Person當做普通函數來呼叫,那麼this指向的就是全域性作用域,資料會被新增到window物件
建構函式雖然有用,但也不是沒有問題。
建構函式的主要問題在於,其定義的方法會在每個範例上都建立一遍。
因此對前面的例子而言,person1和person2都有名為sayName()的方法,但這兩個方法不是同一個Function 範例。
我們知道,ECMAScript中的函數是物件,因此每次定義函數時,都會初始化一個物件。
如果把方法分離出來,在全域性作用域中進行定義,在當前物件需要多個方法,那麼就要在全域性作用域中定義多個函數.
這個問題可以通過原型模式解決
(他來了,被行銷號譽為Js三大難點的"原型"他來了)
理解複雜概念之前我們先從簡單的地方入手(比如新華字典)
然後我們知道,原型指的是原來的模型
每個函數都會建立一個prototype屬性,這個屬性是一個物件,包含應該由特定參照型別的範例共用的屬性和方法。
實際上,這個物件就是通過呼叫建構函式建立的物件的原型。
使用原型物件的好處是,在它上面定義的屬性和方法可以被物件範例共用。
原來在建構函式中直接賦給物件範例的值,可以直接賦值給它們的原型,
function Person(){};
Person.prototype.name = "panghu";
Person.prototype.age = "20";
Person.prototype.job = "student";
Person.prototype.getName =function(){
console.log(this.name);
}
let person_1 = new Person();
person_1.getName();
let person_2 = new Person();
person_2.getName();
然後,我們來理解一下這段程式碼,
首先,我們要把Person的原型當成一個物件來看待,
於是我們現在有了三方勢力,Person建構函式,Person原型物件,person_1和person_2兩個範例物件
看看這幅圖,
Person建構函式Person.prototype指向原型物件,而Person原型物件的constructor指向Person建構函式,
兩個範例都只有唯一屬性[[Prototype]]指向Person.prototype
在通過物件存取屬性時,會按照這個屬性的名稱開始搜尋,搜尋開始於物件範例本身
如果在物件範例上找到了,則返回對應的值,如果沒找到,則搜尋會沿著指標進入原型物件,然後在原型物件上找到屬性後,再返回對應的值
function Person(){};
Person.prototype.name = "panghu";
Person.prototype.age = "20";
Person.prototype.job = "student";
Person.prototype.getName =function(){
console.log(this.name);
}
let person_1 = new Person();
person_1.getName();
person_1.name ="xiaofu";
person_1.getName();
delete person_1.name
person_1.getName();
只要給物件範例新增一個屬性,這個屬性就會遮蔽原型物件上的同名屬性,雖然不會修改,但會遮蔽對它的存取
因為從原型上搜尋值的過程是動態的,所以即使範例在修改原型之前已經存在,任何時候對原型物件所做的修改也會在範例上反映出來
function Person(){};
Person.prototype.saysomething =function(){
console.log("yes,we can");
}
let person_1 = new Person();
person_1.saysomething();
但重寫原型又是另一碼事了
function Person() {};
let person_1 = new Person();
Person.prototype = {
constructor: Person,
name: "panghu",
age: "20",
job: "student",
saySomething() {
console.log("yes,we can");
}
}
person_1.saySomething();
雖然隨時能給原型新增屬性和方法,並能夠立即反映在所有物件範例上、但這跟重寫整個原型是兩回事。
範例的[[Prototype]]指標是在呼叫建構函式時自動賦值的,這個指標即使把原型修改為不同的物件也不會變。
重寫整個原型會切斷最初原型與建構函式的聯絡,但範例參照的仍然是最初的原型。記住,範例只有指向原型的指標,沒有指向建構函式的指標。
Person的新範例是在重寫原型物件之前建立的。在呼叫person_1.saySomething()的時候,會導致錯誤。
這是因為person_1指向的原型還是最初的原型,而這個原型上並沒有saySomething屬性。
That's all