认识对象
- 对象是数据(方法也是数据)的集合,以键值对(
键: 值
)的形式体现,键就是属性名,值就是属性值。 - 对象字面量使用
{}
包围。 - 空对象是没有任何数据的对象。
const empty = { }
const robot = {
name: 'balance car',
priceExpected: 899,
isChecked: true,
parts: ['balanceSys', 'dynamicalSys', 'model'],
agency: { warranty: '2Y', address: ['A city', 'B city'] },
forward: function () {
console.log('前进');
},
backward: function () {
console.log('后退');
},
brake: () => {
console.log('刹车')
},
turnLeft() {
console.log('左转');
},
turnRight() {
console.log('右转');
}
};
属性和属性值
属性是属性名与属性值的结合(属性=属性名+属性值
)。
- 属性名只能是
String
或Symbol
两种类型;- 属性名没有关键字的限制,除
Symbol
外都被转成字符串了,任何对象作为属性名时,被转成字符串[object Object]
; - 没有空格的属性不需要被引号包围;
- 有空格的属性名需要被引号包围;
- 赋值器(
getter
)、取值器(setter
)等例外。
- 属性名没有关键字的限制,除
- 属性值可以是任何数据(数、字符串、数组、对象、函数等);
- 判断属性是否存在,使用运算符
in
,语法是:key in object
。
属性值的访问
属性值的访问(获取、更新等):
obj.key
点链式;obj['key']
方括号式,具有数字特征的属性名,只能使用此式。
let person1 = {
1: 'id',
name: 'Peter',
gender: 'Man',
habit: { sport: 'jog', game: 'LOL' }
_age: 28,
'birth year': 2000, // 存在空格,需要引号
get age() { return this.age; },
set age(age) { this.age = age; },
}
'name' in person1 // true
person1.1 // 报错 person1[1]/person1['1']是允许的
person1['name'] // 'Peter' --- [] 式
person1.age // 28 --- . 式
person1.gender = 'Woman' // 将属性更新
person1.habit.sport = 'football'
属性的定义
- 在对象字面量里以键值对形式定义;
- 通过
.
式添加属性; - 通过
[]
式添加属性。
let a = 10, b = 20, c = 30;
let obj = {
a: a, // 第一个 a是属性名,第二个是属性值(=10、引用)
sum() { let s = 0; for (let k in this) if(k != 'sum') s += this[k]; return s; }
}
obj.sum // 10
obj.b = b;
obj['c'] = c
obj.sum // 60
计算属性 - 属性表达式
- 属性名的书写可以是一个表达式,计算出的值是最终的属性名;
- 在对象字面量的书写中,使用
[]
将其包围。
let what = prompt("输入你要的属性名?", "what");
let value = prompt("输入你要的属性值?", "1");
let o = {
[1 + 2]: '三', // 1+2是一个表达式, 计算结果3 是最终属性名
[what]: value // what 是一个变量, 它的右边是表达式, 输入的提示词是最终的属性名
}
/* 如果what==name,value=joe */
o // { 3: '三', name: 'joe' }
o.name // 'joe'
Symbol 作为属性名
- 使用
Symbol(描述字符串)
创建唯一标识符; - 就算相同描述,内存指向也不同,是不同的标识;
- 在对象字面量中,作为属性表达式进行添加,需要
[]
包围; - 可以通过方括号式
obj[key]
添加,不能通过点链式obj.key
进行添加; - 在一些遍历操作中被忽略。
let id1 = Symbol('ID');
let id3 = Symbol('ID');
let o = {
a: 1,
b: 2,
[id1]: 1,
[Symbol('id')]: 2
}
o[id3] = 3
o // { a: 1, b: 2, Symbol('ID'): 1, Symbol('ID'): 2, Symbol('ID'): 3 }
属性的描述器
- 每个属性都有一个描述器(Descriptor),用来控制该属性的行为;
- 通过
Object.getOwnPropertyDescriptor(object, key)
进行读取; - 描述了属性的值以及是否可写、是否可枚举、是否可配置(决定前二者)等。
let o = { a: 1, b: 2 };
Object.getOwnPropertyDescriptor(o, 'a')
/*
{
value: 123, // 值
writable: true, // 可写
enumerable: true, // 可枚举
configurable: true // 可配置
}
*/
属性的可枚举性和遍历
- 根据可枚举性,至少有四个操作会忽略不可枚举的(
enumerable: false
)的属性:for...in
循环遍历:只遍历对象自身的和继承的可枚举的属性(不含Symbol属性);Object.keys()
:返回对象自身的所有可枚举的属性的键名(不含Symbol属性);JSON.stringify()
:只串行化对象自身的可枚举的属性(不含Symbol属性);Object.assign()
:ES6、只拷贝对象自身的可枚举的属性。
- 属性的遍历:
for in
循环遍历,遍历(对象自身的、继承的)可枚举的属性名;Object.keys(obj)
,返回一个数组,包含(对象自身的)可枚举的属性名;Object.getOwnPropertyNames(obj)
,数组,包含(对象自身的)除 Symbol 外的属性名;Object.getOwnPropertySymbols(obj)
,数组,包含(对象自身的)Symbol 属性名;Reflect.ownKeys(obj)
,返回一个数组,包含(对象自身的)所有属性名。
- 遍历的次序规则:
- 首先遍历所有数值键,按照数值升序排列;
- 其次遍历所有字符串键,按照加入时间升序排列;
- 最后遍历所有 Symbol 键,按照加入时间升序排列。
/* 只有 for in 循环遍历,会遍历继承的属性 */
const obj = { c: 3 };
let o = { a: 1, b: 2, [Symbol('id')]: 1 }
o.__proto__ = obj
o.c // 3
for (let k in o) console.log(k) // 1, 2, 3
Object.getOwnPropertySymbols(o) // [Symbol('id')]
对象方法与 this
对象方法
- 对象方法,就是属性值是函数表达式的属性;
- 一种简写:省略
:
与function
; - 语法:函数表达式,参考开头的第一个例子。
this 的含义与指向
- 关键字
this
指向的是,当前代码运行时的对象; - 谁调用就指向谁。
let dog1 = {
name: 'Kitty';
bark() { console.log(`Dog ${this.name} is barking!`) } // this 指向 dog1
}
let dog2 = {
name: 'Gimmy';
bark() { console.log(`Dog ${this.name} is barking!`) } // this 指向 dog2
}
构造函数
- 目的是使用函数构造对象,即函数的返回值是一个对象;
- 解决了对象一些初始化的问题;
- 通过
new
、this
引导实现:new
开辟一个对象空间,this
指向并绑定对象,调用时需要new
引导; - 注意事项:
- 调用时,没有使用
new
引导,没有开辟对象空间,则this
可能指向全局对象,所有创建的属性也归属之; - 函数体有
return
语句,则返回对应的值。
- 调用时,没有使用
function Pet(type, name) {
// this = {} // 隐式实现
this.type = type; // 'dog'? or 'cat'? or anyelse
this.name = name;
this.bio = function () {
console.log(`${this.name} is my ${this.type} pet!`);
};
// return this; // 隐式实现
// return { a: 1, b: 2 } // 撒子或疯子会这样写
}
let pet = new Pet('dog', '二哈'); // pet = { type: 'dog', .... }
let p = Pet('cat', '咪咪'); // p = undefined 需要new开辟对象空间
/* 传统的构造对象的函数 */
function Pet(type, name) {
const pet = {};
pet.type = type; // 'dog'? or 'cat'? or anyelse
pet.name = name;
pet.bio = function () {
console.log(`${pet.name} is my ${pet.type} pet!`);
};
return pet;
}
对象的引用和赋值
- 原始类型(数字等),只有单一的值,一个实例就是一个值,不用建立复杂的引用关系;
- 对象是“引用类型”,创建或更新对象,是将索引(
键
)关联到数据(值
)的过程; - 对象的基本操作(赋值等),通过
键
指定对应的值
(就像指针指向地址)实现; - 一些对象赋值的基本结论:
- 将对象简单赋值给变量,这个变量只是拿到了数据的索引,进而可以引用或更新数据,但是不复制数据,不开辟新空间以存储复制的数据;
- 拷贝赋值,方法之一就是使用
for in
遍历以拷贝到数据进行赋值,另外就是使用Object.assign
将源对象的数据拷贝赋值给目标对象,两方法近似等价; - 深层拷贝赋值,为了解决属性值是一个对象的问题(一层拷贝不够),方法之一是使用
lodash
库的_.cloneDeep(obj)
。
let o1 = { a: 1, b: 2 }
let o2 = o1
o1 == o2 // true
o1 === o2 // true
let obj1 = { a: 0 }, obj2 = { a: 0 };
obj1 == obj2 // false 不是同一个数据,尽管内容一样
/* 拷贝赋值 Object.assign(dest, [src1, src2, ...]) src个数0或1或多个 */
let obj3 = { a: 1, b: 2 };
let obj = Object.assign(obj1, obj3);
obj // { a: 1, b: 2 } 相同的属性被覆盖
/* 深层拷贝赋值 */
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"></script>
可选链 - “?.”
- 当“不确定属性是否存在”时,一般可通过条件语句判断或逻辑表达式来避免错误,这样的工作量大(语句冗余、方法调用多等),特别是很多不确定的场景。一个基本要求是,尽量存在不确定的场景使用。
- 一个很棒的选择就是,使用可选链
?.
。如果有实在的属性(!=null,undefined),就继续引用;否则停止并返回undefined
。 - 尽管
?.
使得其前面的数据作为判断的主体,判断主体需要事先声明,主体不存在(未定义或为空等情况),会“短路”马上停止。
let resident = {};
user.address ? user.address.street ? user.address.street.name : null : null // 通过条件判断
resident.address && resident.address.street && resident.address.street.name // 通过逻辑表达式
/* 下面使用可选链 */
resident?.address?.street?.name
resident.address?.street?.name // address 这个属性可以确定
可选链的变体
?.()
,面向方法(函数),不确定是否可调用;?.[]
方括号式,和点链式?.
一样访问不确定的属性。
let o = {
a: 1,
b: 2,
s() { return this.a + this.b; }
}
o.s?.() // 3,s 是o的方法,可以被调用
o.x?.() // undefined,o.x===undefined
o.a?.() // 报错,a是确定的,不可被调用的属性
o?.['a'] // 1
o?.['c'] // undefined