【校招VIP】使用Object.defineProperty进行数据劫持,实现响应式原理

08月24日 收藏 0 评论 0 前端开发

【校招VIP】使用Object.defineProperty进行数据劫持,实现响应式原理

转载声明:原文链接:https://blog.csdn.net/weixin_45792953/article/details/112472338

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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实现一个响应式计算器,
核心部分在:数据被修改(set)时重新计算结果
html部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<!DOCTYPE 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">0</div>
        <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>
    <!-- 引入js文件 -->
    <script src="./js/index.js"></script>
</body>
</html>

js部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
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();



C 0条回复 评论

帖子还没人回复快来抢沙发