简介
- JS作为脚本语言,主要是在浏览器中执行阶段性的特定任务,所以有其本身的特点:
- 一切都是对象,函数(function)也不例外
- 每个对象都有内置变量constructor和__proto__,constructor指向构造函数,__proto__指向构造函数的原型
- 后续的不断发展,在很多方面得到了延伸:
- 模块化(AMD/CMD/CommonJs)
- MVVM框架(React/Vue/Angular)
- 前后端适配(Node/Browser)
- 在推出新的规范ES6的同时,引入了类及继承。本文简要介绍继承,并讨论其在JS中的实现。
- 继承是面向对象四大特征(抽象、封装、继承、多态)之一,主要为了复用已有功能。使子类可直接拥有或访问,父类允许的成员变量和方法
- JS的继承方式比较奇特,不像传统的面向对象语言(C++、Java等),而是通过其原型链
- 一个对象在访问变量或者函数时,首先在类内部查找,其次是父类内部,最后是其.__proto__
实现继承
创建基类
创建一个基类
1
2
3
4
5
6
7
8
9var Widget = function(){
// 内部成员变量
this.messages = [];
};
// 方法
Widget.prototype.push = function(element) {
this.messages.push(element);
}子类SubWidget继承后,可访问父类的变量和方法:
1
2
3
4
5
6
7
8var sub1 = new SubWidget( 'foo' );
var sub2 = new SubWidget( 'bar' );
sub1.messages.push( 'foo' );
sub2.messages.push( 'bar' );
console.log(sub1.messages); // ['foo']
console.log(sub2.messages); // ['bar']
实例继承
1 | var SubWidget = new Widget(); |
- 优点:SubWidget是一个实例(非函数),可以访问父类及原型的所有变量和方法
- 缺点:没有prototype属性,并且共享所有元素
原型继承
继承可以分成两部分:父类成员(变量或方法)和prototype成员
1 继承父类成员
1 | var SubWidget = function( name ){ |
后初始化自有成员变量或方法,可覆盖父类的同名变量或方法。此时,SubWidget.prototype指向空对象
2 继承父类prototype
只要能显示 访问父类prototype中的变量和方法 即可
2.1 原型为父类的实例1
SubWidget.prototype = new Widget();
子类prototype中有父类的内部变量,且constructor指向父类(而不是子类本身)。
2.2 原型为父类原型1
SubWidget.prototype = Widget.prototype;
改变子类prototype的同时,会改变父类prototype
2.3 混合1
2
3
4
5
6
7function Super(parent) {
var F = function(){};
F.prototype = parent.prototype;
this.prototype = new F();
this.prototype.constructor = this;
}
super.call(SubWidget, Widget);
需要增加一个内部函数
完整实现
结合1和2.3的完整实现如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16function superProtoType(parent) {
var F = function(){};
F.prototype = parent.prototype;
this.prototype = new F();
this.prototype.constructor = this;
}
var SubWidget = (function(){
var constructFunc = function(name) {
Widget.call(this, this.arguments);
this.name = name;
};
superProtoType.call(constructFunc, Widget);
return constructFunc;
})();
是不是类似于ES6实现的继承方式^_^

继承与实例
函数.prototype.constructor需要指向自身(Code1:函数默认创建时,即指向自身),否则会改变实例的constructor指向
1
2
3
4
5
6
7var testCls = function() {
this.array = [];
}
var inst = new testCls();
console.log(inst.constructor); // testCls
console.log(testCls.prototype.constructor); // testCls
console.log(testCls.prototype); // Object{constructor: function}Code1:log中可以看出,.constructor都指向testCls
1
2
3
4
5
6
7
8
9
10
11
12var testCls = function() {
this.array = [];
}
testCls.prototype = {
push: function() {
}
};
var inst = new testCls();
console.log(inst.constructor); // Object function
console.log(testCls.prototype.constructor); // Object function
console.log(testCls.prototype); // Object {push: function}Code2:改变.prototype指向后,.constructor都指向改变后的函数
1
2
3
4
5
6
7
8
9
10
11
12
13var testCls = function() {
this.array = [];
}
testCls.prototype = {
push: function() {
},
constructor: testCls
};
var inst = new testCls();
console.log(inst.constructor); // testCls
console.log(testCls.prototype.constructor); // testCls
console.log(testCls.prototype); // Object {push: function, constructor: function}Code3:与Code1默认行为一致
A instanceof B 的判断逻辑为:A.__proto__…__proto__ === B.prototype。所以在图1中,sub1 instanceof SubWidget和Widget均返回true。
- Function、Object的构造函数为Function,所以Function(Object).__proto__ === Function.prototype
图2红线所示,Function,Object为Function的实例
- Function.prototype是一个函数,构造函数是Function。但Function.prototype.__proto__ === Object.prototype,由3可替换为Function(Object).__proto__.__proto__ === Object.prototype
图2红线和绿线所示,Function,Object为Object的实例
