使用Object.defineProperty进行数据劫持,实现响应式原理-剖析vue2.0

数据响应式是vue的特性之一,在面试过程中也会常常被问起响应式原理,现在就让我们深入了解一下vue2.0中如何实现响应式,

下图是Vue2.0中对响应式原理的描述,其核心就是使用Object.defineProperty中的get/set进行数据劫持,
虽然Vue3.0中使用Proxy(代理)去实现响应式,其实原理都差不多,在3.0中主要是使用Proxy的get和set实现响应式,如果理解defineProperty,Proxy也会很快理解的

使用Object.defineProperty进行数据劫持,实现响应式原理-剖析vue2.0_第1张图片

Object.defineProperty是什么?

define是定义的意思 Property是属性/描述的意思
其实defineProperty就是定义属性
主要有value、writable、configurable、enumerable、get、set这几个配置项
Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象

let object1={
     
	property1:0
}
//例如
Object.defineProperty(object1, 'property1', {
     
    value: 42,
    writable: false, //是否可写
    configurable: false, //是否可删除
    enumerable: false, //是否可枚举(遍历)
});
//get set配置项
Object.defineProperty(object1, 'property1', {
     
    get() {
     //get方法在进行获取的时候触发
        return property1
    },
    set(newVal) {
     //get方法在进行设置的时候触发,主要在此实现数据劫持
        property1 = newVal
    }
});

//我这边常用一种复数形式Object.defineProperties
Object.defineProperties(object1, {
     
    property1: {
     
        get() {
      //get方法在进行获取的时候触发
            return property1
        },
        set(newVal) {
      //get方法在进行设置的时候触发,主要在此实现数据劫持
            property1 = newVal
        }
    }
})

使用上述的内容实现Vue2.0中的双向绑定

实现出来的效果

使用Object.defineProperty进行数据劫持,实现响应式原理-剖析vue2.0_第2张图片

下面使用Object.defineProperty实现一个响应式计算器,
核心部分在:数据被修改(set)时重新计算结果

html部分


<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>响应式计算器title>
    <style>
        .btnGroup > button.active{
      
            background-color: orange;
            color: #fff;
        }
    style>
head>
<body>
    <div id="computed">
        <div class="result">0div>
        <div class="inputGroup">
            <input type="number" class="ipt1" value="0" />
            <br/>
            <input type="number" class="ipt2" value="0" />
        div>
        <div class="btnGroup">
            <button data-field="add" class="active">+button>
            <button data-field="sub">-button>
            <button data-field="mul">*button>
            <button data-field="div">/button>
        div>
    div>
    
    <script src="./js/index.js">script>
body>
html>

js部分

class Arithmetic {
      //定义算法类
    add(a, b) {
     
        return a + b
    }
    sub(a, b) {
     
        return a - b
    }
    mul(a, b) {
     
        return a * b
    }
    div(a, b) {
     
        return a / b
    }
}

class Computed extends Arithmetic {
     
    constructor() {
     
        super();
        this.result = document.getElementsByClassName('result')[0]; //获取结果dom
        this.ipt1 = document.getElementsByClassName('ipt1')[0]; //获取输入框
        this.ipt2 = document.getElementsByClassName('ipt2')[0];
        this.btnGroup = document.getElementsByClassName('btnGroup')[0];
        this.btnItems = this.btnGroup.getElementsByTagName('button');
        this.data = this.defineData();
        this.btnIndex = 0; //记录选中btn索引
    }
    // 1.进行初始化
    init() {
     
        this.bindEvent();
    }

    // 2.绑定事件
    bindEvent() {
     
        this.btnGroup.addEventListener('click', this.onFieldBtnClick.bind(this), false)
        this.ipt1.addEventListener('input', this.onNumberIpt.bind(this), false)
        this.ipt2.addEventListener('input', this.onNumberIpt.bind(this), false)
    }

    // 6.使用Object.defineProperty进行数据劫持
    defineData() {
     
        let _obj = {
     },
            field = 'add', //保存计算方法字段
            fNumber = 0, //inp1的value
            sNumber = 0; //inp2的value
        let that = this;
        // 进行数据劫持
        Object.defineProperties(_obj, {
     
            fNumber: {
     
                get() {
     
                    return fNumber
                },
                set(newVal) {
     
                    fNumber = newVal
                    // 重点:在fNumber改变之后计算结果,此处vue使用的是Dom diff算法
                    that.computedResult(fNumber, sNumber, field);
                }
            },
            sNumber: {
     
                get() {
     
                    return sNumber
                },
                set(newVal) {
     
                    sNumber = newVal
                    that.computedResult(fNumber, sNumber, field);
                }
            },
            field: {
     
                get() {
     
                    return field
                },
                set(newVal) {
     
                    field = newVal
                    that.computedResult(fNumber, sNumber, field);
                }
            }
        })
        return _obj
    }

    // 3.btn点击事件
    onFieldBtnClick(ev) {
     
        let e = ev || window.event,
            tar = e.target || e.srcElement,
            tarName = tar.tagName.toLowerCase();
        tarName === 'button' && this.update(tar);
    }

    // 4.btn进行选项卡切换并更改field
    update(target) {
     
        this.btnItems[this.btnIndex].className = '';
        this.btnIndex = [].indexOf.call(this.btnItems, target);
        target.className += ' active';
        this.data.field = target.getAttribute('data-field');
    }

    // 5.ipt改变(input)事件
    onNumberIpt(ev) {
     
        let e = ev || window.event,
            tar = e.target || e.srcElement,
            className = tar.className,
            val = Number(tar.value.replace(/\s+/g, '')) || 0; //去除input的空格
        switch (className) {
     
            case 'ipt1':
                // 改变fNumber触发set()将重新计算结果
                this.data.fNumber = val;
                break;
            case 'ipt2':
                // 改变sNumber触发set()将重新计算结果
                this.data.sNumber = val;
                break;
            default:
                break;
        }
    }

    // 7.使用继承算法类的方法进行结果计算
    computedResult(fNumber, sNumber, field) {
     
        this.result.innerHTML = this[field](fNumber, sNumber)
    }
}

new Computed().init();

你可能感兴趣的:(笔记,随手笔记,总结一些相关面试题,前端,vue.js,vue,vue-cli3,javascript)