成人无码视频,亚洲精品久久久久av无码,午夜精品久久久久久毛片,亚洲 中文字幕 日韩 无码

資訊專欄INFORMATION COLUMN

Vue面試題精選:Vue原理以及雙向數(shù)據(jù)綁定的實戰(zhàn)過程

malakashi / 1481人閱讀

摘要:雙向數(shù)據(jù)綁定指的是,將對象屬性變化與視圖的變化相互綁定。數(shù)據(jù)雙向綁定已經(jīng)了解到是通過數(shù)據(jù)劫持的方式來做數(shù)據(jù)綁定的,其中最核心的方法便是通過來實現(xiàn)對屬性的劫持,達到監(jiān)聽數(shù)據(jù)變動的目的。和允許觀察數(shù)據(jù)的更改并觸發(fā)更新。

1 MVVM

雙向數(shù)據(jù)綁定指的是,將對象屬性變化與視圖的變化相互綁定。換句話說,如果有一個擁有name屬性的user對象,與元素的內(nèi)容綁定,當給user.name賦予一個新值,頁面元素節(jié)點也會相應(yīng)的顯示新的數(shù)據(jù)。同樣的,如果頁面元素(通常是input)上的數(shù)據(jù)改變,輸入一個新的值會導(dǎo)致user對象中的name屬性發(fā)生變化。

MVVM最早由微軟提出來,它借鑒了桌面應(yīng)用程序的MVC思想,在前端頁面中,把Model用純JavaScript對象表示,View負責顯示,兩者做到了最大限度的分離。
把Model和View關(guān)聯(lián)起來的就是ViewModel。ViewModel負責把Model的數(shù)據(jù)同步到View顯示出來,還負責把View的修改同步回Model。

總之一句話,數(shù)據(jù)與表現(xiàn)分離,當某一個數(shù)據(jù)改變時,頁面上所有使用這個數(shù)據(jù)的元素的內(nèi)容都會改變。下面是一個最簡單的數(shù)據(jù)綁定的例子,來自Vue2.0源碼閱讀筆記–雙向綁定實現(xiàn)原理,這個例子十分簡單粗暴,就做了三件事:

創(chuàng)建 obj 對象,用來保存數(shù)據(jù)
監(jiān)聽 keyup 事件,當事件觸發(fā)時,把選定的 input 標簽的值賦給 obj 對象的 hello 屬性。
改變 obj 對象 的 hello 屬性的 set 方法,當 hell 被賦值時,將這個值同時賦值給選中的兩個元素。







1.1 實現(xiàn)數(shù)據(jù)雙向綁定的方式

雙向數(shù)據(jù)綁定底層的思想非常的基本,它可以被壓縮成為三個步驟:

我們需要一個方法來識別哪個UI元素被綁定了相應(yīng)的屬性(上面的例子里直接選中了元素,而沒有提供對外的函數(shù))
我們需要監(jiān)視屬性和UI元素的變化
我們需要將所有變化傳播到綁定的對象和元素
常見的實現(xiàn)數(shù)據(jù)綁定的方法,有大致如下幾種:

發(fā)布者-訂閱者模式
臟值檢查
數(shù)據(jù)劫持
其中最簡單也是最有效的途徑,是使用發(fā)布者-訂閱者模式。上面的例子就使用到了。

發(fā)布者-訂閱者模式的思想很簡單:使用自定義的data屬性在HTML代碼中指明綁定。所有綁定起來的JavaScript對象以及DOM元素都將“訂閱”一個發(fā)布者對象。任何時候,如果某一個被綁定的內(nèi)容(如JavaScript對象或者一個HTML輸入字段)被偵測到發(fā)生了變化,我們將代理事件到發(fā)布者-訂閱者模式,這會反過來將變化廣播并傳播到所有綁定的對象和元素。下面是一個來自談?wù)凧avaScript中的雙向數(shù)據(jù)綁定的例子,我在注釋里添加了一些我的理解。

function DataBinder(object_id){

//創(chuàng)建一個簡單地PubSub對象   
var pubSub = { // 一個pubSub 對象,內(nèi)部有一個 callbacks 對象,保存回調(diào)函數(shù)
    callbacks: {}, // 鍵名為觸發(fā)回調(diào)函數(shù)的自定義事件名稱,值為一個數(shù)組,每一項都是一個回調(diào)函數(shù)
    on: function(msg,callback){ // on 方法 傳入?yún)?shù),一個字符串(就是自定義事件的名稱),一個回調(diào)函數(shù)
        this.callbacks[msg] = this.callbacks[msg] || []; // 以 msg 作為鍵名,創(chuàng)建數(shù)組(如果存在,等于原數(shù)組)
        this.callbacks[msg].push(callback); // 將新的回調(diào)函數(shù)加入數(shù)組
    },
    publish: function(msg){ // publish 方法
        this.callbacks[msg] = this.callbacks[msg] || []; // 根據(jù) msg 傳入的參數(shù),調(diào)用 this.callbacks 對象 的 msg 屬性保存的數(shù)組,如果沒有,等于新建的空數(shù)組
        for(var i = 0, len = this.callbacks[msg].length; i

}

//在model的設(shè)置器中
function User(uid){

var binder = new DataBinder(uid), // 返回一個 pubSub 對象,其上保存了由傳入?yún)?shù) uid 確定的元素所有綁定的回調(diào)函數(shù)
user = {
    attributes: {}, // 保存需要同步的數(shù)據(jù)
    set: function(attr_name,val){ // 調(diào)用 set 方法,將需要同步的數(shù)據(jù)通過 publish 方法傳給監(jiān)聽的元素
        this.attributes[attr_name] = val;
        //使用“publish”方法  
        binder.publish(uid+ ":change", attr_name, val,this);
    },
    get: function(attr_name){
        return this.attributes[attr_name];
    }
}
return user; // 函數(shù)作為一個構(gòu)造函數(shù)時,返回一個對象,作為這個構(gòu)造函數(shù)的實例

}

var user = new User(123); // 返回一個 user 對象,對象有一個 attributes 屬性指向一個對象,這個對象保存這需要同步的數(shù)據(jù)
user.set("name","Wolfgang"); // 所有帶有 data-bind-123="name" 屬性的 html 標簽都會被監(jiān)聽,它們的值會同步改變,保持相同
然后說臟檢查,臟檢查是一種不關(guān)心你如何以及何時改變的數(shù)據(jù),只關(guān)心在特定的檢查階段數(shù)據(jù)是否改變的數(shù)據(jù)監(jiān)聽技術(shù)。簡單來說,臟檢查是直接檢測數(shù)據(jù)是否改變,如果某一個被監(jiān)聽的數(shù)據(jù)改變,就將這個值傳給所有被被監(jiān)聽者。

而數(shù)據(jù)劫持,就是通過對屬性的 set get 方法進行改造,來監(jiān)測數(shù)據(jù)的改變,發(fā)布消息給訂閱者,觸發(fā)相應(yīng)的監(jiān)聽回調(diào)。

2 vue 數(shù)據(jù)雙向綁定

已經(jīng)了解到vue是通過數(shù)據(jù)劫持的方式來做數(shù)據(jù)綁定的,其中最核心的方法便是通過Object.defineProperty()來實現(xiàn)對屬性的劫持,達到監(jiān)聽數(shù)據(jù)變動的目的。

要實現(xiàn)mvvm的雙向綁定,主要進行了:

實現(xiàn)一個數(shù)據(jù)監(jiān)聽器Observer,能夠?qū)?shù)據(jù)對象的所有屬性進行監(jiān)聽,如有變動可拿到最新值并通知訂閱者
實現(xiàn)一個指令解析器Compile,對每個元素節(jié)點的指令進行掃描和解析,根據(jù)指令模板替換數(shù)據(jù),以及綁定相應(yīng)的更新函數(shù)
實現(xiàn)一個Watcher,作為連接Observer和Compile的橋梁,能夠訂閱并收到每個屬性變動的通知,執(zhí)行指令綁定的相應(yīng)回調(diào)函數(shù),從而更新視圖
mvvm入口函數(shù),整合以上三者
例子大體來自這篇文章的,我根據(jù)自己的理解做了些修改,添加了一些注釋

為了便于理解,首先,來實現(xiàn)一個消息的儲存中轉(zhuǎn)的構(gòu)造函數(shù):

var uid = 0; // 通過全局的 uid 給 Dep 實例增加唯一 id,以區(qū)分不同實例

function Dep() {

this.id = uid++; // 給 Dep 實例添加 id,并將全局的 uid 加1
this.subs = [];

}
Dep.prototype = {

addSub: function(sub) { // 增加 sub
    this.subs.push(sub);
},

depend: function() {
    Dep.target.addDep(this); // 將全局對象 Dep 的 target 屬性指向的對象(這個函數(shù)的調(diào)用者 this)添加的 subs 里
},

removeSub: function(sub) { // 刪處 sub
    var index = this.subs.indexOf(sub);
    if (index != -1) {
        this.subs.splice(index, 1);
    }
},

notify: function() { // 通知所有 subs 數(shù)據(jù)已更新
    this.subs.forEach(function(sub) {
        sub.update();
    });
}

};
通過修改對象的屬性,每一個綁定的屬性都會有一個 Dep 實例。每一個 Dep 實例都會有一個 subs 屬性,用來存儲需要通知的對象,當對象屬性改變時,通過 set 方法,調(diào)用這個屬性的 Dep 實例的原型的 notify 方法,根據(jù) subs 數(shù)組保存的內(nèi)容,通知綁定了這個屬性值的數(shù)據(jù)修改內(nèi)容。

function Observer(data) {

this.data = data;
this.walk(data); // 調(diào)用原型的方法,處理對象

}

Observer.prototype = {

walk: function(data) {
    var me = this;
    Object.keys(data).forEach(function(key) { // 遍歷 data 的屬性,修改屬性的 get / set
        me.convert(key, data[key]);
    });
},
convert: function(key, val) {
    this.defineReactive(this.data, key, val);
},

defineReactive: function(data, key, val) { // 對屬性進行修改
    var dep = new Dep();
    var childObj = observe(val);

    Object.defineProperty(data, key, {
        enumerable: true, // 可枚舉
        configurable: false, // 不能再define
        get: function() {
            if (Dep.target) {
                dep.depend(); // 將全局的 Dep.target 添加到 dep 實例的 subs 數(shù)組里
            }
            return val;
        },
        set: function(newVal) {
            if (newVal === val) {
                return;
            }
            val = newVal;
            // 新的值是object的話,進行監(jiān)聽
            childObj = observe(newVal);
            // 通知訂閱者
            dep.notify();
        }
    });
}

};

function observe(value, vm) {

if (!value || typeof value !== "object") {
    return;
}

return new Observer(value);

};
然后對 html 模板進行編譯,根據(jù)每個節(jié)點及其的屬性,判斷是否包含 ‘{{}}’,’v-‘,’on’ 等特殊字符串,判斷是否進行了綁定,將綁定了的屬性個 get set 進行處理,

function Compile(el, vm) {

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) {
    var fragment = document.createDocumentFragment(),
        child;

    // 將原生節(jié)點拷貝到fragment
    while (child = el.firstChild) { // 如果 el 有資源素,就將其賦值給 child,返回 true
        fragment.appendChild(child); // 將 child 從 el 轉(zhuǎn)移到 fragment 下,el 會少一個資源素,進行下一輪循環(huán)
    }

    return fragment; // 返回 fragment
},

init: function() {
    this.compileElement(this.$fragment); // 對 fragment 進行改造
},

compileElement: function(el) {
    var childNodes = el.childNodes,
        me = this;

    [].slice.call(childNodes).forEach(function(node) { // 循環(huán)遍歷節(jié)點,處理屬性
        var text = node.textContent;
        var reg = /{{(.*)}}/;

        if (me.isElementNode(node)) {
            me.compile(node); // 處理元素節(jié)點

        } else if (me.isTextNode(node) && reg.test(text)) { // 處理文本節(jié)點
            me.compileText(node, RegExp.$1);
        }

        if (node.childNodes && node.childNodes.length) {
            me.compileElement(node); // 遞歸調(diào)用,處理子元素
        }
    });
},

compile: function(node) {
    var nodeAttrs = node.attributes, // 獲得 dom 節(jié)點在 html 代碼里設(shè)置的屬性
        me = this;

    [].slice.call(nodeAttrs).forEach(function(attr) { // 對屬性進行遍歷,設(shè)置
        var attrName = attr.name;
        if (me.isDirective(attrName)) { // 判斷是普通屬性還是綁定指令,如果是指令,對指令進行處理
            var exp = attr.value;
            var dir = attrName.substring(2);
            // 綁定了事件指令
            if (me.isEventDirective(dir)) {
                compileUtil.eventHandler(node, me.$vm, exp, dir);
                // 普通指令
            } else {
                compileUtil[dir] && compileUtil[dir](node, me.$vm, exp);
            }
            node.removeAttribute(attrName); // 移除原本屬性
        }
    });
},
compileText: function(node, exp) {
    compileUtil.text(node, this.$vm, exp);
},

isDirective: function(attr) {
    return attr.indexOf("v-") == 0;
},

isEventDirective: function(dir) {
    return dir.indexOf("on") === 0;
},

isElementNode: function(node) { // 判斷是不是元素節(jié)點
    return node.nodeType == 1;
},

isTextNode: function(node) { // 判斷是不是文本節(jié)點
    return node.nodeType == 3;
}

}
最后,實現(xiàn) watch,監(jiān)視屬性的變化。watch 的每個實例,會添加到希望監(jiān)聽的屬性的 dep.subs 數(shù)組中,當監(jiān)聽的數(shù)據(jù)發(fā)生變化,調(diào)用 notify 函數(shù),然后函數(shù)內(nèi)部調(diào)用 subs 中所以 watch 實例的 updata 方法,通知監(jiān)聽這個數(shù)據(jù)的對象。受到通知后,對象判斷值是否改變,如果改變,調(diào)用回調(diào)函數(shù),更改視圖

function Watcher(vm, exp, cb) {

this.cb = cb;
this.vm = vm;
this.exp = exp;
// 此處為了觸發(fā)屬性的getter,從而在dep添加自己,結(jié)合Observer更易理解
this.value = this.get(); 

}
Watcher.prototype = {

update: function() {
    this.run(); // 屬性值變化收到通知
},
run: function() {
    var value = this.get(); // 取到最新值
    var oldVal = this.value;
    if (value !== oldVal) { // 判斷值是否改變
        this.value = value;
        this.cb.call(this.vm, value, oldVal); // 執(zhí)行Compile中綁定的回調(diào),更新視圖
    }
},
get: function() {
    Dep.target = this;  // 將當前訂閱者指向自己
    var value = this.vm[exp];   // 觸發(fā)getter,添加自己到屬性訂閱器中
    Dep.target = null;  // 添加完畢,重置
    return value;
}

};
最后,通過 MVVM 構(gòu)造器,將上面及部分整合起來,實現(xiàn)數(shù)據(jù)綁定。

function MVVM(options) {

this.$options = options;
var data = this._data = this.$options.data;
observe(data, this);
this.$compile = new Compile(options.el || document.body, this)

}
上面的內(nèi)容只是實現(xiàn)數(shù)據(jù)綁定的大概思路,其他內(nèi)容我再慢慢完善。

3 vue 數(shù)據(jù)雙向綁定的缺陷

3.1 vue 實例創(chuàng)建后,再向其上添加屬性,不能監(jiān)聽

當創(chuàng)建一個Vue實例時,將遍歷所有 DOM 對象,并為每個數(shù)據(jù)屬性添加了 get 和 set。 get 和 set 允許 Vue 觀察數(shù)據(jù)的更改并觸發(fā)更新。但是,如果你在 Vue 實例化后添加(或刪除)一個屬性,這個屬性不會被 vue 處理,改變 get 和 set。

如果你不想創(chuàng)建一個新的對象,你可以使用Vue.set設(shè)置一個新的對象屬性。該方法確保將屬性創(chuàng)建為一個響應(yīng)式屬性,并觸發(fā)視圖更新:

function addToCart (id) {

var item = this.cart.findById(id);
if (item) {
    item.qty++
} else {
    // 不要直接添加一個屬性,比如 item.qty = 1
    // 使用Vue.set 創(chuàng)建一個響應(yīng)式屬性
    Vue.set(item, "qty", 1)
    this.cart.push(item)
}

}
addToCart(myProduct.id);
3.2 數(shù)組

Object.defineProperty 的一個缺陷是無法監(jiān)聽數(shù)組變化。

當直接使用索引(index)設(shè)置數(shù)組項時,不會被 vue 檢測到:

app.myArray[index] = newVal;
然而Vue的文檔提到了Vue是可以檢測到數(shù)組變化的,但是只有以下八種方法, vm.items[indexOfItem] = newValue 這種是無法檢測的。

push();
pop();
shift();
unshift();
splice();
sort();
reverse();
同樣可以使用Vue.set來設(shè)置數(shù)組項:

Vue.set(app.myArray, index, newVal);
3.3 proxy 與 defineproperty

Proxy 對象在ES2015規(guī)范中被正式發(fā)布,用于定義基本操作的自定義行為(如屬性查找,賦值,枚舉,函數(shù)調(diào)用等)。

它在目標對象之前架設(shè)一層“攔截”,外界對該對象的訪問,都必須先通過這層攔截,因此提供了一種機制,可以對外界的訪問進行過濾和改寫。

我們可以這樣認為,Proxy是Object.defineProperty的全方位加強版,具體的文檔可以查看此處;

Proxy有多達13種攔截方法,不限于apply、ownKeys、deleteProperty、has等等,是Object.defineProperty不具備的。
Proxy返回的是一個新對象,我們可以只操作新的對象達到目的,而Object.defineProperty只能遍歷對象屬性直接修改。
Proxy作為新標準將受到瀏覽器廠商重點持續(xù)的性能優(yōu)化,也就是傳說中的新標準的性能紅利。
當然,Proxy的劣勢就是兼容性問題,而且無法用polyfill磨平,因此Vue的作者才聲明需要等到下個大版本(3.0)才能用Proxy重寫。

喜歡的可以關(guān)注小編哈~

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

轉(zhuǎn)載請注明本文地址:http://m.hztianpu.com/yun/54814.html

相關(guān)文章

  • 前端面試總結(jié)(js、html、小程序、React、ES6、Vue、算法、全棧熱門視頻資源)

    摘要:并總結(jié)經(jīng)典面試題集各種算法和插件前端視頻源碼資源于一身的文檔,優(yōu)化項目,在瀏覽器端的層面上提升速度,幫助初中級前端工程師快速搭建項目。 本文是關(guān)注微信小程序的開發(fā)和面試問題,由基礎(chǔ)到困難循序漸進,適合面試和開發(fā)小程序。并總結(jié)vue React html css js 經(jīng)典面試題 集各種算法和插件、前端視頻源碼資源于一身的文檔,優(yōu)化項目,在瀏覽器端的層面上提升速度,幫助初中級前端工程師快...

    pumpkin9 評論0 收藏0
  • 前端面試總結(jié)(js、html、小程序、React、ES6、Vue、算法、全棧熱門視頻資源)

    摘要:并總結(jié)經(jīng)典面試題集各種算法和插件前端視頻源碼資源于一身的文檔,優(yōu)化項目,在瀏覽器端的層面上提升速度,幫助初中級前端工程師快速搭建項目。 本文是關(guān)注微信小程序的開發(fā)和面試問題,由基礎(chǔ)到困難循序漸進,適合面試和開發(fā)小程序。并總結(jié)vue React html css js 經(jīng)典面試題 集各種算法和插件、前端視頻源碼資源于一身的文檔,優(yōu)化項目,在瀏覽器端的層面上提升速度,幫助初中級前端工程師快...

    Carson 評論0 收藏0
  • 前端面試總結(jié)(js、html、小程序、React、ES6、Vue、算法、全棧熱門視頻資源)

    摘要:并總結(jié)經(jīng)典面試題集各種算法和插件前端視頻源碼資源于一身的文檔,優(yōu)化項目,在瀏覽器端的層面上提升速度,幫助初中級前端工程師快速搭建項目。 本文是關(guān)注微信小程序的開發(fā)和面試問題,由基礎(chǔ)到困難循序漸進,適合面試和開發(fā)小程序。并總結(jié)vue React html css js 經(jīng)典面試題 集各種算法和插件、前端視頻源碼資源于一身的文檔,優(yōu)化項目,在瀏覽器端的層面上提升速度,幫助初中級前端工程師快...

    muzhuyu 評論0 收藏0

發(fā)表評論

0條評論

malakashi

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<