做者從 Java 與 C# 中經典的 Getters/Setters 引入,討論了 Vue.js 中從組件渲染函數、數據的 Getter、Setter 劫持、監聽器的控制以及重渲染觸發整個生命流程。
原文連接:Understanding Vue.js Reactivity in Depth with Object.defineProperty()javascript
本人是Java背景,許多年前剛接觸JavaScript時有點怪怪的,由於它沒有 getters
和 setters
。隨着時間的推移,我開始喜歡上這個缺失的特性,由於相比Java大量的 getter
和 setter
,它讓代碼更簡潔。例如,咱們看看下面的Java代碼:html
class Person{
String firstName;
String lastName;
// 這個Demo中省略了一些構造器代碼 :)
public void setFirstName(firstName) {
this.firstName = firstName;
}
public String getFirstName() {
return firstName;
}
public void setLastName(lastName) {
this.lastName = lastName;
}
public String getLastName() {
return lastName;
}
}
// Create instance
Person bradPitt = new Person();
bradPitt.setFirstName("Brad");
bradPitt.setLastName("Pitt");複製代碼
JavaScript開發人員永遠不會這樣作,相反他們會這樣:vue
var Person = function () {
};
var bradPitt = new Person();
bradPitt.firstName = 'Brad';
bradPitt.lastName = 'Pitt';複製代碼
這要簡潔的多。一般簡潔更好,不是嗎?java
的確如此,但有時我想獲取一些能夠被修改的屬性,但我不用知道這些屬性是什麼。例如,咱們在Java代碼中擴展一個新的方法 getFullName()
:react
class Person{
private String firstName;
private String lastName;
// 這個Demo中省略了一些構造器代碼 :)
public void setFirstName(firstName) {
this.firstName = firstName;
}
public String getFirstName() {
return firstName;
}
public void setLastName(lastName) {
this.lastName = lastName;
}
public String getLastName() {
return lastName;
}
public String getFullName() {
return firstName + " " + lastName;
}
}
Person bradPitt = new Person();
bradPitt.setFirstName("Brad");
bradPitt.setLastName("Pitt");
// Prints 'Brad Pitt'
System.out.println(bradPitt.getFullName());複製代碼
在上面例子中, fullName
是一個計算過的屬性,它不是私有屬性,但總能返回正確的結果。git
咱們來看看 C# 特性之一:隱式的 getters/setters,我真的很喜歡它。在 C# 中,若是須要,你能夠定義 getters/setters,可是並不用這樣作,可是若是你決定要這麼作,調用者就沒必要調用函數。調用者只須要直接訪問屬性,getter/setter 會自動在鉤子函數中運行:github
public class Foo {
public string FirstName {get; set;}
public string LastName {get; set;}
public string FullName {get { return firstName + " " + lastName }; private set;}
}複製代碼
我以爲這很酷...瀏覽器
如今,若是我想在JavaScript中實現相似的功能,我會浪費不少時間,好比:bash
var person0 = {
firstName: 'Bruce',
lastName: 'Willis',
fullName: 'Bruce Willis',
setFirstName: function (firstName) {
this.firstName = firstName;
this.fullName = `${this.firstName} ${this.lastName}`;
},
setLastname: function (lastName) {
this.lastName = lastName;
this.fullName = `${this.firstName} ${this.lastName}`;
},
};
console.log(person0.fullName);
person0.setFirstName('Peter');
console.log(person0.fullName);複製代碼
它會打印出:ide
"Bruce Willis"
"Peter Willis"複製代碼
但使用 setXXX(value)
的方式並不夠'javascripty'(是個玩笑啦)。
下面的方式能夠解決這個問題:
var person1 = {
firstName: 'Brad',
lastName: 'Pitt',
getFullName: function () {
return `${this.firstName} ${this.lastName}`;
},
};
console.log(person1.getFullName()); // 打印 "Brad Pitt"複製代碼
如今咱們回到被計算過的 getter。你能夠設置 first 或 last
name,並簡單的合併它們的值:
person1.firstName = 'Peter'
person1.getFullName(); // 返回 "Peter Pitt"複製代碼
這的確更方便,但我仍是不喜歡它,由於咱們要定義一個叫「getxxx()」的方法,這也不夠'javascripty'。許多年來,我一直在思考如何更好的使用 JavaScript。
在個人Youtube頻道,不少和Vue教程有關的視頻都講到,我習慣響應式開發,在更早的Angular1時代,咱們叫它:數據綁定(Data Binding)。它看起來很簡單。你只須要在Vue實例的 data()
塊中定義一些數據,並綁定到HTML:
var vm = new Vue({
data() {
return {
greeting: 'Hello world!',
};
}
})複製代碼
<div>{greeting}</div>複製代碼
顯然它會在用戶界面打印出 「Hello world!」。
如今,若是你改變「greeting」的值,Vue引擎會對此做出反應並相應地更新視圖。
methods: {
onSomethingClicked() {
this.greeting = "What's up";
},
}複製代碼
很長一段時間我都在想,它是如何工做的?當某個對象的屬性發生變化時會觸發某個事件?或者Vue不停的調用 setInterval
去檢查是否更新?
經過閱讀Vue官方文檔,我才知道,改變一個對象屬性將隱式調用getter/setter,再次通知觀察者,而後觸發從新渲染,以下圖,這個例子來自官方的vue.js文檔:
但我還想知道:
怎麼讓數據自帶getter/setters
?
這些隱式調用內部是怎樣的?
第一個問題很簡單:Vue爲咱們準備好了一切。當你添加新數據,Vue將會經過其屬性爲其添加 getter/setters
。可是我讓 foo.bar = 3?
會發生什麼?
這個問題的答案出如今我和SVG & Vue專家Sarah Drasner的Twitter對話中:
Timo:
foo.bar=value;
是怎麼作到實時響應的?
Sarah: 這個問題很難在Twitter說清楚,能夠看這篇文章
Timo: 但這篇文章並無解釋上面提到的問題。
Timo: 它們就像:分配一個值->調用setter->通知觀察者,不理解爲何在不使用setInterval和Event的狀況下,setter/getter就存在了。
Sarah: 個人理解是:你獲取的全部數據都在Vue實例data{}中被代理了。
顯然,她也是參考的官方文檔,以前我也讀過,因此我開始閱讀Vue源碼,以便更好的理解發生了什麼。過了一會我想起在官方文檔看到一個叫 Object.defineProperty()
的方法,我找到它,以下:
/** * 給對象定義響應的屬性 */
export function defineReactive ( obj: Object, key: string, val: any, customSetter?: ?Function, shallow?: boolean ) {
const dep = new Dep()
const property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) {
return
}
// 預約義getter/setters
const getter = property && property.get
const setter = property && property.set
let childOb = !shallow && observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
if (Dep.target) {
dep.depend()
if (childOb) {
childOb.dep.depend()
}
if (Array.isArray(value)) {
dependArray(value)
}
}
return value
},
set: function reactiveSetter (newVal) {
const value = getter ? getter.call(obj) : val
/* 禁用eslint 不進行自我比較 */
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
/* 開啓eslint 不進行本身比較 */
if (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter()
}
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
childOb = !shallow && observe(newVal)
dep.notify()
}
})
}複製代碼
因此答案一直存在於文檔中:
把一個普通 JavaScript 對象傳給 Vue 實例的 data 選項,Vue 將遍歷此對象全部的屬性,並使用 Object.defineProperty 把這些屬性所有轉爲 getter/setter。Object.defineProperty 是僅 ES5 支持,且沒法 shim 的特性,這也就是爲何 Vue 不支持 IE8 以及更低版本瀏覽器的緣由。
我只想簡單的瞭解 Object.defineProperty()
作了什麼,因此我用一個例子簡單的給你講解一下:
var person2 = {
firstName: 'George',
lastName: 'Clooney',
};
Object.defineProperty(person2, 'fullName', {
get: function () {
return `${this.firstName} ${this.lastName}`;
},
});
console.log(person2.fullName); // 打印 "George Clooney"複製代碼
還記得文章開頭C#的隱式 getter
嗎?它們看起來很相似,但ES5纔開始支持。你須要作的是使用 Object.defineProperty()
定義現有對象,以及什麼時候獲取這個屬性,這個getter被稱爲響應式——這實際上就是Vue在你添加新數據時背後所作的事。
學完這一切,我一直在想,Object.defineProperty()
是否能讓Vue變的更簡化?現今愈來愈多的新術語,是否是真的有必要把事情變得過於複雜,變的讓初學者難以理解(Redux也是一樣):
Mutator - 或許你在說(隱式)setter
Getters - 爲何不用 Object.defineProperty()
替換成(隱式)
getter
store.commit() - 爲何不簡化成 foo = bar
,而是 store.commit("setFoo", bar);
?
你是怎麼認爲的?Vuex必須是複雜的仍是能夠像 Object.defineProperty()
同樣簡單?
本文譯者:餘震(Freak)
譯文出處:Rockjins Blog
版權聲明:本博客全部文章除特別聲明外,均採用 CC BY-NC-SA 3.0 CN許可協議。轉載請註明出處!