JavaScript原型和继承的理解
一、_proto_和prototype的关系
只有function对象才有prototype
生成function对象的时候,会生成prototype属性,并将function对象赋值给prototype.constructor。
后面的例子都以Base作为父类
//父类
function Base(){
this.x = 0;
this.y = 0;
}
Base.prototype.constructor === Base//true
使用 new function对象,可以生成一个新的对象。new做法是新建一个obj对象o1,并且让o1的__proto__指向了Base.prototype对象。并且使用call 进行强转作用环境。从而实现了实例的创建。
new的时实际上执行的是:
var o1 = new Object();
o1.[[Prototype]] = Base.prototype;//__proto__指向prototype
Base.call(o1);
所以
new Base().__proto__ === Base.prototype
构造函数生成了一个原型,new可以基于这个生成的原型和构造函数生成实例
总结起来的话,prototype是一个在构造函数中,用来共享数据结构的模板。
__proto__是prototype在对象中的具体表现
二、继承的实现
2.1 构造函数到底声明了什么
一个构造函数其实定义了两部分数据结构。以Base为例:
- 自身的x,y
- 用来共享的prototype。
2.2 如何来实现继承
继承其实就是想办法继承这两部分数据
首先还是以Base为例定义被继承的父类
//父类
function Base(){
this.x = 0;
this.y = 0;
}
Base.prototype.getAdd = function(){
return this.x+this.y
}
2.2.1 原型链继承
将父类的实例直接复制到子类的prototype里面,依靠原型链来访问父类,这样自身属性和原型链都放到了原型链上
//父类
function Base(){
this.x = 0;
this.y = 0;
}
Base.prototype.className = "Base"
function PrototypeChildren(){
}
PrototypeChildren.prototype = new Base()
var pc1 = new PrototypeChildren()
var pc2= new PrototypeChildren()
需要注意的是通过子类可以读取原型链上的属性,但是无法对原型链上的属性赋值
pc1.className = "pc1"
console.log(pc2.className)//Base
console.log(pc1.className)//pc1
直接赋值会在子对象上动态生成一个新的属性,不会修改父类的prototype。
如果要修改原型链上的属性需要修过父类的prototype
PrototypeChildren.prototype.className = "Children"
console.log(pc2.className)//Children
console.log(pc1.className)//pc1
2.2.2 构造函数继承
使用call方法,把构造函数中的属性复制到新对象里面,继承了构造函数里面的数据结构,只继承了构造函数内声明的属性,没有继承原型中的属性
function ConChildren(){
Base.call(this)
}
var cc = new ConChildren()
这样只继承了属性,但是原型链并没有继承
我们要把原型链修改成为可以用下面的组合法
2.2.3 组合式继承
通过构造函数和原型链相结合的方式实现继承
function SubTypeChildren(){
Base.call(this)
}
// 空函数F:
function F() {
}
// 把F的原型指向Base.prototype:
F.prototype = Base.prototype;
SubTypeChildren.prototype = new F()
var sc = new SubTypeChildren()
这里面没有直接使用,对Base.prototype进行了保护,避免修改SubTypeChildren.prototype时直接修改了Base.prototype放到了,同时将Base.prototype放到了SubTypeChildren的原型链上
SubTypeChildren.prototype = Base.prototype
但是这样SubTypeChildren.prototype.constructor指向了Base
可以手动改回来
SubTypeChildren.prototype.constructor = SubTypeChildren
空函数F写法很繁琐,看起来很奇怪,ES5之后可以用Object.create代替。Object.create规范了组合继承的写法
function SubTypeChildren(){
Base.call(this)
}
SubTypeChildren.prototype = Object.create(Base.prototype);
SubTypeChildren.prototype.constructor = SubTypeChildren
Object.create(Base.prototype)和 new Base()的区别。Object.create(Base.prototype)生成一个对象只包含了Base的原型而没有生成构造函数中的属性,避免了多次调用构造函数生成重复属性
2.3 多继承
可以使用Object.assign混入结合Object.create实现多继承
function MyClass() {
SuperClass.call(this);
OtherSuperClass.call(this);
}
// 继承一个类
MyClass.prototype = Object.create(SuperClass.prototype);
// 混合其它
Object.assign(MyClass.prototype, OtherSuperClass.prototype);
// 重新指定constructor
MyClass.prototype.constructor = MyClass;
MyClass.prototype.myMethod = function() {
// do a thing
};
2.4 class和extends继承
2.4.1 class
新的关键字class从ES6开始正式被引入到JavaScript中。class是生成类的语法糖,目的就是让定义类更简单。
旧的写法
function Student(name) {
this.name = name;
}
Student.prototype.hello = function () {
alert('Hello, ' + this.name + '!');
}
新的class写法
class Student {
constructor(name) {
this.name = name;
}
hello() {
alert('Hello, ' + this.name + '!');
}
}
2.4.2 extends继承
现在,原型继承的中间对象,原型对象的构造函数等等都不需要考虑了,直接通过extends来实现:
class PrimaryStudent extends Student {
constructor(name, grade) {
super(name); // 记得用super调用父类的构造方法!
this.grade = grade;
}
myGrade() {
alert('I am at grade ' + this.grade);
}
}
对比组合继承和extends继承
优势:
- extends实现的继承语义更清晰
- 生成的对象的原型链接更简洁易于理解