【JS系列】一起理解对象的7种创建方式(全)

1,096 阅读5分钟

前言

起初接触JS时常见的简单地创建单个对象的方式有下面三种:

  • 字面量,即var obj = {}
  • new Object()
  • Object.create()

以上三种方式在需要创建很多对象时会产生大量重复代码。显然需要更高效的方式,下面将介绍7种创建对象的方式

1. 工厂模式

1.1介绍

工厂模式正是源自设计模式中的工厂模式,其基本思想:

  • 既然创建对象数量多时产生了大量重复代码
  • 那就建一座工厂,通过工厂来创建加工
  • 再返回创建好的对象
function createPerson(name, age) {
  var obj = new Object()
  obj.name = name
  obj.age = age
  obj.getName = function() {
    console.log(this.name)
  }
  return obj
}

var person1 = createPerson("AAA", 23)
person1.getName()   //AAA

var person2 = createPerson("BBB", 30)
person2.getName()   //BBB

1.2 优劣分析

  • 解决了复用性的问题
  • 但无法判断对象的类型(只知道是Objcet类型)

2. 构造函数模式

2.1 介绍

构造函数可创建特定类型的对象,类似Object和Array(实际上Object和Array本身就是构造函数)

function Person(name, age) {
  this.name = name
  this.age = age
  this.getName = function() {
    console.log(this.name)
  }
}

var person1 = new Person("AAA", 23)
person1.getName()  //AAA

var person2 = new Person("BBB", 30)
person2.getName()  //BBB

通过上面代码我们可以看到构造函数模式:

  • 没有通过new Object()显示创建对象
  • 属性和方法赋值给了this
  • 无需return

2.2 构造函数也是函数

构造函数其实也是个函数,只是可以通过new调用来创建对象,当然也可作为普通函数使用

//作为普通函数使用
Person("CCC", 24)
Person.getName() //CCC

//在一个对象内调用
var obj = {}
Person.call(obj, "DDD", 25)
obj.getName()    //DDD

2.3 优劣分析

  • 构造函数模式可以创建具体类型的对象
console.log(person1 instanceof Object)  //true
console.log(person1 instanceof Person)  //true
  • 然而,构造函数中的方法是通用的,但每个实例都创建了一份属于自己的副本,造成内存浪费
console.log(person1.getName === person2.getName)  //false

3. 原型模式

3.1 介绍

原型模式即通过原型对象来实现属性和方法的共享,实例对象不会创建各自的副本

要理解原型模式,建议先理解透彻JS原型的概念——推荐阅读一张图彻底KO原型链(prototype,__proto__)

function Person() {
}

Person.prototype.name = "AAA"
Person.prototype.age = 23
Person.prototype.getName = function() {
  console.log(this.name)
}

var person1 = new Person()
person1.getName()    //AAA

var person2 = new Person()
person2.name = "BBB"
person2.getName()    //BBB

console.log(person1.getName === person2.getName)  //true

然而原型对象也存在缺陷——对于引用类型的属性,各实例对象间指向同一个地址,某个对象修改了属性,所有对象都会受到影响

function Person() {
}

Person.prototype.arr = [1,2,3]
Person.prototype.getArr = function() {
  console.log(this.arr)
}

var person1 = new Person()
person1.arr.push(4)
person1.getArr()    //1,2,3,4

var person2 = new Person()
person2.getArr()    //1,2,3,4

person1.arr.push(5)
person1.getArr()    //1,2,3,4,5
person2.getArr()    //1,2,3,4,5

3.2 优劣分析

  • 原型模式解决了属性和方法的共享问题
  • 但对于引用类型的属性,各实例对象会相互影响

4. 组合模式

4.1介绍

组合模式即组合构造函数模式和原型模式,取二者之长,构造函数模式用于定义实例的属性,原型模式用于定义方法和共享的属性

function Person(name, age) {
  this.name = name
  this.age = age
  this.arr = [1,2,3]
}

Person.prototype = {
  getName: function() {
    console.log(this.name)
  }
}

var person1 = new Person("AAA", 23)
person1.arr.push(4)
console.log(person1.arr)  //1,2,3,4
person1.getName()         //AAA

var person2 = new Person("BBB", 30)
console.log(person2.arr)  //1,2,3
person2.getName()         //BBB

4.2 优劣分析

  • 组合模式是使用最广泛的一种模式,尤其在需要定义引用类型时

5. 动态原型模式

5.1 介绍

刚说组合模式是认可度最高的一种模式,然而也有些美中不足——每创建一个实例对象,原型方法都被重复定义一次

动态原型模式正是解决这个问题,使用if语句,使得原型方法只初始化一次

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

  //这里只需要使用任何一个方式或属性,不一定是getName,getAge也可以
  //只要保证if里面的代码只执行一次就行
  if(typeof this.getName !== 'function') {
    Person.prototype.getName = function() {
      console.log(this.name)
    }

    Person.prototype.getAge = function() {
      console.log(this.age)
    }
  }
}

var person = new Person("AAA", 23)
person.getName()    //AAA

5.2 优劣分析

  • 动态原型模式在组合模式的基础上做了优化,可谓更加完美

6. 寄生构造函数模式

6.1 介绍

长得和工厂模式一样,但调用方式和构造函数模式一样的模式,通过new调用构造函数,本该返回一个实例对象,但return语句重写了构造函数的返回值

function Person(name, age) {
  var obj = new Object()
  obj.name = name
  obj.age = age
  obj.getName = function() {
    console.log(this.name)
  }
  return obj
}

var person = new Person("AAA", 23)
person.getName()   //AAA

6.2 优劣分析

  • 这种方式和工厂模式没什么不同,要说不同的就是通过new来调用,仍然存在工厂模式的无法判别对象类型的问题

  • 个人认为这是一种多余的方式,复杂而不好理解,也没有什么应用场景

7. 稳妥构造函数模式

7.1 介绍

先介绍一个概念——稳妥对象:

  • 没有公共属性
  • 不使用this
  • 不使用new调用

稳妥构造函数模式和工厂模式类似,但遵循稳妥对象的原则

function Person(name, age) {
  var obj = new Object()

  //getName是唯一读取name的方式
  obj.getName = function() {
    console.log(name)
  }
  return obj
}

var person = Person("AAA", 23)
person.name = "BBB" //无效
person.getName()    //AAA

7.2 优劣分析

  • 稳妥构造函数模式的特点就是安全性,适用于某些需要安全执行的环境
  • 类似工厂模式,无法判别对象类型

8. 总结

  1. 工厂模式:解决了大量重复代码的问题,但无法判别对象类型

  2. 构造函数模式:实例对象又具体的类型,但每个实例对象都有方法的副本,造成内存浪费

  3. 原型模式:可共享方法和属性,但存在引用类型相互影响的问题

  4. 组合模式:取构造函数模式和原型模式之长 (最常用)

  5. 动态原型模式:优化了组合模式

  6. 寄生构造函数模式:类似工厂模式,只是通过new调用

  7. 稳妥构造函数模式:类似工厂模式,只是提升了安全性

后记

希望本文对你有帮助,更多文章将持续更新……

感谢你的点赞和鼓励

与本文相关的文章有:

【JS系列】一张图彻底KO原型链(prototype,__proto__)

【JS系列】对象详解

【JS系列】继承的这6种方式!(上)

【JS系列】继承的这6种方式!(下)