国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

vue.js動(dòng)態(tài)數(shù)據(jù)綁定學(xué)習(xí)

UsherChen / 2073人閱讀

摘要:對(duì)于的動(dòng)態(tài)數(shù)據(jù)綁定,經(jīng)過(guò)反復(fù)地看源碼和博客講解,總算能夠理解它的實(shí)現(xiàn)了,心累分享一下學(xué)習(xí)成果,同時(shí)也算是做個(gè)記錄。

對(duì)于vue.js的動(dòng)態(tài)數(shù)據(jù)綁定,經(jīng)過(guò)反復(fù)地看源碼和博客講解,總算能夠理解它的實(shí)現(xiàn)了,心累~ 分享一下學(xué)習(xí)成果,同時(shí)也算是做個(gè)記錄。完整代碼GitHub地址:https://github.com/hanrenguang/Dynamic-data-binding。也可以到倉(cāng)庫(kù)的 README 閱讀本文。

整體思路

不知道有沒(méi)有同學(xué)和我一樣,看著vue的源碼卻不知從何開(kāi)始,真叫人頭大。硬生生地看了observer, watcher, compile這幾部分的源碼,只覺(jué)得一臉懵逼。最終,從這里得到啟發(fā),作者寫得很好,值得一讀。

關(guān)于動(dòng)態(tài)數(shù)據(jù)綁定呢,需要搞定的是 Dep , Observer , Watcher , Compile 這幾個(gè)類,他們之間有著各種聯(lián)系,想要搞懂源碼,就得先了解他們之間的聯(lián)系。下面來(lái)理一理:

Observer 所做的就是劫持監(jiān)聽(tīng)所有屬性,當(dāng)有變動(dòng)時(shí)通知 Dep

WatcherDep 添加訂閱,同時(shí),屬性有變化時(shí),Observer 通知 Dep,Dep 則通知 Watcher

Watcher 得到通知后,調(diào)用回調(diào)函數(shù)更新視圖

Compile 則是解析所綁定元素的 DOM 結(jié)構(gòu),對(duì)所有需要綁定的屬性添加 Watcher 訂閱

由此可以看出,當(dāng)屬性發(fā)生變化時(shí),是由Observer -> Dep -> Watcher -> update view,Compile 在最開(kāi)始解析 DOM 并添加 Watcher 訂閱后就功成身退了。

從程序執(zhí)行的順序來(lái)看的話,即 new Vue({}) 之后,應(yīng)該是這樣的:先通過(guò) Observer 劫持所有屬性,然后 Compile 解析 DOM 結(jié)構(gòu),并添加 Watcher 訂閱,再之后就是屬性變化 -> Observer -> Dep -> Watcher -> update view,接下來(lái)就說(shuō)說(shuō)具體的實(shí)現(xiàn)。

從new一個(gè)實(shí)例開(kāi)始談起

網(wǎng)上的很多源碼解讀都是從 Observer 開(kāi)始的,而我會(huì)從 new 一個(gè)MVVM實(shí)例開(kāi)始,按照程序執(zhí)行順序去解釋或許更容易理解。先來(lái)看一個(gè)簡(jiǎn)單的例子:




    
    test


    

{{user.name}}

{{user.age}}

接下來(lái)都將以其為例來(lái)分析。下面來(lái)看一個(gè)簡(jiǎn)略的 MVVM 的實(shí)現(xiàn),在此將其命名為 hue。為了方便起見(jiàn),為 data 屬性設(shè)置了一個(gè)代理,通過(guò) vm._data 來(lái)訪問(wèn) data 的屬性顯得麻煩且冗余,通過(guò)代理,可以很好地解決這個(gè)問(wèn)題,在注釋中也有說(shuō)明。添加完屬性代理后,調(diào)用了一個(gè) observe 函數(shù),這一步做的就是 Observer 的屬性劫持了,這一步具體怎么實(shí)現(xiàn),暫時(shí)先不展開(kāi)。先記住他為 data 的屬性添加了 gettersetter

function Hue(options) {
    this.$options = options || {};
    let data = this._data = this.$options.data,
        self = this;

    Object.keys(data).forEach(function(key) {
        self._proxyData(key);
    });

    observe(data);

    self.$compile = new Compile(self, options.el || document.body);
}

// 為 data 做了一個(gè)代理,
// 訪問(wèn) vm.xxx 會(huì)觸發(fā) vm._data[xxx] 的getter,取得 vm._data[xxx] 的值,
// 為 vm.xxx 賦值則會(huì)觸發(fā) vm._data[xxx] 的setter
Hue.prototype._proxyData = function(key) {
    let self = this;
    Object.defineProperty(self, key, {
        configurable: false,
        enumerable: true,
        get: function proxyGetter() {
            return self._data[key];
        },
        set: function proxySetter(newVal) {
            self._data[key] = newVal;
        }
    });
};

再往下看,最后一步 new 了一個(gè) Compile,下面我們就來(lái)講講 Compile。

Compile

new Compile(self, options.el || document.body) 這一行代碼中,第一個(gè)參數(shù)是當(dāng)前 Hue 實(shí)例,第二個(gè)參數(shù)是綁定的元素,在上面的示例中為class為 .test 的div。

關(guān)于 Compile,這里只實(shí)現(xiàn)最簡(jiǎn)單的 textContent 的綁定。而 Compile 的代碼沒(méi)什么難點(diǎn),很輕易就能讀懂,所做的就是解析 DOM,并添加 Watcher 訂閱。關(guān)于 DOM 的解析,先將根節(jié)點(diǎn) el 轉(zhuǎn)換成文檔碎片 fragment 進(jìn)行解析編譯操作,解析完成后,再將 fragment 添加回原來(lái)的真實(shí) DOM 節(jié)點(diǎn)中。來(lái)看看這部分的代碼:

function Compile(vm, el) {
    this.$vm = vm;
    this.$el = this.isElementNode(el)
        ? el
        : document.querySelector(el);

    if (this.$el) {
        this.$fragment = this.node2Fragment(this.$el);
        this.init();
        this.$el.appendChild(this.$fragment);
    }
}

Compile.prototype.node2Fragment = function(el) {
    let fragment = document.createDocumentFragment(),
        child;

    // 也許有同學(xué)不太理解這一步,不妨動(dòng)手寫個(gè)小例子觀察一下他的行為
    while (child = el.firstChild) {
        fragment.appendChild(child);
    }

    return fragment;
};

Compile.prototype.init = function() {
    // 解析 fragment
    this.compileElement(this.$fragment);
};

以上面示例為例,此時(shí)若打印出 fragment,可觀察到其包含兩個(gè)p元素:

{{user.name}}

{{user.age}}

下一步就是解析 fragment,直接看代碼及注釋吧:

Compile.prototype.compileElement = function(el) {
    let childNodes = Array.from(el.childNodes),
        self = this;

    childNodes.forEach(function(node) {
        let text = node.textContent,
            reg = /{{(.*)}}/;

        // 若為 textNode 元素,且匹配 reg 正則
        // 在上例中會(huì)匹配 "{{user.name}}" 及 "{{user.age}}"
        if (self.isTextNode(node) && reg.test(text)) {
            // 解析 textContent,RegExp.$1 為匹配到的內(nèi)容,在上例中為 "user.name" 及 "user.age"
            self.compileText(node, RegExp.$1);
        }

        // 遞歸
        if (node.childNodes && node.childNodes.length) {
            self.compileElement(node);
        }
    });
};

Compile.prototype.compileText = function(node, exp) {
    // this.$vm 即為 Hue 實(shí)例,exp 為正則匹配到的內(nèi)容,即 "user.name" 或 "user.age"
    compileUtil.text(node, this.$vm, exp);
};

let compileUtil = {
    text: function(node, vm, exp) {
        this.bind(node, vm, exp, "text");
    },

    bind: function(node, vm, exp, dir) {
        // 獲取更新視圖的回調(diào)函數(shù)
        let updaterFn = updater[dir + "Updater"];

        // 先調(diào)用一次 updaterFn,更新視圖
        updaterFn && updaterFn(node, this._getVMVal(vm, exp));

        // 添加 Watcher 訂閱
        new Watcher(vm, exp, function(value, oldValue) {
            updaterFn && updaterFn(node, value, oldValue);
        });
    },

    // 根據(jù) exp,獲得其值,在上例中即 "vm.user.name" 或 "vm.user.age"
    _getVMVal: function(vm, exp) {
        let val = vm;
        exp = exp.trim().split(".");
        exp.forEach(function(k) {
            val = val[k];
        });
        return val;
    }
};

let updater = {
    // Watcher 訂閱的回調(diào)函數(shù)
    // 在此即更新 node.textContent,即 update view
    textUpdater: function(node, value) {
        node.textContent = typeof value === "undefined"
            ? ""
            : value;
    }
};

正如代碼中所看到的,Compile 在解析到 {{xxx}} 后便添加了 xxx 屬性的訂閱,即 new Watcher(vm, exp, callback)。理解了這一步后,接下來(lái)就需要了解怎么實(shí)現(xiàn)相關(guān)屬性的訂閱了。先從 Observer 開(kāi)始談起。

Observer

從最簡(jiǎn)單的情況來(lái)考慮,即不考慮數(shù)組元素的變化。暫時(shí)先不考慮 DepObserver 的聯(lián)系。先看看 Observer 構(gòu)造函數(shù):

function Observer(data) {
    this.data = data;
    this.walk(data);
}

Observer.prototype.walk = function(data) {
    const keys = Object.keys(data);
    // 遍歷 data 的所有屬性
    for (let i = 0; i < keys.length; i++) {
        // 調(diào)用 defineReactive 添加 getter 和 setter
        defineReactive(data, keys[i], data[keys[i]]);
    }
};

接下來(lái)通過(guò) Object.defineProperty 方法給所有屬性添加 gettersetter,就達(dá)到了我們的目的。屬性有可能也是對(duì)象,因此需要對(duì)屬性值進(jìn)行遞歸調(diào)用。

function defineReactive(obj, key, val) {
    // 對(duì)屬性值遞歸,對(duì)應(yīng)屬性值為對(duì)象的情況
    let childObj = observe(val);

    Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get: function() {
            // 直接返回屬性值
            return val;
        },
        set: function(newVal) {
            if (newVal === val) {
                return;
            }
            // 值發(fā)生變化時(shí)修改閉包中的 val,
            // 保證在觸發(fā) getter 時(shí)返回正確的值
            val = newVal;

            // 對(duì)新賦的值進(jìn)行遞歸,防止賦的值為對(duì)象的情況
            childObj = observe(newVal);
        }
    });
}

最后補(bǔ)充上 observe 函數(shù),也即 Hue 構(gòu)造函數(shù)中調(diào)用的 observe 函數(shù):

function observe(val) {
    // 若 val 是對(duì)象且非數(shù)組,則 new 一個(gè) Observer 實(shí)例,val 作為參數(shù)
    // 簡(jiǎn)單點(diǎn)說(shuō):是對(duì)象就繼續(xù)。
    if (!Array.isArray(val) && typeof val === "object") {
        return new Observer(val);
    }
}

這樣一來(lái)就對(duì) data 的所有子孫屬性(不知有沒(méi)有這種說(shuō)法。。)都進(jìn)行了“劫持”。顯然到目前為止,這并沒(méi)什么用,或者說(shuō)如果只做到這里,那么和什么都不做沒(méi)差別。于是 Dep 上場(chǎng)了。我認(rèn)為理解 DepObserverWatcher 之間的聯(lián)系是最重要的,先來(lái)談?wù)?DepObserver 里做了什么。

Observer & Dep

在每一次 defineReactive 函數(shù)被調(diào)用之后,都會(huì)在閉包中新建一個(gè) Dep 實(shí)例,即 let dep = new Dep()Dep 提供了一些方法,先來(lái)說(shuō)說(shuō) notify 這個(gè)方法,它做了什么事?就是在屬性值發(fā)生變化的時(shí)候通知 Dep,那么我們的代碼可以增加如下:

function defineReactive(obj, key, val) {
    let childObj = observe(val);
    const dep = new Dep();

    Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get: function() {
            return val;
        },
        set: function(newVal) {
            if (newVal === val) {
                return;
            }

            val = newVal;
            childObj = observe(newVal);

            // 發(fā)生變動(dòng)
            dep.notify();
        }
    });
}

如果僅考慮 ObserverDep 的聯(lián)系,即有變動(dòng)時(shí)通知 Dep,那么這里就算完了,然而在 vue.js 的源碼中,我們還可以看到一段增加在 getter 中的代碼:

// ...
get: function() {
    if (Dep.target) {
        dep.depend();
    }
    return val;
}
// ...

這個(gè) depend 方法呢,它又做了啥?答案是為閉包中的 Dep 實(shí)例添加了一個(gè) Watcher 的訂閱,而 Dep.target 又是啥?他其實(shí)是一個(gè) Watcher 實(shí)例,???一臉懵逼,先記住就好,先看一部份的 Dep 源碼:

// 標(biāo)識(shí)符,在 Watcher 中有用到,先不用管
let uid = 0;

function Dep() {
    this.id = uid++;
    this.subs = [];
}

Dep.prototype.depend = function() {
    // 這一步相當(dāng)于做了這么一件事:this.subs.push(Dep.target)
    // 即添加了 Watcher 訂閱,addDep 是 Watcher 的方法
    Dep.target.addDep(this);
};

// 通知更新
Dep.prototype.notify = function() {
    // this.subs 的每一項(xiàng)都為一個(gè) Watcher 實(shí)例
    this.subs.forEach(function(sub) {
        // update 為 Watcher 的一個(gè)方法,更新視圖
        // 沒(méi)錯(cuò),實(shí)際上這個(gè)方法最終會(huì)調(diào)用到 Compile 中的 updaterFn,
        // 也即 new Watcher(vm, exp, callback) 中的 callback
        sub.update();
    });
};

// 在 Watcher 中調(diào)用
Dep.prototype.addSub = function(sub) {
    this.subs.push(sub);
};

// 初始時(shí)引用為空
Dep.target = null;

也許看到這還是一臉懵逼,沒(méi)關(guān)系,接著往下。大概有同學(xué)會(huì)疑惑,為什么要把添加 Watcher 訂閱放在 getter 中,接下來(lái)我們來(lái)說(shuō)說(shuō)這 WatcherDep 的故事。

Watcher & Dep

先讓我們回顧一下 Compile 做的事,解析 fragment,然后給相應(yīng)屬性添加訂閱:new Watcher(vm, exp, cb)。new 了這個(gè) Watcher 之后,Watcher 怎么辦呢,就有了下面這樣的對(duì)話:

Watcher:hey Dep,我需要訂閱 exp 屬性的變動(dòng)。

Dep:這我可做不到,你得去找 exp 屬性中的 dep,他能做到這件事。

Watcher:可是他在閉包中啊,我無(wú)法和他聯(lián)系。

Dep:你拿到了整個(gè) Hue 實(shí)例 vm,又知道屬性 exp,你可以觸發(fā)他的 getter 啊,你在 getter 里動(dòng)些手腳不就行了。

Watcher:有道理,可是我得讓 dep 知道是我訂閱的啊,不然他通知不到我。

Dep:這個(gè)簡(jiǎn)單,我?guī)湍?,你每次觸發(fā) getter 前,把你的引用告訴 Dep.target 就行了。記得辦完事后給 Dep.target 置空。

于是就有了上面 getter 中的代碼:

// ...
get: function() {
    // 是否是 Watcher 觸發(fā)的
    if (Dep.target) {
        // 是就添加進(jìn)來(lái)
        dep.depend();
    }
    return val;
}
// ...

現(xiàn)在再回頭看看 Dep 部分的代碼,是不是好理解些了。如此一來(lái), Watcher 需要做的事情就簡(jiǎn)單明了了:

function Watcher(vm, exp, cb) {
    this.$vm = vm;
    this.cb = cb;
    this.exp = exp;
    this.depIds = new Set();

    // 返回一個(gè)用于獲取相應(yīng)屬性值的函數(shù)
    this.getter = parseGetter(exp.trim());

    // 調(diào)用 get 方法,觸發(fā) getter
    this.value = this.get();
}

Watcher.prototype.get = function() {
    const vm = this.$vm;
    // 將 Dep.target 指向當(dāng)前 Watcher 實(shí)例
    Dep.target = this;
    // 觸發(fā) getter
    let value = this.getter.call(vm, vm);
    // Dep.target 置空
    Dep.target = null;
    return value;
};

Watcher.prototype.addDep = function(dep) {
    const id = dep.id;
    if (!this.depIds.has(id)) {
        // 添加訂閱,相當(dāng)于 dep.subs.push(this)
        dep.addSub(this);
        this.depIds.add(id);
    }
};

function parseGetter(exp) {
    if (/[^w.$]/.test(exp)) {
        return;
    }

    let exps = exp.split(".");

    return function(obj) {
        for (let i = 0; i < exps.length; i++) {
            if (!obj)
                return;
            obj = obj[exps[i]];
        }
        return obj;
    };
}

最后還差一部分,即 Dep 通知變化后,Watcher 的處理,具體的函數(shù)調(diào)用流程是這樣的:dep.notify() -> sub.update(),直接上代碼:

Watcher.prototype.update = function() {
    this.run();
};

Watcher.prototype.run = function() {
    let value = this.get();
    let oldVal = this.value;

    if (value !== oldVal) {
        this.value = value;
        // 調(diào)用回調(diào)函數(shù)更新視圖
        this.cb.call(this.$vm, value, oldVal);
    }
};
結(jié)語(yǔ)

到這就算寫完了,本人水平有限,若有不足之處歡迎指出,一起探討。

參考資料

https://github.com/DMQ/mvvm

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

轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/83072.html

相關(guān)文章

  • vue.js學(xué)習(xí)筆記

    摘要:指令的職責(zé)是,當(dāng)表達(dá)式的值改變時(shí),將其產(chǎn)生的連帶影響,響應(yīng)式地作用于。對(duì)象形式佐客湯姆咪口修飾符修飾符是以半角句號(hào)指明的特殊后綴,用于指出一個(gè)指令應(yīng)該以特殊方式綁定。修飾符修飾符允許你控制由精確的系統(tǒng)修飾符組合觸發(fā)的事件。 指令 指令(Directives)是帶有v-前綴的特殊屬性。指令的職責(zé)是,當(dāng)表達(dá)式的值改變時(shí),將其產(chǎn)生的連帶影響,響應(yīng)式地作用于DOM。 v-if條件判斷 T...

    levy9527 評(píng)論0 收藏0
  • Vue.js基礎(chǔ)詳解

    摘要:指令帶有前綴,以表示它們是提供的特殊屬性。最后,我們需要為賦值世界舞王尼古拉斯趙四世界舞王尼古拉斯趙四初學(xué)就到這里了,相信你已經(jīng)在腦子里確定了的原理的概念也已經(jīng)非常清楚了,希望你能夠在學(xué)習(xí)的道路上越走越遠(yuǎn),最后感謝你的瀏覽。 vue.js vue介紹 Vue.js(讀音 /vju?/,類似于 view) 是一套構(gòu)建用戶界面的漸進(jìn)式框架。與其他重量級(jí)框架不同的是,Vue 采用自底向上增量...

    omgdog 評(píng)論0 收藏0
  • Vue.js-計(jì)算屬性和class與style綁定

    摘要:每一個(gè)計(jì)算屬性都包含一個(gè)和一個(gè)。使用計(jì)算屬性的原因在于它的依賴緩存。及與綁定的主要用法是動(dòng)態(tài)更新元素上的屬性。測(cè)試文字當(dāng)?shù)谋磉_(dá)式過(guò)長(zhǎng)或邏輯復(fù)雜時(shí),還可以綁定一個(gè)計(jì)算屬性。 學(xué)習(xí)筆記:前端開(kāi)發(fā)文檔 計(jì)算屬性 所有的計(jì)算屬性都以函數(shù)的形式寫在Vue實(shí)例中的computed選項(xiàng)內(nèi),最終返回計(jì)算后的結(jié)果。 計(jì)算屬性的用法 在一個(gè)計(jì)算屬性中可以完成各種復(fù)雜的邏輯,包括運(yùn)算、函數(shù)調(diào)用等,只要最終...

    Shihira 評(píng)論0 收藏0
  • vue學(xué)習(xí)筆記(三)

    摘要:直接創(chuàng)建組件使用標(biāo)簽名組件模板,根據(jù)組件構(gòu)造器來(lái)創(chuàng)建組件。相應(yīng)的,實(shí)例也被稱為父組件。而且不允許子組件直接修改父組件中的數(shù)據(jù),強(qiáng)制修改會(huì)報(bào)錯(cuò)。 一、組件 (一)什么是組件 組件(Component)是 Vue.js最強(qiáng)大的功能之一。組件可以擴(kuò)展 HTML元素,封裝可重用的代碼組件是自定義元素(對(duì)象)。 (二)創(chuàng)建組件的兩種方式 官方推薦組件標(biāo)簽名是使用-連接的組合詞,例如:。 1、使用...

    fsmStudy 評(píng)論0 收藏0
  • vue學(xué)習(xí)筆記(三)

    摘要:直接創(chuàng)建組件使用標(biāo)簽名組件模板,根據(jù)組件構(gòu)造器來(lái)創(chuàng)建組件。相應(yīng)的,實(shí)例也被稱為父組件。而且不允許子組件直接修改父組件中的數(shù)據(jù),強(qiáng)制修改會(huì)報(bào)錯(cuò)。 一、組件 (一)什么是組件 組件(Component)是 Vue.js最強(qiáng)大的功能之一。組件可以擴(kuò)展 HTML元素,封裝可重用的代碼組件是自定義元素(對(duì)象)。 (二)創(chuàng)建組件的兩種方式 官方推薦組件標(biāo)簽名是使用-連接的組合詞,例如:。 1、使用...

    zsirfs 評(píng)論0 收藏0
  • Vue學(xué)習(xí)筆記(一)

    摘要:一介紹也稱為,讀音類似,錯(cuò)誤讀音,由華人尤雨溪開(kāi)源并維護(hù)。隱藏四事件之前說(shuō)了一些關(guān)于事件的指令,這里詳細(xì)學(xué)習(xí)一下事件的相關(guān)知識(shí)。還有一些其他鍵盤事件,具體參考官方文檔。模板就是,用來(lái)進(jìn)行數(shù)據(jù)綁定,顯示在頁(yè)面中,也稱為語(yǔ)法。 一、Vue.js介紹 Vue.js也稱為Vue,讀音類似view,錯(cuò)誤讀音v-u-e,由華人尤雨溪開(kāi)源并維護(hù)。 Vue有以下特點(diǎn): 是一個(gè)構(gòu)建用戶界面的框架 是一...

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

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

0條評(píng)論

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