為什麼在JavaScript中使用getter和setter是一個壞主意
如你所知,getter和setter已經成為了JavaScript的一部分。它們廣泛支援所有的主流瀏覽器,甚至是IE8。
我不認為這個點子通常是錯誤的,但我認為它不是非常適合JavaScript。可能看起來getter和setter可以簡化程式碼和節省時間,但其實它們會帶來隱藏錯誤,並且這些錯誤第一眼看並不明顯。
getter和setter如何工作?
首先小小地總結一下這些是什麼東西:
有時候,我們希望能允許訪問一個會返回動態計算值的屬性,或者你可能想要反映內部變數的狀態,而不使用顯式的方法呼叫。
為了說明它們是如何工作的,讓我們來看一個有著兩個屬性的person物件,這兩個屬性為:firstName和lastName,以及一個計算值:fullName。
-
var obj = {
-
firstName: "Maks",
-
lastName: "Nemisj"
-
}
小編是一個有著5年工作經驗的java程式設計師,對於java,自己有做資料的整合,一個完整學習java的路線,學習資料和工具,相信這裡有很多學習java的小夥伴,我創立了一個2000人學習扣群,479121291。每晚都有java的直播課程。無論是初級還是進階的小夥伴小編我都歡迎!
計算值fullName會返回firstName和lastName兩者的串聯。
-
Object.defineProperty(person, 'fullName', {
-
get: function () {
-
return this.firstName + ' ' + this.lastName;
-
}
-
});
為了得到fullName的計算值,不需要像person.fullName()帶可怕的括號,只需要使用簡單的var fullName = person.fullName。
這同樣適用於setter,你可以通過使用函式設定值:
-
Object.defineProperty(person, 'fullName', {
-
set: function (value) {
-
var names = value.split(' ');
-
this.firstName = names[0];
-
this.lastName = names[1];
-
}
-
});
使用就和getter一樣簡單:person.fullName = ‘Boris Gorbachev’。這將呼叫上面定義的函式,並分離Boris Gorbachev成firstName和lastName。
問題在哪裡?
你也許在想:“嘿,我喜歡getter和setter方法,它們感覺更自然,就像JSON一樣。”你說得對,它們的確是這樣的,但是我們先退一步來看一看fullName在getter和setter之前是如何工作的。
為得到值,我們將使用類似於getFullName()的一些東西,以及為了設定值,我們將使用person.setFullName(‘Maks Nemisj’)。
如果拼錯函式名,person.getFullName()寫成person.getFulName()會發生什麼呢?
JavaScript會給出一個錯誤:
-
person.getFulName();
-
^
-
TypeError: undefined is not a function
這個錯誤會在適當的時候適當的地方被觸發。訪問函式不存在的物件將觸發錯誤——這是好的。
現在,讓我們來看看當用錯誤的名稱來使用setter的時候會發生什麼?
-
person.fulName = 'Boris Gorbachev';
什麼也沒有。物件是可擴充套件的,可以動態分配鍵和值,因此不會有錯誤在執行時被丟擲。
這樣的行為意味著錯誤可能顯示在使用者介面上的某個地方,或者,當某些操作被執行在錯誤的值上時,而並非是打字錯誤的時刻。
跟蹤應該發生在過去但卻顯示在將來的程式碼流上的錯誤是如此有意思。
seal行不行
這個問題可以通過sealAPI來部分解決。只要物件是密封的,它就不能突變,也就是意味著fulName將試圖分配一個新鍵到person物件,並且它會失敗。
出於某種原因,當我在Node.js V4.0測試這個的時候,它沒有按照我期待的那樣工作。所以,我不能確保這個解決方案。
而更令人沮喪的是,對於setter一點也沒有解決方法。正如我前面提到的,物件是可擴充套件和可故障保護的,這意味著訪問一個不存在的鍵不會導致任何錯誤。
如果這種情況只適用於物件的文字的話,我不會多此一舉地寫這篇文章,但在ECMAScript 2015(ES6)和用類定義getter和setter能力的興起之後,我決定寫下關於潛在陷阱的部落格。
類的到來
我知道當前類在一些JavaScript社群不是非常受歡迎。人們對在函式式/基於原型的語言,例如JavaScript中是否需要它們,爭執不休。然而,事實是,類就在ECMAScript 2015(ES6)規範說明中,並且將存在於此一段時間。
對我來說,類是指定在類的外部世界(消費者)和應用程式的內部世界之間的定義良好的API的一種方式。這就是白紙黑字放入規則的抽象,並且我們假定這些規則不會很快改變。
改進person物件,做一個它的real類。person定義了介面用於獲取和設定fullName。
-
class Person {
-
constructor(firstName, lastName) {
-
this.firstName = firstName;
-
this.lastName = lastName;
-
}
-
getFullName() {
-
return this.firstName + ' ' + this.lastName;
-
}
-
setFullName(value) {
-
var names = value.split(' ');
-
this.firstName = names[0];
-
this.lastName = names[1];
-
}
-
}
類定義了一個嚴格的介面描述,但getter和setter方法使其變得不太嚴格。我們已經習慣了臃腫的錯誤,當工作於物件文字和JSON時的鍵中出現拼寫錯誤的時候。我希望至少類能夠更嚴格,並且在這個意義上,提供更好的反饋給開發人員。
雖然這種情況在定義getter和setter在一個類上的時候沒有任何不同。但它不會阻止任何人拼錯。
-
class Person {
-
constructor(firstName, lastName) {
-
this.firstName = firstName;
-
this.lastName = lastName;
-
}
-
get fullName() {
-
return this.firstName + ' ' + this.lastName;
-
}
-
set fullName(value) {
-
var names = value.split(' ');
-
this.firstName = names[0];
-
this.lastName = names[1];
-
}
-
}
有拼寫錯誤的執行不會給出任何錯誤:
-
var person = new Person('Maks', 'Nemisj');
-
console.log(person.fulName);
同樣不嚴格,不冗長,不可追蹤的行為導致可能會出錯。
在我發現這一點後,我有一個問題:在使用getter和setter的時候,有沒有什麼可以做的,以便於使得類更嚴格?我發現:有是肯定有,但是這值得嗎?增加額外層次的複雜性到程式碼就只是為了使用數量更少的括號?對於API定義,也可以不使用getter和setter,而這樣一來就能解決這個問題。除非你是一個鐵桿開發人員,並願意繼續進行,不然還有另一種解決方案,如下所述。
proxy來幫助?
除了getter和setter方法,ECMAScript 2015(ES6)還自帶proxy物件。proxy可以幫助你確定委託方法,這些委託方法可以在實際訪問鍵執行之前,用來執行各種操作。事實上,它看起來像動態getter / setter方法。
proxy物件可以用來捕捉任何到類的例項的訪問,並且如果在類中沒有找到預先定義的getter或setter就會丟擲錯誤。
為了做到這一點,必須執行下面兩個操作:
-
建立基於Person原型的getter和setter清單。
-
建立將測試這些清單的Proxy物件。
讓我們來實現它。
首先,為了找出什麼樣的getter和setter方法可以用在類Person上,可以使用getOwnPropertyNames和getOwnPropertyDescriptor:
-
var names = Object.getOwnPropertyNames(Person.prototype);
-
var getters = names.filter((name) => {
-
var result = Object.getOwnPropertyDescriptor(Person.prototype, name);
-
return !!result.get;
-
});
-
var setters = names.filter((name) => {
-
var result = Object.getOwnPropertyDescriptor(Person.prototype, name);
-
return !!result.set;
-
});
在此之後,建立一個Proxy物件:
-
var handler = {
-
get(target, name) {
-
if (getters.indexOf(name) != -1) {
-
return target[name];
-
}
-
throw new Error('Getter "' + name + '" not found in "Person"');
-
},
-
set(target, name) {
-
if (setters.indexOf(name) != -1) {
-
return target[name];
-
}
-
throw new Error('Setter "' + name + '" not found in "Person"');
-
}
-
};
-
person = new Proxy(person, handler);
現在,只要你嘗試訪問person.fulName,就會顯示Error: Getter “fulName” not found in “Person”的訊息。