国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁(yè) > 編程 > JavaScript > 正文

JavaScript之實(shí)現(xiàn)一個(gè)簡(jiǎn)單的Vue示例

2019-11-19 12:16:53
字體:
來(lái)源:轉(zhuǎn)載
供稿:網(wǎng)友

vue的使用相信大家都很熟練了,使用起來(lái)簡(jiǎn)單。但是大部分人不知道其內(nèi)部的原理是怎么樣的,今天我們就來(lái)一起實(shí)現(xiàn)一個(gè)簡(jiǎn)單的vue

Object.defineProperty()

實(shí)現(xiàn)之前我們得先看一下Object.defineProperty的實(shí)現(xiàn),因?yàn)関ue主要是通過(guò)數(shù)據(jù)劫持來(lái)實(shí)現(xiàn)的,通過(guò)getset來(lái)完成數(shù)據(jù)的讀取和更新。

var obj = {name:'wclimb'}var age = 24Object.defineProperty(obj,'age',{  enumerable: true, // 可枚舉  configurable: false, // 不能再define  get () {    return age  },  set (newVal) {    console.log('我改變了',age +' -> '+newVal);    age = newVal  }})> obj.age> 24> obj.age = 25;> 我改變了 24 -> 25> 25

從上面可以看到通過(guò)get獲取數(shù)據(jù),通過(guò)set監(jiān)聽(tīng)到數(shù)據(jù)變化執(zhí)行相應(yīng)操作,還是不明白的話(huà)可以去看看Object.defineProperty文檔。

流程圖

html代碼結(jié)構(gòu)

<div id="wrap">  <p v-html="test"></p>  <input type="text" v-model="form">  <input type="text" v-model="form">  <button @click="changeValue">改變值</button>  {{form}}</div>

js調(diào)用

 new Vue({    el: '#wrap',    data:{      form: '這是form的值',      test: '<strong>我是粗體</strong>',    },    methods:{      changeValue(){        console.log(this.form)        this.form = '值被我改變了,氣不氣?'      }    }  })

Vue結(jié)構(gòu)

  class Vue{    constructor(){}    proxyData(){}    observer(){}    compile(){}    compileText(){}  }  class Watcher{    constructor(){}    update(){}  }
  • Vue constructor 構(gòu)造函數(shù)主要是數(shù)據(jù)的初始化
  • proxyData 數(shù)據(jù)代理
  • observer 劫持監(jiān)聽(tīng)所有數(shù)據(jù)
  • compile 解析dom
  • compileText 解析dom里處理純雙花括號(hào)的操作
  • Watcher 更新視圖操作

Vue constructor 初始化

  class Vue{    constructor(options = {}){      this.$el = document.querySelector(options.el);      let data = this.data = options.data;       // 代理data,使其能直接this.xxx的方式訪問(wèn)data,正常的話(huà)需要this.data.xxx      Object.keys(data).forEach((key)=> {        this.proxyData(key);      });      this.methods = options.methods // 事件方法      this.watcherTask = {}; // 需要監(jiān)聽(tīng)的任務(wù)列表      this.observer(data); // 初始化劫持監(jiān)聽(tīng)所有數(shù)據(jù)      this.compile(this.$el); // 解析dom    }  }

上面主要是初始化操作,針對(duì)傳過(guò)來(lái)的數(shù)據(jù)進(jìn)行處理

proxyData 代理data

class Vue{    constructor(options = {}){      ......    }    proxyData(key){      let that = this;      Object.defineProperty(that, key, {        configurable: false,        enumerable: true,        get () {          return that.data[key];        },        set (newVal) {          that.data[key] = newVal;        }      });    }  }

上面主要是代理data到最上層,this.xxx的方式直接訪問(wèn)data

observer 劫持監(jiān)聽(tīng)

class Vue{    constructor(options = {}){      ......    }    proxyData(key){      ......    }    observer(data){      let that = this      Object.keys(data).forEach(key=>{        let value = data[key]        this.watcherTask[key] = []        Object.defineProperty(data,key,{          configurable: false,          enumerable: true,          get(){            return value          },          set(newValue){            if(newValue !== value){              value = newValue              that.watcherTask[key].forEach(task => {                task.update()              })            }          }        })      })    }  }

同樣是使用Object.defineProperty來(lái)監(jiān)聽(tīng)數(shù)據(jù),初始化需要訂閱的數(shù)據(jù)。

把需要訂閱的數(shù)據(jù)到pushwatcherTask里,等到時(shí)候需要更新的時(shí)候就可以批量更新數(shù)據(jù)了。👇下面就是;
遍歷訂閱池,批量更新視圖。

  set(newValue){    if(newValue !== value){      value = newValue      // 批量更新視圖      that.watcherTask[key].forEach(task => {        task.update()      })    }  }       

compile 解析dom

class Vue{    constructor(options = {}){      ......    }    proxyData(key){      ......    }    observer(data){      ......    }    compile(el){      var nodes = el.childNodes;      for (let i = 0; i < nodes.length; i++) {        const node = nodes[i];        if(node.nodeType === 3){          var text = node.textContent.trim();          if (!text) continue;          this.compileText(node,'textContent')                }else if(node.nodeType === 1){          if(node.childNodes.length > 0){            this.compile(node)          }          if(node.hasAttribute('v-model') && (node.tagName === 'INPUT' || node.tagName === 'TEXTAREA')){            node.addEventListener('input',(()=>{              let attrVal = node.getAttribute('v-model')              this.watcherTask[attrVal].push(new Watcher(node,this,attrVal,'value'))              node.removeAttribute('v-model')              return () => {                this.data[attrVal] = node.value              }            })())          }          if(node.hasAttribute('v-html')){            let attrVal = node.getAttribute('v-html');            this.watcherTask[attrVal].push(new Watcher(node,this,attrVal,'innerHTML'))            node.removeAttribute('v-html')          }          this.compileText(node,'innerHTML')          if(node.hasAttribute('@click')){            let attrVal = node.getAttribute('@click')            node.removeAttribute('@click')            node.addEventListener('click',e => {              this.methods[attrVal] && this.methods[attrVal].bind(this)()            })          }        }      }    },    compileText(node,type){      let reg = //{/{(.*?)/}/}/g, txt = node.textContent;      if(reg.test(txt)){        node.textContent = txt.replace(reg,(matched,value)=>{          let tpl = this.watcherTask[value] || []          tpl.push(new Watcher(node,this,value,type))          if(value.split('.').length > 1){            let v = null            value.split('.').forEach((val,i)=>{              v = !v ? this[val] : v[val]            })            return v          }else{            return this[value]          }        })      }    }  }

這里代碼比較多,我們拆分看你就會(huì)覺(jué)得很簡(jiǎn)單了

首先我們先遍歷el元素下面的所有子節(jié)點(diǎn),node.nodeType === 3 的意思是當(dāng)前元素是文本節(jié)點(diǎn),node.nodeType === 1 的意思是當(dāng)前元素是元素節(jié)點(diǎn)。因?yàn)榭赡苡械氖羌兾谋镜男问剑?code>純雙花括號(hào)就是純文本的文本節(jié)點(diǎn),然后通過(guò)判斷元素節(jié)點(diǎn)是否還存在子節(jié)點(diǎn),如果有的話(huà)就遞歸調(diào)用compile方法。下面重頭戲來(lái)了,我們拆開(kāi)看:

if(node.hasAttribute('v-html')){  let attrVal = node.getAttribute('v-html');  this.watcherTask[attrVal].push(new Watcher(node,this,attrVal,'innerHTML'))  node.removeAttribute('v-html')}

上面這個(gè)首先判斷node節(jié)點(diǎn)上是否有v-html這種指令,如果存在的話(huà),我們就發(fā)布訂閱,怎么發(fā)布訂閱呢?只需要把當(dāng)前需要訂閱的數(shù)據(jù)pushwatcherTask里面,然后到時(shí)候在設(shè)置值的時(shí)候就可以批量更新了,實(shí)現(xiàn)雙向數(shù)據(jù)綁定,也就是下面的操作

that.watcherTask[key].forEach(task => {  task.update()})

然后push的值是一個(gè)Watcher的實(shí)例,首先他new的時(shí)候會(huì)先執(zhí)行一次,執(zhí)行的操作就是去把純雙花括號(hào) -> 1,也就是說(shuō)把我們寫(xiě)好的模板數(shù)據(jù)更新到模板視圖上。
最后把當(dāng)前元素屬性剔除出去,我們用Vue的時(shí)候也是看不到這種指令的,不剔除也不影響

至于Watcher是什么,看下面就知道了

Watcher

class Watcher{  constructor(el,vm,value,type){    this.el = el;    this.vm = vm;    this.value = value;    this.type = type;    this.update()  }  update(){    this.el[this.type] = this.vm.data[this.value]  }}

之前發(fā)布訂閱之后走了這里面的操作,意思就是把當(dāng)前元素如:node.innerHTML = '這是data里面的值'、node.value = '這個(gè)是表單的數(shù)據(jù)'

那么我們?yōu)槭裁床恢苯尤ジ履兀€需要update做什么,不是多此一舉嗎?
其實(shí)update記得嗎?我們?cè)谟嗛喅乩锩嫘枰扛拢褪峭ㄟ^(guò)調(diào)用Watcher原型上的update方法。

效果

在線效果地址,大家可以瀏覽器看一下效果,由于本人太懶了,gif效果圖就先不放了,哈哈😄😄

完整代碼

完整代碼已經(jīng)放到github上了 -> MyVue

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持武林網(wǎng)。

發(fā)表評(píng)論 共有條評(píng)論
用戶(hù)名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 阜阳市| 汽车| 布尔津县| 台山市| 施秉县| 星子县| 东明县| 长丰县| 许昌县| 双桥区| 万宁市| 渑池县| 灵石县| 玉溪市| 茶陵县| 南宫市| 雷波县| 余江县| 甘孜| 靖安县| 昆明市| 永登县| 屯昌县| 弋阳县| 定远县| 平乐县| 永川市| 阜宁县| 鞍山市| 绥滨县| 仁寿县| 长葛市| 清河县| 庆安县| 老河口市| 连山| 房产| 大英县| 鸡泽县| 正安县| 苍梧县|