深度解读JavaScript 对象以及常用方法的差异性

160 阅读8分钟

JavaScript 对象概述

JavaScript 对象是一种灵活的数据结构,用于存储键值对。键通常是字符串,值可以是任何数据类型,包括其他对象、数组、函数等。

对象的常用方法及其差异性

1. 属性访问
  • 直接访问

    const obj = { key: 'value' };
    console.log(obj.key); // 'value'
    
  • 使用括号表示法

    const obj = { key: 'value' };
    const key = 'key';
    console.log(obj[key]); // 'value'
    

差异性

  • 直接访问适用于键名是固定的字符串。
  • 括号表示法适用于动态获取键名的情况。

注意事项

  • 括号表示法可以处理包含特殊字符(如空格)的键名。
  • 括号表示法可以处理计算得到的键名。

兼容性

  • 所有现代浏览器都支持这两种访问方式。
2. 属性操作
  • 添加属性

    const obj = {};
    obj.key = 'value';
    console.log(obj); // { key: 'value' }
    
  • 删除属性

    const obj = { key: 'value' };
    delete obj.key;
    console.log(obj); // {}
    
  • 检查属性是否存在

    const obj = { key: 'value' };
    console.log('key' in obj); // true
    console.log(obj.hasOwnProperty('key')); // true
    

差异性

  • 'key' in obj 会检查原型链上的属性。
  • obj.hasOwnProperty('key') 只检查对象自身的属性。

注意事项

  • hasOwnProperty 可以避免原型链上的属性干扰。
  • in 操作符更快,但可能不准确。

兼容性

  • 所有现代浏览器都支持这两种方法。
3. 遍历对象
  • for...in 循环

    const obj = { key1: 'value1', key2: 'value2' };
    for (const key in obj) {
      if (obj.hasOwnProperty(key)) {
        console.log(`${key}: ${obj[key]}`);
      }
    }
    
  • Object.keys()

    const obj = { key1: 'value1', key2: 'value2' };
    const keys = Object.keys(obj);
    console.log(keys); // ['key1', 'key2']
    
  • Object.values()

    const obj = { key1: 'value1', key2: 'value2' };
    const values = Object.values(obj);
    console.log(values); // ['value1', 'value2']
    
  • Object.entries()

    const obj = { key1: 'value1', key2: 'value2' };
    const entries = Object.entries(obj);
    console.log(entries); // [['key1', 'value1'], ['key2', 'value2']]
    

差异性

  • for...in 会遍历原型链上的属性。
  • Object.keysObject.valuesObject.entries 只遍历对象自身的可枚举属性。

注意事项

  • 使用 for...in 时,最好结合 hasOwnProperty 来过滤原型链上的属性。
  • Object.keysObject.valuesObject.entries 更加精确,但速度稍慢。

兼容性

  • for...in 在所有现代浏览器中都支持。
  • Object.keysObject.valuesObject.entries 在 ES2017+ 的浏览器中支持。
4. 对象合并
  • Object.assign()

    const obj1 = { key1: 'value1' };
    const obj2 = { key2: 'value2' };
    const mergedObj = Object.assign({}, obj1, obj2);
    console.log(mergedObj); // { key1: 'value1', key2: 'value2' }
    
  • 扩展运算符

    const obj1 = { key1: 'value1' };
    const obj2 = { key2: 'value2' };
    const mergedObj = { ...obj1, ...obj2 };
    console.log(mergedObj); // { key1: 'value1', key2: 'value2' }
    

差异性

  • Object.assign 可以合并多个对象,并且可以指定目标对象。
  • 扩展运算符更简洁,但只能合并两个对象。

注意事项

  • Object.assign 会覆盖相同键名的属性。
  • 扩展运算符同样会覆盖相同键名的属性。

兼容性

  • Object.assign 在 ES6+ 的浏览器中支持。
  • 扩展运算符在 ES2018+ 的浏览器中支持。

对象的 set 和 get 方法原理

1. set 方法
  • 定义

    const obj = {
      name: 'John',
      get fullName() {
        return `${this.firstName} ${this.lastName}`;
      },
      set fullName(value) {
        const parts = value.split(' ');
        this.firstName = parts[0];
        this.lastName = parts[1];
      }
    };
    
    obj.fullName = 'Jane Doe';
    console.log(obj.firstName); // 'Jane'
    console.log(obj.lastName); // 'Doe'
    
  • 原理

    • set 方法允许你在设置属性时执行自定义逻辑。
    • set 方法接收一个值,并可以在内部进行处理。
2. get 方法
  • 定义

    const obj = {
      firstName: 'John',
      lastName: 'Doe',
      get fullName() {
        return `${this.firstName} ${this.lastName}`;
      }
    };
    
    console.log(obj.fullName); // 'John Doe'
    
  • 原理

    • get 方法允许你在读取属性时执行自定义逻辑。
    • get 方法没有参数,但可以在内部访问其他属性。

创建对象的方式

1. 字面量表示法
  • 定义

    const obj = { key: 'value' };
    
  • 特点

    • 最简单的方式。
    • 易于理解和维护。
2. 构造函数
  • 定义

    function Person(name) {
      this.name = name;
    }
    
    const person = new Person('John');
    
  • 特点

    • 适合创建具有相似属性和方法的对象。
    • 可以通过原型链共享方法。
3. 类
  • 定义

    class Person {
      constructor(name) {
        this.name = name;
      }
    }
    
    const person = new Person('John');
    
  • 特点

    • 更符合面向对象的编程风格。
    • 支持继承和封装。
4. 工厂函数
  • 定义

    function createPerson(name) {
      return { name };
    }
    
    const person = createPerson('John');
    
  • 特点

    • 简单易用。
    • 不支持继承。
5. 对象创建器函数
  • 定义

    function createPerson(name) {
      return { name };
    }
    
    const person = createPerson('John');
    
  • 特点

    • 简单易用。
    • 不支持继承。

对象属性描述符

  • Object.defineProperty()

    const obj = {};
    Object.defineProperty(obj, 'key', {
      value: 'value',
      writable: true,
      enumerable: true,
      configurable: true
    });
    console.log(obj); // { key: 'value' }
    
  • Object.getOwnPropertyDescriptor()

    const obj = { key: 'value' };
    const descriptor = Object.getOwnPropertyDescriptor(obj, 'key');
    console.log(descriptor); // { value: 'value', writable: true, enumerable: true, configurable: true }
    

差异性

  • Object.defineProperty 用于定义或修改属性描述符。
  • Object.getOwnPropertyDescriptor 用于获取属性描述符。

注意事项

  • 使用 Object.defineProperty 可以控制属性的行为。
  • 使用 Object.getOwnPropertyDescriptor 可以查看属性的具体配置。

兼容性

  • 所有现代浏览器都支持这两种方法。
 对象保护
  • Object.freeze()

    const obj = { key: 'value' };
    Object.freeze(obj);
    obj.key = 'new value'; // 不会改变
    console.log(obj); // { key: 'value' }
    
  • Object.seal()

    const obj = { key: 'value' };
    Object.seal(obj);
    obj.key = 'new value'; // 会改变
    obj.newKey = 'new value'; // 不允许添加新属性
    console.log(obj); // { key: 'new value' }
    

差异性

  • Object.freeze 完全冻结对象,不允许修改任何属性。
  • Object.seal 封装对象,不允许添加新属性,但允许修改现有属性。

注意事项

  • 使用 Object.freeze 可以确保对象不可变。
  • 使用 Object.seal 可以防止添加新属性,但仍可以修改现有属性。

兼容性

  • 所有现代浏览器都支持这两种方法。
 对象继承
  • Object.create()

    const prototype = { key: 'default' };
    const obj = Object.create(prototype);
    
    obj.key = 'value';
    console.log(obj.key); // 'value'
    console.log(Object.getPrototypeOf(obj).key); // 'default'
    

差异性

  • Object.create 用于创建一个新对象,并指定原型对象。
  • Object.create 可以实现原型继承。

注意事项

  • 使用 Object.create 可以创建具有特定原型链的对象。
  • 使用 Object.getPrototypeOf 可以获取对象的原型。

兼容性

  • 所有现代浏览器都支持这两种方法。
Proxy 对象
  • 定义 Proxy 对象

    const handler = {
      get(target, prop, receiver) {
        console.log(`Getting ${prop}`);
        return Reflect.get(target, prop, receiver);
      },
      set(target, prop, value, receiver) {
        console.log(`Setting ${prop} to ${value}`);
        return Reflect.set(target, prop, value, receiver);
      }
    };
    
    const obj = new Proxy({ key: 'value' }, handler);
    
    obj.key; // 输出 "Getting key"
    obj.key = 'new value'; // 输出 "Setting key to new value"
    

差异性

  • Proxy 对象提供了拦截和定义对象行为的能力。
  • Proxy 对象适用于需要动态拦截和修改对象行为的场景。

注意事项

  • 使用 Proxy 可以实现复杂的对象行为拦截。
  • 使用 Reflect 方法可以保持与普通对象行为一致。

兼容性

  • 所有现代浏览器都支持 Proxy 和 Reflect

总结

在JavaScript中,对象属于堆内存(Heap Memory)。堆内存是一种动态内存分配区域,适合存储复杂的数据结构,比如对象、数组和函数等。与栈内存不同,栈内存用于存储基本数据类型(例如,数字、字符串、布尔值)和函数调用的上下文。

堆内存的特点:

  1. 动态分配:堆内存是动态分配的,可以根据需要创建和销毁对象。
  2. 大小不定:对象的大小通常在创建时不能确定,因此堆内存允许存储任意大小的数据。
  3. 引用访问:在JavaScript中,对象是通过引用的方式存取的。当你将对象赋值给另一个变量时,实际上传递的是该对象的引用。

栈内存(Stack Memory)的特点:

1. 结构简单:栈内存采用后进先出(LIFO,Last In First Out)的结构,最后压入栈中的数据最先被弹出。

2. 自动管理:栈内存的分配和释放是自动进行的。函数调用时,相关的局部变量和上下文信息会被推入栈中,函数返回时,相关信息会自动从栈中弹出。因此,无需手动释放栈内存。

3. 高速访问:由于栈内存的结构简单且生命周期短,数据的访问速度非常快,通常比堆内存更快。

4. 空间有限:栈内存的大小通常有限,受系统或程序运行环境的约束。如果栈中的数据超出预设的大小,会导致栈溢出(Stack Overflow)。

5. 存储基本数据类型:栈内存主要用于存储基本数据类型的值(如整数、浮点数、字符、布尔值等)和函数调用的信息,包括局部变量和返回地址。

6. 局部性:栈内存的生命周期与函数的执行相关,局部变量的作用域仅限于其所在的函数,函数结束后,局部变量将不再可用。

7. 不适合存储大对象:由于栈内存的空间有限,通常不适合存储大对象或数组。较大的数据结构一般会使用堆内存分配。

垃圾回收:

由于对象是在堆内存中分配的,JavaScript 引擎使用垃圾回收机制来自动管理内存。它会定期监控不再被引用的对象,并释放这些对象占用的内存,以避免内存泄漏。

JavaScript 对象提供了丰富的功能和方法,每种方法都有其特定的用途和适用场景。理解这些方法的差异性和注意事项可以帮助你更好地管理对象。此外,了解对象的 set 和 get 方法原理以及不同创建对象的方式,可以帮助你在实际开发中做出更合适的选择。希望这些详细解释对你有所帮助!

特别提醒

每一篇前端笔记都是花了时间处理和总结的(有的是自己笔记的博客md文档直接挪过来的),采纳了别人的意见和融合了自己的想法可能有纰漏也可能有的观点不对(这种情况麻烦及时留言或者一起探讨解决呢),但是都为提供知识查缺补漏相当于自己的前端技术词典各位掘友觉得有帮助就点点关注会持续输出每天坚持输出干货,不废话,篇幅有点长但是废话没有;旨在输出高质量文章供大家一起探讨学习,非允许请勿直接转载谢谢合作。。。