在上边博文,简单讲了对象和函数中的几个属性,总结如下
prototype | Js中函数特有一个属性,指向这个函数的原型对象 |
_proto_ | Js中对象特有的一个属性,指向对象的原型对象 |
constructor | Js对象特有的一个属性,指向这个对象的构造函数,每个对象都会有构造函数,只不过函数对象的构造函数是其自身 |
同时我们也了解,Javascript是通过new 构造函数创建对象
function Shape() {
this.name = 'Shape';
this.toString = function () {
return this.name;
};
}
var aShape = new Shape() ;
有了上边几个概念,设计师就可以通过这些概念组合,形成Js基于原型的继承方式,接下来,由浅及深,从个人角度,去考虑,设计师如何考虑继承
原型链是通过 _proto_、 prototype 、constructor实现的 , 因此想要修改继承关系,通过修改构造函数的原型对象,就会让修改后的构造函数,new 创建的对象,满足继承关系
比如下边是三个构造函数 ,它们此时是没有任何关系的,接下来,我们通过修改 原型对象,让他们形成继承关系
// 构造器函数
function Shape() {
this.name = 'Shape';
this.toString = function () {
return this.name;
};
}
function TwoDShape() {
this.name = '2D shape';
}
function Triangle(side, height) {
this.name = 'Triangle';
this.side = side;
this.height = height;
this.getArea = function () {
return this.side * this.height / 2;
};
}
方式一 : 原型指向 “父亲的对象”
-
让儿子的构造函数原型指向的是,用父亲构造函数 新创建的对象,这样的有点是,修改父亲的原型,不会影响子类。语法如下
TwoDShape.prototype = new Shape(); TwoDShape.prototype.constructor = TwoDShape
具体代码如下
TwoDShape.prototype = new Shape(); TwoDShape.prototype.constructor = TwoDShape Triangle.prototype = new TwoDShape(); Triangle.prototype.constructor = Triangle
测试一下 , TwoDShape 创建的对象name属性是不是 自己,删除自己的name属性之后,能否获取到父亲的name属性,输出结果 证明了 两者存在父子关系
var tds = new TwoDShape() console.log(tds.name) // 输出 2D shape delete tds.name console.log(tds.name) // 输出 Shape
在看一下 tds的 _proto_ 指向的是不是 创建的 New Shape()
console.log(tds.__proto__) //输出 : Shape {name: "Shape", toString: ƒ, constructor: ƒ} // 说明 TwoDShape 确实 实现了 "继承" 这一功能 // 测试 原型是否是新建对象的原型 console.log(TwoDShape.prototype.isPrototypeOf(tds)) // true console.log(Shape.prototype.isPrototypeOf(tds)) // true
-
实现继承之后, 以调用toString()方法为例,看Js引擎如何查找一个对象的属性(方法)
var my = new Triangle(5, 10); console.log(my.toString()) //Triangle
- 它会遍历 my 对象中的所有属性,但没有找到
- 去查看 my._proto_所指向的对象 (该对象应该是在继承关系构建过程中由 new TwoDShape()所创建的实体) , 但没有找到
- 继续检查该实体的_proto_属性。 (该_proto_属性所指向的实体是由 new Shape()所创建的) , 成功找到
- 调用方法(方法中this 绑定的是调用此方法的对象)
方式二 : 只继承原型
-
方式一的优缺点
继承的是父亲构造函数创建的一个对象,这样的设计,修改父亲的原型时,不会影响子类。
var par = function (){this.name = 'par',this.pro = 1} var son = function(){this.age = 11;this.name = 'son'} son.prototype = new par() son.prototype.constructor = son var s1 = new son() console.log(s1.pro) ; //输出 1
如果父亲的原型也有一个 pro属性,修改了父亲原型,发现并不会影响儿子
par.prototype.pro = 2 console.log(s1.pro) ; //输出 1
当然 和接下来方式一笔,缺点就是 原型指向的是一个new的对象,一是消耗大,二是要找父亲原型中的属性,需要多找一层对象
-
继承方式二 : 直接继承原型的方式
TwoDShape.prototype = Shape.prototype; TwoDShape.prototype.constructor = TwoDShape;
测试代码 :
TwoDShape.prototype.name = '2D shape'; // 测试代码 var tds = new TwoDShape(); tds.toString(); //输出 2D shape
上边代码优势:
执行 tds.toString() 时, 先去找 tds对象,没找到 ; 再去找该对象的原型属性(指向Shape的原型) ;因为都是引用传递,所以速度块。
另一个方面,可以满足这么一种需求 , 子对象对原型做修改,同时父对象也会被修改Shape.prototype.ps = '111'; TwoDShape.prototype = Shape.prototype // 本质上只有一个 原型对象 TwoDShape.prototype.ps = '2D shape'; var s = new Shape(); console.log(s.ps) // 输出 2D shape
方式三: 创建临时构造器
-
方式一和方式二的问题
方式一 继承的是父亲构造函数创建的对象,方式二是直接继承父亲的对象。方式一效率低,方式二修改父亲的原型会影响子类,有副作用
-
创建的临时构造器
创建一个临时构造函数,让这构造函数指向父亲的原型,然后继承的是这个临时的构造函数
var F = function() {}; F.prototype = Shape.prototype; TwoDShape.prototype = new F(); TwoDShape.prototype.constructor = TwoDShape var F = function(){}; F.prototype = TwoDShape.prototype; Triangle.prototype = new F(); Triangle.prototype.constructor = Triangle;
优化 : 将继承的代码放到原型之中
-
让我们能通过以下简单的调用来实现继承
function extend(Child, Parent) { // 创建临时函数, 直接继承原型,儿子的原型通过new临时函数, var F = function(){}; F.prototype = Parent.prototype; Child.prototype = new F(); Child.prototype.constructor = Child; Child.uber = Parent.prototype; }
上边三种方式是通过修改儿子的原型,让儿子构造函数的原型对象能够连接到父亲,接下来还有另外的方式,直接copy属性
属性之间copy
-
在构建可重用的继承代码时,我们也可以简单地将父对象的属性拷贝给子对象
-
属性copy 实现代码复用
function extend2(Child, Parent) { var p = Parent.prototype; var c = Child.prototype; for (var i in p) { c[i] = p[i]; } // 子对象需要访问父对象的方法,我们可以通过设置 uber 属性来实现 c.uber = p; }
- 子对象需要访问父对象的方法,我们可以通过设置 uber 属性来实现
var Shape = function(){}; var TwoDShape = function(){}; Shape.prototype.name = 'shape'; Shape.prototype.toString = function(){ return this.uber ? this.uber.toString() + ', ' + this.name : this.name; };
深copy
-
深拷贝的实现方式
与浅拷贝基本相同,也需要通过遍历对象的属性来进行拷贝操作。只是在遇到一个对象引用性的属性时,我们需要再次对其调用深拷贝函数
function deepCopy(p, c) { c = c || {}; for (vari in p) { if (p.hasOwnProperty(i)) { if (typeof p[i] === 'object') { c[i] = Array.isArray(p[i]) ? [] : {}; deepCopy(p[i], c[i]); } else { c[i] = p[i]; } } } return c; }
-
在拷贝每个属性之前,建议使用 hasOwnProperty()来确认不会误拷贝不需要
的继承属性 -
由于区分 Array 对象和普通 Object 对象相当繁琐,所以 ES5 标准中实现了
Array.isArray()函数
子类访问父类属性 : 类似 java 中的 super
-
下边代码中 在构建原型继承关系时,指定一个自定义属性 uber 指向父的原型
-
在调用方法内 (toString () )中 ,使用 uber属性
function Shape(){} // augment prototype Shape.prototype.name = 'shape'; Shape.prototype.toString = function(){ var const = this.constructor; return const.uber ? this.const.uber.toString() + ', ' + this.name : this.name; }; function TwoDShape(){} // take care of inheritance var F = function(){}; F.prototype = Shape.prototype; TwoDShape.prototype = new F(); TwoDShape.prototype.constructor = TwoDShape; TwoDShape.uber = Shape.prototype; // augment prototype TwoDShape.prototype.name = '2D shape'; function Triangle(side, height) { this.side = side; this.height = height; }
Object()
- 可以用object()函数来接收父对象,并返回一个以该对象为原型的新对象
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
- 如果需要访问 uber属性,可以继续object() 函数
function object(o) {
var n;
function F() {}
F.prototype = o;
n = new F();
n.uber = o;
return n;
}
原型继承与属性copy混合应用
具体而言就是:
-
使用原型继承的方式,将一个已有对象设置为新对象的原型。
-
新建一个对象后,将另一个已有对象的所有属性拷贝过来
// 其中对象 o 用于继承,而另一个对象 stuff 则用于拷贝方法与属性 function objectPlus(o, stuff) { var n; function F() {} F.prototype = o; n = new F(); n.uber = o; for (var i in stuff) { n[i] = stuff[i]; } return n; }
-
测试代码
var shape = {
name: 'shape',
toString: function() {
return this.name;
}
};
var twoDee = objectPlus(shape, {
name: '2D shape',
toString: function(){
return this.uber.toString() + ', ' + this.name;
}
});
var triangle = objectPlus(twoDee, {
name: 'Triangle',
getArea: function(){
return this.side * this.height / 2;
},
side: 0,
height: 0
});
var my = objectPlus(triangle, {
side: 4, height: 4
});
my.getArea()); // 8
console.log(my.toString()); // "shape, 2D shape, Triangle, Triangle"
多重继承
-
多重继承,通常指的是一个子对象中有不止一个父对象的继承模式
-
注意的是, multi()中的循环是按照对象的输入顺序来进行遍历的。如果其中两个
对象拥有相同的属性,前一个就会被后一个覆盖
function multi() {
var n = [],
stuff, j = 0,
len = arguments.length;
for (j = 0; j < len; j++) {
stuff = arguments[i];
for (var i in stuff) {
n[i] = stuff[i];
}
}
return n;
}
- 用到了混合插入的理念
混合插入( mixins)的技术。我们可以将其看做一种为对象提供某些实用功能的技术,每当我们新建一个对象时,可以选择将其他对象的内容混合到我们的新对象中去