好家伙,本篇為《JS高級程式設計》第八章“物件、類與面向物件編程”學習筆記
1.工廠模式
工廠模式是另外一種關注物件創建概念的創建模式,
它的領域中同其它模式的不同之處在于它并沒有明確要求我們使用一個構造器,
取而代之,一個工廠能提供一個創建物件的公共介面,我們可以在其中指定我們希望被創建的工廠物件的型別,
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;
2.建構式模式
前面幾章提到過,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物件
2.2. 建構式的問題
建構式雖然有用,但也不是沒有問題,
建構式的主要問題在于,其定義的方法會在每個實體上都創建一遍,
因此對前面的例子而言,person1和person2都有名為sayName()的方法,但這兩個方法不是同一個Function 實體,
我們知道,ECMAScript中的函式是物件,因此每次定義函式時,都會初始化一個物件,
如果把方法分離出來,在全域作用域中進行定義,在當前物件需要多個方法,那么就要在全域作用域中定義多個函式.
這個問題可以通過原型模式解決
3.原型模式
(他來了,被營銷號譽為Js三大難點的"原型"他來了)
理解復雜概念之前我們先從簡單的地方入手(比如新華字典)

然后我們知道,原型指的是原來的模型
每個函式都會創建一個prototype屬性,這個屬性是一個物件,包含應該由特定參考型別的實體共享的屬性和方法,
實際上,這個物件就是通過呼叫建構式創建的物件的原型,
使用原型物件的好處是,在它上面定義的屬性和方法可以被物件實體共享,
原來在建構式中直接賦給物件實體的值,可以直接賦值給它們的原型,
3.1.實體共享原型模式的屬性和方法
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
3.2.原型層級
在通過物件訪問屬性時,會按照這個屬性的名稱開始搜索,搜索開始于物件實體本身
如果在物件實體上找到了,則回傳對應的值,如果沒找到,則搜索會沿著指標進入原型物件,然后在原型物件上找到屬性后,再回傳對應的值
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();

只要給物件實體添加一個屬性,這個屬性就會遮蔽原型物件上的同名屬性,雖然不會修改,但會屏蔽對它的訪問

3.3.原型的動態性
因為從原型上搜索值的程序是動態的,所以即使實體在修改原型之前已經存在,任何時候對原型物件所做的修改也會在實體上反映出來
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
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/540683.html
標籤:其他
上一篇:Web 標準 & W3C 規范
