面试官:说说js中有哪些创建对象的模式

251 阅读5分钟

JS创建对象的方式

JS创建对象是一个老生常谈的面试题,非常考验JS的基础功底。可能有些同学会想,创建对象?不就是const obj = {};么?

这里我们将讲述其他的几种方式,来进行对象的创建。

前置知识

我们知道对象实例中,都有一个指针[[prototype]]指向对象构造函数的原型;

而构造函数的原型中有一个constructor属性指向构造函数;

构造函数可以通过prototype访问到对应的原型;

原型链就是访问对象的属性,当对象内部不存在,则去原型对象中寻找,如果还是不存在,再去原型对象的原型去寻找,然后一直找下去就是原型链

创建对象的6种模式

1.工厂模式

方式: 通过一个普通的函数,内部通过new Object()创建一个对象,依次给创建的对象添加新的属性后return 该对象。

优点: 实现了一个简单的对象创建,每次调用该函数,都会创建一个新的对象。

问题:

  1. constructor始终指向了Object
  2. 工厂函数内的方法始终都是一样的,但是确重复声明了多次
  function factory() {
    const obj = new Object();
    obj.a = 1;
    obj.say = function () {
      console.log("我是一个工厂函数");
    };
    return obj;
  }
  const factoryObj1 = factory();
  const factoryObj2 = factory();
  factoryObj1.say(); // 我是一个工厂函数
  console.log(factoryObj1 === factoryObj2); // false

2.构造函数模式

方式: 通过创建一个构造函数来实现,首字母大写!!!通过new来创建实例

优点: 能够通过constructor和instanceof来识别出实例的类型

问题: 多个实例的方法都是实现一样的效果确存储多次

注意 构造函数中,默认是返回this的(一般这个this指向的是新的实例),如果不通过new 来调用中,则会给全局对象添加方法和属性

  function ConstructFn(name, age) {
    this.name = name;
    this.age = age;
    this.say = function () {
      console.log(this.name, "这是构造函数创建的对象");
    };
  }

  const constructObj = new ConstructFn("test", 1);
  console.log(constructObj); // {name:'test',age:1,say:f()}
  console.log(constructObj instanceof ConstructFn); // true

当我们直接执行构造函数的时候ConstructFn("window", "all");我们能在window中找到对应的属性。

image.png


3.原型模式

方式: 创建一个构造函数,构造函数函数体内不进行操作,在外部给构造函数的原型增加属性和方法。

优点: 每个实例都共享原型上的方法和属性

问题:

  1. 如果出现引用属性的时候,因为引用类型是按照引用地址来访问的,多个实例共享了同个引用地址,其中一个实例更改了这个引用的值,同样也会反馈到其他实例上。
  2. 实例调用方法或者属性的时候,会搜索两次。第一次搜索实例本身,第二次根据原型链去搜索实例的原型。
  3. 没法创建实例自己的属性和方法

注意: 如果直接用字面量的方式给构造函数的原型赋值会造成原型constructor属性的改变,所以需要将constructor指回构造函数。

  function PrototypeFn() {}
  PrototypeFn.prototype.name = "hanmeimei";
  PrototypeFn.prototype.say = function () {
    console.log("这是原型模式创建的对象");
  };
  PrototypeFn.prototype.arr = [1, 2];
  const obj1 = new PrototypeFn();
  const obj2 = new PrototypeFn();
  obj1.say(); // 这是原型模式创建的对象
  obj1.arr.push(3); // 通过实例obj1更改原型上的引用类型
  console.log(obj2.arr); // [1,2,3]

4.构造函数和原型组合模式

方式: 结合构造函数模式和原型模式,方法使用原型模式来创建,属性使用构造函数模式来创建

优点: 这是一个相对比较完美的实现

缺点: 每次实例化都会为构造函数的原型重新赋值

  function ConstructAndPrototypeFn(name) {
    this.name = name;
    this.friends = ["lilei"];
    ConstructAndPrototypeFn.prototype.say = function () {
        console.log("这是组合模式创建的");
    };
  }
 
  const obj = new ConstructAndPrototypeFn("hanmeimei");
  obj.say(); //这是组合模式创建的

5.动态原型模式

方式: 在上述组合模式的基础上,给原型添加方法时增加一层判断,如果已经存在某个方法或者属性则不进行添加。

优点: 检查某个应该存在的方法是否有效,来决定是否需要初始化原型

注意: 只需要检查一次就可以了,不能用对象字面量的方式对原型进行赋值操作。

function dynamicFn(name) {
    this.name = name;
    if (typeof this.say !== "function") {
      dynamicFn.prototype.say = function () {
        alert(this.name);
      };
    }
  }

6.寄生构造函数模式

方式: 这种模式和工厂模式比较像,返回的对象与构造函数或者与构造函数的原型属性之间没有关系,与构造函数内部的new的构造函数有关系。

优点: 可以在特殊的情况下用来为对象创建构造函数

缺点: 不能用instanceof来判断类型

  function ParasitismConstructorFn(name) {
    var o = new Object();
    o.name = name;
    o.say = function () {
      alert(this.name);
    };
    return o;
  }
  var obj1 = new ParasitismConstructorFn("hanmeimei");
  console.log(obj1);

7.稳妥构造函数模式

方式: 和寄生构造函数有点类似,但是不会将实参赋值给构造函数内创建的实例,而是通过构造函数内的方法去访问。

优点: 安全,那么好像成为了私有变量,只能通过构造函数内的方法去进行访问。

缺点: 不能区分实例的类型

function Person(name) {
    var o = new Object();
    o.say = function () {
      console.log(name);
    };  
    return o
  }

  var person1 = new Person("hanmeimei");
  person1.name; // undefined
  person1.say(); // hanmeimei

总结

总体对象的创建有以上7种方式,有问题的小伙伴可以给我留言,感谢大家的支持。