一篇完全搞懂對象,今後不用擔憂沒對象啦;
本文從對象定義方法,對象屬性,Symbol數據類型,遍歷幾種方法,對象拷貝,vue2.x和vue3.x攔截對象屬性方法及代碼實現幾個方面由淺入深介紹對象
var test2 = {x:123,y:345}; console.log(test2);//{x:123,y:345}; console.log(test2.x);//123 console.log(test2.__proto__.x);//undefined console.log(test2.__proto__.x === test2.x);//false
var test1 = new Object({x:123,y:345}); console.log(test1);//{x:123,y:345} console.log(test1.x);//123 console.log(test1.__proto__.x);//undefined console.log(test1.__proto__.x === test1.x);//false
new的做用:
1.創了一個新對象;
2.this指向構造函數;
3.構造函數有返回,會替換new出來的對象,若是沒有就是new出來的對象vue
Obejct.create(obj,descriptor),obj是對象,describe描述符屬性(可選)es6
let test = Object.create({x:123,y:345}); console.log(test);//{} console.log(test.x);//123 console.log(test.__proto__.x);//3 console.log(test.__proto__.x === test.x);//true
1.功能:都能實現對象的聲明,並可以賦值和取值
2.繼承性:內置方法建立的對象繼承到__proto__屬性上
3.隱藏屬性:三種聲明方法會默認爲內部的每一個成員(屬性或方法)生成一些隱藏屬性,這些隱藏屬性是能夠讀取和可配置的,屬性分類見下面
4.屬性讀取:Object.getOwnPropertyDescriptor()或getOwnPropertyDescriptor()
5.屬性設置:Object.definePropertype或Object.definePropertiessegmentfault
1.數據屬性4個特性:
configurable(可配置),enumerable(可枚舉),writable(可修改),value(屬性值)數組
2.訪問器屬性2個特性:
get(獲取),set(設置)函數
3.內部屬性
由JavaScript引擎內部使用的屬性;
不能直接訪問,可是能夠經過對象內置方法間接訪問,如:[[Prototype]]能夠經過 Object.getPrototypeOf()訪問;
內部屬性用[[]]包圍表示,是一個抽象操做,沒有對應字符串類型的屬性名,如[[Prototype]].this
1.定義:將一個屬性的全部特性編碼成一個對象返回
2.描述符的屬性有:數據屬性和訪問器屬性
3.使用範圍:
做爲方法Object.defineProperty, Object.getOwnPropertyDescriptor, Object.create的第二個參數,編碼
1.訪問對象存在的屬性es5
特性名 | 默認值 |
---|---|
value | 對應屬性值 |
get | 對應屬性值 |
set | undefined |
writable | true |
enumerable | true |
configurable | true |
因此經過上面三種聲明方法已存在的屬性都是有這些默認描述符
2.訪問對象不存在的屬性spa
特性名 | 默認值 |
---|---|
value | undefined |
get | undefined |
set | undefined |
writable | false |
enumerable | false |
configurable | false |
get,set與wriable,value是互斥的,若是有交集設置會報錯雙向綁定
1.定義屬性的函數有兩個:Object.defineProperty和Object.defineProperties.例如:
Object.defineProperty(obj, propName, desc)
2.在引擎內部,會轉換成這樣的方法調用:
obj.[[DefineOwnProperty]](propName, desc, true)
1.賦值運算符(=)就是在調用[[Put]].好比:
obj.prop = v;
2.在引擎內部,會轉換成這樣的方法調用:
obj.[[Put]]("prop", v, isStrictModeOn)
名稱 | 含義 | 用法 |
---|---|---|
in | 若是指定的屬性在指定的對象或其原型鏈中,則in 運算符返回true | 'name' in test //true |
hasOwnProperty() | 只判斷自身屬性 | test.hasOwnProperty('name') //true |
.或[] | 對象或原型鏈上不存在該屬性,則會返回undefined | test.name //"lei" test["name"] //"lei" |
是一種數據類型;
不能new,由於Symbol是一個原始類型的值,不是對象。
Symbol(),能夠傳參
var s1 = Symbol(); var s2 = Symbol(); s1 === s2 // false // 有參數的狀況 var s1 = Symbol("foo"); var s2 = Symbol("foo"); s1 === s2 // false
1.不能與其餘類型的值進行運算;
2.做爲屬性名
let mySymbol = Symbol(); // 第一種寫法 var a = {}; a[mySymbol] = 'Hello!'; // 第二種寫法 var a = { [mySymbol]: 'Hello!' }; // 第三種寫法 var a = {}; Object.defineProperty(a, mySymbol, { value: 'Hello!' }); // 以上寫法都獲得一樣結果 a[mySymbol] // "Hello!"
3.做爲對象屬性名時,不能用點運算符,能夠用[]
let a = {}; let name = Symbol(); a.name = 'lili'; a[name] = 'lucy'; console.log(a.name,a[name]);
4.遍歷不會被for...in、for...of和Object.keys()、Object.getOwnPropertyNames()取到該屬性
1.定義:在全局中搜索有沒有以該參數做爲名稱的Symbol值,若是有,就返回這個Symbol值,不然就新建並返回一個以該字符串爲名稱的Symbol值
2.舉例:
var s1 = Symbol.for('foo'); var s2 = Symbol.for('foo'); s1 === s2 // true
1.定義:返回一個已登記的Symbol類型值的key
2.舉例:
var s1 = Symbol.for("foo"); Symbol.keyFor(s1) // "foo" var s2 = Symbol("foo"); Symbol.keyFor(s2) // undefined
方法 | 特性 |
---|---|
for ... in | 遍歷對象自身的和繼承的可枚舉屬性(不含Symbol屬性) |
Object.keys(obj) | 返回一個數組,包括對象自身的(不含繼承的)全部可枚舉屬性(不含Symbol屬性) |
Object.getOwnPropertyNames(obj) | 返回一個數組,包括對象自身的全部可枚舉屬性(不含Symbol屬性) |
Object.getOwnPropertySymbols(obj) | 返回一個數組,包含對象自身的全部Symbol屬性 |
Reflect.ownKeys(obj) | 返回一個數組,包含對象自身的全部(不枚舉、可枚舉和Symbol)屬性 |
Reflect.enumerate(obj) | 返回一個Iterator對象,遍歷對象自身的和繼承的全部可枚舉屬性(不含Symbol屬性) |
總結:1.只有Object.getOwnPropertySymbols(obj)和Reflect.ownKeys(obj)能夠拿到Symbol屬性
2.只有Reflect.ownKeys(obj)能夠拿到不可枚舉屬性
數據模型:
var treeNodes = [ { id: 1, name: '1', children: [ { id: 11, name: '11', children: [ { id: 111, name: '111', children:[] }, { id: 112, name: '112' } ] }, { id: 12, name: '12', children: [] } ], users: [] }, ];
遞歸:
var parseTreeJson = function(treeNodes){ if (!treeNodes || !treeNodes.length) return; for (var i = 0, len = treeNodes.length; i < len; i++) { var childs = treeNodes[i].children; console.log(treeNodes[i].id); if(childs && childs.length > 0){ parseTreeJson(childs); } } }; console.log('------------- 遞歸實現 ------------------'); parseTreeJson(treeNodes);
1.定義:將源對象(source)的全部可枚舉屬性,複製到目標對象(target)
2.用法:
合併多個對象 var target = { a: 1, b: 1 }; var source1 = { b: 2, c: 2 }; var source2 = { c: 3 }; Object.assign(target, source1, source2);
3.注意:
這個是僞深度拷貝,只能拷貝第一層
1.原理:是將對象轉化爲字符串,而字符串是簡單數據類型
function deepClone(source){ const targetObj = source.constructor === Array ? [] : {}; // 判斷複製的目標是數組仍是對象 for(let keys in source){ // 遍歷目標 if(source.hasOwnProperty(keys)){ if(source[keys] && typeof source[keys] === 'object'){ // 若是值是對象,就遞歸一下 targetObj[keys] = source[keys].constructor === Array ? [] : {}; targetObj[keys] = deepClone(source[keys]); }else{ // 若是不是,就直接賦值 targetObj[keys] = source[keys]; } } } return targetObj; }
定義:利用對象內置方法,設置屬性,進而改變對象的屬性值
1.ES5出來的方法;
2.三個參數:對象(必填),屬性值(必填),描述符(可選);
3.defineProterty的描述符屬性
數據屬性:value,writable,configurable,enumerable 訪問器屬性:get,set 注:不能同時設置value和writable,這兩對屬性是互斥的
4.攔截對象的兩種狀況:
let obj = {name:'',age:'',sex:'' }, defaultName = ["這是姓名默認值1","這是年齡默認值1","這是性別默認值1"]; Object.keys(obj).forEach(key => { Object.defineProperty(obj, key, { get() { return defaultName; }, set(value) { defaultName = value; } }); }); console.log(obj.name); console.log(obj.age); console.log(obj.sex); obj.name = "這是改變值1"; console.log(obj.name); console.log(obj.age); console.log(obj.sex); let objOne={},defaultNameOne="這是默認值2"; Object.defineProperty(obj, 'name', { get() { return defaultNameOne; }, set(value) { defaultNameOne = value; } }); console.log(objOne.name); objOne.name = "這是改變值2"; console.log(objOne.name);
5.攔截數組變化的狀況
let a={}; bValue=1; Object.defineProperty(a,"b",{ set:function(value){ bValue=value; console.log("setted"); }, get:function(){ return bValue; } }); a.b;//1 a.b=[];//setted a.b=[1,2,3];//setted a.b[1]=10;//無輸出 a.b.push(4);//無輸出 a.b.length=5;//無輸出 a.b;//[1,10,3,4,undefined]; 結論:defineProperty沒法檢測數組索引賦值,改變數組長度的變化; 可是經過數組方法來操做能夠檢測到
6.存在的問題
不能監聽數組索引賦值和改變長度的變化 必須深層遍歷嵌套的對象,由於defineProterty只能劫持對象的屬性,所以咱們須要對每一個對象的每一個屬性進行遍歷,若是屬性值也是對象那麼須要深度遍歷,顯然能劫持一個完整的對象是更好的選擇
1.ES6出來的方法,實質是對對象作了一個攔截,並提供了13個處理方法
13個方法詳情請戳,阮一峯的proxy介紹
2.兩個參數:對象和行爲函數
let handler = { get(target, key, receiver) { console.log("get", key); return Reflect.get(target, key, receiver); }, set(target, key, value, receiver) { console.log("set", key, value); return Reflect.set(target, key, value, receiver); } }; let proxy = new Proxy(obj, handler); proxy.name = "李四"; proxy.age = 24;
3.問題和優勢
reflect對象沒有構造函數
能夠監聽數組索引賦值,改變數組長度的變化,
是直接監聽對象的變化,不用深層遍歷
1.defineProterty是es5的標準,proxy是es6的標準;
2.proxy能夠監聽到數組索引賦值,改變數組長度的變化;
3.proxy是監聽對象,不用深層遍歷,defineProterty是監聽屬性;
3.利用defineProterty實現雙向數據綁定(vue2.x採用的核心)
請戳,剖析Vue原理&實現雙向綁定MVVM4.利用proxy實現雙向數據綁定(vue3.x會採用)