【每日一题】【vue2源码学习】vue如何检测数组的变化
数组可以用defineProperty
进行监听。但是考虑性能原因,不能数组一百万项每一项都循环监听(那样性能太差了)。所以没有使用Ojbect.defineProperty对数组每一项进行拦截,而是选择劫持数组原型上的个别方法并重写。
具体重写的有:
push
、pop
、shift
、unshift
、sort
、reverse
、splice
(这七个都是会改变原数组的)
另外要注意的是:
不是直接粗暴重写了Array.prototype
上的push等方法,而是通过原型链继承与函数劫持进行的移花接木。并且只监听调用了defineReactive
函数时传进来的数组。
具体实现思路:
以push为例,而是利用Object.create(Array.prototype)
生成新的数组对象,该对象的__proto__指向Array.prototype。并在对象身上创建push等函数,利用函数劫持,在函数内部Array.prototype.push.call调用原有push方法,并执行自己劫持的代码(如视图更新)。最后将需要绑定的数组的__proto__由指向Array.prototype改向指成拥有重写方法的新数组对象。具体看下边源码仿写,真实Array.prototype里的祖宗级别push等方法没有动。
思考:
为啥不重写map等也是修改原数组的方法呢?
特别注意:
在Vue中修改数组的索引和长度,是无法被监控到并做响应式视图更新的。需要通过以上7种变异方法修改数组才会触发数组对应watcher进行更新。 数组中如果是对象数据类型的也会进行递归劫持。 如果情节需要,通过索引来修改数组里的内容。可以通过Vue.set()方法来进行处理,或者使用splice方法实现。(其实set内部的核心也是splice方法)
原理mock:
let state = [1,2,3]; //待监听的数据
// 1、响应式数据-函数劫持实现数组原型方法重写
let OriginalArray = Array.prototype; // 并不是直接改写原型上的方法。而是给当前待监听的数组原型链上加了push等方法劫持了Array原型的push方法。
let arrayMethods = Object.create(OriginalArray) // 创建一个新对象(对象or数组由第一个参数决定),带着指定的原型对象(Array.prototype)
console.log(
arrayMethods,
// 原型修改
arrayMethods.__proto__ === OriginalArray,
arrayMethods.__proto__ === Array.prototype
)
function defineReactive(obj) {
// 【函数劫持】改写这个新对象身上的push、splice等数组方法
arrayMethods.push = function(...args){
// 并还是调用原生的push方法
OriginalArray.push.apply(this, args) // 或者用call(this, ...args)
// 然后这里边做自己的事情,比如视图更新(具体源码怎么更新的视图?)
render()
}
//
obj.__proto__ = arrayMethods // 修改传进来的、被监听的数组的原型链,链接数组与被重写的方法。原本__proto__指向Array.prototype,现在中间给他包了一层,指向我们重写的原型方法。并在重写的原型方法里再调用Array.prototype的同名原型方法。
}
defineReactive(state);
// 操作dom
function render() {
app.innerHTML = state;
}
render()
// 更改数据,观察dom修改
btn.onclick = () => {
state.push(state[state.length - 1] + 1)
}
源码位置:
github:src/core/observer/array.js:8
本文使用 mdnice 排版
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- PHP递归算法的简单实例
- Yii2框架操作数据库的方法分析【以mysql为例】
- 使用OpenCV实现道路车辆计数的使用方法
- python中tab键是什么意思
- PHP实时统计中文字数和区别
- PHP单例模式实例分析【防继承,防克隆操作】
- Python基于network模块制作电影人物关系图
- tp5(thinkPHP5框架)使用DB实现批量删除功能示例
- 统计PHP目录中的文件数方法
- python json.dumps() json.dump()的区别详解
- Win10下用Anaconda安装TensorFlow(图文教程)
- TP5(thinkPHP框架)实现后台清除缓存功能示例
- PHP递归的三种常用方式
- python实现xlwt xlrd 指定条件给excel行添加颜色
- PHP解析url并得到url参数方法总结