对象在 JavaScript 中被称为引用类型,引用类型都是基于 Object 的,如 Array,Data,RegExp……
对象基础
创建对象
const obj = new Object(); // `new` 操作符
const obj = {}; // 字面量
对象的属性
Object 可以看成一堆属性的集合,属性由键(key)和值(value)组成。
添加属性
属性的 key 必须是字符串或 Symbol
const person = new Object();
obj.name = 'Tom';
obj.age = 18;
const person = {
name: 'Tom',
age: 18
};
注意:对于多词的属性键我们无法直接书写和访问,因此要使用计算属性和方括号语法。
访问属性
- 点语法:
obj.key
- 方括号语法:
obj[key]
删除属性
使用delete
关键字delete person.name
遍历属性
特殊的 for 循环:for key in obj
MDN:for…in 语句以任意顺序迭代一个对象的除 Symbol 以外的可枚举属性,包括继承的可枚举属性。
let person = {
name: 'Tom',
age: 18
};
for (let key in person) {
console.log(key);
}
注意:对于顺序问题,如果写的是数字,先输出数字,会按照字典序排序,而其他的则是按照书写顺序遍历,但是在大多数情况下,我们一般知道它是无序的就行了。
let person = {
name: 'Tom',
age: 18,
1: 1,
3: 3,
2: 2
};
person.job = 'FE';
for (let key in person) {
console.log(key); // 1, 2, 3, name, age, job
}
对象的复制
对象是引用类型,单纯地将一个对象赋值给另一个变量,只是复制了一个引用而已,它们指向的还是同一个对象。
那如何正确的复制一个对象呢?
浅拷贝
遍历
let person = {
name: 'Tom',
age: 18
};
let clone = {};
for (let key in person) {
clone[key] = person[key];
}
Object.assign
它是对象上的一个方法,用于合并对象。
Object.assign(dest, [src1, src2, src3...])
// dest 是目标对象,src 是源对象
// 该方法将所有的 src 的属性拷贝至 dest 中
// 结果返回 dest
注意:如果已经存在同名属性,则会进行覆盖。
let person = {
name: 'Tom',
age: 18
};
let clone = Object.assign({}, person);
console.log(clone === person); // false
Array.prototype.slice
slice 本身是用来对数组切片用的,它会返回一个数组
let arr = [1, 2, 3];
let clone = arr.slice();
console.log(arr === clone); // false
Array.prototype.concat
concat 是用来拼接数组的
let arr = [1, 2, 3];A
let clone = [].concat(arr);
console.log(arr === clone); // false
Array.from
MDN:Array.from() 方法对一个类似数组或可迭代对象创建一个新的,浅拷贝的数组实例。
let arr = [1, 2, 3];
let clone = Array.from(arr);
console.log(arr === clone); // false
Rest Params
let arr = [1, 2, 3];
let clone = [...arr];
console.log(arr === clone); // false
深拷贝
其实上面的浅拷贝会存在下面的问题,如果一个对象中的属性值依然是对象,它还是引用的同一个对象,这个时候就需要使用深拷贝,不管对象的属性值是否是引用值,都对它进行复制
let person = {
name: 'Tom',
age: 18,
hobby: ['running', 'swimming']
};
let clone = Object.assign({}, person);
console.log(clone.hobby === person.hobby); // true
JSON.parse(JSON.stringify())
let person = {
name: 'Tom',
age: 18,
hobby: ['running', 'swimming']
};
let clone = JSON.parse(JSON.strinigfy(person));
console.log(clone.hobby === person.hobby); // false
对于这个方法,还是存在很多弊端,毕竟是专门用来处理 JSON 数据的,对象中会存在许多复杂的情况。
手写深拷贝
其实深拷贝这个常用的操作肯定在常见的函数库中已经实现了,手写可以锻炼一下自己
简单版本:
function deepClone(target) {
if (typeof target === 'object') {
let res = Array.isArray(target) ? [] : {}
for (const key in target) {
res[key] = deepClone(target[key])
}
return clone
} else {
return target
}
}
附:复杂版本
垃圾回收
可达性
可达性:JavaScript 中主要的内存管理概念是可达性。简而言之,“可达”值是那些以某种方式可访问或可用的值。它们一定是存储在内存中的。
标记清除
标记清除:
- 垃圾收集器找到所有的根,并“标记”(记住)它们。
- 然后它遍历并“标记”来自它们的所有引用。
- 然后它遍历标记的对象并标记 它们的 引用。所有被遍历到的对象都会被记住,以免将来再次遍历到同一个对象。
- ……如此操作,直到所有可达的(从根部)引用都被访问到。
- 没有被标记的对象都会被删除。
对象方法
在 OOP 编程中,对象中的属性值如果是函数,则称为方法。
const person = {
name: 'Tom',
age: 18,
say() {
console.log(`I'm ${obj.name}`);
}
};
person.say();
方法中的 this
在方法中可以使用 this
关键字,用于访问调用方法的对象。
const person = {
name: 'Tom',
age: 18,
say() {
console.log(this.name);
}
};
其实,在函数中都可以使用 this
关键字,它在 JavaScript 运行时才能确定指向。
this 丢失
const obj = {
a: 'a',
func() {
console.log(this.a);
}
}
const func = obj.func;
func(); // undefined
箭头函数没有 this
箭头函数中不存在 this
,如果在箭头函数中使用了 this
关键字,它会沿着作用域链向上找,也就是外部上下文的 this
。
构造函数
什么是构造函数
一般我们认为遵循下面的写法就是(不是语法强制):
- 首字母大写的函数
-
new 的过程
创建一个空对象
- 将
__proto__
属性指向构造函数的原型对象 - 把刚刚创建的对象作为
this
的上下文 - 如果构造函数没有返回对象,则返回
this
函数中的 new.target
用于检测函数是否为new
操作符调用 ```javascript function Person() { console.log(new.target); }
Person(); // undefined
new Person(); // function Person { … }
<a name="fPwZl"></a>
## Object.keys, values, entries, fromEntries
- Object.keys(obj): 返回一个包含所有键的数组
- Object.values(obj): 返回一个包含所有值的数组
- Object.entries(obj): 返回一个包含对象所有 [key, value] 的数组
- Object.fromEntries(): 把键值对列表转化成对象
注意:与 `for...in` 一样,它们会忽略符号
<a name="gJY3J"></a>
## 对象属性配置
<a name="MTYBY"></a>
### 属性描述符
前面只提到属性有属性值(value)<br />其实,对象属性上还有三个特性:
- writable —— 属性是否可以被修改
- enumerable —— 属性是否可以枚举
- configurable —— 属性是否可以被删除,特性是否可以被修改
<a name="vvkib"></a>
#### Object.getOwnPropertyDescriptor(obj, propertyName)
```javascript
let person = {
name: 'Tom'
};
let descriptor = Object.getOwnPropertyDescriptor(person, 'name');
console.log(descriptor);
// {value: 'Tom', writable: true, enumerable: true, configurable: true}
Object.defineProperty(obj, propertyName, descriptor)
let person = {};
Object.defineProperty(person, 'name', {
value: 'Tom'
});
let descriptor = Object.getOwnPropertyDescriptor(person, 'name');
console.log(descriptor);
// {value: 'Tom', writable: false, enumerable: false, configurable: false}
// 可以看到,如果不对三个特性进行配置,它们默认都是 false
总结
- writable 为 false 时不能给属性重新赋值,非严格模式下会被忽略,严格模式下报错
- enumerable 为 false 时不会被
for...in
和Object.keys()
枚举到 configurable 为 false 时属性不可删除和配置,非严格模式下会被忽略,严格模式下报错
一次定义和获取多个属性描述符
Object.defineProperties
Object.getOwnPropertyDescriptors
其他
Object.preventExtensions(obj): 禁止向对象添加新属性
- Object.seal(obj): 禁止添加和删除属性,所有现有属性设置
configurable: false
- Object.freeze(obj): 禁止添加、删除和更改属性,所有现有属性设置
configurable: false, writable: false
- Object.isExtensible(obj)
- Object.isSealed(obj)
- Object.isFrozen(obj)
属性的 getter 和 setter
使用
对象除了具有数据属性外,还具有访问器属性:getter 和 setter ```javascript // getter let person = { firstName: ‘Kobe’, lastName: ‘Bryant’, get fullName() { return this.firstName + ‘ ‘ + this.lastName; } }; console.log(person.fullName); // Kobe Bryant person.fullName = ‘Lebron James’; // 严格模式下报错,非严格模式忽略
// 定义 setter 才能修改 let person = { firstName: ‘Kobe’, lastName: ‘Bryant’, get fullName() { return this.firstName + ‘ ‘ + this.lastName; }, set fullName(value) { [this.firstName, this.lastName] = value.split(‘ ‘); } }; console.log(person.fullName); // ‘Kobe Bryant’ person.fullName = ‘Lebron James’; console.log(person.fullName); // ‘Lebron James’
<a name="SiRgw"></a>
#### 访问器描述符
- get
- set
- enumerable
- configurables
```javascript
let person = {
firstName: 'Kobe',
lastName: 'Bryant'
};
Ojbect.defineProperty(person, 'fullName', {
get() {
return this.firstName + ' ' + this.lastName;
},
set(value) {
[this.firstName, this.lastName] = value.split(' ');
}
});