一句话解释双向数据绑定,让面试不尴尬。什么是 object.defineProperty() 。手写v-model。硬核

1、vue.js 则是采用数据劫持结合发布者-订阅者模式的方式,通过`Object.defineProperty()`来劫持各个属性的`setter`,`getter`,在数据变动时发布消息给订阅者,触发相应的监听回调

2、当 data 有变化的时候,通过ES5 中的 Object.defineProperty() 方法中的 set 方法劫持属性值的设置操作和 get 方法劫持属性值的获取操作,实现数据变化视图 view 跟着变化;

而视图 view 变化数据跟着变化则是通过底层的 input 事件来进行 data 的响应更改

3、实现步骤:

1.实现一个监听者Oberver来劫持并监听所有的属性,一旦有属性发生变化就通知订阅者

2.实现一个订阅者watcher来接受属性变化的通知并执行相应的方法,从而更新视图

3.实现一个解析器compile,可以扫描和解析每个节点的相关指令,并根据初始化模板数据以及初始化相对应的订阅者

4、什么是 Object.defineProperty() 看一下代码

let obj = { name: '阿豪', value: '真帅' }
    Object.defineProperty(obj, 'sss', {
        value: "18",
        enumerable: true, //这一句控制属性可以枚举 enumerable 改为true 就可以参与遍历了   默认值false
        writable: true,// 控制属性可以被修改 加上这一句话就可以个修改了  默认值false
        configurable: true, // 控制属性可以被删除   默认值false
    })
    console.log(obj); // 这样可以添加一个  sss 是比较浅色的 不参与遍历
    obj.sss = '100'   // 现在修改 sss 为 100  Object.defineProperty 来修改修改不了 sss
    console.log(obj);
    delete (obj.sss)  // 删除 sss
    console.log(obj);
var num = 222
    let obj = { name: '阿豪', value: '真帅' }
    Object.defineProperty(obj, 'sss', {
        get() { // 当有人读取obj.sss 的时候  get函数就会调用,并且返回就是 sss 的值
            console.log('有人读取sss 属性了');
            return num
        },
        set(value) {// 当有人修改obj.sss 的时候  get函数就会调用, 且会收到具体的值
            console.log('有人修改了sss 值 值是' + value);
            num = value  // 做个牵引 修改成功
        }
    })
    console.log(obj.sss); // 有人读取sss 属性了    // hello
    obj.sss = 9999
    console.log(obj); // 有人修改了sss 值 值是9999

5、硬核手写v-model

<!DOCTYPE html>
<head>
    <meta charset="UTF-8">
    <title>双向绑定</title>
</head>
<body>
    <!-- 实现vue -->
    <div id="app">
        <input type="text" v-model="text">
        {{ text }}
    </div>
 
    <script type="text/javascript">
 
        function defineReactive(obj, key, val) {
            var dep = new Dep();
            Object.defineProperty(obj, key, {
                get: function () {
                    if (Dep.target) {
                        dep.addSub(Dep.target);
                    }
                    return val
                },
                set: function (newVal) {
                    if (newVal === val) {
                        return
                    }
                    val = newVal;
                    console.log('新值:' + val);
 
                    // 一旦更新立马通知
                    dep.notify();
                }
            })
        }
 
        /*观察者函数*/
        function observe(obj, vm) {
            for (let key of Object.keys(obj)) {
                defineReactive(vm, key, obj[key]);
            }
        }
 
 
        function nodeToFragment(node, vm) {
            var fragment = document.createDocumentFragment();
            var child;
            while (child = node.firstChild) {
                compile(child, vm);
                fragment.appendChild(child);
            }
            return fragment
        }
 
        /*编译函数*/
        function compile(node, vm) {
            var reg = /\{\{(.*)\}\}/; // 来匹配 {{ xxx }} 中的xxx
            // 如果是元素节点
            if (node.nodeType === 1) {
                var attr = node.attributes;
                // 解析元素节点的所有属性
                for (let i = 0; i < attr.length; i++) {
                    if (attr[i].nodeName == 'v-model') {
                        var name = attr[i].nodeValue; // 看看是与哪一个数据相关
                        node.addEventListener('input', function (e) {
                            vm[name] = e.target.value; // 将实例的text 修改为最新值
                        });
                        node.value = vm[name]; // 将data的值赋给该node
                        node.removeAttribute('v-model');
                    }
                };
            }
            // 如果是文本节点
            if (node.nodeType === 3) {
                if (reg.test(node.nodeValue)) {
                    var name = RegExp.$1; // 获取到匹配的字符串
                    name = name.trim();
                    // node.nodeValue = vm[name];  // 将data的值赋给该node
 
                    new Watcher(vm, node, name); // 不直接通过赋值的操作,而是通过绑定一个订阅者
                }
            }
        }
 
        /*Watcher构造函数*/
        function Watcher(vm, node, name) {
            Dep.target = this; // Dep.target 是一个全局变量
            this.vm = vm;
            this.node = node;
            this.name = name;
            this.update();
            Dep.target = null;
        }
        Watcher.prototype = {
            update() {
                this.get();
                this.node.nodeValue = this.value; // 注意,这是更改节点内容的关键
            },
            get() {
                this.value = this.vm[this.name]; // 触发相应的get
            }
        }
 
        /*dep构造函数*/
        function Dep() {
            this.subs = [];
        }
        Dep.prototype = {
            addSub(sub) {
                this.subs.push(sub);
            },
            notify() {
                this.subs.forEach(function (sub) {
                    sub.update();
                })
            }
        }
 
        /*Vue构造函数*/
        function Vue(options) {
            this.data = options.data;
            var data = this.data;
 
            observe(data, this);
 
            var id = options.el;
            var dom = nodeToFragment(document.getElementById(id), this);
            // 处理完所有dom节点后,重新将内容添加回去
            document.getElementById(id).appendChild(dom);
        }
 
        var vm = new Vue({
            el: 'app',
            data: {
                text: 'hello world'
            }
        });
 
    </script>
</body>
 
</html>

上一篇:Java之StringOther


下一篇:Java类中返回引用对象之后导致类中数据被修改的情况