JavaScript构造函数

构造函数也称之为构造器(constructor),通常是我们在创建对象时会调用的函数; 在其他面向的编程语言里面,构造函数是存在于类中的一个方法,称之为构造方法; 但是JavaScript中的构造函数有点不太一样;
在JavaScript中,构造函数就是一个普普通通的函数,从表现形式来看,和其他函数没有区别。当被new关键字所调用时,就变成了构造函数。

new操作调用函数时的流程

  1. 在内存中开辟一块新的空间(空对象)。
  2. 这个对象内部的[[prototype]]属性会被赋值为该构造函数的prototype属性。
  3. 构造函数内部的this会指向新创建出来的对象。
  4. 指向内部函数代码
  5. 如果没有返回非空对象,则会把创建出来的对象给返回。

    对象与函数的原型

    对象的原型——[[prototype]]

    JavaScript每一个对象都有一个特殊的内置属性[[prototype]](proto),这个特殊的对象指向另外一个对象。 :::warning __proto__是早期浏览器自己添加的,可能存在浏览器等兼容性问题,也是历史遗留下的问题。在现代的js中,推荐使用[Object.getPrototypeOf()](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/GetPrototypeOf)或者[Reflect.getPrototypeOf()](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect/getPrototypeOf) :::

    函数的属性——prototype

    JavaScript中所有函数都有一个prototype属性。
    prototype属性可以被重写。

    [[prototype]]和prototype两者的区别

    :::info [[prototype]]是对象的原型
    prototype 是函数的属性 :::

new一个对象时的内存表现

image.png


修改prototype(重写prototype)

  1. function Foo() {}
  2. Foo.prototype = {
  3. msg: "hello",
  4. x: 100,
  5. };
  6. const foo = new Foo();
  7. console.log(foo);
  8. console.log(foo.__proto__);
  9. console.log(foo.msg);
  10. console.log(foo.x);

image.png
内存表现
image.png


给prototype添加属性

  1. function Foo() {}
  2. Foo.prototype.msg = "hello";
  3. Foo.prototype.x = 100;
  4. const foo = new Foo();
  5. console.log(foo);
  6. console.log(foo.__proto__);
  7. console.log(foo.msg);
  8. console.log(foo.x);

image.png
内存表现
image.png

函数与prototype结合使用

直接在构造方法上添加方法的方式。

  1. function Person(name, age) {
  2. this.name = name;
  3. this.age = age;
  4. this.eat = function () {
  5. console.log("eat");
  6. };
  7. }

:::warning 通过这种方式创建一个对象有一个弊端,就是每次创建一个新的Person对象的时候,对象上的eat()函数会被重复创建。非常影响性能。 ::: 在函数原型上添加方法,这样通过该方式,所有创建出来的Person对象,可以共享一个eat函数。

  1. function Person(name, age) {
  2. this.name = name;
  3. this.age = age;
  4. }
  5. Person.prototype.eat = function() {
  6. console.log("eat");
  7. }

prototype.constructor

每一个函数都有一个prototype,而prototype上都会有一个constructor属性,这个constructor指向该函数。
image.png

Object是所有类的父类

通过var foo = new Foo(),会将Foo的原型对象Foo.prototype赋值给foo__proto__,而Foo.prototype中的__proto__默认指向Object.prototype,也就是[Object: null prototype]
image.png
因为无论是通过显式原型prototype还是隐式原型[[prototype]],它的值只能是对象或者null。当其值为对象时,该对象肯定又会有一个隐式原型,如此无论怎样都会最终指到Object

ES5中对象的继承方案

寄生组合式继承

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

Person.prototype.eat = function () {
  console.log(this.name + " is eating...");
};

function Student(name, age, stuNumber) {
  // 借用构造函数
  Person.call(this, name, age);
  this.stuNumber = stuNumber;
}

// 继承prototype
Student.prototype = Object.create(Person.prototype);

// 重定义constructor
Object.defineProperty(Student.prototype, "constructor", {
  value: Student,
  writable: true,
  enumerable: false,
  configurable: true,
});

Student.prototype.study = function () {
  console.log(this.name + " is studying...");
};

Object、Function及自定义函数之间的关系

image.png
在JavaScript中,任何一个函数本质上都是通过new Function()出来的,所以它们的[[prototype]]都指向Function.prototype,每个函数中的prototype中的[[prototype]]默认指向Object.prototype,也就是说默认继承自Object

ES6中的class类

通过前面的方式创建一个类,因为形式上与普通的function过于类似,而且代码不容易理解。在ES6中,添加了一种新的方式定义一个类,但本质上依然是构造函数,只是一种语法糖。 :::tips 如果想查看转化成ES5的代码,可以通过babel工具。 :::

类和构造函数的区别

本质上依然是构造函数,只是一种语法糖。

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

const person = new Person("abc", 18);

console.log(person);
console.log(person.__proto__);
console.log(person.__proto__ === Person.prototype);

console.log(Person.prototype);
console.log(Person.prototype.__proto__);
console.log(typeof Person);

image.png

类的构造函数

每个类都有自己的构造方法,名称为constructor,且每个类只能有一个,如果有多个就会报错。
该构造方法与构造函数的运行原理一致。

类的实例方法

eatrun即为实例方法,与function.prototype.eat方式基本一致。

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

  eat() {
    console.log(`${this.name} is eating~`);
  }

  run() {
    console.log(`${this.name} is running~`);
  }
}

类的访问器方法——getter、setter

通过getset修饰符即可添加gettersetter函数

class Person {
  constructor(name, age) {
    this._name = name;
    this.age = age;
  }

  set name(name) {
    this._name = name;
  }

  get name() {
    return this._name;
  }
}

类的静态方法——static

通过static修饰,可以直接通过类.静态方法调用。

class Person {
  constructor(name) {
    this.name = name;
  }

  static count() {
    return 10;
  }
}

console.log(Person.count());

类的继承——extends

通过extends关键字可以帮助快速实现继承。

class Person {
  constructor(name) {
    this.name = name;
  }
  eat() {
    console.log(`${this.name} is eating~`);
  }
}

class Student extends Person {
  constructor(name, score) {
    super(name); // super()调用父类的构造方法
    this.score = score;
  }
  study() {
    console.log(`${this.name} is ${this.score}`);
  }
}

:::warning 在子(派生)类的构造函数中使用this或者返回默认对象之前,必须先通过super调用父类的构造函数!
super的使用位置有三个:

  • 子类的构造函数
  • 实例方法
  • 静态方法
    :::