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

資訊專欄INFORMATION COLUMN

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

malakashi / 1444人閱讀

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

1 MVVM

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

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

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

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







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

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

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

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

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

function DataBinder(object_id){

//創(chuàng)建一個(gè)簡(jiǎn)單地PubSub對(duì)象   
var pubSub = { // 一個(gè)pubSub 對(duì)象,內(nèi)部有一個(gè) callbacks 對(duì)象,保存回調(diào)函數(shù)
    callbacks: {}, // 鍵名為觸發(fā)回調(diào)函數(shù)的自定義事件名稱,值為一個(gè)數(shù)組,每一項(xiàng)都是一個(gè)回調(diào)函數(shù)
    on: function(msg,callback){ // on 方法 傳入?yún)?shù),一個(gè)字符串(就是自定義事件的名稱),一個(gè)回調(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 對(duì)象 的 msg 屬性保存的數(shù)組,如果沒(méi)有,等于新建的空數(shù)組
        for(var i = 0, len = this.callbacks[msg].length; i

}

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

var binder = new DataBinder(uid), // 返回一個(gè) pubSub 對(duì)象,其上保存了由傳入?yún)?shù) uid 確定的元素所有綁定的回調(diào)函數(shù)
user = {
    attributes: {}, // 保存需要同步的數(shù)據(jù)
    set: function(attr_name,val){ // 調(diào)用 set 方法,將需要同步的數(shù)據(jù)通過(guò) publish 方法傳給監(jiān)聽(tīng)的元素
        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è)構(gòu)造函數(shù)時(shí),返回一個(gè)對(duì)象,作為這個(gè)構(gòu)造函數(shù)的實(shí)例

}

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

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

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

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

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

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

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

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

function Dep() {

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

}
Dep.prototype = {

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

depend: function() {
    Dep.target.addDep(this); // 將全局對(duì)象 Dep 的 target 屬性指向的對(duì)象(這個(gè)函數(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();
    });
}

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

function Observer(data) {

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

}

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) { // 對(duì)屬性進(jìn)行修改
    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 實(shí)例的 subs 數(shù)組里
            }
            return val;
        },
        set: function(newVal) {
            if (newVal === val) {
                return;
            }
            val = newVal;
            // 新的值是object的話,進(jìn)行監(jiān)聽(tīng)
            childObj = observe(newVal);
            // 通知訂閱者
            dep.notify();
        }
    });
}

};

function observe(value, vm) {

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

return new Observer(value);

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

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é)點(diǎn)拷貝到fragment
    while (child = el.firstChild) { // 如果 el 有資源素,就將其賦值給 child,返回 true
        fragment.appendChild(child); // 將 child 從 el 轉(zhuǎn)移到 fragment 下,el 會(huì)少一個(gè)資源素,進(jìn)行下一輪循環(huán)
    }

    return fragment; // 返回 fragment
},

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

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

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

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

        } else if (me.isTextNode(node) && reg.test(text)) { // 處理文本節(jié)點(diǎn)
            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é)點(diǎn)在 html 代碼里設(shè)置的屬性
        me = this;

    [].slice.call(nodeAttrs).forEach(function(attr) { // 對(duì)屬性進(jìn)行遍歷,設(shè)置
        var attrName = attr.name;
        if (me.isDirective(attrName)) { // 判斷是普通屬性還是綁定指令,如果是指令,對(duì)指令進(jìn)行處理
            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é)點(diǎn)
    return node.nodeType == 1;
},

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

}
最后,實(shí)現(xiàn) watch,監(jiān)視屬性的變化。watch 的每個(gè)實(shí)例,會(huì)添加到希望監(jiān)聽(tīng)的屬性的 dep.subs 數(shù)組中,當(dāng)監(jiān)聽(tīng)的數(shù)據(jù)發(fā)生變化,調(diào)用 notify 函數(shù),然后函數(shù)內(nèi)部調(diào)用 subs 中所以 watch 實(shí)例的 updata 方法,通知監(jiān)聽(tīng)這個(gè)數(shù)據(jù)的對(duì)象。受到通知后,對(duì)象判斷值是否改變,如果改變,調(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;  // 將當(dāng)前訂閱者指向自己
    var value = this.vm[exp];   // 觸發(fā)getter,添加自己到屬性訂閱器中
    Dep.target = null;  // 添加完畢,重置
    return value;
}

};
最后,通過(guò) MVVM 構(gòu)造器,將上面及部分整合起來(lái),實(shí)現(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)容只是實(shí)現(xiàn)數(shù)據(jù)綁定的大概思路,其他內(nèi)容我再慢慢完善。

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

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

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

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

function addToCart (id) {

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

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

Object.defineProperty 的一個(gè)缺陷是無(wú)法監(jiān)聽(tīng)數(shù)組變化。

當(dāng)直接使用索引(index)設(shè)置數(shù)組項(xiàng)時(shí),不會(huì)被 vue 檢測(cè)到:

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

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

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

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

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

我們可以這樣認(rèn)為,Proxy是Object.defineProperty的全方位加強(qiáng)版,具體的文檔可以查看此處;

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

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

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

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

相關(guān)文章

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

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

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

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

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

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

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

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

0條評(píng)論

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