對象的擴展
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
屬性不是在該方法上面,而是該方法的屬性的描述對象的get
和set
屬性上面,返回值是方法名前加上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"
Object.getOwnPropertyDescriptor():
Object.getOwnPropertyDescriptor()
方法返回指定對象上一個自有屬性對應的屬性描述符。(自有屬性指的是直接賦予該對象的屬性,不需要從原型鏈上進行查找的屬性)
3.兩種特殊情況:
bind
方法創造的函數,name
屬性返回bound
加上原函數的名字Function
構造函數創造的函數,name
屬性返回anonymous
4.如果對象的方法是一個 Symbol 值,那麼name
屬性返回的是這個 Symbol 值的描述。
屬性的可枚舉性和遍歷
1.可枚舉性:對象的每個屬性都有一個描述對象(Descriptor),用來控制該屬性的行爲。Object.getOwnPropertyDescriptor
方法可以獲取該屬性的描述對象。
2.描述對象的enumerable
屬性,稱爲“可枚舉性”,如果該屬性爲false
,就表示某些操作會忽略當前屬性。
3.目前,有四個操作會忽略enumerable
爲false
的屬性。
for...in
循環:只遍歷對象自身的和繼承的可枚舉的屬性。Object.keys()
:返回對象自身的所有可枚舉的屬性的鍵名。JSON.stringify()
:只串行化對象自身的可枚舉的屬性。Object.assign()
: 忽略enumerable
爲false
的屬性,只拷貝對象自身的可枚舉的屬性。
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
是解構賦值所在的對象。它獲取等號右邊的所有尚未讀取的鍵(a
和b
),將它們連同值一起拷貝過來。
2.如果等號右邊是undefined
或null
,就會報錯,因爲它們無法轉爲對象。
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');
}
}
};