JS 继承
JS 继承
搬运
https://segmentfault.com/a/1190000000766541
http://www.ruanyifeng.com/blog/2011/06/designing_ideas_of_inheritance_mechanism_in_javascript.html
为什么js没有类?
截止ES5, JS并没有类的概念, 为何呢
class太正式了, 增加了初学者的难度 因此没有引入class 的概念
为什么要有prototype
function DOG(name){
this.name = name;
this.species = '犬科';
this.say = function(){ ... }
}
var dogA = new DOG('大毛');
var dogB = new DOG('二毛');
生成的每一个对象都有自己的属性和方法副本!! 方法副本!!
造成资源浪费,因此引入prototype属性
这个属性包含一个对象(以下简称"prototype对象"),所有实例对象需要共享的属性和方法,都放在这个对象里面;那些不需要共享的属性和方法,就放在构造函数里面。
实例对象一旦创建,将自动引用prototype对象的属性和方法。也就是说,实例对象的属性和方法,分成两种,一种是本地的,另一种是引用的。
原型继承在开发中经常用到。它有别于类继承是因为继承不在对象本身,而在对象的原型上(prototype)。每一个对象都有原型(字面量对象也有 只不过其__proto__
是object),在浏览器中它体现在一个隐藏的__proto__
属性上。在
Object.create()
Object.create()创建的对象, 拥有指定的原型和属性
Object.create(prototype, optionalObjects)
var rectangle = {
area : function(){
return this.width * this.height ;
}
} ;
var rect = Object.create(rectangle) ;
这里rect的原型是 rectangle
对象的继承 使用原型链
把子对象的prototype属性,指向父对象,从而使得子对象与父对象连在一起。
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
比如医生继承中国人
var Chinese = {
nation:'中国'
};
var doctor ={
career:'医生'
}
var doctor = object(Chinese);
alert(doctor.nation); //中国
对象继承 使用deepcopy
function deepCopy(p, c) {
var c = c || {};
for (var i in p) {
if (typeof p[i] === 'object') {
c[i] = (p[i].constructor === Array) ? [] : {};
deepCopy(p[i], c[i]);
} else {
c[i] = p[i];
}
}
return c;
}
var Doctor = deepCopy(Chinese);
经典的额原型继承
var Father = function(name) {
this.name = name;
}
Father.prototype.a = function() {
console.log(this.name)
}
var Child = function(name){
this.name = name;
}
//开始继承
Child.prototype = new Father();
var man = new Child('huhu');
man.a();
借用构造函数的继承
原型继承不是挺好的么 为什么还要有一个构造函数的继承呢?
最大的弊病, 就是父类构造函数中的东东 , 子类还要再写一遍, 很明显不符合逻辑
所以借用父类构造函数
Father.apply(this, arguments); 这样就好啦
var Father = function(name, age) {
this.name = name;
this.age = age;
}
Father.prototype.a = function() {
console.log(this.name + ' ' +this.age)
}
var Child = function(name, age, hehe) {
Father.apply(this, arguments);
this.hehe = hehe;
}
Child.prototype = new Father();
var man = new Child('huhu',22,'hehe');
man.a();
console.log(man.hehe);
另外需要注意的是此刻man.constructor == Father 而不是Children
因为Child.prototype = xxx 的方式改了整个prototype的指向
所以还要再加上一个 Child.prototype.constructor = Child;
看起来挺完美了 然而父类的构造函数被调用了两次
第一次是 Child.prototype = new Father(); 第二次Child的构造函数又call了父类构造函数
所以肯定不能用 Child.prototype = new Father() 了
临时构造函数
使用临时的构造函数 避免了父类构造函数被调用2次的问题
var Father = function(name, age) {
this.name = name;
this.age = age;
}
Father.prototype.a = function() {
console.log(this.name + ' ' +this.age)
}
var Child = function(name, age, hehe) {
Father.apply(this, arguments);
this.hehe = hehe;
}
var Temp = function(){}
Temp.prototype = Father.prototype;
Child.prototype = new Temp();
var man = new Child('huhu',22,'hehe');
man.a();
前面不是提到了Object.create嘛 他可以创建指定原型的对象
也可以避免父类函数被多次调用
var Father = function(name, age) {
console.log('father constructor');
this.name = name;
this.age = age;
}
Father.prototype.a = function() {
console.log(this.name + ' ' +this.age)
}
Father.prototype.obj = {b:1};
var Child = function(name, age, hehe) {
Father.apply(this, arguments);
this.hehe = hehe;
}
Child.prototype = Object.create(Father.prototype);
Child.constructor = Child;
var man = new Child('huhu',22,'hehe');
Child.prototype = Father.prototype 有什么问题
var Father = function(name, age) {
console.log('father constructor');
this.name = name;
this.age = age;
}
Father.prototype.a = function() {
console.log(this.name + ' ' +this.age)
}
Father.prototype.obj = {b:1};
var Child = function(name, age, hehe) {
Father.apply(this, arguments);
this.hehe = hehe;
}
Child.prototype = Father.prototype;
Child.constructor = Child;
var man = new Child('huhu',22,'hehe');
Child.prototype.a = function(){
console.log(this.name + ' ' +this.age + ' '+ this.hehe);
}
new Father('hehe', 44).a(); //hehe 44 undefined //父类的a方法被改了
//Child.prototype = Father.prototype; 导致任何对Child原型上的改动都会影响到Father
所以前面提到的经典原型继承 借用构造函数 Object.create() 就没有问题了吗?
注意上面那句话 导致任何对Child原型上的改动都会影响到Father
是 "任何" 也就是说经典原型等上述三种在某些情况下 也会影响到父类的原型
比如
var Father = function(name, age) {
// console.log('father constructor');
this.name = name;
this.age = age;
}
Father.prototype.a = function() {
console.log(this.name + ' ' +this.age)
}
Father.prototype.obj = {b:1};
var Child = function(name, age, hehe) {
Father.apply(this, arguments);
this.hehe = hehe;
}
Child.prototype = Object.create(Father.prototype);
Child.constructor = Child;
var man = new Child('huhu',22,'hehe');
//这样是肯定不会影响到父类原型的 不论是不是糟糕的原型直接赋值都不会
//因为下面的做法不过是给man对象增加了一个a属性
// man.a = function(){
// console.log(this.name + ' ' +this.age + ' '+ this.hehe);
// }
// man.a();
//这种当然也不会影响
Child.prototype.a = function(){
console.log(this.name + ' ' +this.age + ' '+ this.hehe);
}
man.a();
new Father('hehe',55).a();
// 这种也不会 因为等于给man对象增加一个属性
// 你也可以认为man的obj这个指针指向了另一个值
// man.obj = {b:2}; console.log(new Father().obj.b);//1
// 影响到父类的原型 因为obj指向的就是 父类原型中的obj对象 我试图修改了这个obj中的值
// man.obj.b = 2;
// console.log(new Father().obj.b);//2 被改了
Child.prototype.obj.b = 2;
console.log(new Father().obj.b);//2 被改了
也就是说稍微对子类的原型做了一些稍微深层的改动 父类的原型就会被影响
只有说子类的原型和父类原型彻底的不存在指向关系, 才有可能解决
Copy继承
function Animal(){}
Animal.prototype.species = "动物";
function extend2(Child, Parent) {
var p = Parent.prototype;
var c = Child.prototype;
for (var i in p) {
c[i] = p[i];
}
c.uber = p;
}
extend2(Cat, Animal);
var cat1 = new Cat("大毛","黄色");
alert(cat1.species); // 动物
当然这个copy还不是很完善, 毕竟深层次的copy就不行了
于是用一个深度clone来处理吧
var Father = function(name, age) {
this.name = name;
this.age = age;
}
Father.prototype.a = function() {
console.log(this.name + ' ' +this.age)
}
var Child = function(name, age, hehe) {
Father.apply(this, arguments);
this.hehe = hehe;
}
var Temp = function(){}
Child.prototype = deepClone(Father.prototype);
var f = new Father('heh',55);
var man = new Child('huhu',22,'hehe');
man.a = function(){
console.log(this.name + ' ' +this.age + ' '+ this.hehe);
}
f.a();
man.a();
// man.obj.b = 2; console.log(new Father().obj.b);// 仍是1
function deepClone(origin) {
var isObj = function(o) {
return Object.prototype.toString.call(o) == '[object Object]';
}
var isArr = function(o) {
// return [].toString.call(o) == '[object Array]';// 数组的toString本身就被Array改写了
return Object.prototype.toString.call(o) == '[object Array]'
}
function clone(origin) {
var rs = isArr(origin) ? [] : {};
for (key in origin) {
if (isObj(origin[key]) || isArr(origin[key])) {
rs[key] = clone(origin[key]);
} else {
if (origin.hasOwnProperty(key)) {
rs[key] = origin[key];
}
}
}
return rs;
}
return clone(origin);
}