JavaScript如何监测数组的变化

 更新时间:2021年7月18日 15:00  点击:1929

前言

之前介绍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

就是在新原型上重新写一个 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();

综合使用 defineProperty 监测对象和数组

现在已经有了监测对象的方法,也有了监测数组的方法,将两个综合起来,就能监测数组里面的对象,对象里面的数组了。

将监测数组和监测对象的的可以单独写成一个文件,方便之后使用。

这里为了方便直接运行代码,直接放在一块了。

/**
 * 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监测数组变化内容请搜索猪先飞以前的文章或继续浏览下面的相关文章希望大家以后多多支持猪先飞!

[!--infotagslink--]

相关文章

  • php中eval()函数操作数组的方法

    在php中eval是一个函数并且不能直接禁用了,但eval函数又相当的危险了经常会出现一些问题了,今天我们就一起来看看eval函数对数组的操作 例子, <?php $data="array...2016-11-25
  • Python 图片转数组,二进制互转操作

    这篇文章主要介绍了Python 图片转数组,二进制互转操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2021-03-09
  • JavaScript判断浏览器及其版本信息

    本篇文章主要分享了通过window.navigator来判断浏览器及其版本信息的实例代码。具有一定的参考价值,下面跟着小编一起来看下吧...2017-01-23
  • js实现浏览器打印功能的示例代码

    这篇文章主要介绍了js如何实现浏览器打印功能,文中示例代码非常详细,帮助大家更好的理解和学习,感兴趣的朋友可以了解下...2020-07-15
  • php数组操作 键名比较 差集 交集赋值

    本文章提供在量的数据中级操作实例有如对键名比较计算数组的差集 计算差集 给指定数组中插入一个元素 反转数组 交集赋值新的数组实例。 //定义回调函数 funct...2016-11-25
  • C#二维数组基本用法实例

    这篇文章主要介绍了C#二维数组基本用法,以实例形式分析了C#中二维数组的定义、初始化、遍历及打印等用法,具有一定参考借鉴价值,需要的朋友可以参考下...2020-06-25
  • C#数组的常用操作方法小结

    Array数组在C#中同样是最基本的数据结构,下面为大家C#数组的常用操作方法小结,皆为细小的代码段,欢迎收看收藏...2020-06-25
  • php curl模拟post请求和提交多维数组的示例代码

    下面一段代码给大家介绍php curl模拟post请求的示例代码,具体代码如下: <&#63;php$uri = "http://www.cnblogs.com/test.php";//这里换成自己的服务器的地址// 参数数组$data = array ( 'name' => 'tanteng'// 'passwor...2015-11-24
  • Nest.js参数校验和自定义返回数据格式详解

    这篇文章主要给大家介绍了关于Nest.js参数校验和自定义返回数据格式的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-03-28
  • 利用JS实现点击按钮后图片自动切换的简单方法

    下面小编就为大家带来一篇利用JS实现点击按钮后图片自动切换的简单方法。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧...2016-10-25
  • 详解前端安全之JavaScript防http劫持与XSS

    作为前端,一直以来都知道HTTP劫持与XSS跨站脚本、CSRF跨站请求伪造。防御这些劫持最好的方法是从后端入手,前端能做的太少。而且由于源码的暴露,攻击者很容易绕过防御手段。但这不代表我们去了解这块的相关知识是没意义的,本文的许多方法,用在其他方面也是大有作用。...2021-05-24
  • js+css实现回到顶部按钮(back to top)

    这篇文章主要为大家详细介绍了js+css实现回到顶部按钮back to top回到顶部按钮,感兴趣的小伙伴们可以参考一下...2016-03-03
  • JavaScript仿支付宝密码输入框

    那么今天我就用JavaScript代码来实现这个效果吧,那么首先介绍一下整个的思路,首先我们先将确定输入密码的位数,我的需求是5位,那么就用一个div标签包住5个input标签...2016-01-02
  • js实现上传图片及时预览

    这篇文章主要为大家详细介绍了js实现上传图片及时预览的相关资料,具有一定的参考价值,感兴趣的朋友可以参考一下...2016-05-09
  • C# 拷贝数组的几种方法(总结)

    下面小编就为大家带来一篇C# 拷贝数组的几种方法(总结)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧...2020-06-25
  • js实现调用网络摄像头及常见错误处理

    这篇文章主要介绍了js实现调用网络摄像头及常见错误处理,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-03-07
  • JS实现随机生成验证码

    这篇文章主要为大家详细介绍了JS实现随机生成验证码,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2021-09-06
  • PHP 二维数组根据某个字段排序的具体实现

    本文记录的要实现的功能类似于 MySQL 中的 ORDER BY,上个项目中有遇到这样的一个需求。 要求:从两个不同的表中获取各自的4条数据,然后整合(array_merge)成一个数组,再根据数据的创建时间降序排序取前4条。 遇到这个...2014-06-07
  • C#实现字符串转换成字节数组的简单实现方法

    这篇文章主要介绍了C#实现字符串转换成字节数组的简单实现方法,仅一行代码即可搞定,非常简单实用,需要的朋友可以参考下...2020-06-25
  • js组件SlotMachine实现图片切换效果制作抽奖系统

    这篇文章主要介绍了js组件SlotMachine实现图片切换效果制作抽奖系统的相关资料,需要的朋友可以参考下...2016-04-19