<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          淺析Vue響應(yīng)系統(tǒng)原理與搭建Vue2.x迷你版

          共 51231字,需瀏覽 103分鐘

           ·

          2021-06-26 22:38

          Vue2.x響應(yīng)式原理怎么實(shí)現(xiàn)的?

          Vue 最獨(dú)特的特性之一,是其非侵入性的響應(yīng)式系統(tǒng)。那么什么是響應(yīng)式原理?

          數(shù)據(jù)模型僅僅是普通的JavaScript對(duì)象,而當(dāng)我們修改數(shù)據(jù)時(shí),視圖會(huì)進(jìn)行更新,避免了繁瑣的DOM操作,提高開發(fā)效率。簡言之,在改變數(shù)據(jù)的時(shí)候,視圖會(huì)跟著更新。

          了解概念之后,那么它是怎么實(shí)現(xiàn)的呢?

          其實(shí)是利用Object.defineProperty()中的gettersetter方法和設(shè)計(jì)模式中的觀察者模式。

          那么,我們先來看下Object.defineProperty()。MDN中它是這樣解釋它的:Object.defineProperty()方法會(huì)直接在一個(gè)對(duì)象上定義一個(gè)新屬性,或者修改一個(gè)對(duì)象的現(xiàn)有屬性,并返回此對(duì)象。

          let data = {
           msg:'hello'
          };

          let vm = {};

          Object.defineProperty(vm, 'msg', {
                  enumerabletrue// 可枚舉(可遍歷)
                  configurabletrue// 可配置(可以使用delete 刪除,可以通過defineProperty重新定義)

                  // 當(dāng)獲取值的時(shí)候執(zhí)行
                  get() {
                      console.log('get', data.msg);
                      return data.msg
                  },
                  // 當(dāng)設(shè)置值的時(shí)候執(zhí)行
                  set(newVal) {
                      if (newVal === data.msg) {
                          return
                      }
                      data.msg = newVal;
                      console.log('set', data.msg);
                  }
          })

          // 測(cè)試

          console.log(vm.msg);
          /* 
          > "get" "hello"
          > "hello"
          */

          vm.msg = 'world'// > "set" "world"

          簡單介紹Object.defineProperty()之后,接著就是了解觀察者模式,看到它,你可能會(huì)想起發(fā)布-訂閱模式。其實(shí)它們的本質(zhì)是相同的,但是也存在一定的區(qū)別。

          我們不妨先來看下發(fā)布-訂閱模式。

          發(fā)布-訂閱者模式里面包含了三個(gè)模塊,發(fā)布者,訂閱者和統(tǒng)一調(diào)度中心。這里統(tǒng)一調(diào)度中心相當(dāng)于報(bào)刊辦事大廳。發(fā)布者相當(dāng)與某個(gè)雜志負(fù)責(zé)人,他來中心這注冊(cè)一個(gè)的雜志,而訂閱者相當(dāng)于用戶,我在中心訂閱了這分雜志。每當(dāng)發(fā)布者發(fā)布了一期雜志,辦事大廳就會(huì)通知訂閱者來拿新雜志。發(fā)布-訂閱者模式由統(tǒng)一調(diào)度中心調(diào)用,因此發(fā)布者和訂閱者不需要知道對(duì)方的存在。

          下面,我們將通過一個(gè)實(shí)現(xiàn)Vue自定義事件的例子來更進(jìn)一步了解發(fā)布-訂閱模式

          function EventEmitter(){
              // 初始化統(tǒng)一調(diào)度中心
              this.subs = Object.create(null); // {'click':[fn1,fn2]}
          }
          // 注冊(cè)事件
          EventEmitter.prototype.$on = function (eventType,handler){
                  console.log(this);
                  this.subs[eventType]= this.subs[eventType]||[];
                  this.subs[eventType].push(handler);
          }
          // 觸發(fā)事件
          EventEmitter.prototype.$emit = function (eventType,data){
                  if(this.subs[eventType]){
                          this.subs[eventType].forEach(handler => {
                              handler(data);
                          });
                  }
          }

          // 測(cè)試
          const em = new EventEmitter();

          //訂閱者
          em.$on('click1',(data)=>{
              console.log(data);
          })
          // 發(fā)布者
          em.$emit('click1','maomin'//maomin

          這種自定義事件廣泛應(yīng)用于Vue同級(jí)組件傳值。

          接下來,我們來介紹觀察者模式。

          觀察者模式是由目標(biāo)調(diào)度,比如當(dāng)事件觸發(fā)時(shí),目標(biāo)就會(huì)調(diào)用觀察者的方法,所以觀察者模式的訂閱者(觀察者)與發(fā)布者(目標(biāo))之間存在依賴。

          // 發(fā)布者(目標(biāo))
          function Dep(){
              this.subs = [];
          }
          Dep.prototype.addSub = function (sub){
              if(sub&&sub.update){
                      this.subs.push(sub);
              }
          }

          Dep.prototype.notify = function (data){
                  this.subs.forEach(sub=>{
                      sub.update(data);
                  })
          }
          // 訂閱者(觀察者)
          function Watcher(){}
              Watcher.prototype.update=function(data){
              console.log(data);
          }

          // 測(cè)試
          let dep = new Dep();
          let watcher = new Watcher();
          // 收集依賴
          dep.addSub(watcher);
          // 發(fā)送通知
          dep.notify('1');
          dep.notify('2');

          下圖是區(qū)分兩種模式。

          實(shí)現(xiàn)Vue2.x迷你版本

          為什么要實(shí)現(xiàn)一個(gè)Vue迷你版本,目的就是加深對(duì)Vue響應(yīng)式原理以及其中一些API的理解。首先我們先來分析Vue2.x 響應(yīng)式原理的整體結(jié)構(gòu)。

          如下圖所示:

          我們接下來,將根據(jù)這幅圖片描述的流程來實(shí)現(xiàn)一款迷你版Vue。Vue2.x采用了Virtual DOM,但是因?yàn)檫@里只需要實(shí)現(xiàn)一個(gè)迷你版,所以我們這里做了簡化,我們這里就是直接操作DOM。

          下面,我們來看下我是如何搭建一款Vue mini的。

          第一步

          頁面結(jié)構(gòu)如下,我們可以先引入Vue2.x完整版本,看下實(shí)現(xiàn)效果。

          <!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>Vue2.x Reactive</title>
          </head>

          <body>
              <div id="app">
                  <h2>文本節(jié)點(diǎn)</h2>
                  <div>{{msg}}</div>
                  <div>{{count}}</div>
                  <div>{{obj.name}}</div>
                  <div>{{arr[0]}}</div>
                  <div>{{obj.inner.age}}</div>
                  <div>{{obj.inner.name}}</div>
                  <h2>v-text</h2>
                  <div v-text="msg"></div>
                  <h2>v-model</h2>
                  <input type="text" v-model="msg">
                  <input type="text" v-model="count">
                  <h2>v-html</h2>
                  <div v-html="html"></div>
                  <h2>v-show</h2>
                  <div v-show="isShow">{{isShow}}</div>
                  <h2>v-on</h2>
                  <button v-on:click="handler">handler</button>
                  <button @click="onClick">onClick</button>
                  <h2>v-if</h2>
                  <div>
                      <p v-if="isIf">{{isIf}}</p>
                  </div>
              </div>
              <script src="https://unpkg.com/[email protected]/dist/vue.js"></script>
              <script>
                  const vm = new Vue({
                          el'#app',
                          data() {
                              return {
                                  msg'maomin',
                                  count1,
                                  obj: {
                                      name'hello',
                                      inner: {
                                          age17
                                      }
                                  },
                                  arr: ['string1'],
                                  html'<div>{{msg}}</div>',
                                  isShowfalse,
                                  isIf:true
                              }
                          },
                          methods: {
                              handler() {
                                  // this.count = 2;
                                  this.isIf = !this.isIf;
                              },
                              onClick() {
                                  this.obj.inner.age = 18;
                                  // console.log(this.obj.inner.age);
                              }
                          }
                      });
              
          </script>
          </body>

          </html>

          經(jīng)過測(cè)試,Vue2.x完整版搭載的頁面顯示如下。我們將使用Vue迷你版本同樣實(shí)現(xiàn)以下頁面效果。

          第二步

          我們將根據(jù)整體結(jié)構(gòu)圖和頁面結(jié)構(gòu)來搭建這個(gè)Vue迷你版本,我們姑且將這個(gè)版本叫做vuemini.js

          通過整體結(jié)構(gòu)圖我們發(fā)現(xiàn),一共有VueObserver、Compiler、Dep、Watcher這幾個(gè)構(gòu)造函數(shù)。我們首先創(chuàng)建這幾個(gè)構(gòu)造函數(shù),這里不使用class類來定義是因?yàn)閂ue源碼大部分也使用構(gòu)造函數(shù),另外,相對(duì)也好拓展。

          Vue

          // 實(shí)例。
          function Vue(options{
              this.$options = options || {};
              this._data = typeof options.data === 'function' ? options.data() : options.data || {};
              this.$el = typeof options.el === 'string' ? document.querySelector(options.el) : options.el;
              // 負(fù)責(zé)把data中的屬性注入到Vue實(shí)例,轉(zhuǎn)換成getter/setter
              this._proxyData(this._data);
              this.initMethods(this, options.methods || {})
              // 負(fù)責(zé)調(diào)用observer監(jiān)聽data中所有屬性的變化
              new Observer(this._data);
              // 負(fù)責(zé)調(diào)用compiler解析指令/插值表達(dá)式
              new Compiler(this);
          }
          // 將data中的屬性掛載到this上
          Vue.prototype._proxyData = function (data{
              Object.keys(data).forEach(key => {
                  Object.defineProperty(this, key, {
                      configurabletrue,
                      enumerabletrue,
                      get() {
                          return data[key]
                      },
                      set(newVal) {
                          if (newVal === data[key]) {
                              return
                          }
                          data[key] = newVal;
                      }
                  })
              })
          }

          function noop(a, b, c{ }
          function polyfillBind(fn, ctx{
              function boundFn(a{
                  var l = arguments.length;
                  return l
                      ? l > 1
                          ? fn.apply(ctx, arguments)
                          : fn.call(ctx, a)
                      : fn.call(ctx)
              }

              boundFn._length = fn.length;
              return boundFn
          }
          function nativeBind(fn, ctx{
              return fn.bind(ctx)
          }
          const bind = Function.prototype.bind
              ? nativeBind
              : polyfillBind;

          // 初始化methods屬性
          Vue.prototype.initMethods = function (vm, methods{
              for (var key in methods) {
                  {
                      if (typeof methods[key] !== 'function') {
                          warn(
                              "Method \"" + key + "\" has type \"" + (typeof methods[key]) + "\" in the component definition. " +
                              "Did you reference the function correctly?",
                              vm
                          );
                      }
                  }
                  vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm);
              }
          }

          Observer

          // 數(shù)據(jù)劫持。
          // 負(fù)責(zé)把data(_data)選項(xiàng)中的屬性轉(zhuǎn)換成響應(yīng)式數(shù)據(jù)。
          function Observer(data{
              this.walk(data);
          }
          Observer.prototype.walk = function (data{
              if (!data || typeof data !== 'object') {
                  return
              }
              Object.keys(data).forEach(key => {
                  this.defineReactive(data, key, data[key]);
              })
          }
          Observer.prototype.defineReactive = function (obj, key, val{
              let that = this;
              // 負(fù)責(zé)收集依賴
              let dep = new Dep();
              // 如果val是對(duì)象,把val內(nèi)部的屬性轉(zhuǎn)換成響應(yīng)式數(shù)據(jù)
              this.walk(val);
              Object.defineProperty(obj, key, {
                  enumerabletrue,
                  configurabletrue,
                  get() {
                      // 收集依賴
                      Dep.target && dep.addSub(Dep.target)
                      return val
                  },
                  set(newVal) {
                      if (newVal === val) {
                          return
                      }
                      val = newVal;
                      // data內(nèi)屬性重新賦值后,使其轉(zhuǎn)化為響應(yīng)式數(shù)據(jù)。
                      that.walk(newVal);
                      // 發(fā)送通知
                      dep.notify();
                  }
              })
          }

          Compiler

          // 編譯模板,解析指令/插值表達(dá)式
          // 負(fù)責(zé)頁面的首次渲染
          // 當(dāng)數(shù)據(jù)變化時(shí)重新渲染視圖
          function Compiler(vm{
              this.el = vm.$el;
              this.vm = vm;
              // 立即編譯模板
              this.compile(this.el);
          }
          // 編譯模板,處理文本節(jié)點(diǎn)和元素節(jié)點(diǎn)
          Compiler.prototype.compile = function (el{
              let childNodes = el.childNodes;
              Array.from(childNodes).forEach(node => {
                  // 處理文本節(jié)點(diǎn)
                  if (this.isTextNode(node)) {
                      this.compileText(node);
                  }
                  // 處理元素節(jié)點(diǎn) 
                  else if (this.isElementNode(node)) {
                      this.compileElement(node);
                  }
                  // 判斷node節(jié)點(diǎn),是否有子節(jié)點(diǎn),如果有子節(jié)點(diǎn),要遞歸調(diào)用compile方法
                  if (node.childNodes && node.childNodes.length) {
                      this.compile(node);
                  }
              })
          }
          // 編譯文本節(jié)點(diǎn),處理插值表達(dá)式
          Compiler.prototype.compileText = function (node{
              // console.dir(node);
              let reg = /\{\{(.+?)\}\}/;
              let value = node.textContent;
              if (reg.test(value)) {
                  let key = RegExp.$1.trim();
                  if (this.vm.hasOwnProperty(key)) {
                      node.textContent = value.replace(reg, typeof this.vm[key] === 'object' ? JSON.stringify(this.vm[key]) : this.vm[key]);
                      // 創(chuàng)建watcher對(duì)象,當(dāng)數(shù)據(jù)改變更新視圖
                      new Watcher(this.vm, key, (newVal) => {
                          node.textContent = newVal;
                      })
                  } else {
                      const str = `this.vm.${key}`;
                      node.textContent = value.replace(reg, eval(str));
                      // 創(chuàng)建watcher對(duì)象,當(dāng)數(shù)據(jù)改變更新視圖
                      new Watcher(this.vm, key, () => {
                          const strw = `this.vm.${key}`;
                          node.textContent = value.replace(reg, eval(strw));
                      })
                  }


              }
          }
          // 判斷節(jié)點(diǎn)是否是文本節(jié)點(diǎn)
          Compiler.prototype.isTextNode = function (node{
              return node.nodeType === 3;
          }
          // 判斷節(jié)點(diǎn)是否是元素節(jié)點(diǎn)
          Compiler.prototype.isElementNode = function (node{
              return node.nodeType === 1;
          }

          // 編譯元素節(jié)點(diǎn),處理指令
          Compiler.prototype.compileElement = function (node{
              // console.log(node.attributes);

              // 遍歷所有的屬性節(jié)點(diǎn)
              Array.from(node.attributes).forEach(attr => {
                  let attrName = attr.name;
                  // console.log(attrName);
                  // 判斷是否是指令
                  if (this.isDirective(attrName)) {
                      // 判斷:如v-on:click
                      let eventName;
                      if (attrName.indexOf(':') !== -1) {
                          const strArr = attrName.substr(2).split(':');
                          attrName = strArr[0];
                          eventName = strArr[1];
                      } else if (attrName.indexOf('@') !== -1) {
                          eventName = attrName.substr(1);
                          attrName = 'on';
                      } else {
                          attrName = attrName.substr(2);
                      }
                      let key = attr.value;

                      this.update(node, key, attrName, eventName);
                  }
              })
          }
          // 判斷元素屬性是否是指令
          Compiler.prototype.isDirective = function (attrName{
              return attrName.startsWith('v-') || attrName.startsWith('@');
          }
          // 指令輔助函數(shù)
          Compiler.prototype.update = function (node, key, attrName, eventName{
              let updateFn = this[attrName + 'Updater'];
              updateFn && updateFn.call(this, node, this.vm[key], key, eventName);
          }
          // 處理v-text指令
          Compiler.prototype.textUpdater = function (node, value, key{
              node.textContent = value;
              new Watcher(this.vm, key, (newVal) => {
                  node.textContent = newVal;
              })
          }
          // 處理v-html指令
          Compiler.prototype.htmlUpdater = function (node, value, key{
              node.insertAdjacentHTML('beforeend', value);
              new Watcher(this.vm, key, (newVal) => {
                  node.insertAdjacentHTML('beforeend', newVal);
              })
          }
          // 處理v-show指令
          Compiler.prototype.showUpdater = function (node, value, key{
              !value ? node.style.display = 'none' : node.style.display = 'block'
              new Watcher(this.vm, key, (newVal) => {
                  !newVal ? node.style.display = 'none' : node.style.display = 'block';
              })
          }
          // 處理v-if指令
          Compiler.prototype.ifUpdater = function (node, value, key{
              const nodew = node;
              const nodep = node.parentNode;
              if (!value) {
                  node.parentNode.removeChild(node)
              }
              new Watcher(this.vm, key, (newVal) => {
                  console.log(newVal);
                  !newVal ? nodep.removeChild(node) : nodep.appendChild(nodew);
              })
          }
          // 處理v-on指令
          Compiler.prototype.onUpdater = function (node, value, key, eventName{
              if (eventName) {
                  const handler = this.vm.$options.methods[key].bind(this.vm);
                  node.addEventListener(eventName, handler);
              }
          }
          // 處理v-model指令
          Compiler.prototype.modelUpdater = function (node, value, key{
              node.value = value;
              new Watcher(this.vm, key, (newVal) => {
                  node.value = newVal;
              })
              // 雙向綁定,視圖變化更新數(shù)據(jù)
              node.addEventListener('input', () => {
                  this.vm[key] = node.value;
              })
          }

          Dep

          // 發(fā)布者。
          // 收集依賴,添加所有的觀察者(watcher)。通知所有的觀察者。
          function Dep({
              // 存儲(chǔ)所有的觀察者watcher
              this.subs = [];
          }
          // 添加觀察者
          Dep.prototype.addSub = function (sub{
              if (sub && sub.update) {
                  this.subs.push(sub);
              }
          }
          // 發(fā)送通知
          Dep.prototype.notify = function ({
              this.subs.forEach(sub => {
                  sub.update();
              })
          }

          Watcher

          function Watcher(vm, key, cb{
              this.vm = vm;
              this.key = key;
              this.cb = cb;
              // 把當(dāng)前watcher對(duì)象記錄到Dep類的靜態(tài)屬性target
              Dep.target = this;
              if (vm.hasOwnProperty(key)) {
                  this.oldVal = vm[key];
              } else {
                  const str = `vm.${key}`;
                  this.oldVal = eval(str);
              }
              Dep.target = null;
          }
          // 當(dāng)數(shù)據(jù)發(fā)生變化的時(shí)候更新視圖
          Watcher.prototype.update = function ({
              let newVal;
              if (this.vm.hasOwnProperty(this.key)) {
                  newVal = this.vm[this.key];
              } else {
                  const str = `this.vm.${this.key}`;
                  newVal = eval(str);
              }
              this.cb(newVal);
          }

          以上這幾個(gè)構(gòu)造函數(shù)就實(shí)現(xiàn)了我們所說的迷你版本,將它們整合成一個(gè)文件vuemini.js。在上面所提示的頁面引入,查看效果。

          另外,我在data中綁定了一個(gè)html屬性,值為一個(gè)'<div>{{msg}}</div>',與之前完整版相比,圖中的v-html下方的maomin文本也被渲染出來。

          尤大開發(fā)的Vue2.x迷你版本

          下面,我們將看下尤大開發(fā)的迷你版本,這個(gè)版本引入了Virtual DOM,但是主要是針對(duì)響應(yīng)式式原理的,可以根據(jù)尤大的迷你版本與上面的版本作個(gè)比較,可以看下有哪些相似之處。

          <!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>vue2mini</title>
          </head>

          <body>
              <div id="app"></div>
              <script>
                  // reactivity ---
                  let activeEffect

                  class Dep {
                      subscribers = new Set()
                      depend() {
                          if (activeEffect) {
                              this.subscribers.add(activeEffect)
                          }
                      }
                      notify() {
                          this.subscribers.forEach(effect => effect())
                      }
                  }

                  function watchEffect(effect{
                      activeEffect = effect
                      effect()
                      activeEffect = null
                  }

                  function reactive(raw{
                      // use Object.defineProperty
                      // 1. iterate over the existing keys
                      Object.keys(raw).forEach(key => {
                          // 2. for each key: create a corresponding dep
                          const dep = new Dep()

                          // 3. rewrite the property into getter/setter
                          let realValue = raw[key]
                          Object.defineProperty(raw, key, {
                              get() {
                                  // 4. call dep methods inside getter/setter
                                  dep.depend()
                                  return realValue
                              },
                              set(newValue) {
                                  realValue = newValue
                                  dep.notify()
                              }
                          })
                      })
                      return raw
                  }

                  // vdom ---
                  function h(tag, props, children{
                      return { tag, props, children };
                  }

                  function mount(vnode, container, anchor{
                      const el = document.createElement(vnode.tag);
                      vnode.el = el;
                      // props
                      if (vnode.props) {
                          for (const key in vnode.props) {
                              if (key.startsWith('on')) {
                                  el.addEventListener(key.slice(2).toLowerCase(), vnode.props[key])
                              } else {
                                  el.setAttribute(key, vnode.props[key]);
                              }
                          }
                      }
                      if (vnode.children) {
                          if (typeof vnode.children === "string") {
                              el.textContent = vnode.children;
                          } else {
                              vnode.children.forEach(child => {
                                  mount(child, el);
                              });
                          }
                      }
                      if (anchor) {
                          container.insertBefore(el, anchor)
                      } else {
                          container.appendChild(el);
                      }
                  }

                  function patch(n1, n2{
                      // Implement this
                      // 1. check if n1 and n2 are of the same type
                      if (n1.tag !== n2.tag) {
                          // 2. if not, replace
                          const parent = n1.el.parentNode
                          const anchor = n1.el.nextSibling
                          parent.removeChild(n1.el)
                          mount(n2, parent, anchor)
                          return
                      }

                      const el = n2.el = n1.el

                      // 3. if yes
                      // 3.1 diff props
                      const oldProps = n1.props || {}
                      const newProps = n2.props || {}
                      for (const key in newProps) {
                          const newValue = newProps[key]
                          const oldValue = oldProps[key]
                          if (newValue !== oldValue) {
                              if (newValue != null) {
                                  el.setAttribute(key, newValue)
                              } else {
                                  el.removeAttribute(key)
                              }
                          }
                      }
                      for (const key in oldProps) {
                          if (!(key in newProps)) {
                              el.removeAttribute(key)
                          }
                      }
                      // 3.2 diff children
                      const oc = n1.children
                      const nc = n2.children
                      if (typeof nc === 'string') {
                          if (nc !== oc) {
                              el.textContent = nc
                          }
                      } else if (Array.isArray(nc)) {
                          if (Array.isArray(oc)) {
                              // array diff
                              const commonLength = Math.min(oc.length, nc.length)
                              for (let i = 0; i < commonLength; i++) {
                                  patch(oc[i], nc[i])
                              }
                              if (nc.length > oc.length) {
                                  nc.slice(oc.length).forEach(c => mount(c, el))
                              } else if (oc.length > nc.length) {
                                  oc.slice(nc.length).forEach(c => {
                                      el.removeChild(c.el)
                                  })
                              }
                          } else {
                              el.innerHTML = ''
                              nc.forEach(c => mount(c, el))
                          }
                      }
                  }

                  // paste all previous code from Codepen
                  const app = {
                      data: reactive({
                          count0
                      }),
                      render() {
                          return h('div', {
                              onClick() => {
                                  app.data.count++
                              }
                          }, String(app.data.count))
                      }
                  }

                  function mountApp(component, selector{
                      let isMounted = false
                      let oldTree
                      watchEffect(() => {
                          if (!isMounted) {
                              mount(oldTree = component.render(), document.querySelector(selector))
                              isMounted = true
                          } else {
                              const newTree = component.render()
                              patch(oldTree, newTree)
                              oldTree = newTree
                          }
                      })
                  }

                  mountApp(app, '#app')
              
          </script>
          </body>

          </html>

          關(guān)于作者

          作者:Vam的金豆之路。曾獲得2019年CSDN年度博客之星,CSDN博客訪問量已達(dá)到數(shù)百萬。掘金博客文章多次推送到首頁,總訪問量已達(dá)到數(shù)十萬。

          另外,我的公眾號(hào):前端歷劫之路,公眾號(hào)持續(xù)更新最新前端技術(shù)及相關(guān)技術(shù)文章。歡迎關(guān)注我的公眾號(hào),讓我們一起在前端道路上歷劫吧!Go!


          瀏覽 46
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  91天堂精品搭讪素人系列 | 拍拍拍免费视频 | 五月天操逼 | 大鸡吧av网站 | 国产成人高清 |