关于JavaScript的构造函数

742 阅读3分钟

本分围绕以下几个方面展开:

  1. 构造函数是什么?
  2. 关键字new的作用是什么?

一 什么是构造函数?

用通俗的话解释,构造函数就一张产品设计图,通过设计图,就可以不断制造出相同的产品。
JS中,构造函数的函数名首字母需要大写(其实小写也没问题,约定俗成罢了),通过new 构造函数,得到实例对象。 ES6出的class,是语法糖,这里不去讨论它,继续基于原生进行理解。

    //函数名大小写不影响构造函数的功能
    function factory() {
      this.name = '这是factory构造函数';
    }

    function Factory() {
      this.name = '这是Factory构造函数';
    }
    let a = new factory();
    let b = new Factory();
    console.log(a);//factory {name: '这是Factory构造函数的实例'}
    console.log(b);//Factory {name: '这是Factory构造函数的实例'}

二 关键字new的作用是什么?

从结构上看,普通函数与构造函数,相差的就是一个 new 关键字,我们先看看去掉了new,会产生什么影响。

    function Factory() {
      this.name = '这是Factory构造函数';
    }
    let a = Factory();
    console.log(a);//undefined

a的值为undefined,说明关键字new给函数Factory隐式return了一个值。那这个值到底是什么,继续往下探究。

    function Test1() {}
    var a = new Test1();
    console.log(a); //Test1 {}

    function Test2() {
      return '这是test2构造函数'
    }
    var b = new Test2();
    console.log(b); //Test2 {}

new关键字 会隐式返回一个值,这个值必须是对象。

    function Test1() {
      this.name = "这是Test1构造函数"
    }
    var a = new Test1();
    console.log(a); //Test1 {name: '这是Test1构造函数'}


    function Test2() {
      var obj = {
        name: "这是Test2构造函数"
      }
    }
    var b = new Test2();
    console.log(b); //Test2 {}


    function Test3() {
      var obj = {
        name: "这是Test3构造函数"
      }
      return obj;
    }
    var c = new Test3();
    console.log(c); //{name: '这是Test3构造函数'}

    function Test4() {
      this.name = 'this的name:这是Test4构造函数'
      var obj = {
        name: "这是Test4构造函数"
      }
      return obj; 
    }
    var d = new Test4();
    console.log(d); //{name: '这是Test4构造函数'}

以上列子说明:
在不更改return的情况下,new关键字会返回一个对象,这个对象是this。
当更改return,new关键字,会对return所返回的值,进行校验,如果不是引用值,则依旧返回this对象,是引用值,则返回引用值
那么问题来了,既然能直接return,那为什么我们平时使用构造函数,还需要往this上挂载数据呢?retrun this和retrun 新对象又有什么区别呢?我们继续分析。

    function Test1() {
      this.name = '这是Test1构造函数'
      return this;
    }
    var a = new Test1();
    console.log(a); //Test1 {name: '这是Test1构造函数'}

    function Test2() {
      var obj = {
        name:'这是Test2构造函数'
      }
      return obj;
    }
    var b = new Test2();
    console.log(b); //{name: '这是Test2构造函数'}

属性.png

对比发现,retrun this 获得的实例对象,会携带着实例化它的构造函数的原型对象。
return obj 获得的实例对象,并没有携带着实例化它的构造函数的原型对象。
也就意味着,实例b无法继承实例化它的构造函数的原型对象上的方法和属性,那实例b对象存在的意义就不大了,需要改造一下。

    Test1.prototype.fn = function () {
      console.log('调用Test1原型对象方法', this.name)
    }

    function Test1() {
      this.name = '这是Test1构造函数'
    }
    var a = new Test1();
    console.log('输出Test1实例----------------------', a);
    a.fn();


    Test2.prototype.fn = function () {
      console.log('调用Test2原型对象方法', this.name)
    }

    function Test2() {
      var obj = Object.create(Test2.prototype);
      obj.name = '这是Test2的构造函数'
      return obj
    }
    var b = new Test2();
    console.log('输出Test2实例----------------------', b);
    b.fn();

构造函数.png

通过对比属性,可以看到两个实例的数据结构基本一致了。其原因就是,通过Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__,使得新创建的对象可以继承Test2构造函数原型对象上的属性和方法。