vue实现原理笔记

首先是对data里面的数据进行代理的理解

 <script type="text/javascript">
    class Vue{
      constructor(options){
        this.$el = document.querySelector(options.el)
        this.$options = options
        // 代理options的data数据
        this.getprory()
      }
      getprory(){
        for (const key in this.$options.data) {
        // 这里理解,this就是new出来的app
        // 下面意思就是,给这个this添加key这个属性,后面就是配置
        // Object.defineProperty方法就是访问this里面的key或者添加key这个属性
          Object.defineProperty(this,key,{
            // 可以不可以被重复设计
            configurable:false,
            // 可以不可以修改定义的值
            enumerable:true,
            // 在使用this[key]的时候,就会返回this.$options.data[key]
            get(){
              return this.$options.data[key]
            },
            // 在使用this[key] = '' 设置值的时候,就会执行下面的办法,就会把val的值,赋值给对应的data里面的key的属性
            set(val){
              this.$options.data[key] = val
            }
          })
        }
      }
    }
  </script>
  <script>
    let options = {
      el:'#app',
      data:{
        msg:'我是大傻逼',
        username:'大傻逼'
      },
      methods: {
        change(){
          this.msg = 'aa'
        }
      },
    }
    let app = new Vue(options)
// 上面代码就是实现如:app.msg , 就能拿到msg的值,只要是使用代理的方式 
// 主要实现再这里
// 在使用this[key]的时候,就会返回this.$options.data[key]
//            get(){
//              return this.$options.data[key]
//            },

数据劫持

指的是在访问或者修改对象的某个属性时,通过一段代码拦截这个行为,进行额外的操作或者修改返回结果。比较典型的是 Object.defineProperty() 和 ES2015 中新增的 Proxy 对象。
以下代码主要说明,在数据进行更新的时候,如何使得绑定数据的地方也进行更新(也算订阅者发布模式)

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <div id="app">
    <input type="text" v-model="msg" name="" id="" value="" />
    <h1>{{msg}}</h1>
    <h1 v-html="msg"></h1>
    <button @click="change()">修改</button>
  </div>
  <script type="text/javascript">
    class Vue{
      constructor(options){
        this.$el = document.querySelector(options.el)
        this.$options = options
        this.$watchEvent = {}
        this.getprory()
        this.observe()
        this.compile()
      }
      getprory(){
        for (const key in this.$options.data) {
          let that = this
          Object.defineProperty(this,key,{
            // 可以不可以被重复设计
            configurable:false,
            // 可以不可以修改定义的值
            enumerable:true,
            get(){
              return this.$options.data[key]
            },
            set(val){
              this.$options.data[key] = val
            }

          })
        }
      }
      // 劫持事件 第一步
      observe(){
        for (const key in this.$options.data) {
          let that = this
          let value = this.$options.data[key]
          // 在访问this.$options.data里面的key属性的时候,就会来到数据劫持
          // 然后对数据进行处理然后再返回
          Object.defineProperty(this.$options.data,key,{
            // 可以不可以被重复设计
            configurable:false,
            // 可以不可以修改定义的值
            enumerable:true,
            // 直接返回
            get(){
              return value
            },
            // 对数据进行处理再返回
            set(val){
              value = val
              // 再进行数据处理的时候,执行方法,来使得绑定这个属性的地方也进行更新
              if (that.$watchEvent[key]) {
                // 通过key把这个属性绑定到 that.$watchEvent里面
                that.$watchEvent[key].forEach((item,index)=>{
                  // 这里的这个item,就是在that.$watchEvent里面key绑定的值,其实就是一个watch(类,在第三步)
                  // 执行watch里面的方法来更新其他使用key属性的值
                 item.update()
                })
                
              }

            }

          })
        }
      }
      // 劫持事件第三步
      // 第三步就是为了把key和watch绑定在一起
      compile(){
        // 使用一个[ ] 括号,可以打印出这个this.$el的属性
        // console.log([this.$el]);
        // 一开始把div#app 的所有节点循环出来
        this.$el.childNodes.forEach((node,index)=>{
          // 拿到元素节点
          if (node.nodeType == 1) {
            // 拿到绑定msg(也就是key的值)
            if (node.hasAttribute('v-html')) {
              // 获取的这个属性的值 ,也就是msg(key)
              let vmkey = node.getAttribute('v-html').trim()
              // 然后赋值给他本来的节点(这里是为了让数据展示出来)
              node.innerHTML = this[vmkey]

              // new出一个绑定key的watch
              let watcher = new Watch(this,vmkey,node,'innerHTML')
              if (this.$watchEvent[vmkey]) {
                this.$watchEvent[vmkey].push(watcher)
              }else{
                this.$watchEvent[vmkey]=[]
                this.$watchEvent[vmkey].push(watcher)
              }
            }
          }
          // 文本节点
          if (node.nodeType == 3) {
            
          }
        })
      }
    }
    // 劫持事件 第二步
    // 为了拿到dom里面的节点
    class Watch{
      constructor(vm,key,node,attr,nType){
        // 比如上面最外面的div,并且是vue对象
        this.vm = vm                
        // 这个就是绑定的data里面的属性,以作为key值放到 that.$watchEvent   如msg
        this.key =key
        // div里面的节点      // 如h1
        this.node = node                                         
        // 绑定上面key属性的标签属性属性  如v-html
        this.attr = attr 
      }
      update(){
        console.log('进来'+this.vm);
        
        // 当数据改变的时候去进行更新有这个key属性的地方
        this.node[this.attr] = this.vm[this.key]
        // 如 h1.innnerTTML = vue.msg
      }
    }
  </script>
  <script>
    let options = {
      el:'#app',
      data:{
        msg:'我是大傻逼',
        username:'大傻逼'
      },
      methods: {
        change(){
          this.msg = 'aa'
        }
      },
    }
    let app = new Vue(options)
  </script>
</body>
</html>

数据劫持实现的v-model

  // 劫持事件第三步
      // 第三步就是为了把key和watch绑定在一起
      compile(cNode){
        // 使用一个[ ] 括号,可以打印出这个this.$el的属性
        // console.log([this.$el]);
        // 一开始把div#app 的所有节点循环出来
        cNode.childNodes.forEach((node,index)=>{
          // 拿到元素节点
          if (node.nodeType == 1) {
            // 拿到绑定msg(也就是key的值)
            if (node.hasAttribute('v-html')) {
              // 获取的这个属性的值 ,也就是msg(key)
              let vmkey = node.getAttribute('v-html').trim()
              // 然后赋值给他本来的节点(这里是为了让数据展示出来)
              node.innerHTML = this[vmkey]

              // new出一个绑定key的watch
              let watcher = new Watch(this,vmkey,node,'innerHTML')
              if (this.$watchEvent[vmkey]) {
                this.$watchEvent[vmkey].push(watcher)
              }else{
                this.$watchEvent[vmkey]=[]
                this.$watchEvent[vmkey].push(watcher)
              }
              // 删除原来绑定的{{}}语句
              node.removeAttribute('v-html')

            }
             
            // 这里就是实现v-model
            // 下面是对于v-model的数据绑定
            // 先判断有没有v-model属性
            if (node.hasAttribute('v-model')) {;
                // 获取绑定的值(msg)
                let vmkey = node.getAttribute('v-model').trim()
                
                // 看有没有这个msg
                if (this.hasOwnProperty(vmkey)) {
                  node.value = this[vmkey]
                  // 然后把他放进watch事件对象中,绑定起来
                  let watcher = new Watch(this,vmkey,node,'value')
                  if (this.$watchEvent[vmkey]) {
                    this.$watchEvent[vmkey].push(watcher)
                  }else{
                    this.$watchEvent[vmkey]=[]
                    this.$watchEvent[vmkey].push(watcher)
                  }
                }
                // 绑定输入框input输入事件
                // 实现改变input框的值来改变所有msg的值
                // 实现input的双向绑定
                node.addEventListener('input',(event)=>{
                  this[vmkey] = node.value
                  
                })
                node.removeAttribute('v-model')
              }
              // 实现点击事件修改
              if (node.hasAttribute('@click')) {
                let vmkey = node.getAttribute('@click').trim();
                // 绑定click点击事件
                node.addEventListener('click',(event)=>{
                  // 绑定当前methods里面的方法,然后执行
                  this.eventFn = this.$options.methods[vmkey].bind(this)
                  this.eventFn()
                  
                })
              }
              // 检测当前节点有没有子节点,有的话继续执行
             if (node.childNodes.length>0) {
                this.compile(node)
              }
              
          }
          
          // 文本节点
          if (node.nodeType == 3) {
            
          }
        })
      }
    }

匹配{{}}语法

          // 文本节点
          if (node.nodeType == 3) {
            // 文本节点的内容放在属性textContent里面
            // 匹配{{}}
            let reg = /\{\{(.*?)\}\}/g;
            // 拿到{{msg}}
            let text = node.textContent;
            node.textContent=  text.replace(reg,(match,vmkey)=>{
              console.log(match);  // {{msg}}  匹配到的内容
              console.log(vmkey);  // msg   .*?匹配到的内容
              vmkey = vmkey.trim()
              
              if (this.hasOwnProperty(vmkey)) {
                let watcher = new Watch(this,vmkey,node,'textContent')
                if (this.$watchEvent[vmkey]) {
                  this.$watchEvent[vmkey].push(watcher)
                  }else{
                    this.$watchEvent[vmkey]=[]
                    this.$watchEvent[vmkey].push(watcher)
                  }
              }
              return this[vmkey]
            })
          }

生命周期函数的设置


  <script type="text/javascript">
 
    class Vue{
      constructor(options){
        this.$el = document.querySelector(options.el)
        
        // 生命周期函数 beforeCreated
        if (typeof options.beforeCreated == 'function') {
          options.beforeCreated.bind(this)()
        }

        this.$options = options

        // 生命周期函数 create
        if (typeof options.created == 'function') {
          options.created.bind(this)()
        }

        this.$watchEvent = {}
        this.getprory()
        this.observe()

        // 生命周期函数 beforeMountate
        if (typeof options.beforeMount == 'function') {
          options.beforeMount.bind(this)()
        }

        this.compile(this.$el)

        // 生命周期函数 mounted
        if (typeof options.mounted == 'function') {
          options.mounted.bind(this)()
        }

      }
    class Watch{
      constructor(vm,key,node,attr,nType){
        this.vm = vm                
        this.key =key
        // div里面的节点      // 如h1
        this.node = node                                         
        // 绑定上面key属性的标签属性属性  如v-html
        this.attr = attr 
      }
      update(){
        
        // 生命周期函数 beforeUpdata
        if (typeof options.beforeUpdata == 'function') {
          options.beforeUpdata.bind(this)()
        }

        // 当数据改变的时候去进行更新有这个key属性的地方
        this.node[this.attr] = this.vm[this.key]
        // 如 h1.innnerTTML = vue.msg

        // 生命周期函数 Updata
        if (typeof options.Updata == 'function') {
          options.Updata.bind(this)()
        }

      }  
    }
  </script>
  <script>
    let options = {
      el:'#app',
      data:{
        msg:'我是大傻逼',
        username:'大傻逼'
      },
      methods: {
        change(){ 
          this.msg = 'aa'
        }
      },
      beforeCreated(){
        console.log('我还没创建');
      },
      created(){
        console.log('我创建');
      },     
      beforeMount(){
        console.log('我还没挂载');
      },   
      mounted(){
        console.log('我挂载');
      },   
      beforeUpdata(){
        console.log('我还没更新');
      },   
      Updata(){
        console.log('我更新');
      },   
    }
    let app = new Vue(options)
  </script>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值