跳至主要內容

对象

wzCoding大约 8 分钟JavaScript对象

对象

对象是一个抽象的概念,我们可以通过观察现实世界中某种事物的外貌及表现,总结归纳其行为特点,然后用对象的方式来定义和描述它,将现实的事物转化为计算机中的数据结构。通过这种方式,我们几乎可以描述现实世界的万事万物

在 JavaScript 中,对象(Object)是一种重要并且复杂的数据类型,它用于存储键值对和更复杂的实体,对象是引用类型的数据(非原始值),它的值存放在内存中,将内存中的引用地址存放在栈中赋值给变量

创建对象

在 JavaScript 中,有几种方法可以创建对象:

  1. 对象字面量:这是最简单的创建对象的方法。你可以使用花括号 {} 来定义一个对象,然后在其中定义属性和方法。例如:
    let myObject = {
         property1: 'value1',
         property2: 'value2',
         method1: function() {
             // do something
         }
     };
    
  2. Object 构造函数:你可以使用 new Object() 来创建一个新的对象实例,然后再为其添加属性和方法。例如:
    let myObject = new Object();
    myObject.property1 = 'value1';
    myObject.property2 = 'value2';
    myObject.method1 = function() {
         // do something
     };
    
  3. 构造函数:你可以定义一个构造函数,然后使用 new 关键字来创建一个新的对象实例。构造函数通常用来创建具有相同属性和方法的多个对象。例如:
    function MyObject(property1, property2) {
         this.property1 = property1;
         this.property2 = property2;
         this.method1 = function() {
             // do something
         };
     }
    
     let myObject1 = new MyObject('value1', 'value2');
     let myObject2 = new MyObject('value3', 'value4');
    

对象创建后,会以键值对的形式将数据存储在内存中,在对象中,对象的键可以是任意字符形式的,对象的值可以是任意值

new 关键字

在上面的示例中,我们使用 new 关键字来创建了对象,那么这其中的过程是怎样的呢?

  1. new 关键字首先会创建一个空对象
  2. 如果构造函数的 prototype 属性是一个对象,则将新对象的 prototype 属性指向构造函数的 prototype 属性,否则指向 Object.prototype
  3. 调用构造函数,并将新对象作为构造函数的 this 进行绑定,将参数传递给构造函数
  4. 如果构造函数返回了一个对象,则返回该对象;否则返回新对象

对象的基本操作

在创建对象后,我们可以在创建的对象上进行一些基本操作

添加属性和方法

我们可以通过对象来描述某种真实事物,而要想将之准确描述,为对象添加属性和方法是必不可少的,向对象添加属性或方法有两种形式:

  1. 直接赋值:通过 .[] 运算符来向对象添加属性或方法。例如:
    let myObject = {};
    myObject.newProperty = '这是一个新属性';
    console.log(myObject.newProperty); // 输出: 这是一个新属性
    
    let myObject = {};
    myObject['newProperty'] = '这是一个新属性';
    console.log(myObject['newProperty']); // 输出: 这是一个新属性
    
  2. 对象方法:通过 Object.defineProperty() 方法向对象添加属性或方法。例如:
    let myObject = {};
    Object.defineProperty(myObject, 'newProperty', {
        value: '这是一个新属性',
        writable: true,
        enumerable: true,
        configurable: true
    });
    console.log(myObject.newProperty); // 输出: 这是一个新属性
    

删除对象的属性

我们可以通过 delete 运算符来删除对象的属性,例如:

let myObject = {
    property1: 'value1',
    property2: 'value2'
};
delete myObject.property1;
console.log(myObject.property1); // 输出: undefined

遍历对象的属性

我们可以通过 for...in 循环来遍历对象的属性,例如:

let myObject = {
    property1: 'value1',
    property2: 'value2'
};

for (let property in myObject) {
    console.log(property + ': ' + myObject[property]);
}

判断属性是否存在

我们可以通过 in 运算符来判断对象是否存在某个属性,例如:

let myObject = {
    property1: 'value1',
    property2: 'value2'
};

console.log('property1' in myObject); // 输出: true
console.log('property3' in myObject); // 输出: false

对象的方法

JS 在对象上内置了一些常用的方法,我们可以通过这些方法来操作对象,例如:

Object.keys()

我们可以通过 Object.keys() 方法来获取对象的所有属性的键,例如:

let myObject = {
    property1: 'value1',
    property2: 'value2'
};

console.log(Object.keys(myObject)); // 输出: ['property1', 'property2']

Object.values()

我们可以通过 Object.values() 方法来获取对象的所有属性的值,例如:

let myObject = {
    property1: 'value1',
    property2: 'value2'
};

console.log(Object.values(myObject)); // 输出: ['value1', 'value2']

Object.entries()

我们可以通过 Object.entries() 方法来获取对象的所有属性的键值对,例如:

let myObject = {
    property1: 'value1',
    property2: 'value2'
};

console.log(Object.entries(myObject)); // 输出: [['property1', 'value1'], ['property2', 'value2']]

Object.assign()

我们可以通过 Object.assign() 方法来将一个或多个对象的所有属性和方法合并到目标对象中,例如:

let target = {
    property1: 'value1',
    property2: 'value2'
};

let source1 = {
    property3: 'value3',
    property4: 'value4'
};

let source2 = {
    property5: 'value5',
    property6: 'value6'
};

Object.assign(target, source1, source2);

console.log(target); // 输出: {property1: 'value1', property2: 'value2', property3: 'value3', property4: 'value4', property5: 'value5', property6: 'value6'}

Object.freeze()

我们可以通过 Object.freeze() 方法来冻结一个对象,即无法再修改该对象的属性和方法,例如:

let myObject = {
    property1: 'value1',
    property2: 'value2'
};

Object.freeze(myObject);

myObject.property1 = 'new value1'; // 无效
console.log(myObject.property1); // 输出: value1

Object.seal()

我们可以通过 Object.seal() 方法来密封一个对象,即无法再添加新的属性和方法,但可以修改已有的属性和方法,例如:

let myObject = {
    property1: 'value1',
    property2: 'value2'
};

Object.seal(myObject);

myObject.property1 = 'new value1'; // 有效
console.log(myObject.property1); // 输出: new value1

myObject.property3 = 'value3'; // 无效
console.log(myObject.property3); // 输出: undefined

Object.is()

我们可以通过 Object.is() 方法来判断两个值是否相等,例如:

let value1 = { property1: 'value1' };
let value2 = { property1: 'value1' };

console.log(Object.is(value1, value2)); // 输出: false

console.log(Object.is(0, -0)); // 输出: false
console.log(Object.is(NaN, NaN)); // 输出: true

对象的克隆

在使用对象时,由于对象引用数据类型的特点,有时我们不希望在使用对象时修改原对象,而是希望创建一个新的对象,来避免对原对象的影响,这时我们可以使用对象的克隆来创建一个新的对象

浅层克隆

浅层克隆是指只复制对象的第一层属性,如果属性值是基本类型,则直接复制;如果属性值是引用类型,则只复制引用地址,即两个对象指向同一个内存地址,修改其中一个对象的属性,会影响到另一个对象的属性。例如:

let originalObject = {
    property1: 'value1',
    property2: 'value2',
    property3: {
        nestedProperty1: 'nested value1',
        nestedProperty2: 'nested value2'
    }
};

let clonedObject = Object.assign({}, originalObject);

clonedObject.property1 = 'new value1'; // 修改克隆对象的属性
clonedObject.property3.nestedProperty1 = 'new nested value1'; // 修改克隆对象的嵌套对象的属性

console.log(originalObject); // 输出: {property1: 'value1', property2: 'value2', property3: {nestedProperty1: 'new nested value1', nestedProperty2: 'nested value2'}
console.log(clonedObject); // 输出: {property1: 'value1', property2: 'value2', property3: {nestedProperty1: 'new nested value1', nestedProperty2: 'nested value2'}

以上是对对象的浅层克隆(也叫浅拷贝)

深层克隆

深层克隆是指复制对象的所有层属性,如果属性值是基本类型,则直接复制;如果属性值是引用类型,则递归复制属性值,即两个对象指向不同的内存地址,修改其中一个对象的属性,不会影响另一个对象的属性。例如:

当对象的结构很复杂时,我们就需要使用深层克隆(也叫深拷贝)来创建一个新的对象,例如:

let originalObject = {
    property1: 'value1',
    property2: 'value2',
    nestedObject: {
        nestedProperty1: 'nested value1',
        nestedProperty2: 'nested value2'
    }
};

function deepClone(obj) {
  if (obj === null) return null;
  if (typeof obj !== 'object') return obj;

  if (obj instanceof Date) return new Date(obj);
  if (obj instanceof RegExp) return new RegExp(obj);

  if (obj instanceof Array) {
    const newArr = [];
    for (let i = 0; i < obj.length; i++) {
      newArr[i] = deepClone(obj[i]);
    }
    return newArr;
  }

  if (obj instanceof Object) {
    const newObj = {};
    for (const key in obj) {
      if (Object.prototype.hasOwnProperty.call(obj, key)) {
        newObj[key] = deepClone(obj[key]);
      }
    }
    return newObj;
  }

  return null;
}

let clonedObject = deepClone(originalObject);

clonedObject.nestedObject.nestedProperty1 = 'new nested value1'; // 修改克隆对象的嵌套对象

console.log(originalObject); // 输出: {property1: 'value1', property2: 'value2', nestedObject: {nestedProperty1: 'new nested value1', nestedProperty2: 'nested value2'}}

对象的类型转换

在 JS 中,对象也可以通过某些规则转换成其他类型的数据(通常是原始值),这对我们开发需求或者解决问题很有帮助

对象转换通常有以下三种类型:

  • string:将对象转换成字符串
  • number:将对象转换成数字
  • default:针对少数运算,通常转换结果为 number

转换规则

对象进行类型转换时,一般遵循如下规则:

  • 首先会调用对象上的 [Symbol.toPrimitive](hint) 方法(如果存在这个方法),这个方法是内建的方法,用来给类型转换方法命名,具体用法如下:

    let obj = {
        text: "hello",
        num: 100,
    
        [Symbol.toPrimitive](hint) {
            console.log(`hint: ${hint}`);
            return hint == "string" ? this.text : this.num;
        }
    }
    
    console.log(obj + '')   // 'hint:default' 100
    console.log(++obj)  // 'hint:number' 101
    console.log(obj - 100)  // 'hint:default' 1
    
  • 如果没有 [Symbol.toPrimitive] 方法,则按照以下顺序进行转换:

    • 如果 hintstring,则先尝试调用 toString() 方法,再尝试调用 valueOf() 方法
    • 如果 hintnumber,则先尝试调用 valueOf() 方法,再尝试调用 toString() 方法

这些方法都必须返回一个原始值

ℹ️提示

hint 参数的取值可以是 stringnumberdefault,分别表示将对象转换成字符串、数字、其他类型,更多详细信息请 点击这里