even

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

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、

 

posted on 2020-12-29 22:42  even_blogs  阅读(100)  评论(0编辑  收藏  举报