整合營銷服務(wù)商

          電腦端+手機(jī)端+微信端=數(shù)據(jù)同步管理

          免費(fèi)咨詢熱線:

          Vue入門教程(一)之基本使用

          、MVVM簡介

          如果你是第一次學(xué)前端,那么本節(jié)知識一定要了解,什么是MVVM。

          MVVM是Model-View-ViewModel的簡寫。它本質(zhì)上就是MVC 的改進(jìn)版。MVVM 就是將其中的View 的狀態(tài)和行為抽象化,讓我們將視圖 UI 和業(yè)務(wù)邏輯分開。當(dāng)然這些事 ViewModel 已經(jīng)幫我們做了,它可以取出 Model 的數(shù)據(jù)同時幫忙處理 View 中由于需要展示內(nèi)容而涉及的業(yè)務(wù)邏輯。MVVM的核心是ViewModel層,負(fù)責(zé)轉(zhuǎn)換Model中的數(shù)據(jù)對象來讓數(shù)據(jù)變得更容易管理和使用。是一種簡化用戶界面的事件驅(qū)動編程方式

          下邊我們來畫張圖來大體了解下MVVM的工作原理圖:

          該層向上與視圖層進(jìn)行雙向數(shù)據(jù)綁定

          向下與Model層通過接口請求進(jìn)行數(shù)據(jù)交互

          (1)View

          View是視圖層, 也就是用戶界面。前端主要由HTH L和csS來構(gòu)建, 為了更方便地展現(xiàn)vi eu to del或者Hodel層的數(shù)據(jù), 已經(jīng)產(chǎn)生了各種各樣的前后端模板語言, 比如FreeMarker,Thyme leaf等等, 各大MV VM框架如Vue.js.Angular JS, EJS等也都有自己用來構(gòu)建用戶界面的內(nèi)置模板語言。

          (2)Model

          Model是指數(shù)據(jù)模型, 泛指后端進(jìn)行的各種業(yè)務(wù)邏輯處理和數(shù)據(jù)操控, 主要圍繞數(shù)據(jù)庫系統(tǒng)展開。這里的難點主要在于需要和前端約定統(tǒng)一的接口規(guī)則

          (3)ViewModel

          ViewModel是由前端開發(fā)人員組織生成和維護(hù)的視圖數(shù)據(jù)層。在這一層, 前端開發(fā)者對從后端獲取的Model數(shù)據(jù)進(jìn)行轉(zhuǎn)換處理, 做二次封裝, 以生成符合View層使用預(yù)期的視圖數(shù)據(jù)模型。

          View Model所封裝出來的數(shù)據(jù)模型包括視圖的狀態(tài)和行為兩部分, 而Model層的數(shù)據(jù)模型是只包含狀態(tài)的

          視圖狀態(tài)和行為都封裝在了View Model里。這樣的封裝使得View Model可以完整地去描述View層。由于實現(xiàn)了雙向綁定, View Model的內(nèi)容會實時展現(xiàn)在View層, 這是激動人心的, 因為前端開發(fā)者再也不必低效又麻煩地通過操縱DOM去更新視圖。 MVVM框架已經(jīng)把最臟最累的一塊做好了, 我們開發(fā)者只需要處理和維護(hù)View Model, 更新數(shù)據(jù)視圖就會自動得到相應(yīng)更新,真正實現(xiàn)事件驅(qū)動編程。 View層展現(xiàn)的不是Model層的數(shù)據(jù), 而是ViewModel的數(shù)據(jù), 由ViewModel負(fù)責(zé)與Model層交互, 這就完全解耦了View層和Model層, 這個解耦是至關(guān)重要的, 它是前后端分離方案實施的重要一環(huán)。


          2、為什么要使用MVVM

          MVVM模式和MVC模式一樣,主要目的是分離視圖(View)和模型(Model),有幾大優(yōu)點

          (1) 低耦合。視圖(View)可以獨(dú)立于Model變化和修改,一個ViewModel可以綁定到不同的"View"上,當(dāng)View變化的時候Model可以不變,當(dāng)Model變化的時候View也可以不變。

          (2) 可重用性。你可以把一些視圖邏輯放在一個ViewModel里面,讓很多view重用這段視圖邏輯。

          (3)獨(dú)立開發(fā)。開發(fā)人員可以專注于業(yè)務(wù)邏輯和數(shù)據(jù)的開發(fā)(ViewModel),設(shè)計人員可以專注于頁面設(shè)計,使用Expression Blend可以很容易設(shè)計界面并生成xaml代碼。

          (4)可測試。界面素來是比較難于測試的,測試可以針對ViewModel來寫


          3、VUE概述

          (1)什么是vue?

          Vue是一套用于構(gòu)建用戶界面的漸進(jìn)式框架。與其它大型框架不同的是,Vue 被設(shè)計為可以自底向上逐層應(yīng)用。Vue 的核心庫只關(guān)注視圖層,不僅易于上手,還便于與第三方庫或既有項目整合

          這是官網(wǎng)給出的介紹,可能不是那么容易理解。簡單來說,Vue是一個視圖層框架,幫助我們更好的構(gòu)建應(yīng)用。

          使用Vue和原生JS一個最顯著的差別就是,Vue不再對DOM直接進(jìn)行操作,而是通過對數(shù)據(jù)的操作,來改變頁面。使用Vue構(gòu)建的頁面,是有一個個的組件組成的,當(dāng)組件中定義的數(shù)據(jù)發(fā)生變化時,組件的顯示也會跟著變化,且此過程無需刷新頁面。

          (2)MVVM模式的實現(xiàn)者

          Model:模型層, 在這里表示JavaScript對象 View:視圖層, 在這里表示DOM(HTML操作的元素) ViewModel:連接視圖和數(shù)據(jù)的中間件, Vue.js就是MVVM中的View Model層的實現(xiàn)者 在MVVM架構(gòu)中, 是不允許數(shù)據(jù)和視圖直接通信的, 只能通過ViewModel來通信, 而View Model就是定義了一個Observer觀察者

          ViewModel能夠觀察到數(shù)據(jù)的變化, 并對視圖對應(yīng)的內(nèi)容進(jìn)行更新 ViewModel能夠監(jiān)聽到視圖的變化, 并能夠通知數(shù)據(jù)發(fā)生改變 至此, 我們就明白了, Vue.js就是一個MV VM的實現(xiàn)者, 他的核心就是實現(xiàn)了DOM監(jiān)聽與數(shù)據(jù)綁定

          (3)為什么要使用Vue

          易用:熟悉HTML、CSS、JavaScript之后,可快速度上手vue。學(xué)習(xí)曲線平穩(wěn)。

          輕量級:Vue.js壓縮后有只有20多kb,超快虛擬DOM

          高效:吸取了Angular(模塊化) 和React(虛擬DOM) 的優(yōu)勢, 并擁有自己獨(dú)特的功能

          開源:文檔齊全,社區(qū)活躍度高


          4、VUE之Hello World!

          步驟一:創(chuàng)建空文件

          <!DOCTYPE html>
          <html lang="en">
          <head>
              <meta charset="UTF-8">
              <title>Document</title>
          </head>
          <body>
          
          </body>
          </html>

          步驟二:引入vue.js (本人下載的開發(fā)版的vue.js,跟本html文件放在了同一目錄下,所以直接引用)

          <script type="text/javascript" src="vue.js"></script>

          步驟三:創(chuàng)建vue實例

          <script type="text/javascript">
                  var vm = new Vue({
                      el:'#app',
                      data:{
                          msg:'Hello World'
                      }
                  });
          </script>

          步驟四:數(shù)據(jù)與頁面元素綁定

          <div id="app">
                  {{msg}}
          </div>

          完整的html

          <!DOCTYPE html>
          <html lang="en">
          <body>
              <div id="app">
                  {{msg}}
              </div>
              <script type="text/javascript" src="vue.js"></script>
              <script type="text/javascript">
                  var vm = new Vue({
                      el:'#app',
                      data:{
                          msg:'Hello World'
                      }
                  });
              </script>
          </body>
          </html>

          瀏覽器打開:

          參數(shù)分析:

          el : '#app' -- 綁定元素的ID(元素的掛載位置,值可以是CSS選擇器或者是DOM元素)

          data : { msg : 'Hello World' } -- 模型數(shù)據(jù),屬性名:msg 值:Hello World

          {{msg}} : 在綁定的元素中使用{{ }}將Vue創(chuàng)建的名為msg的屬性包起來, 即可實現(xiàn)數(shù)據(jù)綁定功能,我們在調(diào)試狀態(tài)下手動修改下msg的值,在不刷新頁面的情況下就會展示我們修改后的值,這就是借助了Vue的數(shù)據(jù)綁定功能實現(xiàn)的。 MV VM模式中要求View Model層就是使用觀察者模式來實現(xiàn)數(shù)據(jù)的監(jiān)聽與綁定, 以做到數(shù)據(jù)與視圖的快速響應(yīng)

          下一篇:VUE入門教程(二)之模板語法(指令)



          于parseHTML函數(shù)代碼實在過于龐大,我這里就不一次性貼出源代碼了,大家可以前往(https://github.com/vuejs/vue/blob/dev/src/compiler/parser/html-parser.js)查看源代碼。

          我們來總結(jié)一下該函數(shù)的主要功能:

          1、匹配標(biāo)簽的 "<" 字符

          匹配的標(biāo)簽名稱不能是:script、style、textarea

          有如下情況:

          1、注釋標(biāo)簽 /^<!\--/

          2、條件注釋 /^<!\[/

          3、html文檔頭部 /^<!DOCTYPE [^>]+>/i

          4、標(biāo)簽結(jié)束 /^<\/ 開頭

          5、標(biāo)簽開始 /^</ 開頭

          然后開始匹配標(biāo)簽的屬性包括w3的標(biāo)準(zhǔn)屬性(id、class)或者自定義的任何屬性,以及vue的指令(v-、:、@)等,直到匹配到 "/>" 標(biāo)簽的結(jié)尾。然后把已匹配的從字符串中刪除,一直 while 循環(huán)匹配。

          解析開始標(biāo)簽函數(shù)代碼:

          function parseStartTag () {
             // 標(biāo)簽的開始 如<div
              const start = html.match(startTagOpen)
              if (start) {
                const match = {
                  tagName: start[1], // 標(biāo)簽名稱
                  attrs: [], // 標(biāo)簽屬性
                  start: index // 開始位置
                }
                 // 減去已匹配的長度
                advance(start[0].length)
                let end, attr
                while (!(end = html.match(startTagClose)) && (attr = html.match(dynamicArgAttribute) || html.match(attribute))) {
                  attr.start = index
                  v
                  advance(attr[0].length)  
                  attr.end = index
                  match.attrs.push(attr) // 把匹配到的屬性添加到attrs數(shù)組
                }
                if (end) { // 標(biāo)簽的結(jié)束符 ">"
                  match.unarySlash = end[1]
                  advance(end[0].length)  // 減去已匹配的長度
                  match.end = index  // 結(jié)束位置
                  return match
                }
              }
            }

          處理過后結(jié)構(gòu)如下:

          接下來就是處理組合屬性,調(diào)用 “handleStartTag” 函數(shù)

           function handleStartTag (match) {
              const tagName = match.tagName // 標(biāo)簽名稱
              const unarySlash = match.unarySlash // 一元標(biāo)簽
              if (expectHTML) {
                if (lastTag === 'p' && isNonPhrasingTag(tagName)) {
                  // 解析標(biāo)簽結(jié)束
                  parseEndTag(lastTag)
                }
                if (canBeLeftOpenTag(tagName) && lastTag === tagName) {
                  parseEndTag(tagName)
                }
              }
             // 是否為一元標(biāo)簽
              const unary = isUnaryTag(tagName) || !!unarySlash
              const l = match.attrs.length
              // 標(biāo)簽屬性集合
              const attrs = new Array(l)
              for (let i = 0; i < l; i++) {
                const args = match.attrs[i]
                const value = args[3] || args[4] || args[5] || ''
                const shouldDecodeNewlines = tagName === 'a' && args[1] === 'href' ? options.shouldDecodeNewlinesForHref : options.shouldDecodeNewlines
                attrs[i] = {
                  name: args[1], // 屬性名稱
                  value: decodeAttr(value, shouldDecodeNewlines) // 屬性值
                }
                if (process.env.NODE_ENV !== 'production' && options.outputSourceRange) {
                  // 開始位置
                  attrs[i].start = args.start + args[0].match(/^\s*/).length
                  // 結(jié)束位置
                  attrs[i].end = args.end
                }
              }
          
              if (!unary) {
                stack.push({ tag: tagName, lowerCasedTag: tagName.toLowerCase(), attrs: attrs, start: match.start, end: match.end })
                lastTag = tagName
              }
          		// 調(diào)用start函數(shù)
              if (options.start) {
                options.start(tagName, attrs, unary, match.start, match.end)
              }
            }

          我們簡單說一下最后調(diào)用的start函數(shù)的作用:

          1、判斷是否為svg標(biāo)簽,并處理svg在ie下的兼容性問題

          2、遍歷標(biāo)簽屬性,驗證其名稱是否有效

          3、標(biāo)簽名是否為 style 或者 script ,如果在服務(wù)端會提示warn警告

          4、檢查屬性是否存在 v-for、v-if、v-once指令

          5、如果是更元素就驗證其合法性,不能是 slot 和 template 標(biāo)簽,不能存在 v-for指令

          以上就是界面html模板的開始標(biāo)簽的分析,接下來我們來分析如何匹配結(jié)束標(biāo)簽。

          請看:Vue源碼全面解析三十 parseHTML函數(shù)(解析html(二)結(jié)束標(biāo)簽)

          如有錯誤,歡迎指正,謝謝。

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

          Object.defineProperty()

          實現(xiàn)之前我們得先看一下Object.defineProperty的實現(xiàn),因為vue主要是通過數(shù)據(jù)劫持來實現(xiàn)的,通過get、set來完成數(shù)據(jù)的讀取和更新。


          var obj = {name:'wclimb'}

          var age = 24

          Object.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

          從上面可以看到通過get獲取數(shù)據(jù),通過set監(jiān)聽到數(shù)據(jù)變化執(zhí)行相應(yīng)操作,還是不明白的話可以去看看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)用

          JavaScript

          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)聽所有數(shù)據(jù)
          • compile 解析dom
          • compileText 解析dom里處理純雙花括號的操作
          • Watcher 更新視圖操作

          Vue constructor 初始化

          class Vue{

          constructor(options = {}){

          this.$el = document.querySelector(options.el);

          let data = this.data = options.data;

          // 代理data,使其能直接this.xxx的方式訪問data,正常的話需要this.data.xxx

          Object.keys(data).forEach((key)=> {

          this.proxyData(key);

          });

          this.methods = obj.methods // 事件方法

          this.watcherTask = {}; // 需要監(jiān)聽的任務(wù)列表

          this.observer(data); // 初始化劫持監(jiān)聽所有數(shù)據(jù)

          this.compile(this.$el); // 解析dom

          }

          }

          上面主要是初始化操作,針對傳過來的數(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的方式直接訪問data

          observer 劫持監(jiān)聽

          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來監(jiān)聽數(shù)據(jù),初始化需要訂閱的數(shù)據(jù)。

          把需要訂閱的數(shù)據(jù)到push到watcherTask里,等到時候需要更新的時候就可以批量更新數(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 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))

          return value.split('.').reduce((val, key) => {

          return this.data[key];

          }, this.$el);

          })

          }

          }

          }

          這里代碼比較多,我們拆分看你就會覺得很簡單了

          1. 首先我們先遍歷el元素下面的所有子節(jié)點,node.nodeType === 3 的意思是當(dāng)前元素是文本節(jié)點,node.nodeType === 1 的意思是當(dāng)前元素是元素節(jié)點。因為可能有的是純文本的形式,如純雙花括號就是純文本的文本節(jié)點,然后通過判斷元素節(jié)點是否還存在子節(jié)點,如果有的話就遞歸調(diào)用compile方法。下面重頭戲來了,我們拆開看:


          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')

          }

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

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

          task.update()

          })

          然后push的值是一個Watcher的實例,首先他new的時候會先執(zhí)行一次,執(zhí)行的操作就是去把純雙花括號 -> 1,也就是說把我們寫好的模板數(shù)據(jù)更新到模板視圖上。

          最后把當(dāng)前元素屬性剔除出去,我們用Vue的時候也是看不到這種指令的,不剔除也不影響

          至于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 = ‘這個是表單的數(shù)據(jù)’

          那么我們?yōu)槭裁床恢苯尤ジ履兀€需要update做什么,不是多此一舉嗎?

          其實update記得嗎?我們在訂閱池里面需要批量更新,就是通過調(diào)用Watcher原型上的update方法。

          效果

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


          主站蜘蛛池模板: 国产在线无码一区二区三区视频| 国产精品区一区二区三| 无码一区二区三区在线观看| 风间由美性色一区二区三区| 精品亚洲A∨无码一区二区三区| 日韩一区二区免费视频| 久久久99精品一区二区| 精品性影院一区二区三区内射| 亚洲av成人一区二区三区观看在线| 国产婷婷色一区二区三区深爱网 | 男人免费视频一区二区在线观看| 精品国产一区二区三区av片| 伊人无码精品久久一区二区| 国产精品无码一区二区在线观 | 一区二区传媒有限公司| 久久一区二区精品| 国产福利一区视频| 深夜福利一区二区| 亚洲AV无码一区二三区| 国产日韩精品一区二区三区| 天堂Av无码Av一区二区三区| 色偷偷av一区二区三区| 亚洲福利一区二区| 久久精品国产亚洲一区二区三区 | 人妻夜夜爽天天爽爽一区| 久久久久人妻一区精品性色av| 日韩视频一区二区| 精品国产AⅤ一区二区三区4区| 国产精品亚洲午夜一区二区三区 | av无码免费一区二区三区| 亚洲视频免费一区| 竹菊影视欧美日韩一区二区三区四区五区 | 亚洲国产日韩在线一区| 精品国产乱子伦一区二区三区 | 国内自拍视频一区二区三区| 久久精品无码一区二区三区免费| 成人中文字幕一区二区三区| 久久青青草原一区二区| 亚洲国产一区明星换脸| 亚洲国产精品一区| 在线欧美精品一区二区三区|