ES6 物件的擴充套件

Zingiber發表於2019-09-06

1.1屬性、方法簡潔表示

允許直接寫入變數和函式作為物件屬性和方法

let birth = '2000/01/01';

const Person = {

  name: '張三',

  //等同於birth: birth
  birth,

  // 等同於hello: function ()...
  hello() { console.log('我的名字是', this.name); }

};
複製程式碼

1.2表示式作為屬性名

在ES5中,字面量方式定義物件時,不可以使用表示式作為屬性名。ES6字面量定義物件時允許這種方式,也就是將表示式放在方括號內,也可以定義方法名。

let lastWord = 'last word';

const a = {
  'first word': 'hello',
  [lastWord]: 'world',
  ['h' + 'ello']() {
    return 'hi';
  }
};

console.log(a['first word']); // "hello"
console.log(a[lastWord]); // "world"
console.log(a['last word']); // "world"
console.log(a.hello()); // hi
console.log(a['last word']); // "world"
複製程式碼

1.3物件方法有name屬性

方法的name屬性返回函式名

const person = {
  sayName() {
    console.log('hello!');
  },
};

person.sayName.name   // "sayName"
複製程式碼

特殊情況:

  1. 物件的方法使用了取值函式(getter)和存值函式(setter),name屬性不在該方法上,而是在該方法的屬性的描述物件的get和set屬性上。
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"
複製程式碼
  1. bind方法創造的函式,name屬性返回bound加上原函式的名字。
  2. Function建構函式創造的函式,name屬性返回anonymous
(new Function()).name // "anonymous"

var doSomething = function() {
  // ...
};
doSomething.bind().name // "bound doSomething"
複製程式碼

1.4屬性可列舉、可遍歷

1.可列舉性

物件每個屬性都有描述物件,描述物件有enumerable屬性,稱可列舉性,如果屬性為false,表示不可列舉。

四個忽略enumerable為false屬性的操作 1.for...in迴圈;Object.keys();JSON.stringify();Object.assign()(ES6新增)

2.屬性的遍歷

(1) for...in:遍歷物件自身和繼承的可列舉屬性(不含Symbol屬性)

(2) Object.keys(obj):返回陣列,包括物件自身的(不含繼承的)所有可列舉屬性(不含 Symbol 屬性)的鍵名。

(3) Object.getOwnPropertyNames(obj返回一個陣列,包含物件自身的所有屬性(不含 Symbol 屬性,但是包括不可列舉屬性)的鍵名。

(4) Object.getOwnPropertySymbols(obj):返回一個陣列,包含物件自身的所有 Symbol 屬性的鍵名。

(5) Reflect.ownKeys(obj):返回一個陣列,包含物件自身的所有鍵名,不管鍵名是 Symbol 或字串,也不管是否可列舉。

1.5 super關鍵字

super關鍵字指向當前物件的原型物件,super表示原型物件時,只能用在物件的方法中。

const proto = {
  foo: 'hello'
};

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

Object.setPrototypeOf(obj, proto);
console.log(obj.find()); // "hello"
複製程式碼

1.6物件的擴充套件運算子

用於取出物件理所有可遍歷的屬性,拷貝到當前物件。

let z = { a: 3, b: 4 };
let n = { ...z };
n // { a: 3, b: 4 }
複製程式碼

擴充套件運算子等同於使用Object.assign(),只拷貝物件例項的屬性

let aClone = { ...a };
// 等同於
let aClone = Object.assign({}, a);
複製程式碼

擴充套件運算子可以用於合併兩個物件。

let ab = { ...a, ...b };
// 等同於
let ab = Object.assign({}, a, b);
複製程式碼

如果使用者自定義的屬性,放在擴充套件運算子後面,則擴充套件運算子內部的同名屬性會被覆蓋掉。方便修改物件部分現有屬性。

let aWithOverrides = { ...a, x: 1, y: 2 };
// 等同於
let aWithOverrides = { ...a, ...{ x: 1, y: 2 } };
// 等同於
let x = 1, y = 2, aWithOverrides = { ...a, x, y };
// 等同於
let aWithOverrides = Object.assign({}, a, { x: 1, y: 2 });
複製程式碼

如果把自定義屬性放在擴充套件運算子前面,就變成了設定新物件的預設屬性值。

let aWithDefaults = { x: 1, y: 2, ...a };
// 等同於
let aWithDefaults = Object.assign({}, { x: 1, y: 2 }, a);
// 等同於
let aWithDefaults = Object.assign({ x: 1, y: 2 }, a);
複製程式碼

其他特點可參照陣列的擴充套件運算子。

1.7物件的解構賦值

用於從一個物件取值

let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
x // 1
y // 2
z // { a: 3, b: 4 }
複製程式碼

參照前面解構賦值

1.8物件的新增方法

1.8.1 Object.is()

比較兩個值是否嚴格相等,與===行為基本一致,不同之處在於:===中,+0等於-0,NaN不等於自身。

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

Object.is(+0, -0) // false
Object.is(NaN, NaN) // true
複製程式碼

1.8.2 Object.assign()

用法

用於將所有可列舉屬性的值從一個或多個源物件複製到目標物件。返回目標物件。

const target = { a: 1 };

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

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

注意點

  1. 如果目標物件與源物件有同名屬性,或多個源物件有同名屬性,則後面的屬性會覆蓋前面的屬性;
  2. 如果只有一個引數會直接返回該引數;
  3. 如果第一個引數不是物件,會轉成物件,undefined和null無法轉成物件,作為引數會報錯;
  4. 如果非物件引數不是第一個引數,且無法轉成物件,就直接跳過。
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" }
複製程式碼
  1. 只拷貝源物件自身屬性,不拷貝繼承屬性,也不拷貝不可列舉屬性
  2. 屬性名為Symbol值的屬性,也會被拷貝
Object.assign({ a: 'b' }, { [Symbol('c')]: 'd' })
// { a: 'b', Symbol(c): 'd' }
複製程式碼
  1. 淺拷貝
  2. 對於巢狀的物件,一旦遇到同名屬性,Object.assign方法會進行替換
const target = { a: { b: 'c', d: 'e' } }
const source = { a: { b: 'hello' } }
Object.assign(target, source)
// { a: { b: 'hello' } }

複製程式碼
  1. 可以處理陣列,把陣列視為物件
Object.assign([1, 2, 3], [4, 5])
// [4, 5, 3]
複製程式碼

Object.assign把陣列視為屬性名為 0、1、2 的物件,因此源陣列的 0 號屬性4覆蓋了目標陣列的 0 號屬性1。 10. Object.assign只能進行值的複製,如果要複製的值是一個取值函式,那麼將求值後再複製。

const source = {
  get foo() { return 1 }
};
const target = {};

Object.assign(target, source)
// { foo: 1 }
複製程式碼

source物件的foo屬性是一個取值函式,Object.assign不會複製這個取值函式,只會拿到值以後,將這個值複製過去。

常見用途

  1. 給物件新增屬性
class Point {
  constructor(x, y) {
    Object.assign(this, {x, y});
  }
}
複製程式碼
  1. 給物件新增方法
Object.assign(obj, {
  someMethod(arg1, arg2) {
    ···
  },
  anotherMethod() {
    ···
  }
});
複製程式碼
  1. 克隆物件(淺拷貝,不能克隆繼承值)
Object.assign({}, origin);
複製程式碼
  1. 合併多個物件
Object.assign(target, ...sources);
複製程式碼
  1. 給屬性指定預設值
const DEFAULTS = {
  logLevel: 0,
  outputFormat: 'html'
};

function processContent(options) {
  options = Object.assign({}, DEFAULTS, options);
}
複製程式碼

1.8.3 Object.getOwnPropertyDescriptors()

用來獲取一個物件的==所有==自身屬性的描述符。引數為任意物件。

解決了Object.assign()無法正確拷貝get和set屬性的問題。Object.getOwnPropertyDescriptors()方法配合Object.defineProperties()方法,實現正確拷貝。

const source = {
  set foo(value) {
    console.log(value);
  }
};

const target2 = {};
Object.defineProperties(target2, Object.getOwnPropertyDescriptors(source));
Object.getOwnPropertyDescriptor(target2, 'foo')
// { get: undefined,
//   set: [Function: set foo],
//   enumerable: true,
//   configurable: true }
複製程式碼

配合Object.create()方法,將物件屬性克隆到新物件(淺拷貝)

const clone = Object.create(Object.getPrototypeOf(obj),
  Object.getOwnPropertyDescriptors(obj));
複製程式碼

實現一個物件繼承另一個物件

const obj = Object.create(
  prot,
  Object.getOwnPropertyDescriptors({
    foo: 123,
  })
);
複製程式碼

1.8.5 Object.keys(), Object.values(), Object.entries()

1.Object.keys()

返回一個陣列,成員是引數物件自身的(不含繼承的)所有可遍歷(enumerable)屬性的鍵名。

引數:obj-要返回其列舉自身屬性的物件

2.Object.values()

返回一個陣列,成員是引數物件自身的(不含繼承的)所有可遍歷(enumerable)屬性的鍵值。

引數:obj-要返回其列舉自身屬性鍵值的物件

Object.entries()

返回一個陣列,成員是引數物件自身的(不含繼承的)所有可遍歷(enumerable)屬性的鍵值對陣列。

引數:obj-可以返回其可列舉屬性的鍵值對的物件。

Object.keys,Object.values和Object.entries,作為遍歷一個物件的補充手段,供for...of迴圈使用。

let {keys, values, entries} = Object;
let obj = { a: 1, b: 2, c: 3 };

for (let key of keys(obj)) {
  console.log(key); // 'a', 'b', 'c'
}

for (let value of values(obj)) {
  console.log(value); // 1, 2, 3
}

for (let [key, value] of entries(obj)) {
  console.log([key, value]); // ['a', 1], ['b', 2], ['c', 3]
}
複製程式碼

1.8.6 ES5常用物件方法

1.Object.defineProperty()

會直接在一個物件上定義一個新屬性,或者修改一個物件的現有屬性, 並返回這個物件。

引數:

(1)obj-要在其上定義屬性的物件

(2)prop-要定義或修改屬性的名稱

(3)descriptor-將被定義或修改的屬性描述符

descriptor可以設定的值有

  • [value]:屬性的值。
  • [writable]:該屬性是否可寫,如果設定成 false,則任何對該屬性改寫的操作都無效(但不會報錯)。
  • [configurable]:如果為false,則任何嘗試刪除目標屬性或修改屬性以下特性(writable, configurable, enumerable)的行為將被無效化。
  • [enumerable]:可列舉性。
  • [get]:一旦目標物件訪問該屬性,就會呼叫這個方法,並返回結果。
  • [set]:一旦目標物件設定該屬性,就會呼叫這個方法。

實際運用

優化物件獲取和修改屬性的方式

//加入有一個目標節點, 想設定其位移
var targetDom = document.getElementById('target');
var transformText = 'translateX(' + 10 + 'px)';
targetDom.style.webkitTransform = transformText;
targetDom.style.transform = transformText;

// 用defineProperty方法優化
Object.defineProperty(dom, 'translateX', {
set: function(value) {
    var transformText = 'translateX(' + value + 'px)';
    dom.style.webkitTransform = transformText;
    dom.style.transform = transformText;
}
//這樣再後面呼叫的時候, 十分簡單
dom.translateX = 10;
dom.translateX = -10;
複製程式碼

MVVM中資料‘雙向繫結’實現

<!DOCTYPE html>
 <html>
  <head>
    <meta charset="utf-8">
    <title>標題</title>
  </head>
  <body>
    <h3>使用Object.defineProperty實現簡單的雙向資料繫結</h3>
    <input type="text" id="input" />
    <div id="div"></div>
    
	<script>
        var obj = {};
        var inputVal = document.getElementById("input");
        var div = document.getElementById("div");
 
        Object.defineProperty(obj, "name", {
          set: function(newVal) {
            inputVal.value = newVal;
            div.innerHTML = newVal;
          }
        });
        inputVal.addEventListener('input', function(e){
          obj.name = e.target.value;
        });
    </script>
	
  </body>
</html>
複製程式碼

2.Object.seal()

封閉一個物件,阻止新增新屬性並將所有現有屬性標記為不可配置, 當前屬性的值只要可寫就可以改變。

引數:obj-要被密封的物件

// 如果屬性值可寫
let obj = Object.defineProperty({},'name',{
  value:'hello',
  writable:true
})
Object.seal(obj);
console.log(obj.name); // hello
obj.name = 'world';
delete obj.name;
console.log(obj.name); // world 

// 如果屬性值不可寫
let obj = Object.defineProperty({},'name',{
  value:'hello',
  writable:false,
})
Object.seal(obj);
console.log(obj.name); // hello
obj.name = 'world';
delete obj.name;
console.log(obj.name); // hello
複製程式碼

3.Object.freeze()

可以凍結一個物件。一個被凍結的物件再也不能被修改;凍結了一個物件則不能向這個物件新增新的屬性,不能刪除已有屬性,不能修改該物件已有屬性的可列舉性、可配置性、可寫性,以及不能修改已有屬性的值。此外,凍結一個物件後該物件的原型也不能被修改。

引數:obj-要被凍結的物件

let obj = Object.defineProperty({},'name',{
  value:'hello',
  writable:true
})
Object.freeze(obj);
console.log(obj.name); // hello
obj.name = 'world';
delete obj.name;
console.log(obj.name); // hello

複製程式碼

摘自阮一峰

參照MDN

相關文章