1、vue数据响应式原理实现
vue2的数据响应是通过函数Object.defineProperty原理来实现的,但是这个函数是不支持数组的
错误案例
let data = { name: 'aaa' } let watchObject = (obj) => { Object.keys(obj).forEach(key => { Object.defineProperty(obj, key, { get() { return obj[key] }, set(value) { obj[key] = value render() //渲染视图层的函数 } }) }) } watchObject(data)
注意:以上代码会报内存溢出,因为在object进行取数的时候,内部会调用get的方法,这里如果使用obj[key]的方式进行调用,那么会导致不断的循环
利用object.defineProperty对data值的监听
//模拟一个data对象 let data = { name: 'aaa', list: ['aaa', 'bbb', 'ccc'], info: { x: 12, y: 24 } } //模拟渲染视图的函数 let render = () => { console.log('模拟渲染视图', data); } //数组的对象方法 let methods = [ 'pop', 'push', 'shift', 'unshift', 'sort', 'splice', 'reverse' ] let arrayProto = Array.prototype let copyArrayProto = Object.create(arrayProto) //创建一个拷贝对象 // console.log(copyArrayProto, copyArrayProto.__proto__) //对数组的原型方法进行扩展 methods.forEach(val => { copyArrayProto[val] = function () { arrayProto[val].call(this, ...arguments) render() } }) //监听对象的变化 let watchObject = (obj) => { if(Array.isArray(obj)) { //如果是数组,那么就改变其原型的方法 obj.__proto__ = copyArrayProto; return } if(obj !== null && typeof obj === 'object') { //如果是对象,那么就监听对象的变化 for(let per in obj) { defineObject(obj, per, obj[per]) } } } //对对象里的项进行监听 let defineObject = (obj, key, val) => { watchObject(val) //如果还是对象,那么就对该对象进行重新定义处理 Object.defineProperty(obj, key, { get() { return val }, set(value) { if(val == value) return; watchObject(value) //如果赋值为新的对象,那么就对新的对象进行监听 val = value render() } }) } //在vue中$set可以实现对象值的更改 let $set = (obj, key, val) => { if(Array.isArray(obj)) return obj.splice(key, 1, val) defineObject(obj, key, val) } //相当于在vue中的$delete实现对值的删除 let $delete = (obj, key) => { if(Array.isArray(obj)) return obj.splice(key, 1); if(obj[key]) { let res = delete obj[key] res && render() } } //监听data对象 watchObject(data) // console.log(data.name) // data.list.push('ddd') $delete(data.list, 0) // console.log(data)
利用proxy来实现对data的监听
介绍:使用proxy来实现对数据的响应式变化可以支持数组,而且不用区分是对象还是数组,但是兼容性问题相对会弱些
//模拟一个data对象 let data = { name: 'aaa', list: ['aaa', 'bbb', 'ccc'], info: { x: 12, y: 24 } } //模拟渲染视图的函数 let render = () => { console.log('模拟渲染视图', data); } //反射机制里的handle函数 let handle = { get(target, key) { if(target !== null && typeof target === 'object') { return new Proxy(target[key], handle) } return Reflect.get(target, key) }, set(target, key, value) { Reflect.set(target, key, value) render() } } let proxy = new Proxy(data, handle) //注意:调用更改的时候是通过proxy这个代码来对data进行访问和修改 // proxy.name = 'bbb' proxy.list[0] = 'fff'
10、源码学习环境配置
在进行学习前需要搭建学习环境,采用rollup编译环境
npm i rollup rollup-plugin-babel rollup-plugin-serve @babel/core @babel/preset-env -D
rollup打包代码用的; rollup-plugin-babel插件的关联;通过babel/core与babel/preset-env进行代码转义 ; rollup-plugin-serve可以配置一个可执行的端口以实现服务
添加配置文件rollup.config.js
import babel from 'rollup-plugin-babel' import serve from 'rollup-plugin-serve' export default { input: './src/index.js', //项目的入口文件 output: { format: 'umd', //模块化的类型,比如有common.js esModule ... name: 'Vue', //全局变量的名字,即所有的方法都会挂在这个变量下 file: 'dist/umd/vue.js', //表示打包到dist目录下的umd目录下的vue.js里 sourcemap: true //把ES6的语法转成es5的语法 }, plugins: [ //打包的插件 babel({ exclude: 'node_modules/**' //表示编译的时候排除的目录以及文件夹 }), serve({ open: true, //是否默认打开一个服务 port: 3000, //配置端口 contentBase: '', //表示当前打开的目录基准,即以哪个目录为当前目录,空表示以当前目录 openPage: '/index.html'//表示当前打开的页面 }) ] }
添加.babelrc配置文件
{ "presets": [ "@babel/preset-env" ] }
修改package.js文件
"scripts": { "dev": "rollup -c -w" //-c表示使用默认的rollup.config.js配置, -w表示实时监听js文件的变化 },
此时文件目录结构为
vueCode ├── dist │ └── umd │ ├── vue.js │ └── vue.js.map ├── index.html //展示的html模板 ├── package-lock.json ├── package.json ├── rollup.config.js //rollup的配置文件 ├── src //js文件夹 │ └── index.js └── node_modules
2、