对象
对象
- 对象就是一组“键值对”(key-value)的集合,是一种无序的复合数据集合
- 对象的所有键名都是字符串(ES6 又引入了 Symbol 值也可以作为键名),所以加不加引号都可以
- 如果键名是数值,会被自动转为字符串。
- 对象的每一个键名又称为“属性”(property),它的“键值”可以是任何数据类型
1 属性 property
1.1 属性创建
- 属性可以动态创建,不必在对象声明时就指定。
var obj = {};
obj.foo = 123;
obj.foo; // 123
- 属性的简洁表示
- ES6 允许在大括号里面,直接写入变量和函数,作为对象的属性和方法。这样的书写更加简洁。
- 属性名就是变量名, 属性值就是变量值
const foo = 'bar';
// 简写
const baz = {foo}; // baz = {foo: "bar"}
// 等同于
const baz = {foo: foo};
1.2 属性取值
- 读取对象的属性,有两种方法,一种是使用点运算符,还有一种是使用方括号运算符。
var obj = { p: "Hello World" };
obj.p; // "Hello World"
obj["p"]; // "Hello World"
with
语句的格式如下:
with (对象) {
语句;
}
- 它的作用是操作同一个对象的多个属性时,提供一些书写的方便。
// 例一
var obj = {
p1: 1,
p2: 2,
};
with (obj) {
p1 = 4;
p2 = 5;
}
// 等同于
obj.p1 = 4;
obj.p2 = 5;
// 例二
with (document.links[0]) {
console.log(href);
console.log(title);
console.log(style);
}
// 等同于
console.log(document.links[0].href);
console.log(document.links[0].title);
console.log(document.links[0].style);
1.3 属性删除 delete
delete
命令用于删除对象的属性,删除成功后返回true
。
var obj = { p: 1 };
Object.keys(obj); // ["p"]
delete obj.p; // true
obj.p; // undefined
Object.keys(obj); // []
1.4 属性存在 in
in
运算符用于检查对象是否包含某个属性(注意,检查的是键名,不是键值),- 如果包含就返回
true
,否则返回false
。它的左边是一个字符串,表示属性名,右边是一个对象。
var obj = { p: 1 };
"p" in obj; // true
"toString" in obj; // true
1.5 属性遍历 keys
Object.keys
:查看一个对象本身的所有属性
var obj = { key1: 1, key2: 2};
Object.keys(obj); // ['key1', 'key2']
-
Object.getOwnPropertyNames
:查看一个对象本身的所有属性- 数组的
length
属性是不可枚举的属性,所以只出现在Object.getOwnPropertyNames
方法的返回结果中。
- 数组的
-
for...in
循环用来遍历一个对象的全部属性。for...in
循环遍历的是对象所有可遍历(enumerable)的属性,会跳过不可遍历的属性。- 它不仅遍历对象自身的属性,还遍历继承的属性。
- 举例来说,对象都继承了
toString
属性,但是for...in
循环不会遍历到这个属性。
var obj = { a: 1, b: 2, c: 3 };
for (var i in obj) {
console.log("键名:", i); // 键名: a // 键名: b // 键名: c
console.log("键值:", obj[i]); // 键值: 1 // 键值: 2 // 键值: 3
}
ES6 一共有 5 种方法可以遍历对象的属性。
for...in
循环遍历对象自身的和继承的可枚举属性(不含 Symbol 属性)。Object.keys
返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含 Symbol 属性)的键名。Object.getOwnPropertyNames
返回一个数组,包含对象自身的所有属性(不含 Symbol 属性,但是包括不可枚举属性)的键名。Object.getOwnPropertySymbols
返回一个数组,包含对象自身的所有 Symbol 属性的键名。Reflect.ownKeys
返回一个数组,包含对象自身的(不含继承的)所有键名,不管键名是 Symbol 或字符串,也不管是否可枚举。
以上的 5 种方法遍历对象的键名,都遵守同样的属性遍历的次序规则。
- 首先遍历所有数值键,按照数值升序排列。
- 其次遍历所有字符串键,按照加入时间升序排列。
- 最后遍历所有 Symbol 键,按照加入时间升序排列。
Reflect.ownKeys({ [Symbol()]:0, b:0, 10:0, 2:0, a:0 })
// ['2', '10', 'b', 'a', Symbol()]
上面代码中,Reflect.ownKeys
方法返回一个数组,包含了参数对象的所有属性。这个数组的属性次序是这样的,首先是数值属性2
和10
,其次是字符串属性b
和a
,最后是 Symbol 属性。
1.7 属性描述对象
- JavaScript 提供了一个内部数据结构,用来描述对象的属性,控制它的行为,比如该属性是否可写、可遍历等等。这个内部数据结构称为“属性描述对象”(attributes object)
- 属性描述对象提供 6 个元属性。
{
value: 123,
writable: false,
enumerable: true,
configurable: false,
get: undefined,
set: undefined
}
- 对象的每个属性都有一个描述对象(Descriptor),用来控制该属性的行为。
let obj = { foo: 123 };
Object.getOwnPropertyDescriptor(obj, 'foo')
// {
// value: 123,
// writable: true,
// enumerable: true,
// configurable: true
// }
-
value
是该属性的属性值,默认为undefined
。 -
writable
是一个布尔值,表示属性值(value)是否可改变(即是否可写),默认为true
。 -
enumerable
是一个布尔值,表示该属性是否可遍历,默认为true
。如果设为false
,会使得某些操作(比如for...in
循环、Object.keys()
)跳过该属性。 -
configurable
是一个布尔值,表示属性的可配置性,默认为true
。如果设为false
,将阻止某些操作改写属性描述对象,比如无法删除该属性,也不得改变各种元属性(value
属性除外)。也就是说,configurable
属性控制了属性描述对象的可写性。 -
get
是一个函数,表示该属性的取值函数(getter),默认为undefined
。 -
set
是一个函数,表示该属性的存值函数(setter),默认为undefined
。 -
Object.getOwnPropertyDescriptor()
方法可以获取属性描述对象。它的第一个参数是目标对象,第二个参数是一个字符串,对应目标对象的某个属性名。 -
Object.getOwnPropertyNames
方法返回一个数组,成员是参数对象自身的全部属性的属性名,不管该属性是否可遍历。
1.7.1 属性可枚举性
对象的每个属性都有一个描述对象(Descriptor),描述对象的“可枚举性”enumerable
属性为false
,表示某些操作会忽略当前属性。
下面四个操作会忽略enumerable
为false
的属性。
for...in
循环:只遍历对象自身的和继承的可枚举的属性。返回继承的属性Object.keys()
:返回对象自身的所有可枚举的属性的键名。忽略继承的属性,只处理对象自身的属性JSON.stringify()
:只串行化对象自身的可枚举的属性。忽略继承的属性,只处理对象自身的属性Object.assign()
: 忽略enumerable
为false
的属性,只拷贝对象自身的可枚举的属性。忽略继承的属性,只处理对象自身的属性,ES6 新增的
实际上,引入“可枚举”(enumerable
)这个概念的最初目的,就是让某些属性可以规避掉for...in
操作,不然所有内部属性和方法都会被遍历到。比如,对象原型的toString
方法,以及数组的length
属性,就通过“可枚举性”,从而避免被for...in
遍历到。
2 赋值
2.1 赋值方法
- 定义时初始化
let obj = {foo: 123}; // obj = {foo: 123}
- 动态创建,不必在对象声明时就指定
let obj = {};
obj.foo = 123; // obj = {foo: 123}
- 属性的简洁表示
- ES6 允许在大括号里面,直接写入变量和函数,作为对象的属性和方法。这样的书写更加简洁。
- 属性名就是变量名, 属性值就是变量值
const foo = 'bar';
const obj = {foo}; // obj = {foo: "bar"}
- 扩展运算符 解构赋值
-
将目标对象自身的所有可遍历的(enumerable)、但尚未被读取的属性,分配到指定的对象上面。所有的键和它们的值,都会拷贝到新对象上面。
-
解构赋值要求等号右边是一个对象
-
解构赋值必须是最后一个参数
-
变量必须与属性同名,才能取到正确的值,顺序不对没事
-
真正被赋值的是属性后面的变量,而不是属性。
-
默认:
let { foo: foo, bar: bar } = { foo: 'aaa', bar: 'bbb' };
-
改名:const { x, y: z } = { x: 1, y: 2 }
let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
x // 1
y // 2
z // { a: 3, b: 4 } 获取等号右边的所有尚未读取的键(`a`和`b`),将它们连同值一起拷贝过来
- 扩展运算符 取出对象可遍历属性
- 对象的扩展运算符(
...
)用于取出参数对象的所有可遍历属性,拷贝到当前对象之中。 - 扩展运算符后面必须是一个变量名
- 给对象加元素:
v = {...v, xxx: y}
let x = 5;
let z = { a: 3, b: 4 };
let n = { x, ...z };
n // {c: 5, a: 3, b: 4}
2.1.1 解构赋值
注意,解构赋值的拷贝是浅拷贝,即如果一个键的值是复合类型的值(数组、对象、函数)、那么解构赋值拷贝的是这个值的引用,而不是这个值的副本。
let obj = { a: { b: 1 } };
let { ...x } = obj;
obj.a.b = 2;
x.a.b // 2
// `x`是解构赋值所在的对象,拷贝了对象`obj`的`a`属性。`a`属性引用了一个对象,修改这个对象的值,会影响到解构赋值对它的引用。
- 扩展运算符的解构赋值,不能复制继承自原型对象的属性。
let o1 = { a: 1 };
let o2 = { b: 2 };
o2.__proto__ = o1;
let { ...o3 } = o2;
o3 // { b: 2 }
o3.a // undefined
// 上面代码中,对象`o3`复制了`o2`,但是只复制了`o2`自身的属性,没有复制它的原型对象`o1`的属性。
2.1.2 扩展运算符
- 由于数组是特殊的对象,所以对象的扩展运算符也可以用于数组。
- 如果扩展运算符后面是一个空对象,则没有任何效果。
- 如果扩展运算符后面不是对象,则会自动将其转为对象。
- 如果扩展运算符后面是字符串,它会自动转成一个类似数组的对象,因此返回的不是空对象。
- 对象的扩展运算符,只会返回参数对象自身的、可枚举的属性,这一点要特别小心,尤其是用于类的实例对象时。
{ ...['a', 'b', 'c'] } // {0: "a", 1: "b", 2: "c"}
{...'hello'} // {0: "h", 1: "e", 2: "l", 3: "l", 4: "o"}
{...{}, a: 1} // { a: 1 }
{...1} // {} // 等同于 {...Object(1)} 等同于 {...Number{1}}
{...true} // {} // 等同于 {...Object(true)}
{...undefined} // {} // 等同于 {...Object(undefined)}
{...null} // {} // 等同于 {...Object(null)}
- 对象的扩展运算符等同于使用
Object.assign()
方法。
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 });
上面代码中,a
对象的x
属性和y
属性,拷贝到新对象后会被覆盖掉。
这用来修改现有对象部分的属性就很方便了。
let newVersion = {
...previousVersion,
name: 'New Name' // Override the name property
};
上面代码中,newVersion
对象自定义了name
属性,其他属性全部复制自previousVersion
对象。
如果把自定义属性放在扩展运算符前面,就变成了设置新对象的默认属性值。
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);
2.2 数据同步更改
- 如果不同的变量名指向同一个对象,那么它们都是这个对象的引用,也就是说指向同一个内存地址。修改其中一个变量,会影响到其他所有变量。
var o1 = {};
var o2 = o1;
o1.a = 1;
o2.a; // 1
o2.b = 2;
o1.b; // 2
- 问题:变量赋值,修改变量会导致原始数据也同步发生改变
- 原因:指向同一个地址
- 解决:
JSON.parse(JSON.stringify(config.data))
转换为字符串再转换为对象后赋值
类似的,在 vue 中,变量赋值,修改变量会导致原始数据也同步发生改变
- 问题:let a = this.file, 修改 a 时,this.file 也改变了
- 原因:这是一个引用传递而不是值传递
- 解决:把 this.file 转换为字符串再转换为对象后赋值
let a = JSON.parse(JSON.stringify(this.file));
3 Object 对象
- JavaScript 的所有其他对象都继承自
Object
对象,即那些对象都是Object
的实例。 Object
对象的原生方法分成两类Object
静态方法:直接定义在Object
对象的方法Object
实例方法:定义在Object
原型对象Object.prototype
上的方法。它可以被Object
实例直接使用
// 静态方法
Object.print = function (o) {
console.log(o);
};
// 实例方法
Object.prototype.print = function () {
console.log(this);
};
var obj = new Object(); //实例`obj`直接继承了`Object.prototype`的属性和方法
obj.print(); // Object
3.1 Object 属性
- constructor 指向 Object 构造函数,构造用
- proto 指向原型对象,原型链,
__proto__
属性(前后各两个下划线),用来读取或设置当前对象的原型对象(prototype)。目前,所有浏览器(包括 IE11)都部署了这个属性。
3.2 Object()
Object()
将任意值转为对象:Object
本身是一个函数,可以当作工具方法使用,将任意值转为对象。Object()
构造函数:Object
不仅可以当作工具函数使用,还可以当作构造函数使用,即前面可以使用new
命令。
3.3 Object 静态方法
(1)对象属性模型的相关方法
Object.keys
:查看一个对象本身的所有属性,返回一个包含所有给定对象自身可枚举属性名称是数组Object.getOwnPropertyNames
:查看一个对象本身的所有属性,返回一个数组,成员是参数对象自身的全部属性的属性名,不管该属性是否可遍历。- 数组的
length
属性是不可枚举的属性,所以只出现在Object.getOwnPropertyNames
方法的返回结果中。
- 数组的
Object.getOwnPropertyDescriptor()
:获取某个属性的描述对象。它的第一个参数是目标对象,第二个参数是一个字符串,对应目标对象的某个属性名。Object.getOwnPropertyDescriptors()
方法,返回指定对象所有自身属性(非继承属性)的描述对象。ES2017 引入Object.defineProperty()
:通过描述对象,定义某个属性。给对象添加一个属性并指定该属性的配置Object.defineProperties()
:通过描述对象,定义多个属性。Object.hasOwn()
:也可以判断是否为自身的属性。
(2)控制对象状态的方法
Object.preventExtensions()
:防止对象扩展。Object.isExtensible()
:判断对象是否可扩展。Object.seal()
:禁止对象配置。Object.isSealed()
:判断一个对象是否可配置。Object.freeze()
:冻结一个对象。Object.isFrozen()
:判断一个对象是否被冻结。
(3)原型链相关方法
Object.is()
比较两个值是否严格相等,与严格比较运算符(===)的行为基本一致Object.assign()
通过复制一个或者多个对象,创建一个对象,浅拷贝,方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。Object.values()
返回给定对象自身可枚举属性的键值的数组Object.create()
:该方法可以指定原型对象和属性,返回一个新的对象。Object.entries()
:方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值对数组。Object.fromEntries()
方法是Object.entries()
的逆操作,用于将一个键值对数组转为对象。Object.getPrototypeOf()
:获取对象的Prototype
对象。该方法与Object.setPrototypeOf
方法配套,用于读取一个对象的原型对象。Object.setPrototypeOf
:方法的作用与__proto__
相同,用来设置一个对象的原型对象(prototype),返回参数对象本身。它是 ES6 正式推荐的设置原型对象的方法。
Object.assign(obj1, obj2, obj3)
- 合并对象: 把 obj2、obj3 合并到 obj1 上
- 如果不是对象的话,它会在内部转换成对象
- 所以如果碰到了 null 或者 undefined 这种不能转换成对象的值的话,assign 就会报错。
- 但是如果源对象 (如 obj2、obj3) 的参数位置,接收到了无法转换为对象的参数的话,会忽略这个源对象参数。
- 如果在这个过程中出现同名的属性(方法),后合并的属性(方法)会覆盖之前的同名属性(方法)
3.4 Object 实例方法
Object.prototype.valueOf()
:返回当前对象对应的值。Object.prototype.toString()
:返回当前对象对应的字符串形式。Object.prototype.toLocaleString()
:返回当前对象对应的本地字符串形式。Object.prototype.hasOwnProperty()
:判断某个属性是否为当前对象自身的属性,还是继承自原型对象的属性。Object.prototype.isPrototypeOf()
:判断当前对象是否为另一个对象的原型。返回一个布尔值,看当前对象是否在指定对象的原型链上Object.prototype.propertyIsEnumerable()
:判断某个属性是否可枚举。Object.prototype.hasPrototypeProperty()
:从字面上就可以知道是检测原型对象上的属性。对象位于原型中,则返回true。
Object.prototype.toString()
- 数组、字符串、函数、Date 对象都分别部署了自定义的
toString
方法,覆盖了Object.prototype.toString
方法。
[1, 2, 3].toString(); // "1,2,3"
var obj = {};
obj.toString(); // "[object Object]"
- 上面代码调用空对象的
toString
方法,结果返回一个字符串object Object
,其中第二个Object
表示该值的构造函数。 - 由于实例对象可能会自定义
toString
方法,覆盖掉Object.prototype.toString
方法,所以为了得到类型字符串,最好直接使用Object.prototype.toString
方法。通过函数的call
方法,可以在任意值上调用这个方法,帮助我们判断这个值的类型。
Object.prototype.toString.call(value);
-
不同数据类型的
Object.prototype.toString
方法返回值如下。- 数值:返回
[object Number]
。 - 字符串:返回
[object String]
。 - 布尔值:返回
[object Boolean]
。 - undefined:返回
[object Undefined]
。 - null:返回
[object Null]
。 - 数组:返回
[object Array]
。 - arguments 对象:返回
[object Arguments]
。 - 函数:返回
[object Function]
。 - Error 对象:返回
[object Error]
。 - Date 对象:返回
[object Date]
。 - RegExp 对象:返回
[object RegExp]
。 - 其他对象:返回
[object Object]
。
- 数值:返回
-
这就是说,
Object.prototype.toString
可以看出一个值到底是什么类型。
Object.prototype.toString.call(2); // "[object Number]"
Object.prototype.toString.call(""); // "[object String]"
Object.prototype.toString.call(true); // "[object Boolean]"
Object.prototype.toString.call(undefined); // "[object Undefined]"
Object.prototype.toString.call(null); // "[object Null]"
Object.prototype.toString.call(Math); // "[object Math]"
Object.prototype.toString.call({}); // "[object Object]"
Object.prototype.toString.call([]); // "[object Array]"