JS创建对象的几种方法

293 阅读4分钟

刚开始接触js,一般使用字面量的形式直接创建对象,还有new Object()Object.create()。这三种应该是最常见的。但是这种创建方式对于创建大量相似对象的时候,会产生大量的重复代码。但 js和一般的面向对象的语言不同,在 ES6 之前它没有类的概念。但是可以使用函数来进行模拟,从而产生出可复用的对象创建方式,常见的有以下几种:

工厂模式

顾名思义,它就像一个工厂一样能大批量生产。工厂模式是最简单的一种创建对象的方式。它通过一个函数来封装创建对象的过程,从而避免了代码的重复。然而,工厂模式有一个明显的缺点:它创建的对象之间没有任何关联,无法通过类型来识别这些对象。

优点:

  • 简单易用,易于理解。
  • 封装了创建逻辑,减少了重复代码。

缺点:

  • 创建的对象无法通过类型来识别。
  • 缺乏面向对象语言中固有的继承机制。
function createPerson(name, age) {
  return {
    name: name,
    age: age,
    greet: function() {
      console.log('Hello, my name is ' + this.name);
    }
  };
}

const person1 = createPerson('Alice', 25);
person1.greet(); // 输出 "Hello, my name is Alice"

构造函数模式

构造函数模式使用 new 关键字来创建对象。这种方式创建的对象可以通过原型链来关联到构造函数,从而可以通过类型来识别这些对象。

优点:

  • 创建的对象可以关联到特定的构造函数。
  • 可以通过原型链来共享方法。

缺点:

  • 如果构造函数中有引用类型的属性,则这些属性会被所有实例共享。
  • 函数方法会在每个实例中创建一份,造成内存浪费。
function Person(name, age) {
  this.name = name;
  this.age = age;
}

Person.prototype.greet = function() {
  console.log('Hello, my name is ' + this.name);
};

const person1 = new Person('Alice', 25);
person1.greet(); // 输出 "Hello, my name is Alice"

原型模式

原型模式利用了 JavaScript 中的原型链特性。它可以将公共的属性和方法放在原型对象上,从而实现代码的复用。

优点:

  • 方法只被创建一次,节省内存空间。
  • 支持动态添加属性和方法。

缺点:

  • 不能给实例传递初始化参数。
  • 所有实例共享引用类型属性。
function Person() {}

Person.prototype = {
  name: 'Unknown',
  age: 0,
  greet: function() {
    console.log('Hello, my name is ' + this.name);
  }
};

const person1 = new Person();
person1.greet(); // 输出 "Hello, my name is Unknown"

组合使用构造函数模式和原型模式

这是最常用的一种模式,结合了构造函数模式和原型模式的优点。构造函数用于初始化实例属性,而原型用于定义共享的方法。

优点:

  • 实例属性由构造函数初始化,保证了每个实例的独立性。
  • 共享的方法存储在原型中,节省内存空间。

缺点:

  • 代码结构相对复杂。
function Person(name, age) {
  this.name = name;
  this.age = age;
}

Person.prototype.greet = function() {
  console.log('Hello, my name is ' + this.name);
};

const person1 = new Person('Alice', 25);
person1.greet(); // 输出 "Hello, my name is Alice"

动态原型模式

动态原型模式是在构造函数内部设置原型属性,确保原型属性只被设置一次。

优点:

  • 避免了在每次构造函数调用时都重新设置原型属性。
  • 保持了组合使用构造函数模式和原型模式的优点。

缺点:

  • 实现稍微复杂一些。
function Person(name, age) {
  this.name = name;
  this.age = age;

  if (!Person.prototype.greet) {
    Person.prototype.greet = function() {
      console.log('Hello, my name is ' + this.name);
    };
  }
}

const person1 = new Person('Alice', 25);
person1.greet(); // 输出 "Hello, my name is Alice"

寄生构造函数模式

寄生构造函数模式类似于工厂模式,但它可以基于现有的构造函数进行扩展,同时保持了原始构造函数的特性。

优点:

  • 可以基于现有类型进行扩展而不影响原始构造函数。
  • 保留了构造函数模式的优点。

缺点:

  • 无法通过类型来识别对象。
function createPerson(name, age) {
  const person = new Person(name, age);
  person.birthday = function() {
    this.age++;
  };

  return person;
}

function Person(name, age) {
  this.name = name;
  this.age = age;
}

Person.prototype.greet = function() {
  console.log('Hello, my name is ' + this.name);
};

const person1 = createPerson('Alice', 25);
person1.greet(); // 输出 "Hello, my name is Alice"
person1.birthday();
console.log(person1.age); // 输出 26

使用 class 类方法

ES6 引入了 class 关键字,它提供了一种更接近传统面向对象语言的语法来定义类和实例化对象。使用 class 创建对象非常直观且易于理解。

实现分为两步:

  • 定义类
  • 创建对象实例

优点:

  • 提供了更接近传统面向对象语言的语法。
  • 易于理解和编写。
  • 自动支持继承和封装。

缺点:

  • 在某些情况下可能不如构造函数模式和原型模式灵活。
  • 不适用于所有旧浏览器,需要转译器支持。(因为是es6新引入的老浏览器不支持)
class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }

  greet() {
    console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
  }

  static describe() {
    console.log('This class represents a person.');
  }
}

const alice = new Person('Alice', 25);
alice.greet(); // 输出 "Hello, my name is Alice and I am 25 years old."

// 调用静态方法
Person.describe(); // 输出 "This class represents a person."