成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

Vue源碼解析(二)Vue的雙向綁定講解及實(shí)現(xiàn)

ckllj / 2350人閱讀

摘要:上篇文章,我們講解了的屬性映射和方法的重定義,鏈接地址如下源碼解析一屬性映射和函數(shù)引用的重定義這篇文章給大家?guī)?lái)的是的雙向綁定講解。這就是的雙向綁定。使用定時(shí)器定時(shí)檢查的值,發(fā)生變化就通知訂閱者。這個(gè)方法不好,定時(shí)器不能實(shí)時(shí)反應(yīng)變化。

文章中的代碼時(shí)階段,可以下載源碼測(cè)試一下。
git項(xiàng)目地址:https://github.com/xubaodian/...

項(xiàng)目使用webpack構(gòu)建,下載后先執(zhí)行:

npm install

安裝依賴后使用指令:

npm run dev

可以運(yùn)行項(xiàng)目。

上篇文章,我們講解了Vue的data屬性映射和方法的重定義,鏈接地址如下:

Vue源碼解析(一)data屬性映射和methods函數(shù)引用的重定義

這篇文章給大家?guī)?lái)的是Vue的雙向綁定講解。

什么是雙向綁定

我們看一張圖:



可以看到,輸入框上方的內(nèi)同和輸入框中的值是一致的。輸入框的之變化,上方的值跟著一起變化。

這就是Vue的雙向綁定。

對(duì)象屬性監(jiān)聽(tīng)實(shí)現(xiàn)

我們先不著急了解Vue時(shí)如何實(shí)現(xiàn)這一功能的,如果我們自己要實(shí)現(xiàn)這樣的功能,如何實(shí)現(xiàn)呢?

我的思路是這樣:



可以分為幾個(gè)步驟,如下:

1、首先給輸入框添加input事件,監(jiān)視輸入值,存放在變量value中。

2、監(jiān)視value變量,確保value變化時(shí),監(jiān)視器可以發(fā)現(xiàn)。

3、若value發(fā)生變化,則重新渲染視圖。

上面三個(gè)步驟,1(addEventListener)和3(操作dom)都很好實(shí)現(xiàn),對(duì)于2的實(shí)現(xiàn),可能有一下兩個(gè)方案:

1、使用Object.defineProperty()重新定義對(duì)象set和get,在值發(fā)生變化時(shí),通知訂閱者。

2、使用定時(shí)器定時(shí)檢查value的值,發(fā)生變化就通知訂閱者。(這個(gè)方法不好,定時(shí)器不能實(shí)時(shí)反應(yīng)value變化)。

Vue源碼中采用了方案1,我們首先用方案1實(shí)現(xiàn)對(duì)對(duì)象值的監(jiān)聽(tīng),代碼如下:

function defineReactive(obj, key, val, customSetter) {
  //獲取對(duì)象給定屬性的描述符
  let property = Object.getOwnPropertyDescriptor(obj, key);
  //對(duì)象該屬性不可配置,直接返回
  if (property && property.configurable === false) {
    return;
  }

  //獲取屬性get和set屬性,若此前該屬性已經(jīng)進(jìn)行監(jiān)聽(tīng),則確保監(jiān)聽(tīng)屬性不會(huì)被覆蓋
  let getter = property && property.get;
  let setter = property && property.set;
  
  if (arguments.length < 3) {
    val = obj[key];
  }

  //監(jiān)聽(tīng)屬性
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      const value = getter ? getter.call(obj) : val;
      console.log(`讀取了${key}屬性`);
      return value;
    },
    set: function reactiveSetter (newVal) {
      const value = getter ? getter.call(obj) : val;
      //如果值沒(méi)有變化,則不做改動(dòng)
      if (newVal === value) {
        return;
      }
      //自定義響應(yīng)函數(shù)
      if (customSetter) {
        customSetter(newVal);
      }
      if (setter) {
        setter.call(obj, newVal);
      } else {
        val = newVal;
      }
      console.log(`屬性${key}發(fā)生變化:${value} => ${newValue}`);
    }
  })
}

下面我們測(cè)試下,測(cè)試代碼如下:

let obj = {
    name: "xxx",
    age: 20
};
defineReactive(obj, "name");
let name = obj.name;
obj.name = "1111";

控制臺(tái)輸出為:

讀取了name屬性
test.html:51 屬性name發(fā)生變化:xxx => 1111

可見(jiàn),我們已經(jīng)實(shí)現(xiàn)了對(duì)obj對(duì)象name屬性讀和寫的監(jiān)聽(tīng)。

實(shí)現(xiàn)了監(jiān)聽(tīng),這沒(méi)問(wèn)題,但是視圖怎么知道這些屬性發(fā)生了變化呢?可以使用發(fā)布訂閱模式實(shí)現(xiàn)。

發(fā)布訂閱模式

什么是發(fā)布訂閱模式呢?

我畫了一個(gè)示意圖,如下:


發(fā)布訂閱模式有幾個(gè)部分構(gòu)成:

1、訂閱中心,管理訂閱者列表,發(fā)布者發(fā)消息時(shí),通知相應(yīng)的訂閱者。

2、訂閱者,這個(gè)是訂閱消息的主體,就像關(guān)注微信公眾號(hào)一樣,有文章就會(huì)通知關(guān)注者。

3、發(fā)布者,類似微信公眾號(hào)的文章發(fā)布者。

訂閱中心的代碼如下:

export class Dep {
  constructor() {
    this.id = uid++;
    //訂閱列表
    this.subs = [];
  }

  //添加訂閱
  addSub(watcher) {
    this.subs.push(watcher);
  }

  //刪除訂閱者
  remove(watcher) {
    let index = this.subs.findIndex(item => item.id === watcher.id);
    if (index > -1) {
      this.subs.splice(index, 1);
    }
  }


  depend () {
    if (Dep.target) {
      Dep.target.addDep(this);
    }
  }

//通知訂閱者
  notify() {
    this.subs.map(item => {
      item.update();
    });
  }
}

//訂閱中心  靜態(tài)變量,訂閱時(shí)使用
Dep.target = null;
const targetStack = [];

export function pushTarget (target) {
  targetStack.push(target);
  Dep.target = target;
}

export function popTarget () {
  targetStack.pop();
  Dep.target = targetStack[targetStack.length - 1];
}

訂閱中心已經(jīng)實(shí)現(xiàn),還有發(fā)布者和訂閱者,先看下發(fā)布者,這里誰(shuí)是發(fā)布者呢?

沒(méi)錯(cuò),就是defineReactive函數(shù),這個(gè)函數(shù)實(shí)現(xiàn)了對(duì)data屬性的監(jiān)聽(tīng),它可以檢測(cè)到data屬性的修改,發(fā)生修改時(shí),通知訂閱中心,所以defineReactive做一些修改,如下:

//屬性監(jiān)聽(tīng)
export function defineReactive(obj, key, val, customSetter) {
  //獲取對(duì)象給定屬性的描述符
  let property = Object.getOwnPropertyDescriptor(obj, key);
  //對(duì)象該屬性不可配置,直接返回
  if (property && property.configurable === false) {
    return;
  }

  //訂閱中心
  const dep = new Dep();

  //獲取屬性get和set屬性,若此前該屬性已經(jīng)進(jìn)行監(jiān)聽(tīng),則確保監(jiān)聽(tīng)屬性不會(huì)被覆蓋
  let getter = property && property.get;
  let setter = property && property.set;
  
  if (arguments.length < 3) {
    val = obj[key];
  }

  //如果監(jiān)聽(tīng)的是一個(gè)對(duì)象,繼續(xù)深入監(jiān)聽(tīng)
  let childOb = observe(val);
  //監(jiān)聽(tīng)屬性
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      const value = getter ? getter.call(obj) : val;
      //這段代碼時(shí)添加訂閱時(shí)使用的
      if (Dep.target) {
        dep.depend();
        if (childOb) {
          childOb.dep.depend();
        }
      }
      return value;
    },
    set: function reactiveSetter (newVal) {
      const value = getter ? getter.call(obj) : val;
      //如果值沒(méi)有變化,則不做改動(dòng)
      if (newVal === value) {
        return;
      }
      //自定義響應(yīng)函數(shù)
      if (customSetter) {
        customSetter(newVal);
      }
      if (setter) {
        setter.call(obj, newVal);
      } else {
        val = newVal;
      }
      //如果新的值為對(duì)象,重新監(jiān)聽(tīng)
      childOb = observe(newVal);
      /**
       * 訂閱中心通知所有訂閱者
       **/
      dep.notify();
    }
  })
}

這里設(shè)計(jì)到閉包的概念,我們?cè)诤瘮?shù)里定義了:

 const dep = new Dep();

由于set和get函數(shù)一直都存在的,所有dep會(huì)一直存在,不會(huì)被回收。

當(dāng)值發(fā)生變化后,利用下面的代碼通知訂閱者:

dep.notify();

訂閱中心和發(fā)布者都有了,我們何時(shí)訂閱呢?或者什么時(shí)間訂閱合適呢?

我們是希望實(shí)現(xiàn)當(dāng)讀取data屬性時(shí)候,實(shí)現(xiàn)訂閱。所以在defineReactive函數(shù)的get監(jiān)聽(tīng)中添加了如下代碼:

    if (Dep.target) {
        dep.depend();
        if (childOb) {
          childOb.dep.depend();
        }
    }
    return value;

Dep.target是一個(gè)靜態(tài)變量,用來(lái)存儲(chǔ)訂閱者的,每次訂閱前指向訂閱者,訂閱者置為null。

訂閱者代碼如下:

let uid = 0;
//訂閱者類
export class Watcher{
  //構(gòu)造器,vm是vue實(shí)例
  constructor(vm, expOrFn, cb) {
    this.vm = vm;
    this.cb = cb;
    this.id = uid++;
    this.deps = [];
    if (typeof expOrFn === "function") {
      this.getter = expOrFn
    } else {
      this.getter = parsePath(expOrFn)
    }
    this.value = this.get();
  }

  //將訂閱這添加到訂閱中心
  get() {
    //訂閱前,設(shè)置Dep.target變量,指向自身
    pushTarget(this)
    let value;
    const vm = this.vm;
    /**
     * 這個(gè)地方讀取data屬性,觸發(fā)下面的訂閱代碼,
     *  if (Dep.target) {
     *      dep.depend();
     *     if (childOb) {
     *       childOb.dep.depend();
     *     }
     *   }
     *   return value;
     **/
    value = this.getter.call(vm, vm);
    //訂閱后,置Dep.target為null
    popTarget();
    return value
  }

  //值變化,調(diào)用回調(diào)函數(shù)
  update() {
    this.cb(this.value);
  }

  //添加依賴
  addDep(dep) {
    this.deps.push(dep);
    dep.addSub(this);
  }
}

//解析類屬性的路徑,例如obj.sub.name,返回實(shí)際的值
export function parsePath (path){
  const segments = path.split(".");
  return function (obj) {
    for (let i = 0; i < segments.length; i++) {
      if (!obj) return;
      obj = obj[segments[i]];
    }
    return obj;
  }
}

除了發(fā)布訂閱以外,雙向綁定還需要編譯dom。

dom編譯和input綁定

主要實(shí)現(xiàn)兩個(gè)功能:

1、將dom中的{{key}}元素替換為Vue中的屬性。

2、檢測(cè)帶有v-model屬性的input元素,添加input事件,有修改時(shí),修改Vue實(shí)例的屬性。

檢測(cè)v-model,綁定事件的代碼如下:

export function initModelMixin(Vue) {
    Vue.prototype._initModel = function () {
        if (this._dom == undefined) {
            if (this.$options.el) {
                let el = this.$options.el;
                let dom = document.querySelector(el);
                if (dom) {
                    this._dom = dom;
                } else {
                    console.error(`未發(fā)現(xiàn)dom: ${el}`);
                }
           } else {
               console.error("vue實(shí)例未綁定dom");
           }
        } 
        bindModel(this._dom, this);
    } 
}

//input輸入框有V-model屬性,則綁定input事件
function bindModel(dom, vm) {
    if (dom) {
        if (dom.tagName === "INPUT") {
            let attrs = Array.from(dom.attributes);
            attrs.map(item => {
                if (item.name === "v-model") {
                    let value = item.value;
                    dom.value = getValue(vm, value);
                    //綁定事件,暫不考慮清除綁定,因此刪除dom造成的內(nèi)存泄露我們暫不考慮,這些問(wèn)題后續(xù)解決
                    dom.addEventListener("input", (event) => {
                        setValue(vm, value, event.target.value);
                    });
                }
            })
        }
        let children = Array.from(dom.children);
        if (children) {
            children.map(item => {
                bindModel(item, vm);
            });
        }
    }
}

替換dom中{{key}}類似的屬性代碼:

export function renderMixin(Vue) {
    Vue.prototype._render = function () {
        if (this._dom == undefined) {
            if (this.$options.el) {
                let el = this.$options.el;
                let dom = document.querySelector(el);
                if (dom) {
                    this._dom = dom;
                } else {
                    console.error(`未發(fā)現(xiàn)dom: ${el}`);
                }
           } else {
               console.error("vue實(shí)例未綁定dom");
           }
        } 
        replaceText(this._dom, this);
    } 
}

//替換dom的innerText
function replaceText(dom, vm) {
    if (dom) {
        let children = Array.from(dom.childNodes);
        children.map(item => {
            if (item.nodeType === 3) {
                if (item.originStr === undefined) {
                    item.originStr = item.nodeValue;
                }
                let str = replaceValue(item.originStr, function(key){
                    return getValue(vm, key);
                });
                item.nodeValue = str;
            } else if (item.nodeType === 1) {
                replaceText(item, vm);
            }
        });
    }
}

到此位置,就實(shí)現(xiàn)了雙向綁定。

測(cè)試代碼如下,因?yàn)槲矣脀ebpack構(gòu)建的前端項(xiàng)目,html模板如下:




  
  
  test
  
  


  
{{name}}

main.js代碼:

import { Vue } from "../src/index";

let options = {
    el: "#app",
    data: {
        name: "xxx",
        age: 18
    },
    methods: {
        sayName() {
            console.log(this.name);
        }
    }
}


let vm = new Vue(options);

效果如下:



可以下載源碼嘗試,git項(xiàng)目地址:https://github.com/xubaodian/...

項(xiàng)目使用webpack構(gòu)建,下載后先執(zhí)行:

npm install

安裝依賴后使用指令:

npm run dev

可以運(yùn)行項(xiàng)目。

如有疑問(wèn),歡迎留言或發(fā)送郵件至[email protected]。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/100593.html

相關(guān)文章

  • Vue原理】VModel - 白話版

    摘要:執(zhí)行的時(shí)候,會(huì)綁定上下文對(duì)象為組件實(shí)例于是中的就能取到組件實(shí)例本身,的代碼塊頂層作用域就綁定為了組件實(shí)例于是內(nèi)部變量的訪問(wèn),就會(huì)首先訪問(wèn)到組件實(shí)例上。其中的獲取,就會(huì)先從組件實(shí)例上獲取,相當(dāng)于。 寫文章不容易,點(diǎn)個(gè)贊唄兄弟專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內(nèi)部詳情,讓我們一起學(xué)習(xí)吧研究基于 Vue版本 【2.5.17】 如果你覺(jué)得...

    keke 評(píng)論0 收藏0
  • 剖析Vue原理&實(shí)現(xiàn)雙向綁定MVVM

    摘要:所以無(wú)需太過(guò)介懷是實(shí)現(xiàn)的單向或雙向綁定。監(jiān)聽(tīng)數(shù)據(jù)綁定更新函數(shù)的處理是在這個(gè)方法中,通過(guò)添加回調(diào)來(lái)接收數(shù)據(jù)變化的通知至此,一個(gè)簡(jiǎn)單的就完成了,完整代碼。 本文能幫你做什么?1、了解vue的雙向數(shù)據(jù)綁定原理以及核心代碼模塊2、緩解好奇心的同時(shí)了解如何實(shí)現(xiàn)雙向綁定為了便于說(shuō)明原理與實(shí)現(xiàn),本文相關(guān)代碼主要摘自vue源碼, 并進(jìn)行了簡(jiǎn)化改造,相對(duì)較簡(jiǎn)陋,并未考慮到數(shù)組的處理、數(shù)據(jù)的循環(huán)依賴等,也...

    melody_lql 評(píng)論0 收藏0
  • 剖析Vue實(shí)現(xiàn)原理 - 如何實(shí)現(xiàn)雙向綁定mvvm(轉(zhuǎn)載)

    摘要:接下來(lái)要看看這個(gè)訂閱者的具體實(shí)現(xiàn)了實(shí)現(xiàn)訂閱者作為和之間通信的橋梁,主要做的事情是在自身實(shí)例化時(shí)往屬性訂閱器里面添加自己自身必須有一個(gè)方法待屬性變動(dòng)通知時(shí),能調(diào)用自身的方法,并觸發(fā)中綁定的回調(diào),則功成身退。 本文能幫你做什么?1、了解vue的雙向數(shù)據(jù)綁定原理以及核心代碼模塊2、緩解好奇心的同時(shí)了解如何實(shí)現(xiàn)雙向綁定為了便于說(shuō)明原理與實(shí)現(xiàn),本文相關(guān)代碼主要摘自vue源碼, 并進(jìn)行了簡(jiǎn)化改造,...

    nemo 評(píng)論0 收藏0
  • Vue原理】VModel - 源碼版 之 表單元素綁定流程

    摘要:首先,兄弟,容我先說(shuō)幾句涉及源碼很多,篇幅很長(zhǎng),我都已經(jīng)分了上下三篇了,依然這么長(zhǎng),但是其實(shí)內(nèi)容都差不多一樣,但是我還是毫無(wú)保留地給你了。 寫文章不容易,點(diǎn)個(gè)贊唄兄弟專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內(nèi)部詳情,讓我們一起學(xué)習(xí)吧研究基于 Vue版本 【2.5.17】 如果你覺(jué)得排版難看,請(qǐng)點(diǎn)擊 下面鏈接 或者 拉到 下面關(guān)注公眾號(hào)也...

    sarva 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<