
本文是 重温基础 系列文章的第十二篇。今日感受:需要总结下2018。
这几天,重重的感冒发烧,在家休息好几天,伤···。
本章节复习的是JS中对象的使用,这是重点。
前置知识:JavaScrip的设计是一个简单的基于对象的范式。对象是一系列属性的集合,一个属性包含一个键和一个值,也叫键值对。若一个属性的值,是一个函数,则称这个属性为方法。
一个对象,可以有很多方法,就像一个杯子,可以有颜色,重量,形状等属性。
注意:对象的名称,对大小写敏感。
1.创建对象
本文主要是复习对象的使用,至于对象的创建,下面这里简单介绍一下常见的创建对象的方法:
创建方法1:
-
let obj = new Object(); // 声明一个空对象
创建方法2:
-
let obj = {}; // 声明一个空对象
上面的 name是对象 obj中的一个属性,对应的值为 "leo"。
2.设置对象属性和访问属性
设置对象属性,也有两种方法:直接设置:
-
let obj = {}; -
obj.name = "leo"; // 为对象添加属性和值
创建时设置:
-
let obj = {name : 'leo'}; -
obj.name = "leo"; // 为对象添加属性和值
当一个对象定义了一个属性,我们可以用点符号( .)来访问它。
-
obj.name; // "leo"
注意:
- 属性的另外一种写法:
-
let obj = {}; -
let n = "name"; -
obj[n] = "leo"; // 属性名使用变量 -
obj["height"] = 188; // 字符串 -
obj["age" + "Is"] = 15; // 字符串拼接 -
console.log(obj); -
/* -
obj = { -
ageIs: 15 -
height: 188 -
name: "leo" -
} -
*/
- 属性的值也可以是个方法:
-
let obj = {}; -
obj.getTime = function(){ -
return new Date(); -
} -
obj.getTime(); // 访问属性的方法 -
//Wed Jan 02 2019 21:07:59 GMT+0800 (中国标准时间)
3.枚举对象的所有属性
从 ECMAScript 5 开始,有三种原生的方法用于列出或枚举对象的属性:
3.1 for...in 循环
该方法依次访问一个对象及其原型链中所有可枚举的属性。
-
let obj = {a:1, b:2, c:3}; -
for (let i in obj) { -
console.log("obj." + i + " = " + obj[i]); -
} -
/* -
"obj.a = 1" -
"obj.b = 2" -
"obj.c = 3" -
*/
3.2 Object.keys(o)
该方法返回一个对象 o 自身包含(不包括原型中)的所有属性的名称的数组。
-
let obj = {a:1, b:2, c:3}; -
let arr = Object.keys(obj); -
console.log(arr); -
/* -
["a", "b", "c"] -
*/ -
arr.forEach(function(val,index,array){ -
console.log("key:" + val+",val:"+obj[val]) -
}) -
/* -
key:a,val:1 -
key:b,val:2 -
key:c,val:3 -
*/
3.3 Object.getOwnPropertyNames(o)
该方法返回一个数组,它包含了对象 o 所有拥有的属性(无论是否可枚举)的名称。
-
let obj = {a:1, b:2, c:3}; -
let arr = Object.getOwnPropertyNames(obj); -
console.log(arr); -
/* -
["a", "b", "c"] -
*/ -
arr.forEach(function(val,index,array){ -
console.log("key:" + val+",val:"+obj[val]) -
}) -
/* -
key:a,val:1 -
key:b,val:2 -
key:c,val:3 -
*/
4.ES6新增:对象的拓展
4.1 属性的简洁表示
-
let a = 'a1'; -
let b = { a }; // b => { a : 'a1' } -
// 等同于 -
let b = { a : a }; -
function f(a, b){ -
return {a, b}; -
} -
// 等同于 -
function f (a, b){ -
return {a:a ,b:b}; -
} -
let a = { -
fun () { -
return 'leo'; -
} -
} -
// 等同于 -
let a = { -
fun : function(){ -
return 'leo'; -
} -
}
4.2 属性名表达式
JavaScript提供2种方法定义对象的属性。
-
// 方法1 标识符作为属性名 -
a.f = true; -
// 方法2 字符串作为属性名 -
a['f' + 'un'] = true;
延伸出来的还有:
-
let a = 'hi leo'; -
let b = { -
[a]: true, -
['a'+'bc']: 123, -
['my' + 'fun'] (){ -
return 'hi'; -
} -
}; -
// b.a => undefined ; b.abc => 123 ; b.myfun() => 'hi' -
// b[a] => true ; b['abc'] => 123 ; b['myfun'] => ƒ ['my' + 'fun'] (){ return 'hi'; }
注意:属性名表达式不能与简洁表示法同时使用,否则报错。
-
// 报错 -
let a1 = 'aa'; -
let a2 = 'bb'; -
let b1 = {[a1]}; -
// 正确 -
let a1 = 'aa'; -
let b1 = { [a1] : 'bb'};
4.3 Object.is()
Object.is() 用于比较两个值是否严格相等,在ES5时候只要使用相等运算符( ==)和严格相等运算符( ===)就可以做比较,但是它们都有缺点,前者会自动转换数据类型,后者的 NaN不等于自身,以及 +0等于 -0。
-
Object.is('a','a'); // true -
Object.is({}, {}); // false -
// ES5 -
+0 === -0 ; // true -
NaN === NaN; // false -
// ES6 -
Object.is(+0,-0); // false -
Object.is(NaN,NaN); // true
4.4 Object.assign()
Object.assign()方法用于对象的合并,将原对象的所有可枚举属性复制到目标对象。基础用法:第一个参数是目标对象,后面参数都是源对象。
-
let a = {a:1}; -
let b = {b:2}; -
Object.assign(a,b); // a=> {a:1,b:2}
注意:
- 若目标对象与源对象有同名属性,则后面属性会覆盖前面属性。
-
let a = {a:1, b:2}; -
let b = {b:3, c:4}; -
Object.assign(a, b); // a => {a:1, b:3, c:4}
- 若只有一个参数,则返回该参数。
-
let a = {a:1}; -
Object.assign(a) === a; // true
- 若参数不是对象,则先转成对象后返回。
-
typeof Object.assign(2); // 'object'
- 由于
undefined或 NaN无法转成对象,所以做为参数会报错。
-
Object.assign(undefined) // 报错 -
Object.assign(NaN); // 报错
-
Object.assign()实现的是浅拷贝。
Object.assign()拷贝得到的是这个对象的引用。这个对象的任何变化,都会反映到目标对象上面。
-
let a = {a: {b:1}}; -
let b = Object.assign({},a); -
a.a.b = 2; -
console.log(b.a.b); // 2
- 将数组当做对象处理,键名为数组下标,键值为数组下标对应的值。
-
Object.assign([1, 2, 3], [4, 5]); // [4, 5, 3]
5.ES8新增:Object.values(),Object.entries()
ES7中新增加的 Object.values()和 Object.entries()与之前的 Object.keys()类似,返回数组类型。回顾下 Object.keys():
-
var a = { f1: 'hi', f2: 'leo'}; -
Object.keys(a); // ['f1', 'f2']
5.1 Object.values()
返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历属性的键值。
-
let a = { f1: 'hi', f2: 'leo'}; -
Object.values(a); // ['hi', 'leo']
如果参数不是对象,则返回空数组:
-
Object.values(10); // [] -
Object.values(true); // []
5.2 Object.entries()
返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历属性的键值对数组。
-
let a = { f1: 'hi', f2: 'leo'}; -
Object.entries(a); // [['f1','hi'], ['f2', 'leo']]
- 用途1:
遍历对象属性。
-
let a = { f1: 'hi', f2: 'leo'}; -
for (let [k, v] of Object.entries(a)){ -
console.log( -
`${JSON.stringfy(k)}:${JSON.stringfy(v)}` -
) -
} -
// 'f1':'hi' -
// 'f2':'leo'
- 用途2: 将对象转为真正的Map结构。
-
let a = { f1: 'hi', f2: 'leo'}; -
let map = new Map(Object.entries(a));
手动实现 Object.entries()方法:
-
// Generator函数实现: -
function* entries(obj){ -
for (let k of Object.keys(obj)){ -
yield [k ,obj[k]]; -
} -
} -
// 非Generator函数实现: -
function entries (obj){ -
let arr = []; -
for(let k of Object.keys(obj)){ -
arr.push([k, obj[k]]); -
} -
return arr; -
}
6.ES8新增:Object.getOwnPropertyDescriptors()
之前有 Object.getOwnPropertyDescriptor方法会返回某个对象属性的描述对象,新增的 Object.getOwnPropertyDescriptors()方法,返回指定对象所有自身属性(非继承属性)的描述对象,所有原对象的属性名都是该对象的属性名,对应的属性值就是该属性的描述对象
-
let a = { -
a1:1, -
get f1(){ return 100} -
} -
Object.getOwnPropetyDescriptors(a); -
/* -
{ -
a:{ configurable:true, enumerable:true, value:1, writeable:true} -
f1:{ configurable:true, enumerable:true, get:f, set:undefined} -
} -
*/
实现原理:
-
function getOwnPropertyDescriptors(obj) { -
const result = {}; -
for (let key of Reflect.ownKeys(obj)) { -
result[key] = Object.getOwnPropertyDescriptor(obj, key); -
} -
return result; -
}
引入这个方法,主要是为了解决 Object.assign()无法正确拷贝 get属性和 set属性的问题。
-
let a = { -
set f(v){ -
console.log(v) -
} -
} -
let b = {}; -
Object.assign(b, a); -
Object.a(b, 'f'); -
/* -
f = { -
configurable: true, -
enumable: true, -
value: undefined, -
writeable: true -
} -
*/
value为 undefined是因为 Object.assign方法不会拷贝其中的 get和 set方法,使用 getOwnPropertyDescriptors配合 Object.defineProperties方法来实现正确的拷贝:
-
let a = { -
set f(v){ -
console.log(v) -
} -
} -
let b = {}; -
Object.defineProperties(b, Object.getOwnPropertyDescriptors(a)); -
Object.getOwnPropertyDescriptor(b, 'f') -
/* -
configurable: true, -
enumable: true, -
get: undefined, -
set: function(){...} -
*/
Object.getOwnPropertyDescriptors方法的配合 Object.create方法,将对象属性克隆到一个新对象,实现浅拷贝。
-
const clone = Object.create(Object.getPrototypeOf(obj), -
Object.getOwnPropertyDescriptors(obj)); -
// 或者 -
const shallowClone = (obj) => Object.create( -
Object.getPrototypeOf(obj), -
Object.getOwnPropertyDescriptors(obj) -
);
7.ES9新增:对象的拓展运算符
7.1 介绍
对象的拓展运算符,即对象的Rest/Spread属性,可将对象解构赋值用于从一个对象取值,搜键值对分配到指定对象上,与数组的拓展运算符类似:
-
let {x, y, ...z} = {x:1, y:2, a:3, b:4}; -
x; // 1 -
y; // 2 -
z; // {a:3, b:4}
对象的解构赋值要求等号右边必须是个对象,所以如果等号右边是 undefined或 null就会报错无法转成对象。
-
let {a, ...b} = null; // 运行时报错 -
let {a, ...b} = undefined; // 运行时报错
解构赋值必须是最后一个参数,否则报错。
-
let {...a, b, c} = obj; // 语法错误 -
let {a, ...b, c} = obj; // 语法错误
注意:
- 1.解构赋值是浅拷贝。
-
let a = {a1: {a2: 'leo'}}; -
let {...b} = a; -
a.a1.a2 = 'leo'; -
b.a1.a2 = 'leo';
- 2.拓展运算符的解构赋值,不能复制继承自原型对象的属性。
-
let o1 = { a: 1 }; -
let o2 = { b: 2 }; -
o2.__proto__ = o1; -
let { ...o3 } = o2; -
o3; // { b: 2 } -
o3.a; // undefined
7.2 使用场景
- 1.取出参数对象所有可遍历属性,拷贝到当前对象中。
-
let a = { a1:1, a2:2 }; -
let b = { ...a }; -
b; // { a1:1, a2:2 } -
// 类似Object.assign方法
- 2.合并两个对象。
-
let a = { a1:1, a2:2 }; -
let b = { b1:11, b2:22 }; -
let ab = { ...a, ...b }; // {a1: 1, a2: 2, b1: 11, b2: 22} -
// 等同于 -
let ab = Object.assign({}, a, b);
- 3.将自定义属性放在拓展运算符后面,覆盖对象原有的同名属性。
-
let a = { a1:1, a2:2, a3:3 }; -
let r = { ...a, a3:666 }; -
// r {a1: 1, a2: 2, a3: 666} -
// 等同于 -
let r = { ...a, ...{ a3:666 }}; -
// r {a1: 1, a2: 2, a3: 666} -
// 等同于 -
let r = Object.assign({}, a, { a3:666 }); -
// r {a1: 1, a2: 2, a3: 666}
- 4.将自定义属性放在拓展运算符前面,就会成为设置新对象的默认值。
-
let a = { a1:1, a2:2 }; -
let r = { a3:666, ...a }; -
// r {a3: 666, a1: 1, a2: 2} -
// 等同于 -
let r = Object.assign({}, {a3:666}, a); -
// r {a3: 666, a1: 1, a2: 2} -
// 等同于 -
let r = Object.assign({a3:666}, a); -
// r {a3: 666, a1: 1, a2: 2}
- 5.拓展运算符后面可以使用表达式。
-
let a = { -
...(x>1? {a:!:{}), -
b:2 -
}
- 6.拓展运算符后面如果是个空对象,则没有任何效果。
-
{...{}, a:1}; // {a:1}
- 7.若参数是
null或 undefined则忽略且不报错。
-
let a = { ...null, ...undefined }; // 不报错
- 8.若有取值函数
get则会执行。
-
// 不会打印 因为f属性只是定义 而不没执行 -
let a = { -
...a1, -
get f(){console.log(1)} -
} -
// 会打印 因为f执行了 -
let a = { -
...a1, -
...{ -
get f(){console.log(1)} -
} -
}
参考资料
1.MDN 使用对象2.W3school JavaScript 对象











