文章目录
对象简介
- 对象是
属性
和方法
的集合。 - 对象是一种
复合值
, 它汇聚多个值(原始值或其他对象)
并允许我们按名字存储和获取这些值。 - 对象是一个属性的
无序集合
, 每个属性都有名字和值。 - 属性名通常是字符串(也可以是符号), 因此可以说对象把字符串映射为值。
- 在
javascript
中, 任何不是字符串
、数值
、符号
或true
、false
、null
、undefined
的值都是对象。即使字符串、数组和布尔值不是对象, 但它们的行为也类似不可修改的对象。
创建对象
对象可以通过对象字面量
、new关键字
和Object.create()
以及构造函数
或工厂模式
来创建。接下来分别介绍一下几种创建方式。
对象字面量
创建对象最简单的方式是在Js代码中直接包含对象字面量
。对象字面量的最简单形式是包含在一对花括号中一组用逗号
分隔的“键:值”
对。
属性名是Js标识符
或字符串字面量
(允许空字符串)。
属性值是任何Js表达式
, 这个表达式的值(可以是原始值或对象值)会变成属性的值。
let empty = {
}; // 没有任何属性的对象
let point = {
x:0,y:0}; // 包含两个数值属性的对象
let person = {
'first name':'张', // 属性名包含空格
'last-name':'三', // 和连字符, 因此要使用字符串字面量
if:'还有如果吗', // if是保留字, 但没有引号
friend:{
// 一个对象
name:'王五',
}
}
前面展示的对象字面量使用了简单的语法
, 这种语法是在Js最初的版本中规定的。这门语言最近的版本中也新增了很多对象字面量的特性, 下面来介绍一下。
简写属性
假设变量name
和age
中保存着值, 而你想创建一个具有属性
name和age且值分别为相应的变量值
的对象。那么在ES6之后, 可以用下面的这种方式来简写属性
。
let name='张三',age=20;
let obj = {
name,age};
console.log(obj); // {name:'张三',age:20}
计算的属性名
有时候, 我们需要创建一个具有特殊属性
的值, 但该属性的名字不是直接写在源代码中的常量
。相反, 你需要的这个属性
保存在一个变量
里, 或者是某个函数的返回值
。不能对这些属性直接使用基本对象字面量。为此, 你需要先创建一个对象, 然后以中括号
的形式为它添加属性。
const str = 'name';
function getName(){
return 'str2' };
let o = {
}; // 创建一个新对象
o[str] = 1; // 使用变量
o[getName()] = 2; // 使用函数
console.log(o); // {name: 1, str2: 2}
在ES6中新增了被称为计算属性
的特性可以更简单的创建对象, 这个特性可以让你直接把前面代码中的方括号
放在对象字面量
中。
const str = 'name';
function getName(){
return 'str2' };
let o = {
[str]: 1, // 变量作为属性名
[getName()]: 2, // 函数作为属性名
}
console.log(o) // {name: 1, str2: 2}
符号作为属性名
计算属性的特性让另一个非常重要的对象字面量特性成为可能。在ES6之后, 属性名可以是字符串
或符号
。如果要把一个值赋给符号属性
, 那么可以使用计算属性语法
将该符号作为属性名。
const extension = Symbol('my extension');
let o = {
[extension]: {
/* 在这个对象中存储扩展数据 */}
}
o[extension].name = '张三';
console.log(o); // {Symbol(my extension): {name: '张三'}}
关于符号类型
的使用可以看我的另一篇文章: Js-Symbol符号类型
使用符号不是为了安全, 而是对Js对象定义安全
的扩展。如果你从一个不受控的第三方
代码得到一个对象, 然后你需要为该对象添加
一些自己的属性
, 但又不希望你的属性与该对象原有的属性冲突, 那么你可以放心的使用符号作为属性名。而且, 你也不用担心第三方代码会意外的修改你的属性。
简写方法
当把函数
定义为对象属性
时, 我们就称该函数
为方法
。在ES6以前, 需要像定义对象的其他属性一样, 通过函数定义表达式
在对象字面量中定义方法。
let circle = {
r: 5,
area: function(){
return this.r*this.r*3.14 },
}
console.log(circle.area()); // 78.5
在ES6中, 对象字面量语法经过扩展, 允许一种省略function关键字和冒号
的简写方法。
let circle = {
r: 5,
area(){
return this.r*this.r*3.14 }
}
console.log(circle.area()); //78.5
在使用这种简写语法写方法时, 属性名可以是对象字面量允许的任何形式
。除了像上面area
这样常规Js标识符之外, 也可以使用字符串字面量
和计算的属性名
,包括符号属性名。
const method = 'method';
const symbol = Symbol();
let o = {
'first-method name'(){
return '张三'},
[method](){
return '李四'},
[symbol](){
return '王五'},
}
console.log( o['first-method name']() ); // 张三
console.log( o[method]() ); // 李四
console.log( o[symbol]() ); // 王五
new关键字
new操作符用于创建和初始化一个新对象。new关键字后面必须跟一个函数调用。以这种方式使用的函数被称为构造函数( constructor
)。Js为内置类型提供了构造函数。例如:
let o = new Object(); // 创建一个空对象, 与{}相同
let a = new Array(); // 创建一个空数组, 与[]相同
let d = new Date(); // 创建一个日期时间对象
let m = new Map(); // 创建一个映射对象
除了内置的构造函数, 我们经常需要定义自己的构造函数来初始化新创建的对象。
Object.create()
Object.create()用于创建一个新对象, 接收一个参数作为新对象的原型。
let o = Object.create({
a:1,b:2}); // o继承属性a和b
console.log(o.a); // 1
console.log(o.b); // 2
如果想创建一个普通的空对象( 类似{}
或new Object()
返回的对象 ), 那么需要传入Object.prototype
let o = Object.create(Object.prototype); // 以Object为原型
console.log(o); // {}
查询和设置属性
要获取一个属性的值, 可以使用 (.
)或方括号([]
)操作符。左边应为一个表达式, 其值为对象。如果使用点操作符
, 右边必须是一个命名属性的简单标识符。如果使用方括号
, 方括号中的值必须是一个表达式, 其结果为包含目标属性的字符串。
let o = {
name: '张三',
age: 20,
'my city': '北京'
}
console.log(o.name); // '张三'
console.log(o['age']); // 20
console.log(o['my city']); // '北京'
要创建或设置属性, 与查询属性一样, 要使用点
或方括号
, 只是把它们放在赋值表达式的左边。
let o = {
'my city': '北京'
}
o.name = '王五'; // 为o创建一个name属性
o['my city'] = '上海'; // 修改o的'my city'属性
console.log(o); // {my city: '上海', name: '王五'}
属性访问错误
属性访问表达式并不是总会返回或设置值。
查询不存在的属性不是错误
。如果在自有属性和继承属性中都没有找到属性, 则求值结果为undefined
。
let o = {
name: '张三' };
console.log(o.age); // undefined 没有age属性
尝试在null
或undefined
上设置属性会导致TypeError
。而且, 尝试在其他值上设置属性也不总是会成功, 因为有些属性是只读
的, 不能设置, 而有些对象不允许添加新属性。
属性的获取方法与设置方法
Js除了数据属性之外, 既有一个名字和一个普通的值。Js还支持访问器属性
, 这种属性不是一个值, 而是一个或两个访问器方法: 一个获取方法
和一个设置方法
。
如果一个属性既有获取方法也有设置方法, 则该属性是一个可读写属性
。
let o = {
// 属性名中的_提示它仅在内部使用
_n:0,
// 通过一对函数定义的一个可读写的访问器属性
get n(){
return this._n },
set n(value){
this._n = value } // set要接收一个value, 为赋值表达式右边的值
}
o.n = 99; // 调用set方法
console.log(o.n); // 调用get方法
如果访问器属性
只有一个获取方法
, 那它就是只读属性
。
let o = {
_n: 0,
// 获取方法
get n(){
return this._n }
}
o.n = 99; // 这样并不会修改对象属性, 因为此时n为只读
console.log(o.n); // 0
如果访问器属性
只有一个设置方法
, 那它就是只写属性
(这种属性是通过数据属性
是无法实现的), 读取这种属性始终会得到undefined
。
let o = {
_n: 0,
// 设置属性
set n(value){
this._n = value }
}
o.n = 99; // 只写属性
console.log(o.n); // undefined
删除属性
delete
操作符用于从对象中移除属性。它唯一的操作数应该是一个属性访问表达式
, 令人惊讶的是delete
并不是操作属性的值
, 而是操作属性本身
。
let o = {
name: '张三',
age: 20
}
delete o.age; // 张三现在没有年龄了
console.log(o); // {name: '张三'}
delete
操作符只删除自有属性
, 不删除继承属性
(要删除继承属性, 必须从定义属性的原型对象
上删除。这样做会影响继承该原型的所有对象)。
测试属性
Js对象可以被想象成一组属性
, 在实际的开发中经常需要测试这组属性中是否有一个给定名字的属性
。
in
操作符要求左边
是一个属性名
, 右边
是一个对象
。如果对象包含相应名字的自有属性
或继承属性
, 则返回true
。
let o = {
x: 1 };
console.log( 'x' in o ); // true, o有自有属性'x'
console.log( 'y' in o ); // false, o没有属性'y'
console.log( 'toString' in o ); // true, o从Object顶层对象继承了toString方法
对象的hasOwnProperty()
方法用于测试对象是否有给定名字的自有属性
。对于继承的属性
, 它返回false
。
let o = {
x: 1 };
console.log(o.hasOwnProperty('x')); // true, o有自有属性'x'
console.log(o.hasOwnProperty('y')); // false, o没有属性'y'
console.log(o.hasOwnProperty('toString')); // false, 对于继承属性,它返回false
根据对象访问未定义的属性会返回undefined
的特性, 所以除了使用in
操作符, 通常只需要简单的属性查询
配合!==
就可以确保其不是未定义就可以了。
let o = {
x: 1 };
console.log( o.x !== undefined ); // true
console.log( o.y !== undefined ); // false
console.log( o.toString !== undefined ); // true
但当属性的值为undefined
时再使用简单的属性访问
技术是不行的, 但in
操作符可以做。in
可以区分不存在的属性或存在但被设置为undefined
的属性。
let o = {
x:undefined };
console.log( o.x !== undefined ); // false, 因为o.x的值为undefined
console.log( 'x' in o ); // true, 因为in是直接判断属性是否存在