JS创建对象有哪几种方式?

387 阅读4分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

1. 字面量方式创建对象

var userInfo = {
        userName: "zhangsan",
        userAge: 18,
        getUserInfo: function () {
          console.log(this.userName + ":" + this.userAge);
        },
      };
      userInfo.getUserInfo();

字面量创建对象比较简单,但是问题也比较突出,每次只能创建一个对象,复用性比较差,如果需要创建多个对象,代码冗余比较高。

2. 通过工厂模式创建对象

工厂模式是一个比较重要的设计模式,该模式提供了一个函数,在该函数中完成对象的创建。

 function createUser(userName, userAge) {
        var o = new Object();
        o.userName = userName;
        o.userAge = userAge;
        o.sayHi = function () {
          console.log(this.userName + ":" + this.userAge);
        };
        return o;
      }
      var user1 = createUser("wangwu", 20);
      var user2 = createUser("lisi", 20);
      console.log(user1.userName + ":" + user2.userName);

通过工厂模式创建对象,解决了字面量创建对象的问题,也就是当创建多个相似对象的时候代码重复的问题。 但是问题是,所创建的所有对象都是Object类型 ,无法进一步的区分对象的具体类型是什么。

3. 通过构造函数创建对象

 function Person(userName, userAge) {
        this.userName = userName;
        this.userAge = userAge;
        this.sayHi = function () {
          console.log(this.userName + ":" + this.userAge);
        };
      }
      var p = new Person("zhangsan", 19);
      p.sayHi();

构造函数创建对象的优点:解决了工厂模式中对象类型无法识别的问题,也就是说通过构造函数创建的对象可以确定其所属的类型。

但是通过构造函数创建对象的问题:

在使用构造函数创建对象的时候,每个方法都会在创建对象时重新创建一遍,也就是说,根据Person构造函数每创建一个对象,我们就会创建一个sayHi方法,但它们做的事情是一样的,因此会造成内存的浪费。

4. 通过原型模式创建对象

每个函数都有一个prototype属性,这个属性指向函数的原型对象,而所谓的通过原型模式创建对象就是将属性和方法添加到prototype属性上。

function Person() {}
      Person.prototype.userName = "wangwu";
      Person.prototype.userAge = 20;
      Person.prototype.sayHi = function () {
        console.log(this.userName + ":" + this.userAge);
      };
      var person1 = new Person();
      person1.sayHi();
      var person2 = new Person();
      console.log(person1.sayHi === person2.sayHi); // true

通过上面的代码,我们可以发现,使用基于原型模式创建的对象,它的属性和方法都是相等的,也就是说不同的对象会共享原型上的属性和方法,这样我们就解决了构造函数创建对象的问题。

但是这种方式创建的对象也是有问题的,因为所有的对象都是共享相同的属性,所以改变一个对象的属性值,会引起其他对象属性值的改变。而这种情况是我们不允许的,因为这样很容易造成数据的混乱。

 function Person() {}
      Person.prototype.userName = "wangwu";
      Person.prototype.userAge = 20;
      Person.prototype.arr = [1, 2];
      Person.prototype.sayHi = function () {
        console.log(this.userName + ":" + this.userAge);
      };
      var p1 = new Person();
      var p2 = new Person();
      console.log(p1.userName);
      p2.userName = "zhangsan";
      console.log(p1.userName); //wangwu,基本数据类型不受影响
      p1.arr.push(3);
      console.log(p1.arr); // [1,2,3]
      console.log(p2.arr); // [1,2,3]
      //引用类型受影响

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

通过构造函数和原型模式创建对象是比较常用的一种方式。

在构造函数中定义对象的属性,而在原型对象中定义对象共享的属性和方法。

//在构造函数中定义对象的属性
      function Person(userName, userAge) {
        this.userName = userName;
        this.userAge = userAge;
      }
      //在原型对象中添加共享的方法
      Person.prototype.sayHi = function () {
        return this.userName;
      };
      var p = new Person("zhangsan", 21);
      var p1 = new Person("lisi", 22);
      console.log(p1.sayHi());
      console.log(p.sayHi());
      // 不同对象共享相同的函数,所以经过比较发现是相等的。
      console.log(p.sayHi === p1.sayHi);
      //修改p对象的userName属性的值,但是不会影响到p1对象的userName属性的值
      p.userName = "admin";
      console.log(p.sayHi());
      console.log(p1.sayHi());

通过构造函数与原型模式组合创建对象的好处就是:每个对象都有自己的属性值,也就是拥有一份自己的实例属性的副本,同时又共享着方法的引用,最大限度的节省了内存。

6. 使用动态原型模式创建对象

所谓的使用动态原型模式创建对象,其实就是将所有的内容都封装到构造函数中,而在构造函数中通过判断只初始化一次原型。

 function Person(userName, userAge) {
        this.userName = userName;
        this.userAge = userAge;
        if (typeof this.sayHi !== "function") {
          console.log("abc"); //只输出一次
          Person.prototype.sayHi = function () {
            console.log(this.userName);
          };
        }
      }
      var person = new Person("zhangsan", 21);
      var person1 = new Person("zhangsan", 21);
      person.sayHi();//abc  zhangsan
      person1.sayHi();//zhangsan

通过上面的代码可以看出,我们将所有的内容写在了构造函数中,并且在构造函数中通过判断只初始化一次原型,而且只在第一次生成实例的时候进行原型的设置。这种方式创建的对象与构造函数和原型混合模式创建的对象功能上是相同的。