ECMAScript 6的一些注意點 第四部分(對象擴展)

對象的擴展

1.屬性簡潔表示法 -- 允許在對象中寫入變量和函數:

let name = 'jack';
let person = {name};
person //{name:"jack"}
let Person = {name:name};
Person //{name:"jack"}

屬性名爲變量名, 屬性值爲變量的值

2.簡寫方法(method):

let obj = {
    method(){
        return 1;
    }
}

3.用於返回值:

function getPoint(){
    let x = 1;
    let y = 10;
    return {x,y};
}
getPoint();    //{x:1,y:10}

4.簡潔寫法的屬性名總是字符串,所以不會因爲它屬於關鍵字,而導致語法解析報錯。

5.如果某個方法的值是一個 Generator 函數,前面需要加上星號。


屬性名表達式

1.允許字面量定義對象時,用表達式作爲對象的屬性名,即把表達式放在方括號內。

let pername = 'name';
let obj = {
    [pername]: "jack",
    ['a' + 'ge']: 20
}

obj // {name:"jack",age:20}

obj[pername] // jack

obj[name]    //jack

2.表達式還可以用於定義方法名。

let halo = 'hello';
let obj = {
    [halo](){
        return 'hello';
    }
}
obj[helo]()    //hello
obj.hello()    //hello

3.屬性名表達式與簡潔表示法,不能同時使用,會報錯。

4.屬性名表達式如果是一個對象,默認情況下會自動將對象轉爲字符串[object Object]。


方法的name屬性

1.方法的name屬性返回函數名(即方法名)。

2.如果對象的方法使用了取值函數(getter)和存值函數(setter),則name屬性不是在該方法上面,而是該方法的屬性的描述對象的getset屬性上面,返回值是方法名前加上getset

const obj = {
  get foo() {},
  set foo(x) {}
};

obj.foo.name
// TypeError: Cannot read property 'name' of undefined

const descriptor = Object.getOwnPropertyDescriptor(obj, 'foo');

descriptor.get.name // "get foo"
descriptor.set.name // "set foo"

Object.getOwnPropertyDescriptor():

Object.getOwnPropertyDescriptor() 方法返回指定對象上一個自有屬性對應的屬性描述符。(自有屬性指的是直接賦予該對象的屬性,不需要從原型鏈上進行查找的屬性)

3.兩種特殊情況:

  • bind方法創造的函數,name屬性返回bound加上原函數的名字
  • Function構造函數創造的函數,name屬性返回anonymous

4.如果對象的方法是一個 Symbol 值,那麼name屬性返回的是這個 Symbol 值的描述。


屬性的可枚舉性和遍歷

1.可枚舉性:對象的每個屬性都有一個描述對象(Descriptor),用來控制該屬性的行爲。Object.getOwnPropertyDescriptor方法可以獲取該屬性的描述對象。

2.描述對象的enumerable屬性,稱爲“可枚舉性”,如果該屬性爲false,就表示某些操作會忽略當前屬性。

3.目前,有四個操作會忽略enumerablefalse的屬性。

  • for...in循環:只遍歷對象自身的和繼承的可枚舉的屬性。
  • Object.keys():返回對象自身的所有可枚舉的屬性的鍵名。
  • JSON.stringify():只串行化對象自身的可枚舉的屬性。
  • Object.assign(): 忽略enumerablefalse的屬性,只拷貝對象自身的可枚舉的屬性。

4.引入“可枚舉”(enumerable)這個概念的最初目的,就是讓某些屬性可以規避掉for...in操作,不然所有內部屬性和方法都會被遍歷到。

5.ES6 規定,所有 Class 的原型的方法都是不可枚舉的。

6.儘量不要用for...in循環,而用Object.keys()代替。

7.屬性的遍歷:

ES6 一共有 5 種方法可以遍歷對象的屬性。

(1)for...in

for...in循環遍歷對象自身的和繼承的可枚舉屬性(不含 Symbol 屬性)

(2)Object.keys(obj)

Object.keys返回一個數組,包括對象自身的(不含繼承的)所有可枚舉屬性(不含 Symbol 屬性)的鍵名

(3)Object.getOwnPropertyNames(obj)

Object.getOwnPropertyNames返回一個數組,包含對象自身的所有屬性(不含 Symbol 屬性,但是包括不可枚舉屬性)的鍵名

(4)Object.getOwnPropertySymbols(obj)

Object.getOwnPropertySymbols返回一個數組,包含對象自身的所有 Symbol 屬性的鍵名

(5)Reflect.ownKeys(obj)

Reflect.ownKeys返回一個數組,包含對象自身的所有鍵名,不管鍵名是 Symbol 或字符串,也不管是否可枚舉

以上的 5 種方法遍歷對象的鍵名,都遵守同樣的屬性遍歷的次序規則。

  • 首先遍歷所有數值鍵,按照數值升序排列。
  • 其次遍歷所有字符串鍵,按照加入時間升序排列。
  • 最後遍歷所有 Symbol 鍵,按照加入時間升序排列。
let {
    log: a
} = console;
// 屬性的遍歷
let objname = 'name'
let objmethod = 'hello';
let obj = {
    [objname]: "jack",
    age: 20,
    [objmethod]() {
        return objmethod;
    },
    [Symbol()]:0
}
for (let i in obj) {  
    a(obj[i])  //  20 hello ƒ [objmethod]() {return objmethod;}
}
let objMsg = Object.keys(obj)
a(objMsg);        //["name", "age", "hello"]
let objgetNames = Object.getOwnPropertyNames(obj)
a(objgetNames);   //["name", "age", "hello"]
let objgetSymbols = Object.getOwnPropertySymbols(obj);
a(objgetSymbols); //[Symbol()]
let objReflect = Reflect.ownKeys(obj)
a(objReflect);    //["name", "age", "hello", Symbol()]

super關鍵字

1.指向當前對象的原型對象。

let proto = {
    foo: 'hello'
}
let obj = {
    foo:"world",
    find(){
        return super.foo;
    }
}
Object.setPrototypeOf(obj,proto);
obj.find();    //hello

setPrototypeOf():

Object.setPrototypeOf() 方法設置一個指定的對象的原型 ( 即, 內部[[Prototype]]屬性)到另一個對象或  null

語法:

Object.setPrototypeOf(obj, prototype)

上面例子將obj的原型指向了proto,然後通過super引用原型對象proto的屬性。

2.super關鍵字表示原型對象時,只能用在對象的方法之中(目前:只有對象方法的簡寫法可以讓 JavaScript 引擎確認,定義的是對象的方法),用在其他地方都會報錯

// 報錯  super用在屬性裏面
const obj = {
  foo: super.foo
}

// 報錯 super用在一個函數裏面,然後賦值給foo屬性
const obj = {
  foo: () => super.foo
}

// 報錯 super用在一個函數裏面,然後賦值給foo屬性   
const obj = {
  foo: function () {
    return super.foo
  }
}

3.引擎內部,super.foo等同於Object.getPrototypeOf(this).foo(屬性)或Object.getPrototypeOf(this).foo.call(this)(方法)。


解構賦值

1.相當於將目標對象自身的所有可遍歷,但尚未被讀取的屬性分配到指定的對象上面,鍵和值都會被拷貝到新的對象。

let {x , y , ...z} = { x:1 , y:2 , a:3 , b:4 };
x    //1
y    //2
z    //{ a : 3 , b : 4 }

z是解構賦值所在的對象。它獲取等號右邊的所有尚未讀取的鍵(ab),將它們連同值一起拷貝過來。

2.如果等號右邊是undefinednull,就會報錯,因爲它們無法轉爲對象。

3.解構賦值必須是最後一個參數,否則會報錯。

let { ...x, y, z } = someObject; // 句法錯誤
let { x, ...y, ...z } = someObject; // 句法錯誤

4.解構賦值的拷貝是淺拷貝,即如果一個鍵的值是複合類型的值(數組、對象、函數)、那麼解構賦值拷貝的是這個值的引用,而不是這個值的副本。

let obj = { a: { b: 1 } };
let { ...x } = obj;
obj.a.b = 2;
x.a.b // 2

5.擴展運算符的解構賦值,不能複製繼承自原型對象的屬性。

let o1 = {
    a: 1
};
let o2 = {
    b: 2
};
o2.__proto__ = o1;
let {...o3} = o2;
console.log(o2.__proto__);    //{a: 1}
console.log(o3.__proto__);    //{constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ,             
                              //hasOwnProperty: ƒ, __lookupGetter__: ƒ, …}

只複製了o2自身的屬性,沒有複製它的原型對象o1的屬性。

6.擴展某個函數的參數,引入其他操作。

// 擴展函數引入操作
function baseFunction({a, b }) {
    return a + b
}
console.log(baseFunction({
    a: 10,
    b: 20
}))    //30

function wrapperFunction({x,y, ...restConfig}) {
    // 使用 x 和 y 參數進行操作
    // 其餘參數傳給原始函數
    let sum = x + y
    console.log(sum);
    return baseFunction(restConfig);
}
console.log(wrapperFunction({
    x: 20,
    y: 50,
    a: 10,
    b: 20
}))    // 70     30
    

拓展運算符

1.取出參數對象的所有可遍歷屬性,拷貝到當前對象之中。

let a  = { x:10 , y:20 };
let b = {...a};
b     //  { x:10 , y:20 }

2.數組是特殊的對象,所以對象的擴展運算符也可以用於數組。

let arr = ['a','b'];
let foo = {...arr};
foo    //{0:"a",1:"b"}

3.擴展運算符後面是一個空對象,則沒有任何效果。

4.擴展運算符後面不是對象,則會自動將其轉爲對象。

// 等同於 {...Object(1)}
{...1} // {}
擴展運算符後面是整數1,會自動轉爲數值的包裝對象Number{1}。由於該對象沒有自身屬性,所以返回一個空對象。

5.擴展運算符後面是字符串,它會自動轉成一個類似數組的對象,因此返回的不是空對象。

{...'hello'}
// {0: "h", 1: "e", 2: "l", 3: "l", 4: "o"}

6.對象的擴展運算符等同於使用Object.assign()方法。

7.想完整克隆一個對象,還拷貝對象原型的屬性,可以採用下面的寫法:

// 寫法一
const clone1 = {
  __proto__: Object.getPrototypeOf(obj),
  ...obj
};
//示例:
let obj = {
    a:"jack",
    __proto__:{
        b:1
    }
}
const clone1 = {
    __proto__ : Object.getPrototypeOf(obj),
    ...obj
}    
console.log(clone1);  
/*
{a: "jack"}
a: "jack"
__proto__: b:1
__proto__: Object
*/
// 寫法二
const clone2 = Object.assign(
  Object.create(Object.getPrototypeOf(obj)),
  obj
);

// 寫法三
const clone3 = Object.create(
  Object.getPrototypeOf(obj),
  Object.getOwnPropertyDescriptors(obj)
)

寫法一的__proto__屬性在非瀏覽器的環境不一定部署,因此推薦使用寫法二和寫法三。

8.如果用戶自定義的屬性,放在擴展運算符後面,則擴展運算符內部的同名屬性會被覆蓋掉。

9.用來修改現有對象部分的屬性就很方便:

let previousVersion = {
    name:"jack",
    age:20,
    work:"It"
}
let newVersion = {
    ...previousVersion,
    name:"New Name"
}
console.log(newVersion);   //{name: "New Name", age: 20, work: "It"}

10.把自定義屬性放在擴展運算符前面,就變成了設置新對象的默認屬性值。

11.與數組的擴展運算符一樣,對象的擴展運算符後面可以跟表達式。

const obj = {
  ...(x > 1 ? {a: 1} : {}),
  b: 2,
};

12.擴展運算符的參數對象之中,如果有取值函數get,這個函數是會執行的。

// 並不會拋出錯誤,因爲 x 屬性只是被定義,但沒執行
let aWithXGetter = {
  ...a,
  get x() {
    throw new Error('not throw yet');
  }
};

// 會拋出錯誤,因爲 x 屬性被執行了
let runtimeError = {
  ...a,
  ...{
    get x() {
      throw new Error('throw now');
    }
  }
};

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章