热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

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

就是在新原型上重新写一个 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 上细看,点击侧边栏看对应的方法

总结


推荐阅读
  • 本文介绍了iOS数据库Sqlite的SQL语句分类和常见约束关键字。SQL语句分为DDL、DML和DQL三种类型,其中DDL语句用于定义、删除和修改数据表,关键字包括create、drop和alter。常见约束关键字包括if not exists、if exists、primary key、autoincrement、not null和default。此外,还介绍了常见的数据库数据类型,包括integer、text和real。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • 如何用JNI技术调用Java接口以及提高Java性能的详解
    本文介绍了如何使用JNI技术调用Java接口,并详细解析了如何通过JNI技术提高Java的性能。同时还讨论了JNI调用Java的private方法、Java开发中使用JNI技术的情况以及使用Java的JNI技术调用C++时的运行效率问题。文章还介绍了JNIEnv类型的使用方法,包括创建Java对象、调用Java对象的方法、获取Java对象的属性等操作。 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • 开发笔记:加密&json&StringIO模块&BytesIO模块
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了加密&json&StringIO模块&BytesIO模块相关的知识,希望对你有一定的参考价值。一、加密加密 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • 阿,里,云,物,联网,net,core,客户端,czgl,aliiotclient, ... [详细]
  • Mac OS 升级到11.2.2 Eclipse打不开了,报错Failed to create the Java Virtual Machine
    本文介绍了在Mac OS升级到11.2.2版本后,使用Eclipse打开时出现报错Failed to create the Java Virtual Machine的问题,并提供了解决方法。 ... [详细]
  • 1,关于死锁的理解死锁,我们可以简单的理解为是两个线程同时使用同一资源,两个线程又得不到相应的资源而造成永无相互等待的情况。 2,模拟死锁背景介绍:我们创建一个朋友 ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • Go Cobra命令行工具入门教程
    本文介绍了Go语言实现的命令行工具Cobra的基本概念、安装方法和入门实践。Cobra被广泛应用于各种项目中,如Kubernetes、Hugo和Github CLI等。通过使用Cobra,我们可以快速创建命令行工具,适用于写测试脚本和各种服务的Admin CLI。文章还通过一个简单的demo演示了Cobra的使用方法。 ... [详细]
  • 本文介绍了如何使用C#制作Java+Mysql+Tomcat环境安装程序,实现一键式安装。通过将JDK、Mysql、Tomcat三者制作成一个安装包,解决了客户在安装软件时的复杂配置和繁琐问题,便于管理软件版本和系统集成。具体步骤包括配置JDK环境变量和安装Mysql服务,其中使用了MySQL Server 5.5社区版和my.ini文件。安装方法为通过命令行将目录转到mysql的bin目录下,执行mysqld --install MySQL5命令。 ... [详细]
  • Java在运行已编译完成的类时,是通过java虚拟机来装载和执行的,java虚拟机通过操作系统命令JAVA_HOMEbinjava–option来启 ... [详细]
  • 模板引擎StringTemplate的使用方法和特点
    本文介绍了模板引擎StringTemplate的使用方法和特点,包括强制Model和View的分离、Lazy-Evaluation、Recursive enable等。同时,还介绍了StringTemplate语法中的属性和普通字符的使用方法,并提供了向模板填充属性的示例代码。 ... [详细]
author-avatar
L爱你j_828
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有