JS之對象(2)

插圖

前言

一篇完全搞懂對象,今後不用擔憂沒對象啦;
本文從對象定義方法,對象屬性,Symbol數據類型,遍歷幾種方法,對象拷貝,vue2.x和vue3.x攔截對象屬性方法及代碼實現幾個方面由淺入深介紹對象

1.對象的聲明方法

1.1 字面量

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

1.2 構造函數

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

1.3 內置方法

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.4 三種方法的優缺點

1.功能:都能實現對象的聲明,並可以賦值和取值
2.繼承性:內置方法建立的對象繼承到__proto__屬性上
3.隱藏屬性:三種聲明方法會默認爲內部的每一個成員(屬性或方法)生成一些隱藏屬性,這些隱藏屬性是能夠讀取和可配置的,屬性分類見下面
4.屬性讀取:Object.getOwnPropertyDescriptor()或getOwnPropertyDescriptor()
5.屬性設置:Object.definePropertype或Object.definePropertiessegmentfault

2.對象的屬性

2.1 屬性分類

1.數據屬性4個特性:
configurable(可配置),enumerable(可枚舉),writable(可修改),value(屬性值)數組

2.訪問器屬性2個特性:
get(獲取),set(設置)函數

3.內部屬性
由JavaScript引擎內部使用的屬性;
不能直接訪問,可是能夠經過對象內置方法間接訪問,如:[[Prototype]]能夠經過 Object.getPrototypeOf()訪問;
內部屬性用[[]]包圍表示,是一個抽象操做,沒有對應字符串類型的屬性名,如[[Prototype]].this

2.2 屬性描述符

1.定義:將一個屬性的全部特性編碼成一個對象返回
2.描述符的屬性有:數據屬性和訪問器屬性
3.使用範圍:
做爲方法Object.defineProperty, Object.getOwnPropertyDescriptor, Object.create的第二個參數,編碼

2.3 屬性描述符的默認值

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

2.3 描述符屬性的使用規則

get,set與wriable,value是互斥的,若是有交集設置會報錯雙向綁定

2.4 屬性定義

1.定義屬性的函數有兩個:Object.defineProperty和Object.defineProperties.例如:
Object.defineProperty(obj, propName, desc)

2.在引擎內部,會轉換成這樣的方法調用:
obj.[[DefineOwnProperty]](propName, desc, true)

2.5 屬性賦值

1.賦值運算符(=)就是在調用[[Put]].好比:
obj.prop = v;

2.在引擎內部,會轉換成這樣的方法調用:
obj.[[Put]]("prop", v, isStrictModeOn)

2.6 判斷對象的屬性

名稱 含義 用法
in 若是指定的屬性在指定的對象或其原型鏈中,則in 運算符返回true 'name' in test //true
hasOwnProperty() 只判斷自身屬性 test.hasOwnProperty('name') //true
.或[] 對象或原型鏈上不存在該屬性,則會返回undefined test.name //"lei" test["name"] //"lei"

3.Symbol

3.1概念

是一種數據類型;
不能new,由於Symbol是一個原始類型的值,不是對象。

3.2 定義方法

Symbol(),能夠傳參

var s1 = Symbol();
var s2 = Symbol();
s1 === s2 // false

// 有參數的狀況
var s1 = Symbol("foo");
var s2 = Symbol("foo");
s1 === s2 // false

3.3 用法

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()取到該屬性

3.4 Symbol.for

1.定義:在全局中搜索有沒有以該參數做爲名稱的Symbol值,若是有,就返回這個Symbol值,不然就新建並返回一個以該字符串爲名稱的Symbol值
2.舉例:

var s1 = Symbol.for('foo');
var s2 = Symbol.for('foo');
s1 === s2 // true

3.5 Symbol.keyFor

1.定義:返回一個已登記的Symbol類型值的key
2.舉例:

var s1 = Symbol.for("foo");
Symbol.keyFor(s1) // "foo"

var s2 = Symbol("foo");
Symbol.keyFor(s2) // undefined

4.遍歷

4.1 一級對象遍歷方法

方法 特性
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)能夠拿到不可枚舉屬性

4.2 多級對象遍歷

數據模型:

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);

5.深度拷貝

5.1 Object.assign

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.注意:
這個是僞深度拷貝,只能拷貝第一層

5.2 JSON.stringify

1.原理:是將對象轉化爲字符串,而字符串是簡單數據類型

5.3 遞歸拷貝

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;
}

6.數據攔截

定義:利用對象內置方法,設置屬性,進而改變對象的屬性值

6.1 Object.defineProterty

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只能劫持對象的屬性,所以咱們須要對每一個對象的每一個屬性進行遍歷,若是屬性值也是對象那麼須要深度遍歷,顯然能劫持一個完整的對象是更好的選擇

6.2 proxy

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對象沒有構造函數
能夠監聽數組索引賦值,改變數組長度的變化,
是直接監聽對象的變化,不用深層遍歷

6.3 defineProterty和proxy的對比

1.defineProterty是es5的標準,proxy是es6的標準;

2.proxy能夠監聽到數組索引賦值,改變數組長度的變化;

3.proxy是監聽對象,不用深層遍歷,defineProterty是監聽屬性;

3.利用defineProterty實現雙向數據綁定(vue2.x採用的核心)
請戳,剖析Vue原理&實現雙向綁定MVVM4.利用proxy實現雙向數據綁定(vue3.x會採用)

相關文章
相關標籤/搜索