数据劫持(数据代理)、vue中v-model实现的原理

数据劫持

  • 数据劫持,其实就是数据代理。
  • 数据劫持,指的是在访问或者修改对象的某个属性时,通过一段代码拦截这个行为,进行额外的操作或者修改返回结果。
  • 实现方法:
    • defineProperty
    • proxy
    • Object.observe(已废弃,暂不研究)
  • 大厂面试题!!!vue中的v-mode双向绑定是如何实现的?数据劫持

defineProperty实现数据劫持

let obj = {
    myname:"张三"
}
console.log(obj); //张三
let value = obj['myname']; 
let newObj = Object.defineProperty(obj,"myname",{
    configurable:true,  //配置可修改对象
    enumerable:true,  //配置枚举属性 可以循环键
    get(){
    	console.log("触发get");
        return value
    },
    set(newValue){
        console.log("触发get",newValue);
        // 错误的写法!!!
        // obj['myname'] = newValue;
        value = newValue;
    }
})
console.log(obj); //李四
//需配置configurable:true
obj.myname = "李四";
console.log(obj);  //李四
//循环键需配置enumerable:true
for(let key in newObj){
   console.log(key);  //myname
} 
  • defineProperty方法必须遍历对象的每个属性,使用 Object.defineProperty() 多数要配合 Object.keys() 和遍历:
Object.keys(obj).forEach(key => {
  Object.defineProperty(obj, key, {
    // ...
  })
})
  • 不能监听数组的变化
let arr = [1, 2, 3]
let obj = {}

Object.defineProperty(obj, 'arr', {
    get() {
        console.log('触发get',arr)
        return arr
    },
    set(newVal) {
        console.log('触发set', newVal)
        arr = newVal
    }
})
obj.arr.push(4) // 触发get [1,2,3] 
obj.arr = [1, 2, 3, 4] // 触发set [1,2,3,4]

proxy实现数据劫持

  • 在ES6中出现了Proxy。外界对某个对象的访问,都必须经过这层拦截。因此它是针对整个对象,而不是对象的某个属性,所以也就不需要对 keys 进行遍历。这解决了上述 Object.defineProperty() 需要遍历对象的每个属性的问题。
let obj = new Proxy({
    name: "张三",
    age: 20
}, {
    get(target, name) {
        console.log("触发了get",target[name]);
        return target[name];
    },
    set(target, name, value) {
        target[name] = value;
        console.log("触发了set",target[name]);
    }
})
console.log(obj.name);  //触发了get 张三  张三
obj.name = "李四";
obj.age = 23;
console.log(obj);  //触发了set 李四  触发了set 23
  • 支持监听数组
let arr = [1, 2, 3]
let proxy = new Proxy(arr, {
    get(target, key) {
        console.log('get', key)
        return Reflect.get(target, key)
    },
    set(target, key, value) {
        console.log('set', key, value)
        return Reflect.set(target, key, value)
    }
})
proxy.push(4)
  • 支持嵌套
let obj = {
  info: {
    name: 'eason',
    blogs: ['webpack', 'babel', 'cache']
  }
}
let handler = {
  get (target, key, receiver) {
    console.log('get', key)
    // 递归创建并返回
    if (typeof target[key] === 'object' && target[key] !== null) {
      return new Proxy(target[key], handler)
    }
    return Reflect.get(target, key, receiver)
  },
  set (target, key, value, receiver) {
    console.log('set', key, value)
    return Reflect.set(target, key, value, receiver)
  }
}
let proxy = new Proxy(obj, handler)
proxy.info.name = 'Zoe';   // name: 'Zoe',
proxy.info.blogs.push('proxy');// blogs: ['webpack', 'babel', 'cache','proxy']

面试题

  • 什么样的 a 可以满足 (a === 1 && a === 2 && a === 3) === true 呢?
  • 思考:每次访问 a 返回的值都不一样,那么肯定会想到数据劫持。
let current = 0
Object.defineProperty(window, 'a', {
  get () {
    current++
    return current
  }
})
console.log(a === 1 && a === 2 && a === 3)  //true

v-model双绑定实现原理

初次编译---->实例化watcher(Dep.target有值)---->触发get---->收集watcher---->设置数据 set---->触发dep的notify(watcher update方法) ---->逻辑处理返还到编译处理

class Kvue{
    constructor(options){
        this.$options = options;
        this.$data = options.data;
        this.observer(this.$data);
        this.complier();
    }
    // 观察数据  劫持数据
    observer(data){
        Object.keys(data).forEach(key=>{
            let value = data[key];
            let _this = this;
            let dep = new Dep();
            Object.defineProperty(data,key,{
                configurable:true,
                enumerable:true,
                get(){
                    // 收集依赖
                    // Dep.target 是 watch 对象
                    if(Dep.target){
                        dep.addSub(Dep.target);
                    }
                    return value;
                },
                set(newValue){
                    // 发布  触发每一个 对应的watcher的update方法
                    dep.notify(newValue);
                    value = newValue;
                }

            })
        })
    }
    // 编译
    complier(){
        let ele = document.querySelector(this.$options.el);
        // 获取子节点
       this.complierChildnodes(ele);
    }
    complierChildnodes(ele){
        let childNodes = ele.childNodes;
        childNodes.forEach(node=>{
            if(node.nodeType === 3){
                let textContent = node.textContent;
                // 匹配大胡子语法
                let reg = /\{\{\s*([^\{\}\s]+)\s*\}\}/g;
                if(reg.test(textContent)){
                   let $1 = RegExp.$1;
                //    文本节点替换data里的为数据
                    // node.textContent = node.textContent.replace(reg,this.$data[$1]);
                    // 实例化Watcher  及   触发 收集依赖
                    new Watcher(this.$data,$1,(newValue)=>{
                        let oldValue = this.$data[$1];
                        let reg = new RegExp(oldValue);
                        node.textContent = node.textContent.replace(reg,newValue);
                    });
                    node.textContent = node.textContent.replace(reg,this.$data[$1]);
                }      
            }else if(node.nodeType === 1){
                // 标签元素
                // 获取属性
                let attrs = node.attributes;
                [...attrs].forEach(attr=>{
                    let attrName = attr.name;
                    let attrValue = attr.value;
                    if(attrName === "v-model"){
                        // 双绑定指令
                        node.value = this.$data[attrValue];
                        addEventListener("input",e=>{
                            // 已经被观察了
                            this.$data[attrValue] = e.target.value;
                        })
                    }else if(attrName === "v-html"){
                        // 初次编译
                        node.innerHTML = this.$data[attrValue];
                        //再次渲染
                        new Watcher(this.$data,attrValue,newValue=>{
                            node.innerHTML = newValue;
                        })
                    }else if(attrName === "v-text"){
                        node.textContent = this.$data[attrValue];
                        new Watcher(this.$data,attrValue,newValue=>{
                            node.innerHTML = newValue;
                        })
                    }
                })
                if(node.childNodes.length>0){
                    this.complierChildnodes(node);
                }
            }
        })
    }
}

// 依赖收集器
class Dep{
    constructor(){
        this.subs = [];
    }
    // 订阅
    addSub(sub){
        this.subs.push(sub);
    }
    // 发布
    notify(newValue){
        this.subs.forEach(sub=>{
            sub.update(newValue);
        })
    }
}
// dep] --->[wather1,watcher2]-->notify-->(wather1,wather1):update方法
//订阅者
class Watcher{
    constructor(data,key,cb){
        Dep.target = this;  //this 指向 new watch
        data[key];
        this.cb = cb;
        Dep.target = null;
    }
    update(newValue){
            this.cb(newValue);
    }
}
  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值