前言

Vue2.0响应式核心是Object.defineProperty,但是它有局限,不能检测到对象属性的新增和删除,也不能检测到对象中属性值为对象的属性的修改,而对于数组类型不能检测到通过push、pop、shift、unshift等会修改数组本身的方法对数组做的更改;而Proxy作为操作对象的代理对象可以弥补Object.defineProperty的缺陷,那么Object.defineProperty和Proxy分别都是如何来创建响应式的呢?

Object.defineProperty实现响应式

第一篇解析:Object.defineProperty实现响应式

<body>
    <input id="input">
    <div>
        2s后自动更新值:<span id="text"></span>
    </div>
</body>
<script>
    function changeValue(e) {
        data.inputValue = e.target.value
    }

    function defineReactiveValue(obj, key, value) {
        Object.defineProperty(obj, key, {
            get: () => {
                return value
            },
            set: (val) => {
                console.log('响应类型:', obj)
                document.querySelector('#input').value = val
                document.querySelector('#text').innerHTML = val
            }
        })
    }

    let data = {
        inputValue: '',
        arr: [{
            name: 'bb'
        }],
        obj: {
            name: 'yy'
        }
    };

    Object.keys(data).forEach((key, index) => {
        defineReactiveValue(data, key, data[key])
    })

    setTimeout(() => {
        console.log('更新基本属性值...')
        data.inputValue = '我是被自动更新的值~'
        console.log('push更新数组属性值...')
        data.arr.push({
            name: 'haha'
        })
        console.log('下标更新数组属性值...')
        data.arr[0] = {
            name: 'haha'
        }
        console.log('更新对象属性中的属性值...')
        data.obj.name = 'changeObjName'
    }, 2000);

    //数组类型
    let dataArr = [{
        name: 'dataArr_bb'
    }]

    dataArr.forEach((item, index) => {
        defineReactiveValue(dataArr, index, item)
    })
    setTimeout(() => {
        console.log('更新数组类型的属性值...')

        console.log('更新数组push方法更新...')
        dataArr.push({
            name: 'haha'
        })

        console.log('更新数组下标法更新...')
        dataArr[0] = {
            name: 'haha'
        }

    }, 4000);

    document.querySelector('#input').addEventListener('keyup', changeValue)
</script>

结果:
image.png

Proxy实现响应式

第一篇Proxy的解析: Proxy

<body>
    <input id="input">
    <div>
        2s后自动更新值:<span id="text"></span>
    </div>

    <ul id="list"></ul>
    <button id="btn">add</button>
    <button id="modify">modify</button>

</body>
<script>
    const list = document.getElementById('list');
    const btn = document.getElementById('btn');
    const modify = document.getElementById('modify');

    // 渲染列表
    const Render = {
        // 初始化
        init: function (arr) {
            list.innerHTML = '';
            const fragment = document.createDocumentFragment();
            for (let i = 0; i < arr.length; i++) {
                const li = document.createElement('li');
                li.textContent = arr[i];
                fragment.appendChild(li);
            }
            list.appendChild(fragment);
        },

    };

    function changeValue(e) {
        data.inputValue = e.target.value
    }

    function defineReactiveValue(target) {
        return new Proxy(target, {
            get: function (target, propKey, receiver) {
                console.log('触发get:', target[propKey])
                return Reflect.get(target, propKey, receiver);
            },
            set: function (target, propKey, value, receiver) {
                if (propKey === 'length') {
                    Render.init(target)
                }
                console.log('触发set,响应类型:', target)
                document.querySelector('#input').value = value
                document.querySelector('#text').innerHTML = value
                return Reflect.set(target, propKey, value, receiver);
            }
        })
    }

    let _data = {
        inputValue: '',
        arr: [{
            name: 'bb'
        }],
        obj: {
            name: 'yy'
        }
    };
    let data = defineReactiveValue(_data);

    setTimeout(() => {
        console.log('更新基本属性值...')
        data.inputValue = '我是被自动更新的值~'
        console.log('push更新数组属性值...')
        data.arr.push({
            name: 'haha'
        })
        console.log('下标更新数组属性值...')
        data.arr[0] = {
            name: 'ttttt'
        }
        console.log('更新对象属性中的属性值...')
        data.obj.name = 'changeObjName'

        console.log('读取值:', data.obj.name)
    }, 2000);

    // 初始数组
    const arr = [1, 2, 3, 4];
    // 监听数组
    const newArr = new Proxy(arr, {
        get: function (target, key, receiver) {
            console.log('get:', key);
            return Reflect.get(target, key, receiver);
        },
        set: function (target, key, value, receiver) {
            console.log('set:', target, key, value, receiver);
            if (key === 'length') {
                Render.init(target);
            }
            if (typeof + key === 'number') {
                target[key] = value;
                Render.init(target);
            }
            return Reflect.set(target, key, value, receiver);
        },
    });

    window.onload = function () {
        Render.init(newArr);
    }

    // push数字
    btn.addEventListener('click', function () {
        newArr.push(6);
    });

    modify.addEventListener('click', function () {
        newArr[0] = 8888

    });

    document.querySelector('#input').addEventListener('keyup', changeValue)
</script>

结果:
image.png

Proxy实现响应式的优势

Proxy可以监听数组通过push、pop、shift、unshift等方法做的修改以及下标修改数组的方式修改的值变化。

Proxy不仅仅只能拦截到对象的读取和赋值,还支持对更多对象的操作的拦截, 例如apply、constructor、delete、defineProperty等等。

Proxy作为新标准将受到浏览器厂商重点持续的性能优化。


贝er
58 声望6 粉丝

不仅仅是程序员