ES6 --(8)對象的擴展、對象的新增方法


2019-11-6:

學習內容:對象的擴展

補充:

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

let obj = { foo: 123 };
Object.getOwnPropertyDescriptor(obj, 'foo')
//  {
//    value: 123,
//    writable: true,
//    enumerable: true,
//    configurable: true
//  }

 


 

 對象的擴展:

一、對象的屬性和方法簡介寫法:

  ES6 允許在大括號里面,直接寫入變量和函數,作為對象的屬性和方法。這樣的書寫更加簡潔。

  屬性名和屬性值一樣時,可以只寫一個(語法自動補足)

function getPoint() {
  const x = 1;
  const y = 10;
  return {x, y};
}

getPoint()
// {x:1, y:10}

 

// 方法:
const o = {
  method() {
    return "Hello!";
  }
};

// 等同於

const o = {
  method: function() {
    return "Hello!";
  }
};

 

打印對象用簡潔表示法需要加大括號:

let user = {
  name: 'test'
};

let foo = {
  bar: 'baz'
};

console.log(user, foo)
// {name: "test"} {bar: "baz"}
console.log({user, foo})
// {user: {name: "test"}, foo: {bar: "baz"}}

  上面代碼中,console.log直接輸出userfoo兩個對象時,就是兩組鍵值對,可能會混淆。把它們放在大括號里面輸出,就變成了對象的簡潔表示法,每組鍵值對前面會打印對象名,這樣就比較清晰了。

 

二、屬性名表達式:

  注意:不能跟簡潔表達法同用,會報錯。

 

(1)定義對象的屬性有兩種方法:

  方法一是直接用標識符作為屬性名,方法二是用表達式作為屬性名,這時要將表達式放在方括號之內。

// 方法一
obj.foo = true;

// 方法二
obj['a' + 'bc'] = 123;

(2)ES6 允許字面量定義對象時,用方法二(表達式)作為對象的屬性名,即把表達式放在方括號內。

// 例子一:
let lastWord = 'last word';

const a = {
  'first word': 'hello',
  [lastWord]: 'world'
};

a['first word'] // "hello"
a[lastWord] // "world"
a['last word'] // "world"


// 例子二:
let obj = {
  ['h' + 'ello']() {
    return 'hi';
  }
};

obj.hello() // hi

(3)注意,屬性名表達式如果是一個對象,默認情況下會自動將對象轉為字符串[object Object](被掩蓋,這一點要特別小心。

const keyA = {a: 1};
const keyB = {b: 2};

const myObject = {
  [keyA]: 'valueA',
  [keyB]: 'valueB'
};

myObject // Object {[object Object]: "valueB"}

 

三、name不僅函數有,對象的方法也有,返回該對象方法的名稱

  特別的,如果對象的方法是一個 Symbol 值,那么name屬性返回的是這個 Symbol 值的描述。

const key1 = Symbol('description');
const key2 = Symbol();
let obj = {
  [key1]() {},
  [key2]() {},
};
obj[key1].name // "[description]"
obj[key2].name // ""

 

四、屬性的可枚舉性與遍歷:

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

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

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

  這四個操作之中,Object.assign()是 ES6 新增的。其中,只有for...in會返回繼承的屬性,其他三個方法都會忽略繼承的屬性,只處理對象自身的屬性。實際上,引入“可枚舉”(enumerable)這個概念的最初目的,就是讓某些屬性可以規避掉for...in操作,不然所有內部屬性和方法都會被遍歷到。比如,對象原型的toString方法,以及數組的length屬性,就通過“可枚舉性”,從而避免被for...in遍歷到。

  

  ES6 規定,所有 Class 的原型的方法都是不可枚舉的。總的來說,操作中引入繼承的屬性會讓問題復雜化,大多數時候,我們只關心對象自身的屬性。所以,盡量不要用for...in循環,而用Object.keys()代替。

 

🌟屬性的遍歷

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 鍵,按照加入時間升序排列

 

五、super關鍵詞:

  this關鍵字總是指向函數所在的當前對象,ES6 又新增了另一個類似的關鍵字super,指向當前對象的原型對象

  注意,super關鍵字表示原型對象時,只能用在對象的方法之中,用在其他地方都會報錯。

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

const proto = {
  foo: 'hello'
};

const obj = {
  foo: 'world',
  find() {
    return super.foo;
  }
};

Object.setPrototypeOf(obj, proto);
obj.find() // "hello"

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

  super.foo指向原型對象protofoo方法,但是綁定的this卻還是當前對象obj,因此輸出的就是world

 

 六、對象的擴展運算符:

  對象的解構賦值用於從一個對象取值,相當於將目標對象自身的所有可遍歷的(enumerable)、但尚未被讀取的屬性,分配到指定的對象上面。所有的鍵和它們的值,都會拷貝到新對象上面。

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

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

 

 


 

對象的新增方法:

一、Object.is()

  ES5 比較兩個值是否相等,只有兩個運算符:相等運算符(==)和嚴格相等運算符(===)。它們都有缺點,前者會自動轉換數據類型,后者的NaN不等於自身,以及+0等於-0。JavaScript 缺乏一種運算,在所有環境中,只要兩個值是一樣的,它們就應該相等。

  ES6 提出Object.is,它用來比較兩個值是否嚴格相等,與嚴格比較運算符(===)的區別:不同之處只有兩個:一是+0不等於-0,二是NaN等於自身

+0 === -0 //true
NaN === NaN // false

Object.is(+0, -0) // false
Object.is(NaN, NaN) // true

 

二、object.assign()

  用於對象的合並,將源對象(source)的所有可枚舉屬性,復制到目標對象(target)。第一個參數是目標對象,后面的參數都是源對象。注意,如果目標對象與源對象有同名屬性,或多個源對象有同名屬性,則后面的屬性會覆蓋前面的屬性。

  實行的是淺拷貝,而不是深拷貝。

const target = { a: 1, b: 1 };

const source1 = { b: 2, c: 2 };
const source2 = { c: 3 };

Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}

  如果該參數不是對象,則會先轉成對象,然后返回。由於undefinednull無法轉成對象,所以如果它們作為參數,就會報錯。

  特殊情況:如果非對象參數出現在源對象的位置(即非首參數),那么處理規則有所不同。首先,這些參數都會轉成對象,如果無法轉成對象,就會跳過。這意味着,如果undefinednull不在首參數,就不會報錯。 

let obj = {a: 1};
Object.assign(obj, undefined) === obj // true
Object.assign(obj, null) === obj // true

  其他類型的值(即數值、字符串和布爾值)不在首參數,也不會報錯。但是,除了字符串會以數組形式,拷貝入目標對象,其他值都不會產生效果。

const v1 = 'abc';
const v2 = true;
const v3 = 10;

const obj = Object.assign({}, v1, v2, v3);
console.log(obj); // { "0": "a", "1": "b", "2": "c" }

objext.assign 拷貝屬性:

  布爾值、數值、字符串分別轉成對應的包裝對象,可以看到它們的原始值都在包裝對象的內部屬性[[PrimitiveValue]]上面,這個屬性是不會被Object.assign拷貝的。只有字符串的包裝對象,會產生可枚舉的實義屬性,那些屬性則會被拷貝。

  Object.assign拷貝的屬性是有限制的,只拷貝源對象的自身屬性(不拷貝繼承屬性),也不拷貝不可枚舉的屬性(enumerable: false

Object(true) // {[[PrimitiveValue]]: true}
Object(10)  //  {[[PrimitiveValue]]: 10}
Object('abc') // {0: "a", 1: "b", 2: "c", length: 3, [[PrimitiveValue]]: "abc"}

用途:

(1)數組的處理:

  可以用來處理數組,但是會把數組視為對象。例子中,Object.assign把數組視為屬性名為 0、1、2 的對象,因此源數組的 0 號屬性4覆蓋了目標數組的 0 號屬性1。 

Object.assign([1, 2, 3], [4, 5])
// [4, 5, 3]

(2)為對象添加屬性:

class Point {
  constructor(x, y) {
    Object.assign(this, {x, y});
  }
}

(3)為對象添加方法:

Object.assign(SomeClass.prototype, {
  someMethod(arg1, arg2) {
    ···
  },
  anotherMethod() {
    ···
  }
});

// 等同於下面的寫法
SomeClass.prototype.someMethod = function (arg1, arg2) {
  ···
};
SomeClass.prototype.anotherMethod = function () {
  ···
};

(4)克隆對象:

將原始對象拷貝到一個空對象,就得到了原始對象的克隆。

function clone(origin) {
  return Object.assign({}, origin);
}

  不過,采用這種方法克隆,只能克隆原始對象自身的值,不能克隆它繼承的值。如果想要保持繼承鏈,可以采用下面的代碼。

function clone(origin) {
  let originProto = Object.getPrototypeOf(origin);
  return Object.assign(Object.create(originProto), origin);
}

(5)合並多個對象:

const merge =
  (target, ...sources) => Object.assign(target, ...sources);


// 如果希望合並后返回一個新對象,可以改寫上面函數,對一個空對象合並。
const merge =
  (...sources) => Object.assign({}, ...sources);

 

三、Object.keys(),Object.values(),Object.entries()

  ES5 引入了Object.keys方法,返回一個數組,成員是參數對象自身的(不含繼承的)所有可遍歷(enumerable)屬性的鍵名。

 

  Object.values方法返回一個數組,成員是參數對象自身的(不含繼承的)所有可遍歷(enumerable)屬性的鍵值。

 

  Object.entries()方法返回一個數組,成員是參數對象自身的(不含繼承的)所有可遍歷(enumerable)屬性的鍵值對數組。只輸出屬性名非 Symbol 值的屬性。除了返回值不一樣,該方法的行為與Object.values基本一致。

const obj = { foo: 'bar', baz: 42 };
Object.entries(obj)
// [ ["foo", "bar"], ["baz", 42] ]

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM