JS创建对象

159 阅读4分钟

在写代码的过程中,我们肯定有使用到对象,然后对这个对象进行一系列的操作,那么对象到底是怎么创建的呢?这个过程又是什么呢?本篇文章将会带你去了解怎么创建一个对象!

字面量方式

    const obj={
      name:"丁时一",
      age:20,
      sayName:function(){
        console.log(this.name);
      }
    }
    obj.sayName()//丁时一

这种方式可以创建一个对象,那么缺点也是显而易见的:创建具有同样接口的多个对象需要编写很多重复的代码。

工厂模式

工厂模式按照下面例子的代码方式创建了对象

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

        obj.name = name;
        obj.age = age;
        obj.sayName = function () {
          console.log(this.name);
        };
        return obj;
      }

      const person1 = Person("丁时一", 21);
      person1.sayName(); //丁时一

      const person2 = Person("大帅哥", 20);
      person2.sayName(); //大帅哥

但是这个模式存在一个问题 -->不知道创建的新对象是什么类型

//我们需要的是打印Person
console.log(typeof person1);//object

构造函数模式

前面提过,构造函数是用于创建特定类型对象的,当然我们也可以自己自定义构造函数来创建自定义类型的对象

比如,上面的例子可以这样写:

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

      const person1 = new Person("丁时一", 21);
      const person2 = new Person("大帅哥", 22);

      person1.sayName();
      person2.sayName();

在这个例子中,构造函数于之前的工厂模式的区别主要在于下面三点:

  1. 没有显示的创建对象
  2. 属性和方法直接赋值给了this
  3. 没有return

那么看到这里,您应该会存在一个疑问:

使用new的过程经历了什么?

要创建Person类的实例,应该使用new操作符

  1. 创建一个对象
  2. 将对象的[[prototype]]赋值构造函数prototype属性
  3. 将this指向新创建的对象
  4. 执行构造函数中的代码,给新对象添加属性
  5. 如果构造函数返回了其他对象,那么这个对象作废,否则返回新创建的这个对象。

下面来简单实现一下new

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

      function TestNew(name, age) {
        const obj = Object.create(NewPerson.prototype); //Object.create的原理
        //将构造函数中的this指向obj并调用构造函数进行赋值
        NewPerson.apply(obj, [name, age]);
        return obj;
      }

      const newPerson1 = TestNew("丁时一", 21);
      console.log(newPerson1.__proto__ === NewPerson.prototype); //true
      console.log(newPerson1.name);

NewPerson.apply(obj, [name, age]);这一句代码相当于,这样就给obj添加上了属性。

  function NewPerson(name, age) {
        obj.name = name;
        obj.age = age;
      }

  NewPerson(name,age)

构造函数可以确定对象的类型,相比于工厂模式会有一个很大的提升。但是构造函数也有缺点 ,如果要创建多个实例,每一个方法都要重新创建一遍。

      function Constr(name) {
        this.name = name;
        this.sayName = function () {
          console.log(this.name);
        };
      }
      const conStr1 = new Constr("丁时一");
      const conStr2 = new Constr("大帅哥");
      conStr1.sayName(); //丁时一
      conStr2.sayName(); //大帅哥
      console.log(conStr1.sayName === conStr2.sayName); //false

      //方法都是相同的,那么每次去创建就会造成内存浪费

这里conStr1.sayName === conStr2.sayName返回false说明两个实例的sayName方法是不同的。那么有什么方法来解决这个问题呢1肯定有哇!哈哈哈哈。

其中一个方法就是把相同的方法放到构造函数外面:

      function Constr1(name) {
        this.name = name;
        this.sayName = sayName;
      }
      function sayName() {
        console.log(this.name);
      }
      const conStr3 = new Constr1("丁时一");
      const conStr4 = new Constr1("大帅哥");
      conStr3.sayName(); //丁时一
      conStr4.sayName(); //大帅哥
      console.log(conStr3.sayName===conStr4.sayName);//true

这样虽然解决了问题,但是又出现了一个新的问题:函数实际上只能在一个对象上调用,如果这个对象需要多个方法,那么就需要在全局作用域中定义多个函数,这会导致自定义类型的代码不能很好的凝聚在一起。,这个问题可以通过原型模式来解决。

原型模式

每个函数都会创建一个prototype属性,这个属性是一个对象,包含应该由特定引用类型的实例共享的属性和方法(简单来说就是共享)。

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

      Person.prototype.sayName = function () {
        console.log(this.name);
      };
      Person.prototype.sayAge = function () {
        console.log(this.age);
      };

      const person1 = new Person("丁时一", 21);
      const person2 = new Person("大帅哥", 21);

      person1.sayName(); //"丁时一"
      person2.sayName(); //大帅哥

      console.log(person1.sayName === person2.sayName); //true

看到这里,肯定又存在一些问题了

为什么实例能访问到sayName属性呢1

每次创建一个新实例的时候,我们在文章之前也提到过将对象的[[prototype]]赋值构造函数prototype属性,而我们当时使用的是Object.create(),这个方法的作用是创建一个对象,将参数对象的prototype赋值给这个对象,然后返回这个对象的[[prototype]]属性,请看下面这个例子:

      function Person() {}
      Person.prototype.say = function () {
        console.log("Person");
      };

      const obj2 = Object.create(Person.prototype);

      console.log(obj2);

      console.log(obj2.__proto__ === Person.prototype);//true

实例内部的[[prottype]]是不可以直接访问的,但是Firefox,Chrome,Safari会在每个对象上暴露__proto__属性通过这个属性可以访问对象的原型

这里我们打印了一下obj对象和Person.prototype,我们会发现,obj.__proto__和Person.prototype是相等的!这就是Object.create()的作用。

image.png

那么有小伙伴就要问了:我obj的__proto__里有say这个方法,但是我的对象实例上并没有啊,怎么就可以调用了呢?
博主在之前的文章里面有提过[[Get]],如果不知道的朋友,可以点击进去简单了解一下哈。

person1.sayName === person2.sayName为啥是true?

因为sayName方法定义在Person的原型上,两个实例并没有自己的sayName方法,都是引用这一个共享的sayName方法,所以在===的时候会返回true啦