JavaScript这些创建对象的方式你都知道吗?

106 阅读5分钟

前言

面试官:在JavaScript中都有哪些创建对象的方式?

我:花括号直接创建啊

面试官:

image.png

然后就没了然后......

接下来为大家详细介绍下JavaScript中创建对象的几种方式,及其优缺点。

new Object()

使用new操作符后跟Object构造函数用以初始化一个新创建的对象。

var person = new Object();
person.name = 'mjj';
person.age = 28;

平时都不这么写,多此一举

缺点:如果要创建多个对象,会产生大量的重复代码

对象字面量语法糖

var person = {
  name:'mjj';
  age:28
}

存在的问题:

  • 如果要创建多个对象,会产生大量的重复代码

Object.create()

从一个实例对象,生成另一个实例对象。

// 原型对象
var A = {
  getX:function(){
    console.log('hello');
  }
};
//实例对象
var B = Object.create(A);
console.log(B.getX); //"hello"

手写Object.create

Object.myCreate = function (proto) {
  function F() {}
  F.prototype = proto  // 把构造函数的prototype属性指向传入的对象,就成了创建的实例对象的原型对象
  return new F()
}

工厂模式

该模式抽象了创建具体对象的过程,用函数来封装以特地接口创建对象的细节。

function createPerson(name, age) {
  return {
    name,
    age,
    sayName() {
      alert(this.name)
    }
  }
}
var p1 = createPerson('mjj',28);
var p2 = createPerson('alex',28);
var p3 = createPerson('阿黄',8);

可以用来创建多个相似对象的问题。

存在的问题:

  • 对象识别的问题,因为使用该模式并没有给出对象的类型。

构造函数模式

function Person(name,age){
  this.name = name;
  this.age = age;
  this.sayName = function(){
    alert(this.name);
  };
}
var person1 = new Person("mjj",28);
var person2 = new Person("alex",25);

存在的问题:

  • 每个方法都要在每个实例上重新创建一遍,创建多个完成相同任务的方法完全没有必要,浪费内存空间。

构造函数拓展模式

基于构造函数模式的问题,将方法定义到全局,这样就不需要重新创建:

function Person(name,age){
  this.name = name;
  this.age = age;
  this.sayName = sayName;
}
function sayName(){
  alert(this.name);
}
var p1 = new Person("mjj",28);
var p2 = new Person("alex",25);
console.log(person1.sayName === person2.sayName);//true

存在的问题:

  • 在全局作用域中定义的函数实际上只能被某个对象调用,这让全局作用域有点名不副实。

  • 如果对象需要定义很多方法,就要定义很多全局函数,严重污染全局空间,这个自定义的引用类型没有封装性可言了

寄生构造函数模式

  • 创建一个函数,该函数的作用仅仅是封装创建对象的代码
  • 再返回新创建的对象。
  • 该模式是工厂模式和构造函数模式的结合。
function Person(name,age){
  var p = new Object();
  p.name = name;
  p.age = age;
  p.sayName = function(){
    alert(this.name);
  }
  return p;
}
var p1 = new Person('mjj',28);
var p2 = new Person('alex',28);
//具有相同作用的sayName()方法在person1和person2这两个实例中却占用了不同的内存空间
console.log(p1.sayName === p2.sayName);//false

存在的问题:

  • 每个方法都要在每个实例上重新创建一遍
  • 使用该模式返回的对象与构造函数之间没有关系

稳妥构造函数模式

特点

  • 所谓稳妥对象指没有公共属性
  • 方法也不引用this对象
  • 不适用new操作符调用构造函数
  • 稳妥对象最适合在一些安全环境中(这些环境会禁止使用this和new)或者在防止数据被其他应用程序改动时使用
function Person(name,age){
  //创建要返回的对象
  var p = new Object();
  //可以在这里定义私有变量和函数
  //添加方法
  p.sayName = function (){
    console.log(name);
  }
  //返回对象
  return p;
}
//在稳妥模式创建的对象中,除了使用sayName()方法之外,没有其他方法访问name的值
var p1 = Person('mjj',28);
p1.sayName();//"mjj"

存在的问题:

  • 每个方法都要在每个实例上重新创建一遍
  • 创建的对象与构造函数之间也没有什么关系

原型模式

特点

  • 让所有实例共享它的属性和方法。不必在构造函数中定义对象实例的信息,而是可以将这些信息直接添加到原型对象中。
function Person(){
  Person.prototype.name = "mjj";
  Person.prototype.age = 29;
  Person.prototype.sayName = function(){
    console.log(this.name);
  }
}
var p1 = new Person();
p1.sayName();//"mjj"
var p2 = new Person();
p2.sayName();//"mjj"
alert(p1.sayName === p2.sayName);//true

更简单的原型模式

为了减少不必要的输入,也为了从视觉上更好地封装原型的功能,用一个包含所有属性的方法的对象字面量来重写整个原型对象

function Person(){};
Person.prototype = {
  constructor:Person,
  name:'mjj',
  age:28,
  sayName:function(){
    console.log(this.name);
  }
}
var p1 = new Person();
p1.sayName();//"mjj"
console.log(p1.constructor === Person);//true
console.log(p1.constructor === Object);//false

存在的问题:

  • 引用类型值属性会被所有的实例对象共享并修改,这也是很少有人单独使用原型模式的原因。

组合模式

特点:

  1. 组合使用构造函数模式和原型模式是创建自定义类型的最常见方式。
  2. 构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性
  3. 这种组合模式还支持向构造函数传递参数。实例对象都有自己的一份实例属性的副本,同时又共享对方法的引用,最大限度地节省了内存。
  4. 该模式是目前使用最广泛、认同度最高的一种创建自定义对象的模式。
function Person(name,age){
  this.name = name;
  this.age = age;
  this.friends = ['alex','阿黄'];
}
Person.prototype = {
  constructor:Person,
  sayName:function(){
    console.log(this.name);
  }
}
var p1 = new Person('mjj',28);
var p2 = new Person('jjm',30);
p1.friends.push('wusir');
alert(p1.friends);//['alex','阿黄','wusir']
alert(p2.friends);//['alex','阿黄']
alert(p1.friends === p2.friends);//false
alert(p1.sayName === p2.sayName);//true

动态原型模式

动态原型模式将组合模式中分开使用的构造函数和原型对象都封装到构造函数中,然后通过检查方法是否被创建,来决定是否初始化原型对象,更具有封装性。

function Person(name,age){
  //属性
  this.name = name;
  this.age = age;
  //方法
  if(typeof this.sayName != "function"){ // 每创建一个对象,就会执行这里,因此需要判断只是在第一次调用时添加原型的属性,方法
    Person.prototype.sayName = function(){
      console.log(this.name);
    }
  }
}
var p1 = new Person('mjj',28);
p1.sayName();//"mjj"