javascript数组实例扩展方法(JavaScript如何监测数组的变化)
javascript数组实例扩展方法
JavaScript如何监测数组的变化前言之前介绍defineProperty的时候说到,其只能监测对象的变化,并不能监测数组的变化。
本文致力于说清楚怎么实现监测数组的变化。
核心思路:找到改变原数组的方法,然后对这些方法进行劫持处理。
上面这句话,是重中之重,务必读三遍,记住了,再往下走。
改变原数组,常用到的方法有push pop shift unshift reverse sort splice。
换言之,这些方法是改变数组的入口。
在数组实例和数组原型之间,加一个新的原型直接修改Array.prototype,是极其危险的。
换一个思路,复制已有的数组原型,然后修改其中的方法,但这里因为原型上的方法不可枚举,自然也就没法复制。
于是再换一个思路,在数组和数组的原型之间,插入一个原型,形成原型链,数组 => 新原型 => 数组的原型,可以在新原型上增加同名的方法就可以了。
先借助伪代码理解:
// 伪代码 let arr = []; arr.__proto__ = newPrototype; newPrototype.__proto__ = Array.prototype; // 然后可以在新原型上添加同名的方法就可以了 newPrototype.push = xxx;
转换成真实的代码如下,核心使用了Object.create,
// Object.create返回一个新对象,而来新对象的__proto__就是传进去的参数。 let newPrototype = Object.create(Array.prototype); // 然后可以在新原型上添加同名的方法就可以了 newPrototype.push = xxx; // 需要监测的数组,绑定新的原型就可以了 let arr = []; arr.__proto__ = newPrototype;
就是在新原型上重新写一个 push 的方法,里面执行老的 push,但除此之外还可以做点别的事。
let newPrototype = Object.create(Array.prototype); // 在新原型上添加同名push newPrototype.push = function(...args) { // 语义化this let curArr = this; console.log("使用了push"); // 最后还是会执行原始的push return Array.prototype.push.call(curArr, ...args); }; // 需要监测的数组,绑定新的原型就可以了 let arr = []; arr.__proto__ = newPrototype; // 执行push的时候,就会打印了 arr.push(1);
然后其他的方法也是类似的,试着写写其他的方法
劫持其他的方法将其他方法也一起写了,因为逻辑一样,直接遍历即可。
let newPrototype = Object.create(Array.prototype); let methods = ["push", "pop", "shift", "unshift", "reverse", "sort", "splice"]; methods.forEach(method => { newPrototype[method] = function(...args) { console.log(`使用了${method}`); return Array.prototype[method].call(this, ...args); }; }); // 需要监测的数组,绑定新的原型就可以了 let arr = []; arr.__proto__ = newPrototype; // 执行的时候,就会打印了 arr.push(1); arr.pop();
这里数组里可能也是有数组的,需要遍历数组的每项,如果是数组的话依然需要指向新的原型。
嗯,对,用到递归了。
let newPrototype = Object.create(Array.prototype); let methods = ["push", "pop", "shift", "unshift", "reverse", "sort", "splice"]; methods.forEach(method => { newPrototype[method] = function(...args) { console.log(`使用了${method}`); return Array.prototype[method].call(this, ...args); }; }); function observeArr(arr) { // 既是条件限制,也是递归的终止条件 if (!Array.isArray(arr)) { return; } // 整个数组指向新的原型 arr.__proto__ = newPrototype; // 数组的每项,如果是数组,也指向新的原型。 arr.forEach(observeArr); } // 需要监测的数组,绑定新的原型就可以了 let arr = [[1, 2, 3]]; observeArr(arr); // 执行的时候,就会打印了 arr[0].push(1); arr[1].pop();
能添加元素的方法:push unshift splice。
找到新加的元素,然后是数组的也同样指向新的原型
let newPrototype = Object.create(Array.prototype); let methods = ["push", "pop", "shift", "unshift", "reverse", "sort", "splice"]; methods.forEach(method => { newPrototype[method] = function(...args) { console.log(`使用了${method}`); let inserted; switch (method) { case "push": case "unshift": inserted = args; break; case "splice": inserted = args.slice(2); break; default: break; } inserted && observeArr(inserted); return Array.prototype[method].call(this, ...args); }; }); function observeArr(arr) { // 即是条件限制,也是递归的终止条件 if (!Array.isArray(arr)) { return; } // 整个数组指向新的原型 arr.__proto__ = newPrototype; // 数组的每项,如果是数组,也指向新的原型。 arr.forEach(observeArr); } // 这里可以导出去,方便别的文件使用 export default observeArr; // 需要监测的数组,绑定新的原型就可以了 let arr = []; observeArr(arr); let addItem = [1, 2, 3]; arr.push(addItem); // 执行的时候,就会打印了 addItem.push(1); addItem.pop();
现在已经有了监测对象的方法,也有了监测数组的方法,将两个综合起来,就能监测数组里面的对象,对象里面的数组了。
将监测数组和监测对象的的可以单独写成一个文件,方便之后使用。
这里为了方便直接运行代码,直接放在一块了。
/** * observeArr的部分 **/ // 生成新的原型 let newPrototype = Object.create(Array.prototype); let methods = ["push", "pop", "shift", "unshift", "reverse", "sort", "splice"]; // 在新原型上面添加以上方法,实现劫持 methods.forEach(method => { newPrototype[method] = function(...args) { console.log(`使用了${method}`); let inserted; switch (method) { case "push": case "unshift": inserted = args; break; case "splice": inserted = args.slice(2); break; default: break; } inserted && observeArr(inserted); return Array.prototype[method].call(this, ...args); }; }); function observeArr(arr) { // 新加!!!是对象的话,需要用对象 if (Object.prototype.toString.call(arr) === "[object Object]") { observeObj(arr); return; } if (Array.isArray(arr)) { // 整个数组指向新的原型 arr.__proto__ = newPrototype; // 数组的每项,如果是数组,也指向新的原型。 arr.forEach(observeArr); } // 不是对象或者数组的,什么都不做 } /** * observeObj的部分 **/ function observeObj(obj) { // 加上参数限制,必须是对象才有劫持,也是递归的终止条件 if (typeof obj !== "object" || obj == null) { return; } // 新加!!!数组交给数组处理 if (Array.isArray(obj)) { observeArr(obj); return; } // 是对象的话 才开始递归 for (let key in obj) { // 直接使用 obj.hasOwnProperty会提示不规范 if (Object.prototype.hasOwnProperty.call(obj, key)) { observeKey(obj, key); // 这里劫持该属性的属性值,如果不是对象直接返回,不影响 observeObj(obj[key]); } } return obj; } function observeKey(obj, key) { let value = obj[key]; Object.defineProperty(obj, key, { get() { console.log("读取属性", value); return value; }, set(newValue) { console.log("设置属性", newValue); value = newValue; } }); } /** * demo试试 **/ let data = { a: 1, b: [1, 2, { c: 2 }] }; observeObj(data); data.a = 2; data.b.push([2, 3]); let arr = [{ a: "数组里的对象" }, 3, 4]; observeArr(arr); arr[0].a = 3;
当然数组其实可以不通过方法改变,比如直接删除数组可以直接使用 length 属性,或者直接arr[0]=xxx改变数组。
但只有当使用"push","pop", "shift","unshift","reverse","sort","splice"才能检测到数组变化。
这也是 vue 的缺陷,当然新版的 proxy 将干掉这个缺陷。
所以在使用 vue 的过程中,要尽量使用以上方法操作数组~~~
附注:查看数组的所有属性和方法在控制台可以输入dir([]),然后能看到数组所有的属性和方法。
具体用法,可以直接到到mdn 上细看,点击侧边栏看对应的方法
总结到此这篇关于JavaScript如何监测数组变化的文章就介绍到这了,更多相关JS监测数组变化内容请搜索开心学习网以前的文章或继续浏览下面的相关文章希望大家以后多多支持开心学习网!
- Extjs中文乱码
- 微信浏览器中JS实现返回操作
- js搜索功能的实现(前端JavaScript实现本地模糊搜索功能的方法实例)
- python class转json(Python对象转换为json的方法步骤)
- js闭包可以解决哪些问题(JavaScript中let避免闭包造成问题)
- videojs播放流媒体(video.js支持m3u8格式直播的实现示例)
- vuejs图片缩放裁切(vue+js点击箭头实现图片切换)
- js获取微信版本号
- JS中sort()和reverse()
- js防抖用法(JavaScript的防抖和节流案例)
- js怎么上传压缩图片(如何用JS有效的压缩图片)
- nodejs子进程调试(Node.js实现断点续传)
- js事件冒泡与事件捕获(基于事件冒泡、事件捕获和事件委托详解)
- vuejs组件使用教程交流(Vue vee-validate插件的简单使用)
- vue前台解析pdf文件流(Vue实现在线预览pdf文件功能利用pdf.js/iframe/embed)
- ExtJs中XTemplate使用
- 这竟然是捏出来的 20种橡皮泥玩法让你轻松hold住魔娃(这竟然是捏出来的)
- 自制橡皮泥(自制橡皮泥)
- 还在卖 禁药西布曲明网上论斤卖(还在卖禁药西布曲明网上论斤卖)
- 微商在朋友圈热卖的 DL减肥咖啡 含违禁药物,你还敢买吗(微商在朋友圈热卖的)
- 八一节,说说中国女兵(八一节说说中国女兵)
- 王治郅菜鸟赛季已让八一带入正轨,大郅七大经典语录或是成功秘诀(王治郅菜鸟赛季已让八一带入正轨)
热门推荐
- mvc在视图中使用@helper封装输出代码
- linuxmysql安装教程5.7.25学习(linux mysql5.5升级至mysql5.7的步骤与踩到的坑)
- python的log函数(Python3 log10函数简单用法)
- nginx 处理服务器错误(nginx服务器异常502 bad gateway原因排查)
- springbootvue数据交互系统(Springboot运用vue+echarts前后端交互实现动态圆环图)
- docker redis配置文件放哪里(最详细的docker中安装并配置redis图文详解)
- python配合docker(Docker构建python Flask+ nginx+uwsgi容器)
- thinkphp远程代码执行教程(ThinkPHP 5.x远程命令执行漏洞复现)
- extjs xtype的使用
- python创建文件的方法(Python3.5文件读与写操作经典实例详解)