构造函数
es6 之前,js 实现面向对象的方法是借助构造函数实现的
前置知识
- 每一个对象都有自己的隐式原型对象
[[prototype]]
,浏览器环境和 node 都会提供一个 API __proto__
属性让开发者访问到该对象上的原型对象,但是该 API 可能会存在一定的兼容性问题,本文都用 __proto__
属性代替 [[prototype]]
- 每一个函数都有自己的显示原型对象
prototype
,这是 ECMAScript 实现的,不存在兼容性问题,注意箭头函数没有 prototype
,因此不能用于构造函数
- 函数的显示原型中存在一个属性
constructor
是一个函数指向它本身
1 2
| function foo() {} console.log(foo.prototype);
|

- 函数也是一个对象,所以函数是既有显示原型对象也有隐式原型对象的。
new 构造函数发生了什么
- 在 new 构造函数的时候内存中执行了以下事件
- 创建了一个空对象
{}
- 将 this 绑定在这个空对象上
- 将函数的
prototype
属性赋值给这个对象的隐式原型 __proto__
上
- 执行函数体中的代码
- 将 this 这个对象返回
1 2 3 4 5 6 7 8 9
| function Person(name, age) { this.name = name; this.age = age; this.eat = function () { console.log(this.name + " eating"); }; } const p = new Person("cy", 24); console.log(p);
|

- 在用字面量声明一个对象的时候本质上是
new Object()
的一个语法糖,因此 Object 这个函数的 prototype
就给了 obj 这个对象的隐式原型 __proto__
这里字面量的__proto__
就是顶层原型了。
1 2 3
| const obj = {}; console.log(obj.__proto__ === Object.prototype); console.log(obj.__proto__);
|

- 函数也是一个对象,因此它也有自己的隐式原型
__proto__
,来自于new Function()
的Function.prototype
,函数也有自己的显式原型prototype
,函数的显示原型是 js 引擎创建的一个对象里面有constructor
属性,显示原型prototype
是一个对象因此也有__proto__
1 2 3 4
| function foo() {}
console.log(foo.prototype.__proto__ === Object.prototype); console.log(foo.__proto__ === Function.prototype);
|
- 顶层 Function 和顶层 Object 之间的关系
- Function 是一个函数,因此 Function.prototype 存在 constructor 指向自己 Function
- Function 也是一个对象,
Function.__proto__
是由 new Function()
而来所以 Function.__proto__
指向 Function.prototype
- Function 的 prototype 也是一个对象,因此也存在
__proto__
,对象是 new Object()而来,因此Function.prototype.__proto__ === Object.prototype
- function Object 是一个函数,所以存在自己的原型对象 prototype
- Object 也是一个对象,所以有自己的隐式原型
__proto__
是 new Function 而来,所以Object.__proto__ === Function.prototype

1 2 3 4 5 6 7
| console.log(foo.__proto__ === Function.prototype); console.log(foo.__proto__ === foo.prototype); console.log(Function.prototype === Function.__proto__); console.log(Function.prototype.__proto__ === Object.prototype); console.log(Object.prototype); console.log(Object.__proto__ === Function.prototype);
|
构造函数封装类
前文对于构造函数的封装,存在不妥之处,主要在于类中方法的实现,当用这个构造函数实现多个实例时可以发现,不同实例上的 eat 方法本应该没有区别,但是在这种方式下实现时,每一个实例的 eat 方法占用了不同的内存空间,也就是说实例化的时候开辟了多余的内存空间对内存造成了很大的浪费。
1 2 3 4 5 6 7 8 9 10
| function Person(name, age) { this.name = name; this.age = age; this.eat = function () { console.log(this.name + " eating"); }; } const p = new Person("cy", 24); const p1 = new Person("tyz", 23); console.log(p.eat === p1.eat);
|
那么我们该如何优化呢?这时候我们就可以考虑借助到原型了,将方法挂载到每一个类的原型上,并不是直接挂载到对象的属性本身上。
1 2 3 4 5 6 7 8 9 10
| function Person(name, age) { this.name = name; this.age = age; } Person.prototype.eat = function () { console.log(this.name + " eat"); }; const p = new Person("cy", 24); const p1 = new Person("tyz", 23); console.log(p.eat === p1.eat);
|
为什么呢?方法(函数)在内存中实际上引用的是一个地址 Perosn 的原型对象上一个属性 eat 存储了一个方法,那么在 new 实例时将 prototype 这个对象的地址给了 p 这个对象的proto, 调用实际的方法是指向的同一个地址,因此就不会造成内存空间的浪费了。

构造函数实现继承
属性的继承
通过 Person.call 可以实现属性的继承 Person 本身就是一个函数,使用 call 方法将 Student 内部创建的 this 绑定上去即可实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| function Person(name, age) { this.name = name; this.age = age; } Person.prototype.eat = function () { console.log(this.name + " eat"); };
function Student(name, age, sno) { Person.call(this, name, age);
this.sno = sno; }
|
方法的继承
一个很常见的想法是直接将Child.prototype
指向Father.prototype
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| function Person(name, age) { this.name = name; this.age = age; } Person.prototype.eat = function () { console.log(this.name + " eat"); };
function Student(name, age, sno) { Person.call(this, name, age); this.sno = sno; }
Student.prototype = Person.prototype; const s = new Student("cy", 24, "1102"); const s1 = new Student("cy", 25, "0729"); console.log(s); console.log(s.eat()); console.log(s.__proto__ === s1.__proto__);
|
但是这种实现方式表面上看实现了继承,但实际上每一个实例化的对象的原型都指向了Person.prototype
,这样给 Student 添加的方法最终都会添加到 Father.prototype 这实际上是违背了面向对象的原则的。因此真正的方法是使用Child.prototype
指向一个全新的对象,这个对象的原型是Father.prototype
的副本,由于本来实例化的对象的原型是 Student.prototype,但是现在是直接使用 Object.create()创造的对象替换了原来的对象,因此 Student.prototype 就缺少了 constructor 这个函数,所以也需要补上。内存图解如下

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| function Person(name, age) { this.name = name; this.age = age; } Person.prototype.eat = function () { console.log(this.name + " eat"); };
function Student(name, age, sno) { Person.call(this, name, age); this.sno = sno; } Student.prototype = Object.create(Person.prototype); Object.defineProperty(Student.prototype, "constructor", { enumerable: false, configurable: true, value: Student, writable: false, }); const s = new Student("cy", 24, "1102"); console.log(s);
|

为了实现的通用性,将继承函数封装一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| function inheritProto(subType, superType) { subType.prototype = Object.create(superType.prototype); Object.defineProperty(subType.prototype, "constructor", { enumerable: false, configurable: true, value: subType, writable: false, }); } function Person(name, age) { this.name = name; this.age = age; } Person.prototype.eat = function () { console.log(this.name + " eat"); }; inheritProto(Student, Person); function Student(name, age, sno) { Person.call(this, name, age); this.sno = sno; }
|
class 实现面向对象
class 是构造函数的语法糖
new class 发生了什么
new class 自动执行了 constructor 函数,执行了以下步骤
- 创建一个空对象
- 将 Person.prototype 赋值给(指向)空对象的proto
- 将 this 绑定给空对象
- 执行代码
- 将 this 对象返回
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| class Person { name: string; age: number; _address: string; constructor(name: string, age: number) {
this.name = name; this.age = age; this._address = "广州"; } eat() { console.log(this.name + "eating"); } get address() { console.log("读取address"); return this._address; } set address(newVal: string) { console.log("修改address"); this._address = newVal; } static personMethod() { console.log("静态方法,构造函数(类)调用,实例不能调用"); } } const p = new Person("cy", 24); console.log((p as any).__proto__ === Person.prototype); p.address = "hhh"; console.log(p.address); Person.personMethod();
|
继承
使用关键字 extends 和 super 实现继承
super()
直接调用,在constructor
内部,用于调用父类的constructor
,实现属性继承
- super 也可以调用父类的方法,实现子类部分方法的逻辑的重写或者复用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| class Person1 { name: string; age: number; _address: string; constructor(name: string, age: number) { this.name = name; this.age = age; this._address = "广州"; } sleep() { console.log(this.name + " sleeping"); } eat() { console.log(this.name + " eating"); } static personMethod() { console.log("静态方法,构造函数(类)调用,实例不能调用"); } }
class Student extends Person1 { sno: number; constructor(name: string, age: number, sno: number) { super(name, age); this.sno = sno; } learn() { console.log(this.name + "learning"); } eat() { super.eat(); console.log("新增student 的eat逻辑"); } } const s = new Student("cy", 24, 112); console.log(s); s.eat(); s.sleep();
|