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

資訊專欄INFORMATION COLUMN

Vue面試題精選:Vue原理以及雙向數據綁定的實戰過程

malakashi / 1412人閱讀

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

1 MVVM

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

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

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

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







1.1 實現數據雙向綁定的方式

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

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

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

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

function DataBinder(object_id){

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

}

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

var binder = new DataBinder(uid), // 返回一個 pubSub 對象,其上保存了由傳入參數 uid 確定的元素所有綁定的回調函數
user = {
    attributes: {}, // 保存需要同步的數據
    set: function(attr_name,val){ // 調用 set 方法,將需要同步的數據通過 publish 方法傳給監聽的元素
        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; // 函數作為一個構造函數時,返回一個對象,作為這個構造函數的實例

}

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

而數據劫持,就是通過對屬性的 set get 方法進行改造,來監測數據的改變,發布消息給訂閱者,觸發相應的監聽回調。

2 vue 數據雙向綁定

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

要實現mvvm的雙向綁定,主要進行了:

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

為了便于理解,首先,來實現一個消息的儲存中轉的構造函數:

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

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 屬性指向的對象(這個函數的調用者 this)添加的 subs 里
},

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

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

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

function Observer(data) {

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

}

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 數組里
            }
            return val;
        },
        set: function(newVal) {
            if (newVal === val) {
                return;
            }
            val = newVal;
            // 新的值是object的話,進行監聽
            childObj = observe(newVal);
            // 通知訂閱者
            dep.notify();
        }
    });
}

};

function observe(value, vm) {

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

return new Observer(value);

};
然后對 html 模板進行編譯,根據每個節點及其的屬性,判斷是否包含 ‘{{}}’,’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;

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

    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) { // 循環遍歷節點,處理屬性
        var text = node.textContent;
        var reg = /{{(.*)}}/;

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

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

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

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

    [].slice.call(nodeAttrs).forEach(function(attr) { // 對屬性進行遍歷,設置
        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) { // 判斷是不是元素節點
    return node.nodeType == 1;
},

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

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

function Watcher(vm, exp, cb) {

this.cb = cb;
this.vm = vm;
this.exp = exp;
// 此處為了觸發屬性的getter,從而在dep添加自己,結合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); // 執行Compile中綁定的回調,更新視圖
    }
},
get: function() {
    Dep.target = this;  // 將當前訂閱者指向自己
    var value = this.vm[exp];   // 觸發getter,添加自己到屬性訂閱器中
    Dep.target = null;  // 添加完畢,重置
    return value;
}

};
最后,通過 MVVM 構造器,將上面及部分整合起來,實現數據綁定。

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)

}
上面的內容只是實現數據綁定的大概思路,其他內容我再慢慢完善。

3 vue 數據雙向綁定的缺陷

3.1 vue 實例創建后,再向其上添加屬性,不能監聽

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

如果你不想創建一個新的對象,你可以使用Vue.set設置一個新的對象屬性。該方法確保將屬性創建為一個響應式屬性,并觸發視圖更新:

function addToCart (id) {

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

}
addToCart(myProduct.id);
3.2 數組

Object.defineProperty 的一個缺陷是無法監聽數組變化。

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

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

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

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

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

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

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

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

喜歡的可以關注小編哈~

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/54814.html

相關文章

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

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

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

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

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

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

    muzhuyu 評論0 收藏0

發表評論

0條評論

malakashi

|高級講師

TA的文章

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