1. 程式人生 > >強大的JS方法Object.defineProperty詳解及VUE.JS雙向繫結原理

強大的JS方法Object.defineProperty詳解及VUE.JS雙向繫結原理

Object.defineProperty是一個很了不起的方法。vue.js之所以能夠實現雙向繫結便是拜它所賜!defineProperty直接翻譯過來即是“定義屬性”,不過該方法可不僅僅是定義屬性這麼簡單,咱們還可以通過它來對屬性進行攔截設定!

我們知道物件是由多個鍵/值對組成的無序集合。物件當中的屬性可以是任意型別的值。我們可以通過建構函式以及字面量的形式來定義物件。

var obj={};//或obj=new Object;
// 新增屬性(描述)
obj.userName="laotie";//或 obj["userName"]="laotie"
// 新增方法(行為)
obj.run=function(){};//或 obj["run"]=function(){};

為物件增加屬性的方法除了上面的方式外,咱們還可以通過Object.defineProperty來定義新屬性,或者對原屬性進行修改。 #####Object.defineProperty() ######語法:

  • Object.defineProperty(obj, prop, descriptor) ######引數說明:
  • obj:必需。目標物件
  • prop:必需。需定義或修改的屬性的名字
  • descriptor:必需。目標屬性所擁有的特性 前兩個引數不多說了,看程式碼就明白了,我們主要看第三個引數descriptor,看看它是個什麼鬼!

1、 value

######通過value可以為物件設定屬性,對應的值可以是任意型別,預設為undefined

var obj={};
console.log(obj.userName);// undefined
Object.defineProperty(obj,"userName",{
    value:"laozhang"
});
console.log(obj.userName);// laozhang

######可以對原有值進行修改:

var obj={};
obj.userName="laoli"
console.log(obj.userName);// laoli
Object.defineProperty(obj,"userName",{
    value:"laozhang"
});
console.log(obj.userName);// laozhang

######返回的值為傳入函式的物件,即第一個引數obj:

var obj={};
var obj2=Object.defineProperty(obj,"userName",{
    value:"laozhang"
});
console.log(obj==obj2);// true

2、writable

用於設定屬性的值是否允許被重寫。true為允許,false不允許被重寫,預設為false ######設定為false不允許被重寫,並沒有錯誤丟擲

var obj={};
Object.defineProperty(obj,"userName",{
    value:"laozhang",
    writable:false
});
obj.userName="laoliu";
console.log(obj.userName);// laozhang

######如果在嚴格描述下會報錯:

"use strict";
var obj={};
Object.defineProperty(obj,"userName",{
    value:"laozhang",
    writable:false
});
obj.userName="laoliu";
//報錯:TypeError: Cannot assign to read only property 'userName' of object

######預設為false,值不允許被修改

var obj={};
Object.defineProperty(obj,"userName",{
    value:"laozhang"
});
obj.userName="laoliu";
console.log(obj.userName);// laozhang

######設定為true,允許被修改

var obj={};
Object.defineProperty(obj,"userName",{
    value:"laozhang",
    writable:true
});
obj.userName="laoliu";
console.log(obj.userName);// laoliu

3、enumerable

該描述決定著指定的屬性是否允許被列舉(使用for…in或Object.keys())。設定為true可以被列舉;設定為false,不能被列舉。預設為false。 ####設定為false不允許被列舉

var obj={
    age:18
};
Object.defineProperty(obj,"userName",{
    value:"laozhang",
    writable:false,
    enumerable:false
});
for(var key in obj){
    console.log(key,obj[key])// age 18
}
console.log(Object.keys(obj));//[ 'age' ]

######預設值為false, 不允許被列舉

var obj={
    age:18
};
Object.defineProperty(obj,"userName",{
    value:"laozhang",
    writable:false
});
for(var key in obj){
    console.log(key,obj[key])// age 18
}
console.log(Object.keys(obj));//[ 'age' ]

######設定為true,允許被列舉

var obj={
    age:18
};
Object.defineProperty(obj,"userName",{
    value:"laozhang",
    writable:false,
    enumerable:true
});
for(var key in obj){
    /*  age 18 
        userName laozhang*/ 
    console.log(key,obj[key]);
}
console.log(Object.keys(obj));//[ 'age', 'userName' ]

4、configurable

configurable是一個總開關,一旦你將它設定為false,就不能刪除指定的屬性也不能再設定他的(value,writable,configurable),設定為true,允許被刪除,也允許被設定。

為false不允許被刪除,不會報錯
var obj={};
Object.defineProperty(obj,"userName",{
    value:"laozhang",
    configurable:false
});
delete obj.userName;
console.log(obj.userName);//laozhang
為false不允許被重新設定,會報錯
var obj={};
Object.defineProperty(obj,"userName",{
    value:"laozhang",
    configurable:false
});
Object.defineProperty(obj,"userName",{
    value:"laoli"
});
//報錯:TypeError: Cannot redefine property: userName
為true時,允許被刪除
var obj={};
Object.defineProperty(obj,"userName",{
    value:"laozhang",
    configurable:true
});
delete obj.userName;
console.log(obj.userName);//undefined

######為true時,允許被重新設定

var obj={};
Object.defineProperty(obj,"userName",{
    value:"laozhang",
    configurable:true
});
Object.defineProperty(obj,"userName",{
    value:"laoli"
});
console.log(obj.userName);//laoli
5、get/set存取器描述

當你需要設定或獲取物件的某個屬性值的時候,可以使用該方法。

var obj={};
var initValue="xixi";
Object.defineProperty(obj,"userName",{
    get(){
        // 當讀取userName時會有輸出
        console.log("執行了get");
        return initValue;
    },
    set(newValue){
        // newValue為寫入的值
        console.log("執行了set");
        initValue= newValue+"吧!"
    }
});

/*  執行了get
    xixi */
console.log(obj.userName);

obj.userName="愛我";// 執行了set

/*  執行了get
    愛我吧! */
console.log(obj.userName);

######get或set不是必須成對出現,任寫其一就可以。

var obj={};
Object.defineProperty(obj,"userName",{
    get(){
        return "lala"
    }
});
console.log(obj.userName);//lala

######當使用了get或set方法,不允許使用writable和value這兩個屬性

var obj={};
Object.defineProperty(obj,"userName",{
    value:"xixi",
    get(){
        return "lala"
    }
});
console.log(obj.userName);
//報錯:Invalid property descriptor. Cannot both specify accessors and a value or writable attribute

接下來看個例項:

var obj={};
var userName="";
var userArr=[];
Object.defineProperty(obj,"userName",{
    get(){
        return userName;
    },
    set(value){
        userArr.push(value);
        userName=value;
    }
});
obj.userName="張三";
obj.userName="李四";
obj.userName="王五";
console.log(userArr);// [ '張三', '李四', '王五' ]

以上例項通過存取器成功將userName曾經擁有過的值進行了儲存。是不是很神奇,很簡單? ######接下來,咱們可以通過defineProperty模擬下VUE.JS的雙向繫結:

<body>
<input type="text" id="myInp"/>
<div id="myDiv"></div>
</body>
<script>
       var myInp=document.querySelector("#myInp");
       var myDiv=document.querySelector("#myDiv");
       var obj={
           v:"haha"
       }
       myInp.value=obj.v;
       myDiv.innerHTML=obj.v;

       Object.defineProperty(obj,"v",{
           set:function(v){
               myDiv.innerHTML=myInp.value=v;
           }
       })
       myInp.onkeyup=function(e){
           console.log(e.target.value);
           obj.v=e.target.value;
       }
</script>

好了,馬上就結束了。可能有的小夥伴會想,既然這個Object.defineProperty如此強大,每次只能設定一個屬性嗎?那麼這玩意兒用起來也挺費勁的!那麼現在大咖上場:Object.defineProperties()。你可以通過該大咖同時設定多個物件描述。

var obj = new Object();
Object.defineProperties(obj, {
    name: {
        value: '張三',
        configurable: false,
        writable: true,
        enumerable: true
    },
    age: {
        value: 18,
        configurable: true
    }
})
console.log(obj.name, obj.age) // 張三, 18