日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

论如何监听一个对象所有属性的变化

發布時間:2025/4/16 编程问答 23 豆豆
生活随笔 收集整理的這篇文章主要介紹了 论如何监听一个对象所有属性的变化 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

前言

本文分為入門和進階兩部分,建議有經驗的讀者直接閱讀進階部分。

本文主要參考了vue和on-change兩個開源庫,若讀者閱讀過它們的源碼可以直接跳過本文 :)

入門

關于Object.defineProperty

首先我們需要知道如何通過Object.defineProperty這個API來監聽一個對象的變化, 注意注釋里的內容!

const obj = {};let val = obj.name; Object.defineProperty(obj, 'name', {set(newVal) {console.warn(newVal);// 想知道為什么不直接寫成obj.name = newVal嗎, 自己試試吧 :)val = newVal;}, });setTimeout(() => {// 一秒鐘后我們將obj這個對象的name屬性賦值為字符串a, 看看會發生什么obj.name = 'a'; }, 1000); 復制代碼

好了,現在你知道如何通過Object.defineProperty這個API來監聽一個對象的變化了吧,不過你還要注意一些細節

const obj = {};let val = obj.name; Object.defineProperty(obj, 'name', {set(newVal) {console.warn(newVal);val = newVal;}, });setTimeout(() => {obj.name = 'a';// 由于我們沒有設置enumerable描述符,所以它是默認值false, 也就是說obj的name屬性是無法被枚舉的console.warn(obj);// 這個很好理解,因為我們沒有設置get方法console.warn(obj.name); }, 1000); 復制代碼

也就是說我們需要加上這些

Object.defineProperty(obj, 'name', {enumerable: true,// 想知道為什么要加上configurable描述符嗎,試試delete obj.name吧configurable: true,get() {return val;},set(newVal) {console.warn(newVal);val = newVal;}, }); 復制代碼

另外,數組對象是個特例,mutable的原型方法我們無法通過Object.defineProperty來監聽到

const obj = {val: [], };let val = obj.val; Object.defineProperty(obj, 'val', {get() {return val;},set(newVal) {console.warn(newVal);val = newVal;}, });setTimeout(() => {// 沒有任何反應obj.val.push('b'); }, 1000); 復制代碼

因此我們還需要去劫持數組對象mutable的原型方法, 包括push, pop, shift, unshift, splice, sort, reverse, 我們以push為例:

const obj = {val: [], };const arrayMethods = Object.create(Array.prototype); arrayMethods.push = function mutator(...args) {console.warn(args);[].push.apply(this, args); };// 如果瀏覽器實現了__proto__, 覆蓋原型對象 if ('__proto__' in {}) {val.__proto__ = arrayMethods; } else {// 要是瀏覽器沒有實現__proto__, 覆蓋對象本身的該方法Object.defineProperty(val, 'push', {value: arrayMethods['push'],enumerable: true,}); }setTimeout(() => {obj.val.push('b'); }, 1000); 復制代碼

好了,以上就是關于如何通過Object.defineProperty這個API來監聽一個對象的變化的全部。

關于Proxy

通過Proxy來監聽對象變化要比Object.defineProperty容易的多

let obj = {};obj = new Proxy(obj, {set(target, prop, newVal) {console.warn(newVal);// 你也可以使用Reflect.set()target[prop] = newVal;return true;}, });setTimeout(() => {// 一秒鐘后我們將obj這個對象的name屬性賦值為字符串aobj.name = 'a';// 顯然我們不需要更多的設置console.warn(obj);console.warn(obj.name); }, 1000); 復制代碼

同樣的對于數組對象的監聽也沒有那么多hacky的味道

const obj = {val: [], };obj.val = new Proxy(obj.val, {set(target, prop, newVal) {const oldVal = target[prop];if (oldVal !== newVal) {console.warn(oldVal, newVal);}target[prop] = newVal;return true;}, });setTimeout(() => {obj.val.push('a'); }, 1000); 復制代碼

好了,以上就是關于如何通過Proxy來監聽一個對象的變化的全部。

進階

關于分類和遞歸

假如我們現在有這樣一個對象obj, 如何監聽它的所有屬性呢

let obj = {b: true,o: { name: 'obj' },a: ['a', 'b', 'c'],odeep: {path: {name: 'obj deep',value: [],},}, }; 復制代碼

我們可以分類討論,先考慮基本類型的變量以及Object類型的變量

function isPlainObject(obj) {return ({}).toString.call(obj) === '[object Object]'; }// 首先先定義一個劫持對象屬性的通用函數 function defineReactive(obj, key, val) {if (isPlainObject(val)) {observe(val);}Object.defineProperty(obj, key, {enumerable: true,configurable: true,get() {return val;},set(newVal) {console.warn(newVal);val = newVal;// 賦的新值不為基本類型, 也同樣需要劫持if (isPlainObject(newVal)) {observe(newVal);}},}); }// 遍歷所有屬性并劫持 function observe(obj) {Object.keys(obj).forEach((key) => {defineReactive(obj, key, obj[key]);}); }observe(obj); setTimeout(() => {// 顯然不會有什么問題obj.b = false;obj.o.name = 'newObj';obj.odeep.path.name = 'newObj deep';obj.b = { name: 'obj created' };obj.b.name = 'newObj created'; }, 1000); 復制代碼

我們再來考慮Array類型的變量

function defineReactive(obj, key, val) {if (isPlainObject(val)) {observe(val);} else if (Array.isArray(val)) {dealAugment(val);observeArray(val);}Object.defineProperty(obj, key, {enumerable: true,configurable: true,get() {return val;},set(newVal) {console.warn(newVal);val = newVal;if (isPlainObject(newVal)) {observe(newVal);} else if (Array.isArray(newVal)) {dealAugment(newVal);observeArray(newVal);}},}); }function dealAugment(val) {const arrayMethods = Object.create(Array.prototype);// 我們以push方法為例arrayMethods.push = function mutator(...args) {console.warn(args);[].push.apply(this, args);};// 如果瀏覽器實現了__proto__, 覆蓋原型對象if ('__proto__' in {}) {obj.val.__proto__ = arrayMethods;} else {// 要是瀏覽器沒有實現__proto__, 覆蓋對象本身的該方法Object.defineProperty(obj.val, 'push', {value: arrayMethods['push'],enumerable: true,});} }function observeArray(obj) {obj.forEach((el) => {if (isPlainObject(el)) {observe(el);} else if (Array.isArray(el)) {observeArray(el);}}); }observe(obj); setTimeout(() => {// 顯然不會有什么問題obj.a.push('d');obj.odeep.path.value.push(1);obj.b = ['a'];obj.b.push('b'); }, 1000); 復制代碼

顯然,Object.defineProperty的版本有些冗長,那么Proxy的版本如何呢?

const handler = {get(target, prop) {try {// 還有比這更簡潔的遞歸嗎return new Proxy(target[prop], handler);} catch (error) {return target[prop]; // 或者是Reflect.get}},set(target, prop, newVal) {const oldVal = target[prop];if (oldVal !== newVal) {console.warn(oldVal, newVal);}target[prop] = newVal;return true;}, };obj = new Proxy(obj, handler);setTimeout(() => {// 試試吧,太不可思議了!obj.b = false;obj.o.name = 'newObj';obj.odeep.path.name = 'newObj deep';obj.b = { name: 'obj created' };obj.b.name = 'newObj created';obj.a.push('d');obj.odeep.path.value.push(1);obj.b = ['a'];obj.b.push('b');obj.b[0] = 'new a'; }, 1000); 復制代碼

以上就是監聽一個對象變化的所有內容了。不過細心的你應該發現了,我們使用了console.warn(newVal)這樣強耦合的寫法, 下篇文章將會介紹如何使用觀察者模式實現類似Vue.prototype.$watch的功能。

轉載于:https://juejin.im/post/5cc68feef265da036c57940a

總結

以上是生活随笔為你收集整理的论如何监听一个对象所有属性的变化的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。