中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

怎么在Vue中對數據進行劫持

發布時間:2021-01-04 14:35:42 來源:億速云 閱讀:258 作者:Leah 欄目:開發技術

本篇文章為大家展示了怎么在Vue中對數據進行劫持,內容簡明扼要并且容易理解,絕對能使你眼前一亮,通過這篇文章的詳細介紹希望你能有所收獲。

Vue會對我們在data中傳入的數據進行攔截:

  • 對象:遞歸的為對象的每個屬性都設置get/set方法

  • 數組:修改數組的原型方法,對于會修改原數組的方法進行了重寫

在用戶為data中的對象設置值、修改值以及調用修改原數組的方法時,都可以添加一些邏輯來進行處理,實現數據更新頁面也同時更新。

Vue中的響應式(reactive): 對對象屬性或數組方法進行了攔截,在屬性或數組更新時可以同時自動地更新視圖。在代碼中被觀測過的數據具有響應性

創建Vue實例

我們先讓代碼實現下面的功能:

<body>
<script>
 const vm = new Vue({
 el: '#app',
 data () {
 return {
 age: 18
 };
 }
 });
 // 會觸發age屬性對應的set方法
 vm.age = 20;
 // 會觸發age屬性對應的get方法
 console.log(vm.age);
</script>
</body>

在src/index.js中,定義Vue的構造函數。用戶用到的Vue就是在這里導出的Vue:

import initMixin from './init';

function Vue (options) {
 this._init(options);
}

// 進行原型方法擴展
initMixin(Vue);
export default Vue;

在init中,會定義原型上的_init方法,并進行狀態的初始化:

import initState from './state';

function initMixin (Vue) {
 Vue.prototype._init = function (options) {
 const vm = this;
 // 將用戶傳入的選項放到vm.$options上,之后可以很方便的通過實例vm來訪問所有實例化時傳入的選項
 vm.$options = options;
 initState(vm);
 };
}

export default initMixin;

在_init方法中,所有的options被放到了vm.$options中,這不僅讓之后代碼中可以更方便的來獲取用戶傳入的配置項,也可以讓用戶通過這個api來獲取實例化時傳入的一些自定義選選項。比如在Vuex 和Vue-Router中,實例化時傳入的router和store屬性便可以通過$options獲取到。

除了設置vm.$options,_init中還執行了initState方法。該方法中會判斷選項中傳入的屬性,來分別進行props、methods、data、watch、computed 等配置項的初始化操作,這里我們主要處理data選項:

import { observe } from './observer';
import { proxy } from './shared/utils';

function initState (vm) {
 const options = vm.$options;
 if (options.props) {
 initProps(vm);
 }
 if (options.methods) {
 initMethods(vm);
 }
 if (options.data) {
 initData(vm);
 }
 if (options.computed) {
 initComputed(vm)
 }
 if (options.watch) {
 initWatch(vm)
 }
}

function initData (vm) {
 let data = vm.$options.data;
 vm._data = data = typeof data === 'function' ? data.call(vm) : data;
 // 對data中的數據進行攔截
 observe(data);
 // 將data中的屬性代理到vm上
 for (const key in data) {
 if (data.hasOwnProperty(key)) {
 // 為vm代理所有data中的屬性,可以直接通過vm.xxx來進行獲取
 proxy(vm, key, data);
 }
 }
}

export default initState;

在initData中進行了如下操作:

  1. data可能是對象或函數,這里將data統一處理為對象

  2. 觀測data中的數據,為所有對象屬性添加set/get方法,重寫數組的原型鏈方法

  3. 將data中的屬性代理到vm上,方便用戶直接通過實例vm來訪問對應的值,而不是通過vm._data來訪問

新建src/observer/index.js,在這里書寫observe函數的邏輯:

function observe (data) {
 // 如果是對象,會遍歷對象中的每一個元素
 if (typeof data === 'object' && data !== null) {
 // 已經觀測過的值不再處理
 if (data.__ob__) {
 return;
 }
 new Observer(data);
 }
}

export { observe };

observe函數中會過濾data中的數據,只對對象和數組進行處理,真正的處理邏輯在Observer中:

/**
 * 為data中的所有對象設置`set/get`方法
 */
class Observer {
 constructor (value) {
 this.value = value;
 // 為data中的每一個對象和數組都添加__ob__屬性,方便直接可以通過data中的屬性來直接調用Observer實例上的屬性和方法
 defineProperty(this.value, '__ob__', this);
 // 這里會對數組和對象進行單獨處理,因為為數組中的每一個索引都設置get/set方法性能消耗比較大
 if (Array.isArray(value)) {
 Object.setPrototypeOf(value, arrayProtoCopy);
 this.observeArray(value);
 } else {
 this.walk();
 }
 }

 walk () {
 for (const key in this.value) {
 if (this.value.hasOwnProperty(key)) {
 defineReactive(this.value, key);
 }
 }
 }

 observeArray (value) {
 for (let i = 0; i < value.length; i++) {
 observe(value[i]);
 }
 }
}

需要注意的是,__ob__屬性要設置為不可枚舉,否則之后在對象遍歷時可能會引發死循環

Observer類中會為對象和數組都添加__ob__屬性,之后便可以直接通過data中的對象和數組vm.value.__ob__來獲取到Observer實例。

當傳入的value為數組時,由于觀測數組的每一個索引會耗費比較大的性能,并且在實際使用中,我們可能只會操作數組的第一項和最后一項,即arr[0],arr[arr.length-1],很少會寫出arr[23] = xxx的代碼。

所以我們選擇對數組的方法進行重寫,將數組的原型指向繼承Array.prototype新創建的對象arrayProtoCopy,對數組中的每一項繼續進行觀測。

創建data中數組原型的邏輯在src/observer/array.js中:

// if (Array.isArray(value)) {
// Object.setPrototypeOf(value, arrayProtoCopy);
// this.observeArray();
// }
const arrayProto = Array.prototype;
export const arrayProtoCopy = Object.create(arrayProto);

const methods = ['push', 'pop', 'unshift', 'shift', 'splice', 'reverse', 'sort'];

methods.forEach(method => {
 arrayProtoCopy[method] = function (...args) {
 const result = arrayProto[method].apply(this, args);
 console.log('change array value');
 // data中的數組會調用這里定義的方法,this指向該數組
 const ob = this.__ob__;
 let inserted;
 switch (method) {
 case 'push':
 case 'unshift':
 inserted = args;
 break;
 case 'splice': // splice(index,deleteCount,item1,item2)
 inserted = args.slice(2);
 break;
 }
 if (inserted) {ob.observeArray(inserted);}
 return result;
 };
});

通過Object.create方法,可以創建一個原型為Array.prototype的新對象arrayProtoCopy。修改原數組的7個方法會設置為新對象的私有屬性,并且在執行時會調用arrayProto 上對應的方法。

在這樣處理之后,便可以在arrayProto中的方法執行前后添加自己的邏輯,而除了這7個方法外的其它方法,會根據原型鏈,使用arrayProto上的對應方法,并不會有任何額外的處理。

在修改原數組的方法中,添加了如下的額外邏輯:

const ob = this.__ob__;
let inserted;
switch (method) {
 case 'push':
 case 'unshift':
 inserted = args;
 break;
 case 'splice': // splice(index,deleteCount,item1,item2)
 inserted = args.slice(2);
 break;
}
if (inserted) {ob.observeArray(inserted);}

push、unshift、splice會為數組新增元素,對于新增的元素,也要對其進行觀測。這里利用到了Observer中為數組添加的__ob__屬性,來直接調用ob.observeArray ,對數組中新增的元素繼續進行觀測。

對于對象,要遍歷對象的每一個屬性,來為其添加set/get方法。如果對象的屬性依舊是對象,會對其進行遞歸處理

function defineReactive (target, key) {
 let value = target[key];
 // 繼續對value進行監聽,如果value還是對象的話,會繼續new Observer,執行defineProperty來為其設置get/set方法
 // 否則會在observe方法中什么都不做
 observe(value);
 Object.defineProperty(target, key, {
 get () {
 console.log('get value');
 return value;
 },
 set (newValue) {
 if (newValue !== value) {
 // 新加的元素也可能是對象,繼續為新加對象的屬性設置get/set方法
 observe(newValue);
 // 這樣寫會新將value指向一個新的值,而不會影響target[key]
 console.log('set value');
 value = newValue;
 }
 }
 });
}

class Observer {
 constructor (value) {
 // some code ...
 if (Array.isArray(value)) {
 // some code ...
 } else {
 this.walk();
 }
 }

 walk () {
 for (const key in this.value) {
 if (this.value.hasOwnProperty(key)) {
 defineReactive(this.value, key);
 }
 }
 }

 // some code ... 
}

數據觀測存在的問題

檢測變化的注意事項

我們先創建一個簡單的例子:

const mv = new Vue({
 data () {
 return {
 arr: [1, 2, 3],
 person: {
 name: 'zs',
 age: 20
 }
 }
 }
})

對于對象,我們只是攔截了它的取值和賦值操作,添加值和刪除值并不會進行攔截:

vm.person.school = '北大'
delete vm.person.age

而對于數組,用索引修改值以及修改數組長度不會被觀測到:

vm.arr[0] = 0
vm.arr.length--

為了能處理上述的情況,Vue為用戶提供了$set和$delete方法:

  • $set: 為響應式對象添加一個屬性,確保新屬性也是響應式的,因此會觸發視圖更新

  • $delete: 刪除對象上的一個屬性。如果對象是響應式的,確保刪除觸發視圖更新。

上述內容就是怎么在Vue中對數據進行劫持,你們學到知識或技能了嗎?如果還想學到更多技能或者豐富自己的知識儲備,歡迎關注億速云行業資訊頻道。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

九寨沟县| 宁都县| 子洲县| 汝南县| 安西县| 南郑县| 定结县| 双流县| 剑河县| 桐柏县| 津南区| 罗甸县| 通榆县| 象山县| 长武县| 吉林市| 乌鲁木齐县| 巴楚县| 余干县| 华亭县| 锦州市| 常山县| 博罗县| 江口县| 乌什县| 西贡区| 双鸭山市| 华安县| 江安县| 清苑县| 额尔古纳市| 神农架林区| 广元市| 浠水县| 将乐县| 闸北区| 伊宁县| 林周县| 齐齐哈尔市| 务川| 武汉市|