接着下来,我们看一下继承的实现。上面就说过,只要prototype有什么东西,它的实例就有什么东西,不论这个属性是后来添加的,还是这整个prototype都是置换上去的。如果我们将这个prototype对象置换为另一个类的原型,那么它就轻而易举得到那个类的所有原型成员。
function A(){}
A.prototype = {
aaa:1
}
function B(){}
B.prototype = A.prototype;
var b= new B;
console.log(b.aaa);//1;
A.prototype.bbb = 2;
console.log(b.bbb);//2;
但由于是引用着相同的一个对象,意味着,如果我们修改A类的原型,也等同于修改了B类的原型。因此我们不能把一个对象赋给两个类,这有两种办法。方法一是通过for in把父类的原型成员逐一赋给子类的原型。方法二是,子类的原型不是直接由父类获得,先将此父类的原型赋给一个函数,然后将这个函数的实例作为子类的原型。
方法一,我们通常要实现mixin这样的方法,比如Prototype.js的extend方法:
function extend(destination, source) {
for(var property in source)
destination[property] = source[property];
return destination;
}
方法二,则是需要如下代码:
A.prototype = {
aa: function() {
alert(1)
}
}
function bridge() {};
bridge.prototype = A.prototype;
function B() {}
B.prototype = new bridge();
var a = new A;
var b = new B;
//false,说明成功分开它们的原型
console.log(A.prototype == B.prototype);
//true,子类共享父类的原型方法
console.log(a.aa === b.aa);
//为父类动态添加新的原型方法
A.prototype.bb = function() {
alert(2)
}
//true,孩子总会得到父亲的遗产
console.log(a.bb === b.bb);
B.prototype.cc = function() {
alert(3)
}
//false,但父亲未必有机会看到孩子的新产业
console.log(a.cc === b.cc);
//并且它能正常通过JS自带验证机制————instanceof
console.log(b instanceof A);//true
console.log(b instanceof B);//true
并且也只有方法二能通过instanceof 验证。不过现在es5就内置了这种方法来实现原型继续,它就是Object.create,如果不考虑第二个参数,它约等于下面代码:
Object.create = function (o) {
function F() {}
F.prototype = o;
return new F();
}
上面方法,要求传入一个父类的原型作为参数,然后返回子类的原型。
不过,这样我们还是遗漏了一点东西——子类不只是继承父类的遗产,还拥有自己的东西,此外,原型继承并没有让子类继承父类的类成员与特权成员。这些我们还得手动添加,比如类成员,我们可以通过上面的extend方法,特权成员我们可以在子类的构造器中,通过apply实现,即:
function inherit(init, Parent, proto){
function Son(){
Parent.apply(this,argument);//先继承父类的特权成员
init.apply(this,argument);//再执行自己的构造器
}
//由于Object.create可能是我们伪造的,因此避免使用第二个参数
Son.prototype = Object.create(Parent.prototype,{});
Son.prototype.toString = Parent.prototype.toString;//处理IE BUG
Son.prototype.valueOf = Parent.prototype.valueOf;//处理IE BUG
Son.prototype.constructor = Son;//确保构造器正常指向自身,而不是Object
extend(Son.prototype, proto);//添加子类特有的原型成员
extend(Son, Parent);//继承父类的类成员
return Son;
}
下面我们做一组实验,测试一下实例的回溯机制。许多资料都说,但总是语焉不详——当我们访问对象的一个属性,那么它先找其特权成员,如果有同名的就返回,没有找原型,再没有找父类的原型……我们尝试把它的原型临时修改一下,看它的属性会变成哪一个!
function A() {}
A.prototype = {
aa: 1
}
var a = new A;
console.log( a.aa);//1
//把它整个原型对象都换掉
A.prototype = {
aa: 2
}
console.log(a.aa);//1,不受影响
//于是我们想到实例都有一个constructor方法,指向其构造器,
//而构造器上面正好有我们的原型,javascript引擎是不是通过该路线回溯属性呢
function B(){}
B.prototype = {
aa: 3
}
a.constructor = B;
console.log( a.aa );//1 不受影响
因此类的实例肯定通过另一条通道进行回溯,翻看esma规范可知每一个对象都有一个内部属性[[Prototype]],它保存着当我们new它时构造器所引用的prototype对象。除IE外的浏览器都暴露了一个叫__proto__属性来访问它。因此只要不动__proto__,上面的代码怎么动,a.aa始终坚定不移的返回1。
我们再来看一下new操作时发生了什么事。
创建一个空对象instance
instance.__proto__ = instanceClass.prototype
将构造器函数里面的this = instance
执行构造器里面的代码
判定有没有返回值,没有返回值默认为undefined,如果返回值为复合数据类型,则直接返回,否则返回this。
于是有下面结果:
function A() {
console.log(this.__proto__.aa); //1
this.aa = 2
}
A.prototype = {
aa: 1
}
var a = new A;
console.log(a.aa); //2
a.__proto__ = {
aa: 3
}
delete a.aa; //删掉特权属性,暴露原型链上的同名属性
console.log(a.aa); //3
有了__proto__,我们可以将原型继承设计得更简洁。我们还是拿上面的例子改一下进行实验
function A() {}
A.prototype = {
aa: 1
}
function bridge() {};
bridge.prototype = A.prototype;
function B() {}
B.prototype = new bridge();
B.prototype.constructor = B;
var b = new B;
B.prototype.cc = function() {
alert(3)
}
console.log(b.__proto__ == B.prototype);
//true 这个大家应该都没有疑问
console.log(b.__proto__.__proto__ === A.prototype);
//true 样就得到父类的原型对象
为什么呢?因为b.__proto__.constructor为B,而B的原型是从bridge中得来的,而bridge.prototype = A.prototype。反过来,我们在定义时,让B.prototype.__proto__ = A.prototype,就轻松实现两个类的继承。
有了__proto__,我们可以将原型继承设计得更简洁。我们还是拿上面的例子改一下进行实验:
function A() {}
A.prototype = {
aa: 1
}
function B() {}
B.prototype.__proto__ = A.prototype;
B.prototype.constructor = B;
var b = new B;
B.prototype.bb = 2;
for(var i in B.prototype){
console.log(i)
}
// aa, bb
function A(){}
A.prototype = {
aaa:1
}
function B(){}
B.prototype = A.prototype;
var b= new B;
console.log(b.aaa);//1;
A.prototype.bbb = 2;
console.log(b.bbb);//2;
但由于是引用着相同的一个对象,意味着,如果我们修改A类的原型,也等同于修改了B类的原型。因此我们不能把一个对象赋给两个类,这有两种办法。方法一是通过for in把父类的原型成员逐一赋给子类的原型。方法二是,子类的原型不是直接由父类获得,先将此父类的原型赋给一个函数,然后将这个函数的实例作为子类的原型。
方法一,我们通常要实现mixin这样的方法,比如Prototype.js的extend方法:
function extend(destination, source) {
for(var property in source)
destination[property] = source[property];
return destination;
}
方法二,则是需要如下代码:
A.prototype = {
aa: function() {
alert(1)
}
}
function bridge() {};
bridge.prototype = A.prototype;
function B() {}
B.prototype = new bridge();
var a = new A;
var b = new B;
//false,说明成功分开它们的原型
console.log(A.prototype == B.prototype);
//true,子类共享父类的原型方法
console.log(a.aa === b.aa);
//为父类动态添加新的原型方法
A.prototype.bb = function() {
alert(2)
}
//true,孩子总会得到父亲的遗产
console.log(a.bb === b.bb);
B.prototype.cc = function() {
alert(3)
}
//false,但父亲未必有机会看到孩子的新产业
console.log(a.cc === b.cc);
//并且它能正常通过JS自带验证机制————instanceof
console.log(b instanceof A);//true
console.log(b instanceof B);//true
并且也只有方法二能通过instanceof 验证。不过现在es5就内置了这种方法来实现原型继续,它就是Object.create,如果不考虑第二个参数,它约等于下面代码:
Object.create = function (o) {
function F() {}
F.prototype = o;
return new F();
}
上面方法,要求传入一个父类的原型作为参数,然后返回子类的原型。
不过,这样我们还是遗漏了一点东西——子类不只是继承父类的遗产,还拥有自己的东西,此外,原型继承并没有让子类继承父类的类成员与特权成员。这些我们还得手动添加,比如类成员,我们可以通过上面的extend方法,特权成员我们可以在子类的构造器中,通过apply实现,即:
function inherit(init, Parent, proto){
function Son(){
Parent.apply(this,argument);//先继承父类的特权成员
init.apply(this,argument);//再执行自己的构造器
}
//由于Object.create可能是我们伪造的,因此避免使用第二个参数
Son.prototype = Object.create(Parent.prototype,{});
Son.prototype.toString = Parent.prototype.toString;//处理IE BUG
Son.prototype.valueOf = Parent.prototype.valueOf;//处理IE BUG
Son.prototype.constructor = Son;//确保构造器正常指向自身,而不是Object
extend(Son.prototype, proto);//添加子类特有的原型成员
extend(Son, Parent);//继承父类的类成员
return Son;
}
下面我们做一组实验,测试一下实例的回溯机制。许多资料都说,但总是语焉不详——当我们访问对象的一个属性,那么它先找其特权成员,如果有同名的就返回,没有找原型,再没有找父类的原型……我们尝试把它的原型临时修改一下,看它的属性会变成哪一个!
function A() {}
A.prototype = {
aa: 1
}
var a = new A;
console.log( a.aa);//1
//把它整个原型对象都换掉
A.prototype = {
aa: 2
}
console.log(a.aa);//1,不受影响
//于是我们想到实例都有一个constructor方法,指向其构造器,
//而构造器上面正好有我们的原型,javascript引擎是不是通过该路线回溯属性呢
function B(){}
B.prototype = {
aa: 3
}
a.constructor = B;
console.log( a.aa );//1 不受影响
因此类的实例肯定通过另一条通道进行回溯,翻看esma规范可知每一个对象都有一个内部属性[[Prototype]],它保存着当我们new它时构造器所引用的prototype对象。除IE外的浏览器都暴露了一个叫__proto__属性来访问它。因此只要不动__proto__,上面的代码怎么动,a.aa始终坚定不移的返回1。
我们再来看一下new操作时发生了什么事。
创建一个空对象instance
instance.__proto__ = instanceClass.prototype
将构造器函数里面的this = instance
执行构造器里面的代码
判定有没有返回值,没有返回值默认为undefined,如果返回值为复合数据类型,则直接返回,否则返回this。
于是有下面结果:
function A() {
console.log(this.__proto__.aa); //1
this.aa = 2
}
A.prototype = {
aa: 1
}
var a = new A;
console.log(a.aa); //2
a.__proto__ = {
aa: 3
}
delete a.aa; //删掉特权属性,暴露原型链上的同名属性
console.log(a.aa); //3
有了__proto__,我们可以将原型继承设计得更简洁。我们还是拿上面的例子改一下进行实验
function A() {}
A.prototype = {
aa: 1
}
function bridge() {};
bridge.prototype = A.prototype;
function B() {}
B.prototype = new bridge();
B.prototype.constructor = B;
var b = new B;
B.prototype.cc = function() {
alert(3)
}
console.log(b.__proto__ == B.prototype);
//true 这个大家应该都没有疑问
console.log(b.__proto__.__proto__ === A.prototype);
//true 样就得到父类的原型对象
为什么呢?因为b.__proto__.constructor为B,而B的原型是从bridge中得来的,而bridge.prototype = A.prototype。反过来,我们在定义时,让B.prototype.__proto__ = A.prototype,就轻松实现两个类的继承。
有了__proto__,我们可以将原型继承设计得更简洁。我们还是拿上面的例子改一下进行实验:
function A() {}
A.prototype = {
aa: 1
}
function B() {}
B.prototype.__proto__ = A.prototype;
B.prototype.constructor = B;
var b = new B;
B.prototype.bb = 2;
for(var i in B.prototype){
console.log(i)
}
// aa, bb