目录
- 一、临时构造器方式
- 1.1 代码实现
- 1.2 代码分析
- 二. 增加uber属性,用于子对象访问父对象
- 2.1 实现分析
- 2.2 代码实现
- 三. 将继承封装成extend()函数
- 3.1 代码实现
- 3.1.1 临时构造器实现extend()
- 3.1.2 原型复制实现extend2()
- 3.2 代码测试
- 3.2.1 测试extend()函数
- 3.2.1 测试extend2() 函数
- 四、实例对象原型继承:以实例对象为原型创建新对象
- 4.1 功能分析
- 4.2 功能测试
书接上集,在上集中我们给出了一个需求说明,要求利用现学的知识实现原型(prototype
)继承,并且我们给出了三种实现方式。但是这三种方式各自有优缺点,都不能很好的满足要求,那是否还有其他更好的实现方式呢?
在看本文之前,诸位可以自己思考下?带着问题去学习总是能够学到更多内容。在以后的岁月中,会找一些有意义的句子,与诸君共勉。
在时间的稿纸上,每个人都在写着自己的历史。当你抓着今天时,你就会前进一步,当你丢弃今天时,你就会停滞不动。
一、临时构造器方式
上集中,我们利用prototype
方式实现继承,会带来一个问题:所有的属性都指向了一个相同的对象,父对象就会受到子对象属性的影响。
如何解决这个问题?
这时就必须利用某种中介来打破这种连锁关系。我们可以用一个临时的构造器函数来充当中介。即:我们创建一个空函数f()
,并将其原型设置为父级构造器,然后我们既可以用new F()
来创建一些不包含父对象属性的对象,同时又可以从父对象prototype
中继承一切了。
1.1 代码实现
function Shape(){}
Shape.prototype.name = "Shape";
Shape.prototype.toString = function () {
return this.name;
}
function TwoDShape(){}
var F = function(){};
F.prototype = Shape.prototype;
TwoDShape.prototype = new F();
TwoDShape.prototype.constructor = TwoDShape;
TwoDShape.prototype.name = "2D Shape";
function Triangle(side, height){
this.side = side;
this.height = height;
}
var F = function(){};
F.prototype = TwoDShape.prototype;
Triangle.prototype = new F();
Triangle.prototype.constructor = Triangle;
Triangle.prototype.name = "Triangle";
Triangle.prototype.getArea = function () {
return this.side * this.height / 2;
}
var my = new Triangle(5, 10);
console.log(my.getArea()); //25
console.log(my.name); //Triangle
console.log(my.toString()); //Triangle
var supers = new Shape();
console.log(supers.name); //Shape 注意
1.2 代码分析
从下图中我们可以看出my
对象的结构
那么这个时候toString()
方法查找分几步呢?
- 在本类中查找
- 在
my
对象的my.[[Prototype]]
中找,此时该[[Prototype]]
对象为TwoDShape
的实例,里面只有一个name = "Triangele"
属性。 - 在
my
对象的my.[[Prototype]].[[Prototype]]
中找,为Shape
的实例,同样里面也只有一个name="2D Shape"
属性 - 在
my.[[Prototype]].[[Prototype]].[[Prototype]]
中找,为Object
实例,此时,就已经找到了toString()
方法。
二. 增加uber属性,用于子对象访问父对象
2.1 实现分析
uber
属性的名字原本应该是“superclass”
,但是这样一来显得JavaScript
中有了类的概念(在ES6
中引入了class
),或许该叫super
,但是super
是JavaScript
的保留字,因为改成uber
。
- 我们给构造函数增加一个
uber
属性,使其值为父类的原型Triangle.uber = TwoDShape.prototype;
- 如下图所示,此时构造函数
Triangle()
中,包含了uber
属性
2.2 代码实现
function Shape(){}
Shape.prototype.name = "Shape";
Shape.prototype.toString = function () {
var result = [];
if(this.constructor.uber){
result[result.length] = this.constructor.uber.toString();
}
result[result.length] = this.name;
return result.join(', ');
}
function TwoDShape(){}
var F = function(){};
F.prototype = Shape.prototype;
TwoDShape.prototype = new F();
TwoDShape.prototype.constructor = TwoDShape;
TwoDShape.uber = Shape.prototype; //增加uber属性,指向父类的原型
TwoDShape.prototype.name = "2D Shape";
function Triangle(side, height){
this.side = side;
this.height = height;
}
var F = function(){};
F.prototype = TwoDShape.prototype;
Triangle.prototype = new F();
Triangle.prototype.constructor = Triangle;
Triangle.uber = TwoDShape.prototype;
Triangle.prototype.name = "Triangle";
Triangle.prototype.getArea = function () {
return this.side * this.height / 2;
}
var my = new Triangle(5, 10);
console.log(my.getArea()); //25
console.log(my.name); //Triangle
console.log(my.toString()); //Shape, 2D Shape, Triangle
var supers = new Shape();
console.log(supers.name); //Shape
三. 将继承封装成extend()函数
- 为了代码的封装和重用,我们可以将实现继承的部分抽取出来,定义一个
extend()
函数。
3.1 代码实现
与extend()
相比,extend2()
显得略逊一筹。因为这里执行的是子对象原型的逐一拷贝,而非简单的原型链查询。所以我们必须要记住,这种方式仅适用于只包含基本数据类型的对象,所有的对象类型(包括函数和数组)都是不可复制的,因为他们只支持引用传递。
这里toString()
方法实际上是同一个函数对象。这里只是一个函数引用,函数本身并没有再次被创建。
优缺点:
extend2()
方法的效率要低于extend()
方法,主要是前者对部分原型属性进行了重建。- 当然了,这对于只包含基本数据类型的对象来说,是有好处的。因为这样做能使属性查找操作更多的停留在对象本身,从而减少了原型链上的查找。
3.1.1 临时构造器实现extend()
//定义extend函数
function extend(Child, Parent){
var F = function () {};
F.prototype = Parent.prototype;
Child.prototype = new F();
Child.prototype.constructor = Child;
Child.uber = Parent.prototype;
}
3.1.2 原型复制实现extend2()
function extend2(Child, Parent){
var p = Parent.prototype;
var c = Child.prototype;
for (var i in p){
c[i] = p[i];
}
c.uber = p; //扩展子类原型,增加一个uber属性
}
3.2 代码测试
3.2.1 测试extend()函数
- 利用上述
extend()
函数实现继承。
//定义Shape类
function Shape(){}
Shape.prototype.name = "Shape";
Shape.prototype.toString = function () {
return this.name;
}
//定义TwoDShape类
function TwoDShape(){}
extend(TwoDShape, Shape);
TwoDShape.prototype.name = "2D Shape";
//定义Triangle类
function Triangle(side, height){
this.side = side;
this.height = height;
}
extend(Triangle, TwoDShape);
Triangle.prototype.name = "Triangle";
Triangle.prototype.getArea = function () {
return this.side * this.height / 2;
}
//测试
var my = new Triangle(5, 10);
console.log(my.getArea()); //25
console.log(my.name); //Triangle
console.log(my.toString()); // Triangle
var supers = new Shape();
console.log(supers.name); //Shape
3.2.1 测试extend2() 函数
//定义Shape类
function Shape(){}
Shape.prototype.name = "Shape";
Shape.prototype.toString = function () {
return this.name;
}
//定义TwoDShape类
function TwoDShape(){}
extend(TwoDShape, Shape);
//extend测试结果
var td = new TwoDShape();
console.log(td.name); //Shape
console.log(TwoDShape.prototype.name); //Shape
console.log(td.__proto__.name); //Shape
console.log(td.hasOwnProperty("name")); //false
console.log(td.__proto__.hasOwnProperty("name")); //false
//extend2测试结果
var td = new TwoDShape();
console.log(td.__proto__.hasOwnProperty("name")); //true
console.log(td.__proto__.hasOwnProperty("toString")); //true
console.log(td.__proto__.toString == Shape.prototype.toString); //true
四、实例对象原型继承:以实例对象为原型创建新对象
4.1 功能分析
基于这种在对象之间直接构建继承关系的理念,我们可以创建一个object()
函数,接受父对象,并返回一个以该对象为原型的新对象。这和Object.create()
类似。如下代码所示:
function object(o){
var F = function(){};
F.prototype = o;
return new F();
}
如果需要访问父类,则可加添加uber属性。
function object(o){
var n;
var F = function(){};
F.prototype = o;
n = new F();
n.uber = o;
return n;
}
4.2 功能测试
//定义Shape类
function Shape(){}
Shape.prototype.name = "Shape";
Shape.prototype.toString2 = function () {
return this.name;
}
var shapeObj = new Shape();
var twoDShape = object(shapeObj);
twoDShape.name = "HHH"
console.log(twoDShape.toString2()); //HHH
twoDShape
对象的结构如下所示