數據模型僅僅是普通的 JavaScript
對象,可是對這些對象進行操做時,卻能影響對應視圖,簡而言之,就是你動我也動
。 它的核心實現就是「響應式系統」,核心內容爲Object.defineProperty 使用方法以下:編程
/* obj: 目標對象 prop: 目標對象的屬性名 descriptor: 描述符 return value 傳入對象 */
Object.defineProperty(obj, prop, descriptor)
複製代碼
descriptor的一些屬性數組
observer
(可觀察到你動了)首先定義一個假的函數來模擬更新app
function updateView(val) {
/* 僞裝是視圖 */
console.log("我動了");
}
複製代碼
而後咱們定義一個 defineReactive
,這個方法經過 Object.defineProperty
來實現對對象的「響應式」化,通過 defineReactive
處理之後,咱們的 target 的 key 屬性在「讀」的時候會觸發 get
方法,而在該屬性被「寫」的時候則會觸發 set
方法。函數
function defineReactive (target, key, val) {
Object.defineProperty(obj, key, {
get() {
return val;
},
set(newVal) {
if (newVal === val) return;
updateView(newVal);
}
});
}
複製代碼
這樣貌似ok了,可是沒人讓他動起來,咱們再封裝一層observer
,這個函數傳入一個 value(須要「響應式」化的對象),經過遍歷全部屬性的方式對該對象的每個屬性都經過 defineReactive 處理。測試
function observer (target) {
if (!target || (typeof target !== 'object')) {
return;
}
Object.keys(target).forEach((key) => {
defineReactive(target, key, target[key]);
});
}
複製代碼
最後爲了好看點,封裝一個Vueui
class Vue {
constructor(options) {
this._data = options.data;
observer(this._data);
}
}
// 測試
let o = new Vue({
data: {
test: "I am test."
}
});
o._data.test = "hello,world."; /* 我動了 */
複製代碼
嵌套對象
就很容易發現問題,不動啦,原理也很簡單,指向同一內存,能夠類比深淺拷貝那麼咱們要進行遞歸調用
this
function defineReactive(target, key, value){
observer(value); // 遞歸 我就將這個對象 繼續攔截
Object.defineProperty(target,key,{
get(){
return value
},
set(newValue){
if(newValue !== value){ // 不一樣值才更新
// o_data.age = {n:200};o _data.age.n = 300;這種狀況就須要從新觀察
observer(newValue)
updateView();
value = newValue
}
}
});
}
複製代碼
改寫observer
, 爲了讓不改變原數組
,巧妙運用切片編程spa
let oldArrayPrototype = Array.prototype;
let proto = Object.create(oldArrayPrototype); // 繼承
['push','shift','unshift'].forEach(method=>{
proto[method] = function(){ //函數劫持 把函數進行重寫 內部 繼續調用老的方法
updateView(); // 切片編程
oldArrayPrototype[method].call(this, ...arguments)
// oldArrayPrototype[method].apply(this, arguments)
}
});
function observer(target){
if(typeof target !== 'object' || target == null){
return target;
}
if(Array.isArray(target)){ // 攔截數組 給數組的方法進行了重寫
Object.setPrototypeOf(target,proto); // 寫個循環 賦予給target
// target.__proto__ = proto;
for(let i = 0; i< target.length ;i++){
observer(target[i]);
}
}else{
Object.keys(target).forEach((key) => {
defineReactive(target, key, target[key]);
});
}
}
複製代碼
let oldArrayPrototype = Array.prototype;
let proto = Object.create(oldArrayPrototype); // 繼承
['push','shift','unshift'].forEach(method=>{
proto[method] = function(){ //函數劫持 把函數進行重寫 內部 繼續調用老的方法
updateView(); // 切片編程
oldArrayPrototype[method].call(this,...arguments)
}
});
function observer(target){
if(typeof target !== 'object' || target == null){
return target;
}
if(Array.isArray(target)){ // 攔截數組 給數組的方法進行了重寫
Object.setPrototypeOf(target,proto); // 寫個循環 賦予給target
// target.__proto__ = proto;
for(let i = 0; i< target.length ;i++){
observer(target[i]);
}
}else{
Object.keys(target).forEach((key) => {
defineReactive(target, key, target[key]);
});
}
}
function defineReactive(target,key,value){
observer(value); // 遞歸 我就將這個對象 繼續攔截
Object.defineProperty(target,key,{
get(){ // get 中會進行依賴收集
return value
},
set(newValue){
if(newValue !== value){
// data.age = {n:200}; data.age.n = 300;這種狀況就須要從新觀察
observer(newValue)
updateView()
value = newValue
}
}
});
}
function updateView(){
console.log('我動啦')
}
class Vue {
constructor(options) {
this._data = options.data;
observer(this._data);
}
}
// 測試
let o = new Vue({
data: {
test: "I am test."
}
});
o._data.test = "hello,world."; /* 我動了 */
複製代碼