對象的擴展

文章編寫參考 阮一峯《ECMAScript 6 入門》


1.屬性的簡介表示方式

ES6 允許直接寫入變量和函數,作爲對象的屬性和方法。這樣的書寫更加簡潔。

var name = "Blue";
var Person = {
    name
}
//等同於
var Person = {
    name: name
}

上面代碼表明,ES6允許在對象中,直接寫入變量。這時【屬性名爲變量名,屬性值爲變量值】

let fun = (x, y) => ({ x, y })

//等同於

var fun = function fun(x, y) {
  return { x: x, y: y };
};
fun(1,2);
//{ x: 1, y: 2 }

除了屬性可以簡寫,方法也可以簡寫

let obj = {
    fun() {
        callme('Blue')
    }
}

//等同於

var obj = {
    fun: function fun() {
        callme('Blue');
    }
};

結合屬性和矢量的簡寫,我們看看下面這一個例子

let gender = '男';
let Me = {
    name: "Blue",
    gender,
    sayHi() {
        console.log('I am ', this.name);
    }
}

有了對象屬性的簡寫,那麼在函數中要返回多個值得時候又有了一種簡潔的方式

let getPoint = () => {
    let x = 1;
    let y = 2;
    return { x, y }
}

2.屬性名表達式

爲對象定義屬性有兩種方式,一種是直接【用標識名作爲屬性名】,第二種是用【表達式作爲屬性名】,這時表達式放在方括號內

obj.name = "Blue";

obj['gen' + 'der'] = 'Man'

但是如果只用字面量方式定義對象(使用大括號),在ES5中只有使用標識符的形式定義屬性

var obj = {
  foo: true,
  abc: 123
};

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

let name = 'Blue';
let obj = {
    name,
    ['gen' + 'der']: 'Man',
    ['say' + 'Hi']() {
        console.log(this.name, this['gen' + 'der']);
    }
}

【注意】屬性名表達式與簡潔表示法,不能同時使用,會報錯。

// 報錯
var foo = 'bar';
var bar = 'abc';
var baz = { [foo] };

// 正確
var foo = 'bar';
var baz = { [foo]: 'abc'};

3.方法的name屬性

函數的name屬性,返回函數名。對象方法也是函數,因此也有name屬性。

const Person = {
    sayName() {
        console.log("Blue");
    }
}
Person.sayName.name;    //sayName

如果對象的方法使用了取值函數或者存值函數,那麼name屬性不是在該方法上面,而是該方法的屬性的描述對象中的get和set屬性上面,返回值的前面加上了get和set

const obj = {
    get foo() {
        return 'Blue';
    },
    set foo(name1) {
        this.foo = name1;
    }
}
obj.foo.name;
// TypeError: Cannot read property 'name' of undefined

const descript = Object.getOwnPropertyDescriptor(obj, 'foo');
descript.get.name;  // "get foo"
descript.set.name;  // "set foo"

有兩種特殊情況:bind方法創造的函數,name屬性返回bound加上原函數的名字;Function構造函數創造的函數,name屬性返回anonymous。

(new Function()).name // "anonymous"

var doSomething = function() {
  // ...
};
doSomething.bind().name // "bound doSomething"

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

const key1 = Symbol('Blue');
const key2 = Symbol('Crazy');
const obj = {
    [key1]() { },
    [key2]() { }
}
obj[key1].name; //[Blue]
obj[key2].name; //[Crazy]

4.Object.is( )

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

【Object.is】用來比較兩個值是否嚴格相等,與嚴格比較運算符(===)的行爲基本一致。

Object.is('foo', 'foo')
// true
Object.is({}, {})
// false

不同之處只有兩個:一是+0不等於-0,二是NaN等於自身。

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

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

5.Object.assign( )

5.1基本用法
Object.assign( )用於合併對象,將源對象所有的可枚舉的屬性,【複製】到目標對象

var target = { a: 1 };

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

Object.assign( )的第一個參數是目標對象,後面的參數全部是源對象。

【注意】根據後面對象合併到目標對象可以很明顯的看出,如果目標對象和源對象有同名屬性,則目標對象的屬性會被源對象的屬性所覆蓋。

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

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

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

【如果只有一個參數,那麼Object.assign( )會直接返回該對象】

var target = { a: 1 };
Object.assign(target);
target; //{ a: 1 }

【如果該參數不是對象,則會先轉換成對象,然後進行返回】

let obj = Object.assign(2);
typeof obj; //object

由於undefined和null無法轉成對象,所以如果它們作爲參數,就會報錯

Object.assign(undefined) // 報錯
Object.assign(null) // 報錯

如果非對象參數出現在源對象的位置(即非首參數),那麼處理規則有所不同。首先,這些參數都會轉成對象,如果無法轉成對象,就會跳過。這意味着,【如果undefined和null不在首參數,就不會報錯】。

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

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

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

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

上面代碼v1是字符串,會被轉換成類數組對象進行拷貝。

Object.assign( )只拷源對象的自身屬性,不拷貝繼承的屬性,也不拷貝不可枚舉的屬性

Object.assign({ b: 'c' }, Object.defineProperty({}, "invisable", {
    enumerable: false,
    value: 'hello'
}))
//{ b: 'c' }

屬性名爲 Symbol 值的屬性,也會被Object.assign拷貝。

 Object.assign({ a: "b" }, { [Symbol("Blue")]: 'Crazy' });
 //{a: "b", Symbol(Blue): "Crazy"}

【注意】Object.assign方法實行的是淺拷貝,而不是深拷貝。也就是說,如果源對象某個屬性的值是對象,那麼目標對象拷貝得到的是這個對象的引用。

var obj1 = {a: {b: 1}};
var obj2 = Object.assign({}, obj1);

obj1.a.b = 2;
obj2.a.b // 2

5.2 常見用途

5.2.1 爲對象添加屬性

class Person {
    constructor(name, age) {
        Object.assign(this, { name, age });
    }
}

上面代碼中通過Object.assign( )向Person類對象添加了name和age兩個屬性。

5.2.2 爲對象添加方法

class Person {
    constructor(name, age) {
        Object.assign(this, {
            sayHi() {

            },
            sayHello() {

            }
        })
    }
}

與添加屬性類似,上面代碼將sayHi和sayHello兩個方法添加到Person類的實例中。

5.2.3 克隆對象

let cloneObj = origion => Object.assign({}, origion);
cloneObj({ a: 'Blue', b: "Crazy" });
//{ a: 'Blue', b: 'Crazy' }

上面代碼將一個對象拷貝到一個空對象,實現了對象的拷貝。但是這樣的克隆只能克隆源對象的自身的可枚舉屬性,不能克隆繼承屬性。

5.2.4 合併多個對象

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

利用擴展運算符和函數參數的結合很容易寫出上面代碼中的合併對象代碼。

5.2.5 爲屬性指定默認值

const DEFAULTS = {
  logLevel: 0,
  outputFormat: 'html'
};

function processContent(options) {
  options = Object.assign({}, DEFAULTS, options);
  console.log(options);
  // ...
}

6.屬性的可枚舉性

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

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

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

ES5 有三個操作會忽略enumerable爲false的屬性。

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

ES6 新增了一個操作Object.assign(),會忽略enumerable爲false的屬性,只拷貝對象自身的可枚舉的屬性。

【ES6規定,所有Class的原型的方法都是不可枚舉的】

總的來說,操作中引入繼承的屬性會讓問題複雜化,大多數時候,我們只關心對象自身的屬性。所以,【儘量不要用for…in循環,而用Object.keys()代替】。


7.屬性的遍歷

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

7.1 for…in
for…in遍歷對象自身和繼承的可枚舉的屬性(不含Symbol屬性)。

7.2 Object.keys(obj)
Object.keys(obj)返回一個數組,包含自生所有的可枚舉的屬性(不含Symbol屬性)。

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

7.4 Object.getOwnPropertySymbols(obj)
Object.getOwnPropertySymbols(obj)返回一個數組,包含對象自身的所有 Symbol 屬性。

7.5 Reflect.ownKeys(obj)
Reflect.ownKeys(obj)返回對象的所有屬性,不管是否是Symbol屬性還是是否可枚舉。


8._proto_屬性,Object.setPrototypeOf(),Object.getPrototypeOf()

8.1 _proto_屬性
_proto_屬性(前後各兩個下劃線),用來讀取或設置當前對象的prototype對象。目前,所有瀏覽器(包括 IE11)都部署了這個屬性。

// es6的寫法
var obj = {
  method: function() { ... }
};
obj.__proto__ = someOtherObj;

// es5的寫法
var obj = Object.create(someOtherObj);
obj.method = function() { ... };

8.2 Object.setPrototypeOf( )
Object.setPrototypeOf方法的作用與_proto_相同,用來設置一個對象的prototype對象,返回參數對象本身。它是 ES6 正式推薦的設置原型對象的方法。

// 格式
Object.setPrototypeOf(object, prototype)

// 用法
var o = Object.setPrototypeOf({}, null);

該方法如同下面的函數

function (obj,proto){
    obj.__proto__ = proto;
    return obj;
}

8.3 Object.getPrototypeOf( )

該方法與Object.setPrototypeOf方法配套,用於讀取一個對象的原型對象。

Object.getPrototypeOf(obj);

下面是一個配套使用的例子

function Person() { };
var p = new Person();
Object.getPrototypeOf(p) === Person.prototype;
//true

Object.setPrototypeOf(p, Object.prototype);
Object.getPrototypeOf(p) === Person.prototype;
//false

Object.getPrototypeOf( )和Object.setPrototypeOf( )要求的參數都是對象,如果參數不是對象會被默認轉換成object,如果轉換失敗則報錯。


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

在數組的擴展中,數組實例擁有這個三個方法,其實在對象上功能類似。

9.1 Object.keys( )

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

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

9.2 Object.values( )

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

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

9.3 Object.entries( )

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

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

10.對象的擴展運算符

數組擁有擴展運算符,其實對象也擁有。

10.1 解構賦值

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

擴展運算符用於對象的解構賦值時,所有未匹配的屬性都會複製到一個對象中。

跟數組一樣,對象的解構賦值也必須是最後一個參數,否則是會報錯的。

let { ...x, y, z } = obj; // 錯誤
let { x, ...y, ...z } = obj; // 錯誤

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

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

解構賦值不會拷貝繼承自原型對象的屬性。

let o1 = { a: 1 };
let o2 = { b: 2 };
o2.__proto__ = o1;
let { ...o3 } = o2;
o3 // { b: 2 }
o3.a // undefined

10.2 擴展運算符

擴展運算符(…)用於取出參數對象的所有可遍歷屬性,拷貝到當前對象之中。

let z = { a: 3, b: 4 };
let n = { ...z };
n // { a: 3, b: 4 }

上面的行爲和Object.assign( )有些相似了。

let aClone = { ...a };
// 等同於
let aClone = Object.assign({}, a);

上面的例子只是拷貝了對象實例的屬性,如果想完整克隆一個對象,還拷貝對象原型的屬性,可以採用下面的寫法。

// 寫法一
const clone1 = {
  __proto__: Object.getPrototypeOf(obj),
  ...obj
};

// 寫法二
const clone2 = Object.assign(
  Object.create(Object.getPrototypeOf(obj)),
  obj
);

上面代碼中,寫法一的_proto_屬性在非瀏覽器的環境不一定部署,因此推薦使用寫法二。

【擴展運算符用於合併兩個對象】

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

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

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

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

{...{}, a: 1}
// { a: 1 }

【如果擴展運算符的參數是null或undefined,這兩個值會被忽略,不會報錯。】

let emptyObject = { ...null, ...undefined }; // 不報錯

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

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

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

11.Object.getOwnPropertyDescriptors()

Object.getOwnPropertyDescriptors方法,返回指定對象所有自身屬性(非繼承屬性)的描述對象。

const obj = {
  foo: 123,
  get bar() { return 'abc' }
};

Object.getOwnPropertyDescriptors(obj)
// { foo:
//    { value: 123,
//      writable: true,
//      enumerable: true,
//      configurable: true },
//   bar:
//    { get: [Function: bar],
//      set: undefined,
//      enumerable: true,
//      configurable: true } }

上面代碼中,Object.getOwnPropertyDescriptors方法返回一個對象,所有原對象的屬性名都是該對象的屬性名,對應的屬性值就是該屬性的描述對象。

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