天天看點

了解前端資料雙向綁定原理——Object.defineProperty()什麼是資料雙向綁定?為什麼要實作資料的雙向綁定?Object.definedProperty方法

什麼是資料雙向綁定?

vue是一個mvvm架構,即資料雙向綁定,即當資料發生變化的時候,視圖也就發生變化,當視圖發生變化的時候,資料也會跟着同步變化。這也算是vue的精髓之處了

為什麼要實作資料的雙向綁定?

在vue中,如果使用vuex,實際上資料還是單向的,之是以說是資料雙向綁定,這是用的UI控件來說,對于我們處理表單,vue的雙向資料綁定用起來就特别舒服了。

即兩者并不互斥, 在全局性資料流使用單項,友善跟蹤; 局部性資料流使用雙向,簡單易操作。

Object.definedProperty方法

Object.definedProperty方法可以在一個對象上直接定義一個新的屬性、或修改一個對象已經存在的屬性,最終傳回這個對象。

vue.js的雙向資料綁定就是通過Object.defineProperty方法實作的,俗稱屬性攔截器。

文法

Object.defineProperty(obj, prop, descriptor)
           

參數

  1. obj

    需要定義屬性的對象。
  2. prop

    需被定義或修改的屬性名。
  3. descriptor

    需被定義或修改的屬性的描述符。

傳回值

函數将傳回傳遞給他的obj對象本身。即第一個參數obj

描述符(descriptor)說明

該方法允許開發者精确的對對象屬性的定義和修改。通過正常指派進行屬性添加而建構的屬性會被枚舉器方法(如for…in循環或Object.keys方法)擷取,進而導緻屬性值被外部方法改變或删除。而Object.defineProperty()可以避免以上描述的情況,預設的,通過Object.defineProperty()添加的屬性是預設不可改變的。

對象裡目前存在的屬性描述符有兩種主要形式:

資料描述符(data descriptor)和通路器描述符(accessor descriptor)。

資料描述符就是一個包含屬性的值,并說明這個值可讀或不可讀的對象;通路器描述符就是包含該屬性的一對getter-setter方法的對象。

一個完整的屬性描述(descriptor)必須是這兩者之一,并且不可以兩者都有。

資料描述符和通路器描述符各自都是對象,他們必須包含以下鍵值對:

configurable

是否可以删除目标屬性或是否可以再次修改屬性的特性(writable, configurable, enumerable)。設定為true可以被删除或可以重新設定特性;設定為false,不能被可以被删除或不可以重新設定特性。預設為false。

這個屬性起到兩個作用:

  1. 目标屬性是否可以使用delete删除
  2. 目标屬性是否可以再次設定特性
//-----------------測試目标屬性是否能被删除------------------------
var obj = {}
//第一種情況:configurable設定為false,不能被删除。
Object.defineProperty(obj,"newKey",{
    value:"hello",
    writable:false,
    enumerable:false,
    configurable:false
});
//删除屬性
delete obj.newKey;
console.log( obj.newKey ); //hello

//第二種情況:configurable設定為true,可以被删除。
Object.defineProperty(obj,"newKey",{
    value:"hello",
    writable:false,
    enumerable:false,
    configurable:true
});
//删除屬性
delete obj.newKey;
console.log( obj.newKey ); //undefined

//-----------------測試是否可以再次修改特性------------------------
var obj = {}
//第一種情況:configurable設定為false,不能再次修改特性。
Object.defineProperty(obj,"newKey",{
    value:"hello",
    writable:false,
    enumerable:false,
    configurable:false
});

//重新修改特性
Object.defineProperty(obj,"newKey",{
    value:"hello",
    writable:true,
    enumerable:true,
    configurable:true
});
console.log( obj.newKey ); //報錯:Uncaught TypeError: Cannot redefine property: newKey

//第二種情況:configurable設定為true,可以再次修改特性。
Object.defineProperty(obj,"newKey",{
    value:"hello",
    writable:false,
    enumerable:false,
    configurable:true
});

//重新修改特性
Object.defineProperty(obj,"newKey",{
    value:"hello",
    writable:true,
    enumerable:true,
    configurable:true
});
console.log( obj.newKey ); //hello
           

除了可以給新定義的屬性設定特性,也可以給已有的屬性設定特性

//定義對象的時候添加的屬性,是可删除、可重寫、可枚舉的。
var obj = {
    test:"hello"
}

//改寫值
obj.test = 'change value';

console.log( obj.test ); //'change value'

Object.defineProperty(obj,"test",{
    writable:false
})


//再次改寫值
obj.test = 'change value again';

console.log( obj.test ); //依然是:'change value'
           

提示:一旦使用Object.defineProperty給對象添加屬性,那麼如果不設定屬性的特性,那麼configurable、enumerable、writable這些值都為預設的false

var obj = {};
//定義的新屬性後,這個屬性的特性中configurable,enumerable,writable都為預設的值false
//這就導緻了neykey這個是不能重寫、不能枚舉、不能再次設定特性
//
Object.defineProperty(obj,'newKey',{

});

//設定值
obj.newKey = 'hello';
console.log(obj.newKey);  //undefined

//枚舉
for( var attr in obj ){
    console.log(attr);
}
           

enumerable

此屬性是否可以被枚舉(使用for…in或Object.keys())。設定為true可以被枚舉;設定為false,不能被枚舉。預設為false。

var obj = {}
//第一種情況:enumerable設定為false,不能被枚舉。
Object.defineProperty(obj,"newKey",{
    value:"hello",
    writable:false,
    enumerable:false
});

//枚舉對象的屬性
for( var attr in obj ){
    console.log( attr );  
}
//第二種情況:enumerable設定為true,可以被枚舉。
Object.defineProperty(obj,"newKey",{
    value:"hello",
    writable:false,
    enumerable:true
});

//枚舉對象的屬性
for( var attr in obj ){
    console.log( attr );  //newKey
}
           

資料描述符可以包含以下可選鍵值對:

value

設定屬性的值,可以是任何JavaScript值類型(number,object,function等類型)。預設為undefined。

var obj = {}
    //第一種情況:不設定value屬性
    Object.defineProperty(obj,"newKey",{
    
    });
    console.log( obj.newKey );  //undefined
    ------------------------------
    //第二種情況:設定value屬性
    Object.defineProperty(obj,"newKey",{
        value:"hello"
    });
    console.log( obj.newKey );  //hello
           

writable

屬性的值是否可以被重寫。設定為true可以被重寫;設定為false,不能被重寫。預設為false。

var obj = {}
//第一種情況:writable設定為false,不能重寫。
Object.defineProperty(obj,"newKey",{
    value:"hello",
    writable:false
});
//更改newKey的值
obj.newKey = "change value";
console.log( obj.newKey );  //hello

//第二種情況:writable設定為true,可以重寫
Object.defineProperty(obj,"newKey",{
    value:"hello",
    writable:true
});
//更改newKey的值
obj.newKey = "change value";
console.log( obj.newKey );  //change value
           

通路器描述符可以包含以下可選鍵值對:

get

getter 是一種獲得屬性值的方法,若屬性沒有getter方法則為undefined。該方法的傳回為屬性的值。預設為undefined。

set

setter是一種設定屬性值的方法。,若屬性沒有setter方法則為undefined。該方法接收唯一的參數,作為屬性的新值。預設為undefined。

在特性中使用get/set屬性來定義對應的方法。

var obj = {};
var initValue = 'hello';
Object.defineProperty(obj,"newKey",{
    get:function (){
        //當擷取值的時候觸發的函數
        return initValue;    
    },
    set:function (value){
        //當設定值的時候觸發的函數,設定的新值通過參數value拿到
        initValue = value;
    }
});
//擷取值
console.log( obj.newKey );  //hello

//設定值
obj.newKey = 'change value';

console.log( obj.newKey ); //change value
           

注意:當使用了getter或setter方法,不允許使用writable和value這兩個屬性 ; 如果不設定方法,則get和set的預設值為undefined

相容性

在ie8下隻能在DOM對象上使用,嘗試在原生的對象使用 Object.defineProperty()會報錯。

簡單的資料雙向綁定實作方法

<!DOCTYPE html>
<html >
<head>
  <meta charset="UTF-8">
  <title>forvue</title>
</head>
<body>
  <input type="text" id="textInput">
  輸入:<span id="textSpan"></span>
  <script>
    var obj = {},
        textInput = document.querySelector('#textInput'),
        textSpan = document.querySelector('#textSpan');

    Object.defineProperty(obj, 'foo', {
      set: function (newValue) {
        textInput.value = newValue;
        textSpan.innerHTML = newValue;
      }
    });

    textInput.addEventListener('keyup', function (e) {
        obj.foo = e.target.value;
    });

  </script>
</body>
</html>
           

最終效果圖:

了解前端資料雙向綁定原理——Object.defineProperty()什麼是資料雙向綁定?為什麼要實作資料的雙向綁定?Object.definedProperty方法

可以看到,實作一個簡單的資料雙向綁定還是不難的: 使用Object.defineProperty()來定義屬性的set函數,屬性被指派的時候,修改Input的value值以及span中的innerHTML;然後監聽input的keyup事件,修改對象的屬性值,即可實作這樣的一個簡單的資料雙向綁定。

小結:

VUE實作雙向資料綁定的原理就是利用了 Object.defineProperty() 這個方法重新定義了對象擷取屬性值(get)和設定屬性值(set)的操作來實作的。它接收三個參數,要操作的對象,要定義或修改的對象屬性名,屬性描述符。重點就是最後的屬性描述符。屬性描述符是一個對象,主要有兩種形式:資料描述符和存取描述符。這兩種對象隻能選擇一種使用,不能混合兩種描述符的屬性同時使用。上面說的get和set就是屬于存取描述符對象的屬性。如若被問到VUE雙向綁定的原理?答:VUE實作雙向資料綁定的原理就是利用了 Object.defineProperty() 這個方法重新定義了對象擷取屬性值(get)和設定屬性值(set)的操作來實作的。