手写一个简易的MVVM

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div id="app">
        <input type="text" v-model="message.name">
        {{message.name}} {{message.oinfo}}
        <div>
            <input type="password" v-model="message.pwd">
            {{message.pwd}}
        </div>
        {{message.obj.name}}
    </div>
    <script src="./Dep.js"></script>
    <script src="./Watcher.js"></script>
    <script src="./Compile.js"></script>
    <script src="./Observer.js"></script>
    <script src="./MVVM.js"></script>
    <script>
        var vm=new MVVM({
            el:"#app",
            data:{
                message:{
                   name:'小明',
                   pwd:'12345',
                   oinfo:'其它信息',
                   obj:{
                       name:'小红'
                   }
                }
            }
        })
    </script>
</body>
</html>

MVVM.js

class MVVM{
    constructor(options){
        this.$el=options.el;
        this.$data=options.data;
        new Observer(this.$data)
        this.proxyData(this,this.$data)
        new Compile(this,this.$el)
    }
    //数据代理,可以直接从vm上获取vm.$data的属性
    proxyData(obj,data){
        Object.keys(data).forEach(key=>{
            Object.defineProperty(obj,key,{
                get(){
                    return data[key]
                },
                set(newValue){
                    data[key]=newValue
                }
            })
        })
    }
}

Observer.js

class Observer{
    constructor(data){
        this.Observe(data)
    }
    Observe(data){
        if(!data || typeof data !=='object'){
            return 
        }
        Object.keys(data).forEach(key=>{
            this.DefinedReactive(data,key,data[key])
            //属性值可能是对象,继续递归处理
            this.Observe(data[key]);
        })
    }
    DefinedReactive(data,key,value){
        let that=this;
        let dep=new Dep()
        //这里的value是之前的值
        Object.defineProperty(data,key,{
            get(){
                // console.log(`获取了${data}[${key}]的值`);
                //假设修改了message.name,那么就会更新界面中用到了mesage.name的地方
                //假设修改了vm.$data.message,那么就会更新界面中用到了vm.$data.message的地方,包含message.name
                //vm.$data.message中的get和set中的dep存在了可以更新用到vm.$data.message的地方的节点,比如message.name
                Dep.target&&dep.addSub(Dep.target)
                return value
            },
            set(newValue){
                if(value!==newValue){
                    console.log(value,newValue)
                    //这样赋值会导致无限递归
                    // data[key]=newValue;
                    //这里很巧妙,使用了闭包,get和set都是用的闭包中的value。
                    //也就是说对象中的value没有被修改,只是用的数据劫持,然后用的是闭包中的value!
                    //这样做,可以避免无限递归的问题了
                    value=newValue
                    //如果赋值的是对象,继续劫持
                    that.Observe(newValue)
                    dep.notify()
                }
            }
        })

    }
}

Compile.js

class Compile{
    constructor(vm,el){
        this.el=this.isElementNode(el)?el:document.querySelector(el);
        this.vm=vm;
        if(this.el){
            let fragment=this.NodeToFragement(this.el);
            this.Compile(fragment)
            //将fragment放到#app中
            this.el.appendChild(fragment)
        }
    }
    isElementNode(node){
        return node.nodeType===1
    }
    NodeToFragement(node){
        //将页面中的节点放到fragment中,方便以后取出
        let fragment=document.createDocumentFragment()
        Array.from(node.childNodes).forEach(child=>{
            //注意:appendChild具有移动的效果,也就是说页面中的节点是真的被移走了。。。
            fragment.appendChild(child)
        })
        return fragment
        
    }
    CompileElement(node){
      let attrs=node.attributes;
      Array.from(attrs).forEach(attr=>{
          if(attr.name.includes('v-')){
              let [,type]=attr.name.split('-');
              let expr=attr.value;
              compileUtil[type](this.vm,expr,node)
          }
      })
    }
    CompileText(node){
        let expr=node.textContent;
        let reg=/\{\{([^}]+)\}\}/g
        //判断是否含有{{}}
        if(reg.test(expr)){
            compileUtil['text'](this.vm,expr,node)
        }
    }
    Compile(node){
        Array.from(node.childNodes).forEach(childNode=>{
            //是元素节点
            if(this.isElementNode(childNode)){
                this.CompileElement(childNode)
                this.Compile(childNode)   
            //是文本节点    
            }else{
                // console.log('是文本节点');
                this.CompileText(childNode)
            }
        })
    }
 
}
    compileUtil={
        getVal(vm,expr){
 			//将类似message.name的表达式,用split分隔为数组       
            let names=expr.split('.')
            return names.reduce((prev,next)=>{
                return prev[next]
            },vm.$data)
        },
        getTextVal(vm,expr){
            let reg=/\{\{([^}]+)\}\}/g
            //可能含有多个表达式,{{message.name}} - {{message.pwd}}
            return expr.replace(reg,(...args)=>{
                return this.getVal(vm,args[1])
            })
        },
        //设置值
        setVal(vm,expr,value){
            let names=expr.split('.')
            names.reduce((prev,next,index)=>{
                if(index===(names.length-1)){
                    prev[next]=value;
                }
                return prev[next]
            },vm.$data)
        },
        //处理非指令
        text(vm,expr,node){
            let reg=/\{\{([^}]+)\}\}/g
            expr.replace(reg,(...args)=>{
                new Watcher(vm,args[1],()=>{
                    this.updateText(node,this.getTextVal(vm,expr));
                })
            })
            this.updateText(node,this.getTextVal(vm,expr))
        },
        //处理v-model指令
        model(vm,expr,node){
            new Watcher(vm,expr,()=>{
                this.updateModel(node,this.getVal(vm,expr))
            })
            //实现双向绑定!
            node.addEventListener('input',(ev)=>{
                this.setVal(vm,expr,ev.target.value)
            })
            this.updateModel(node,this.getVal(vm,expr))
        },
        //更新input中的value
        //根据v-model指令对应的数据,更新node中的value
        updateModel(node,value){
            node.value=value
        },
        //更新文本节点中的textContent
        //根据node中的{{}}中的数据,更新node中的textContent
        updateText(node,value){
            node.textContent=value
        }
    }

Watcher.js

class Watcher{
    constructor(vm,expr,cb){
        this.vm=vm;
        this.expr=expr;
        //cb是回调函数
        this.cb=cb;
        //设置这个以后,执行到下面取值的操作时,触发set和get后
        //在get中可以把当前的watcher放到对应get中的dep中了
        //为什么我要说对应的get中的dep呢
        //因为在栈中有不同的get,get(闭包方法)保存了对dep的引用
        Dep.target=this;
        //保存旧的值
        this.value=this.getVal()
        //防止get中的dep重复添加相同的watcher
        //也防止了其它的get中的dep也添加了这里的watcher
        Dep.target=null;
    }
    getVal(){
        let names=this.expr.split('.')
        //假设读取的是message.name
        //那么在下面的代码中会读取到vm.$data.message和vm.$data.message.name
        //这样会触发两个get,在这两个get中的dep会把当前的watcher添加进去的
        //所以以后你修改vm.$data.message和vm.$data.message.name触发对应set方法后,里面的dep会调用notify让这里的watcher调用update方法更新页面的
        return names.reduce((prev,next)=>{
            return prev[next]
        },this.vm.$data)
    }
    update(){
        let newValue=this.getVal()
        if(this.value!==newValue){
            this.cb()
        }
    }
}

Dep.js

class Dep{
    constructor(){
        this.subs=[]
    }
    addSub(watcher){
        this.subs.push(watcher)
    }
    notify(){
        this.subs.forEach(watcher=>{
            watcher.update()
        })
    }
}

问题

在这里插入图片描述

这个简易的MVVM,如果给vm中的message直接赋值一个对象的话,界面是可以更新的,但是以后修改message中属性时,界面是无法更新的。之所以产生这个问题,是因为修改vm中message属性(这里用了数据代理,实则访问的是vm. d a t a . m e s s a g e ) 会 触 发 v m . data.message)会触发vm. data.message)vm.data中message属性的get和set,而此时的dep中的subs存放了可以更新界面中使用了带message的表达式的节点的watcher。但是给message重新赋值一个对象后,修改message中的属性时,虽可以触发其set和get,但是其中的dep中的subs数组没有watcher。

上面这个问题在vue中是没有的,希望大家可以帮我解决这个问题。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
在信号处理领域,DOA(Direction of Arrival)估计是一项关键技术,主要用于确定多个信号源到达接收阵列的方向。本文将详细探讨三种ESPRIT(Estimation of Signal Parameters via Rotational Invariance Techniques)算法在DOA估计中的实现,以及它们在MATLAB环境中的具体应用。 ESPRIT算法是由Paul Kailath等人于1986年提出的,其核心思想是利用阵列数据的旋转不变性来估计信号源的角度。这种算法相比传统的 MUSIC(Multiple Signal Classification)算法具有较低的计算复杂度,且无需进行特征值分解,因此在实际应用中颇具优势。 1. 普通ESPRIT算法 普通ESPRIT算法分为两个主要步骤:构造等效旋转不变系统和估计角度。通过空间平移(如延时)构建两个子阵列,使得它们之间的关系具有旋转不变性。然后,通过对子阵列数据进行最小二乘拟合,可以得到信号源的角频率估计,进一步转换为DOA估计。 2. 常规ESPRIT算法实现 在描述中提到的`common_esprit_method1.m`和`common_esprit_method2.m`是两种不同的普通ESPRIT算法实现。它们可能在实现细节上略有差异,比如选择子阵列的方式、参数估计的策略等。MATLAB代码通常会包含预处理步骤(如数据归一化)、子阵列构造、旋转不变性矩阵的建立、最小二乘估计等部分。通过运行这两个文件,可以比较它们在估计精度和计算效率上的异同。 3. TLS_ESPRIT算法 TLS(Total Least Squares)ESPRIT是对普通ESPRIT的优化,它考虑了数据噪声的影响,提高了估计的稳健性。在TLS_ESPRIT算法中,不假设数据噪声是高斯白噪声,而是采用总最小二乘准则来拟合数据。这使得算法在噪声环境下表现更优。`TLS_esprit.m`文件应该包含了TLS_ESPRIT算法的完整实现,包括TLS估计的步骤和旋转不变性矩阵的改进处理。 在实际应用中,选择合适的ESPRIT变体取决于系统条件,例如噪声水平、信号质量以及计算资源。通过MATLAB实现,研究者和工程师可以方便地比较不同算法的效果,并根据需要进行调整和优化。同时,这些代码也为教学和学习DOA估计提供了一个直观的平台,有助于深入理解ESPRIT算法的工作原理。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值