对象在 JavaScript 中被称为引用类型,引用类型都是基于 Object 的,如 Array,Data,RegExp……

对象基础

创建对象

  1. const obj = new Object(); // `new` 操作符
  2. const obj = {}; // 字面量

对象的属性

Object 可以看成一堆属性的集合,属性由键(key)和值(value)组成。

添加属性

属性的 key 必须是字符串或 Symbol

  1. const person = new Object();
  2. obj.name = 'Tom';
  3. obj.age = 18;
  4. const person = {
  5. name: 'Tom',
  6. age: 18
  7. };

注意:对于多词的属性键我们无法直接书写和访问,因此要使用计算属性和方括号语法。

访问属性

  1. 点语法:obj.key
  2. 方括号语法:obj[key]

    删除属性

    使用 delete 关键字
    1. delete person.name

    遍历属性

    特殊的 for 循环:for key in obj

    MDN:for…in 语句以任意顺序迭代一个对象的除 Symbol 以外的可枚举属性,包括继承的可枚举属性。

  1. let person = {
  2. name: 'Tom',
  3. age: 18
  4. };
  5. for (let key in person) {
  6. console.log(key);
  7. }

注意:对于顺序问题,如果写的是数字,先输出数字,会按照字典序排序,而其他的则是按照书写顺序遍历,但是在大多数情况下,我们一般知道它是无序的就行了。

  1. let person = {
  2. name: 'Tom',
  3. age: 18,
  4. 1: 1,
  5. 3: 3,
  6. 2: 2
  7. };
  8. person.job = 'FE';
  9. for (let key in person) {
  10. console.log(key); // 1, 2, 3, name, age, job
  11. }

对象的复制

对象是引用类型,单纯地将一个对象赋值给另一个变量,只是复制了一个引用而已,它们指向的还是同一个对象。
那如何正确的复制一个对象呢?

浅拷贝

遍历
  1. let person = {
  2. name: 'Tom',
  3. age: 18
  4. };
  5. let clone = {};
  6. for (let key in person) {
  7. clone[key] = person[key];
  8. }

Object.assign

它是对象上的一个方法,用于合并对象。

  1. Object.assign(dest, [src1, src2, src3...])
  2. // dest 是目标对象,src 是源对象
  3. // 该方法将所有的 src 的属性拷贝至 dest 中
  4. // 结果返回 dest

注意:如果已经存在同名属性,则会进行覆盖。

  1. let person = {
  2. name: 'Tom',
  3. age: 18
  4. };
  5. let clone = Object.assign({}, person);
  6. console.log(clone === person); // false

Array.prototype.slice

slice 本身是用来对数组切片用的,它会返回一个数组

  1. let arr = [1, 2, 3];
  2. let clone = arr.slice();
  3. console.log(arr === clone); // false

Array.prototype.concat

concat 是用来拼接数组的

  1. let arr = [1, 2, 3];A
  2. let clone = [].concat(arr);
  3. console.log(arr === clone); // false

Array.from

MDN:Array.from() 方法对一个类似数组或可迭代对象创建一个新的,浅拷贝的数组实例。

  1. let arr = [1, 2, 3];
  2. let clone = Array.from(arr);
  3. console.log(arr === clone); // false

Rest Params
  1. let arr = [1, 2, 3];
  2. let clone = [...arr];
  3. console.log(arr === clone); // false

深拷贝

其实上面的浅拷贝会存在下面的问题,如果一个对象中的属性值依然是对象,它还是引用的同一个对象,这个时候就需要使用深拷贝,不管对象的属性值是否是引用值,都对它进行复制

  1. let person = {
  2. name: 'Tom',
  3. age: 18,
  4. hobby: ['running', 'swimming']
  5. };
  6. let clone = Object.assign({}, person);
  7. console.log(clone.hobby === person.hobby); // true

JSON.parse(JSON.stringify())
  1. let person = {
  2. name: 'Tom',
  3. age: 18,
  4. hobby: ['running', 'swimming']
  5. };
  6. let clone = JSON.parse(JSON.strinigfy(person));
  7. console.log(clone.hobby === person.hobby); // false

对于这个方法,还是存在很多弊端,毕竟是专门用来处理 JSON 数据的,对象中会存在许多复杂的情况。

手写深拷贝

其实深拷贝这个常用的操作肯定在常见的函数库中已经实现了,手写可以锻炼一下自己
简单版本:

  1. function deepClone(target) {
  2. if (typeof target === 'object') {
  3. let res = Array.isArray(target) ? [] : {}
  4. for (const key in target) {
  5. res[key] = deepClone(target[key])
  6. }
  7. return clone
  8. } else {
  9. return target
  10. }
  11. }

附:复杂版本

垃圾回收

可达性

可达性:JavaScript 中主要的内存管理概念是可达性。简而言之,“可达”值是那些以某种方式可访问或可用的值。它们一定是存储在内存中的。

标记清除

标记清除:

  • 垃圾收集器找到所有的根,并“标记”(记住)它们。
  • 然后它遍历并“标记”来自它们的所有引用。
  • 然后它遍历标记的对象并标记 它们的 引用。所有被遍历到的对象都会被记住,以免将来再次遍历到同一个对象。
  • ……如此操作,直到所有可达的(从根部)引用都被访问到。
  • 没有被标记的对象都会被删除。

对象方法

在 OOP 编程中,对象中的属性值如果是函数,则称为方法。

  1. const person = {
  2. name: 'Tom',
  3. age: 18,
  4. say() {
  5. console.log(`I'm ${obj.name}`);
  6. }
  7. };
  8. person.say();

方法中的 this

在方法中可以使用 this 关键字,用于访问调用方法的对象。

  1. const person = {
  2. name: 'Tom',
  3. age: 18,
  4. say() {
  5. console.log(this.name);
  6. }
  7. };

其实,在函数中都可以使用 this 关键字,它在 JavaScript 运行时才能确定指向。

this 丢失

  1. const obj = {
  2. a: 'a',
  3. func() {
  4. console.log(this.a);
  5. }
  6. }
  7. const func = obj.func;
  8. func(); // undefined

箭头函数没有 this

箭头函数中不存在 this,如果在箭头函数中使用了 this 关键字,它会沿着作用域链向上找,也就是外部上下文的 this

构造函数

什么是构造函数

一般我们认为遵循下面的写法就是(不是语法强制):

  1. 首字母大写的函数
  2. 只通过 new 操作符来执行

    new 的过程

  3. 创建一个空对象

  4. __proto__ 属性指向构造函数的原型对象
  5. 把刚刚创建的对象作为 this 的上下文
  6. 如果构造函数没有返回对象,则返回 this

    函数中的 new.target

    用于检测函数是否为 new 操作符调用 ```javascript function Person() { console.log(new.target); }

Person(); // undefined

new Person(); // function Person { … }

  1. <a name="fPwZl"></a>
  2. ## Object.keys, values, entries, fromEntries
  3. - Object.keys(obj): 返回一个包含所有键的数组
  4. - Object.values(obj): 返回一个包含所有值的数组
  5. - Object.entries(obj): 返回一个包含对象所有 [key, value] 的数组
  6. - Object.fromEntries(): 把键值对列表转化成对象
  7. 注意:与 `for...in` 一样,它们会忽略符号
  8. <a name="gJY3J"></a>
  9. ## 对象属性配置
  10. <a name="MTYBY"></a>
  11. ### 属性描述符
  12. 前面只提到属性有属性值(value)<br />其实,对象属性上还有三个特性:
  13. - writable —— 属性是否可以被修改
  14. - enumerable —— 属性是否可以枚举
  15. - configurable —— 属性是否可以被删除,特性是否可以被修改
  16. <a name="vvkib"></a>
  17. #### Object.getOwnPropertyDescriptor(obj, propertyName)
  18. ```javascript
  19. let person = {
  20. name: 'Tom'
  21. };
  22. let descriptor = Object.getOwnPropertyDescriptor(person, 'name');
  23. console.log(descriptor);
  24. // {value: 'Tom', writable: true, enumerable: true, configurable: true}

Object.defineProperty(obj, propertyName, descriptor)

  1. let person = {};
  2. Object.defineProperty(person, 'name', {
  3. value: 'Tom'
  4. });
  5. let descriptor = Object.getOwnPropertyDescriptor(person, 'name');
  6. console.log(descriptor);
  7. // {value: 'Tom', writable: false, enumerable: false, configurable: false}
  8. // 可以看到,如果不对三个特性进行配置,它们默认都是 false

总结

  1. writable 为 false 时不能给属性重新赋值,非严格模式下会被忽略,严格模式下报错
  2. enumerable 为 false 时不会被 for...inObject.keys() 枚举到
  3. configurable 为 false 时属性不可删除和配置,非严格模式下会被忽略,严格模式下报错

    一次定义和获取多个属性描述符

  4. Object.defineProperties

  5. Object.getOwnPropertyDescriptors

    其他

  6. Object.preventExtensions(obj): 禁止向对象添加新属性

  7. Object.seal(obj): 禁止添加和删除属性,所有现有属性设置 configurable: false
  8. Object.freeze(obj): 禁止添加、删除和更改属性,所有现有属性设置 configurable: false, writable: false
  9. Object.isExtensible(obj)
  10. Object.isSealed(obj)
  11. 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’

  1. <a name="SiRgw"></a>
  2. #### 访问器描述符
  3. - get
  4. - set
  5. - enumerable
  6. - configurables
  7. ```javascript
  8. let person = {
  9. firstName: 'Kobe',
  10. lastName: 'Bryant'
  11. };
  12. Ojbect.defineProperty(person, 'fullName', {
  13. get() {
  14. return this.firstName + ' ' + this.lastName;
  15. },
  16. set(value) {
  17. [this.firstName, this.lastName] = value.split(' ');
  18. }
  19. });