1.构造函数
特点:
- 函数名首字母大写
- 函数内部使用了this关键字,代表了所要生成的实例对象
- 生成对象的时候必须使用new命令
2.new 命令
new 命令的作用就是执行构造函数生成实例对象
new 命令的原理:使用 new 命令时,后面的构造函数依次执行了下面的步骤:
- 创建一个空对象,作为要返回的实例对象
- 将这个空对象的原型,指向构造函数的 prototype 属性
- 将这个空对象赋值给函数内部的 this 关键字
- 开始执行构造函数内部的代码
3. 原型链
首先理解三个概念:
- prototype:原型对象,每个函数都有一个 prototype 属性,再通过 new 命令实例对象时,该属性会成为该实例的原型对象。
- constructor:构造函数。指向原型对象的 constructor
- __proto__:实例对象的原型
首先看第一张图:
Person是构造函数,它的 prototype 属性是 Person.prototype;
反过来,Person.prototype 的 constructor 属性又指向 Person
person 是 Person 通过 new 命令生成的实例对象,它拥有__proto__属性,指向构造函数的原型对象,即 Person.prototype;
对于数组:let arr = [];
对于 Array.prototype 虽然是实例原型,但本质上还是一个对象,也就是构造函数 Object 的实例对象,所以:
而对于 Object.prototype,它的实例原型(__proto__)是 null;
于是,我们得出这样一条原型链:
同样对于构造函数 Array,它可以被当做为构造函数 Function 的实例对象:
对于 Function.prototype,本质上也是一个对象,也是构造函数 Object 的实例对象,所以:
同理,对于普通函数:let func = function(){}
类似于 String(), Number(), Boolean() 等,都能得到这样的原型链。
还剩最后两个 Function 和 Object;
Object 同样当做是 Function 的实例对象,所以:Object.__proto__ === Function.prototype;
而 Function 也可以当做是 Function 的实例对象,所以:Function.__proto__ === Function.prototype;
以上,算是把原型链搞清楚了。
4.constructor
prototype 对象上有一个 constructor 属性,默认指向 prototype 对象所在的构造函数;就是说 constructor 属性定义在 prototype 对象上面,意味着可以被实例对象所继承。
function P() {}
P.prototype.constructor === P // true
function P() {}
var p = new P();
p.constructor === P // true
p.constructor === P.prototype.constructor // true
p.hasOwnProperty('constructor') // false
constructor 属性表示原型对象和构造函数之间的关系,如果修改了原型对象,一般都会修改 constructor 属性,防止出错;
function Person (name) {
this.name = name;
}
Person.prototype.constructor === Person // true
Person.prototype = {
method: function () {}
};
Person.prototype.constructor === Person // false
Person.prototype.constructor === Object // true
以上代码中,prototype 对象被修改后,它的 constructor 属性不再指向 Person,而是指向一个普通对象,而普通对象的 constructor 指向 Object;
所以,在修改原型对象时,一般要同时修改 constructor 属性的指向。(这段话很重要)
// 坏的写法
C.prototype = {
method1: function (...) { ... },
// ...
};
// 好的写法
C.prototype = {
constructor: C,
method1: function (...) { ... },
// ...
};
// 更好的写法
C.prototype.method1 = function (...) { ... };
5.构造函数的继承
第一步:是在子类的构造函数中,调用父类的构造函数
第二步:让子类的原型对象指向父类的原型,这样子类就可以继承父类;
举例来说,下面是一个 Shape 构造函数。
function Shape() {
this.x = 0;
this.y = 0;
}
Shape.prototype.move = function (x, y) {
this.x += x;
this.y += y;
console.info('Shape moved.');
};
我们需要让 Rectangle 构造函数继承 Shape。
// 第一步,子类继承父类的实例
function Rectangle() {
Shape.call(this); // 调用父类构造函数
}
// 另一种写法
function Rectangle() {
this.base = Shape;
this.base();
}
// 第二步,子类继承父类的原型
Rectangle.prototype = Object.create(Shape.prototype); Rectangle.prototype.constructor = Rectangle;
如果子类只想继承父类的单个方法,这是可以采用下面的方法:
ClassB.prototype.print = function() {
ClassA.prototype.print.call(this);
// some code
}
6.多重继承
function M1 () {
this.hello = 'hello';
}
function M2 () {
this.world = 'world';
}
function S () {
M1.call(this);
M2.call(this);
}
// 继承
M1 S.prototype = Object.create(M1.prototype);
// 继承链上加入
M2 Object.assign(S.prototype, M2.prototype);
// 指定构造函数
S.prototype.constructor = S;
var s = new S();
s.hello // 'hello'
s.world // 'world'