js 对象继承
/*
约定
*/
function Fun(){
//私有属性
var val = 1; //私有基本属性
var arr = [1]; //私有引入属性
function fun(){}; //私有函数(引用属性)
//实例属性
this.val = 1; //实例基本属性
this.arr = [1]; //实例引用属性
this.fun = function(){}; //实例函数(引用属性);
};
//原型属性
Fun.prototype.val = "jack"; //原型基本属性
Fun.prototype.arr = [1]; //原型引用属性
Fun.prototype.fun = function(){}; //原型函数(引用属性)
js变量可分为两部分,基本类型和引用类型。
基本类型:基本类型比较简单,包括Undefined,Null,Boolean,Number,String,基本类型就是简单的数据段;
引用类型:引用类型值可能由多个值构成,引用类型保存在内存中,而js是不能直接访问内存的,所以对于引用类型,操作的不是实际的对象,而是对对象的引用。
一、简单原型链
function Super(){
this.val = 1;
this.arr = [1];
};
function Sub(){
};
Sub.prototype = new Super();
var Sub1 = new Sub();
var Sub2 = new Sun();
Sub1.val = 5;
Sub1.arr.push(2);
alert(Sub1.val); //5
alert(Sub2.val); //1
alert(Sub1.arr); //1,2
alert(Sub2.arr); //1,2
//可以看到,修改sub1.arr后sub2.arr也变了,因为来自原型对象的引用属性是所有实例共享的。
如果不懂,可查看js基本类型与引用类型详解
二、借用构造函数
function gou(val){
console.log(typeof val);
this.val = val;
this.arr = [1];
this.fun = function(){
};
};
function fun(val){
gou.call(this,val);
};
var fun1 = new fun(1);
var fun2 = new fun(2);
fun1.arr.push(2);
console.log(fun1.val); //1
console.log(fun2.val); //2
console.log(fun1.arr); //1,2
console.log(fun2.arr); //1
alert(sub1.fun === sub2.fun); // false
借父类的构造函数来增强子类实例,等于是把父类的实例属性复制了一份给子类实例装上了(完全没有用到原型)
缺点:
无法实现函数复用,每个子类实例都持有一个新的fun函数,太多了就会影响性能,内存爆炸。。
P.S.好吧,刚修复了共享引用属性的问题,又出现了这个新问题。
三、组合继承(最常用)
function zuhe(){
this.val = 1;
this.arr = [1];
};
zuhe.prototype.fun1 = function(){};
zuhe.prototype.fun2 = function(){};
function newSub(){
//通过call函数继承父类的基本数据和引用数据。
zuhe.call(this); //核心
};
newSub.prototype = new zuhe();
var zh1 = new newSub();
var zh2 = new newSub();
zh1.val = 2;
zh1.arr.push(2);
console.log(zh1); //{val:2,arr:[1,2]}
console.log(zh2); //{val:1,arr:[1]}
console.log(zh1.fun1 === zh2.fun1); //true
把实例函数都放在原型对象上,以实现函数复用。同时还要保留借用构造函数方式的优点
四、寄生组合继承
组合继承是js最常用的继承模式,组合继承最大的问题就是无论在什么情况下,都会调用两次构造函数:一次是在创建子类型原型时,另一次是在子类型构造函数内部。
function SuperType(name){
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
alert(this.name);
}
function SubType(name, age){
SuperType.call(this, name); //第二次调用SuperType()
this.age = age;
}
SubType.prototype = new SuperType(); //第一次调用SuperType()
SubType.prototype.sayAge = function(){
alert(this.age);
}
在第一次调用SuperType构造函数时,SubType.prototype会得到两个属性: name和colors; 他们都是SuperType的实例属性,只不过现在位于SubType的原型中。
当调用SubType构造函数时,又会调用一次SuperType构造函数,这一次又在新对象上创建了实例属性name和colors。
于是这两个属性就屏蔽了原型中的两个同名属性。
寄生组合式继承就是为了解决这一问题。
function inheritPrototype (subType,superType){
var protoType = Object.create(superType.prototype); //创建对象
protoType.constructor = subType; //增强对象
subType.prototype = protoType; //指定对象
};
function SuperType(name){
this.name = name;
this.colors = ["red","blue","green"];
};
SuperType.prototype.sayName = function(){
alert(this.name);
};
function SubType(name,age){
SuperType.call(this,name);
this.age = age;
};
inheritPrototype(SubType,SuperType);
SubType.prototype.sayAge = function(){
alert(this.age);
};
var instance = new SubType("jack",28);
instance.sayName(); //jack
instance.sayAge(); //28
## 方法二
function beget(obj){ // 生孩子函数 beget:龙beget龙,凤beget凤。
var F = function(){};
F.prototype = obj;
return new F();
}
function Super(){
// 只在此处声明基本属性和引用属性
this.val = 1;
this.arr = [1];
}
// 在此处声明函数
Super.prototype.fun1 = function(){};
Super.prototype.fun2 = function(){};
//Super.prototype.fun3...
function Sub(){
Super.call(this); // 核心
// ...
}
var proto = beget(Super.prototype); // 核心
proto.constructor = Sub; // 核心
Sub.prototype = proto; // 核心
var sub = new Sub();
alert(sub.val);
alert(sub.arr);
五、原型式
function beget(obj){
var F = function(){};
F.prototype = obj;
return new F();
};
function Super (){
this.val = 1;
this.arr = [1];
};
//拿到父类对象
var sup = new Super ();
//生孩子
var sub = beget(sup);
//增强
sub.attr1 = 1;
sub.attr2 = 2;
console.log(sub);
console.log(sub.attr1);
console.log(sub.arr);
优点:
从已有对象衍生新对象,不需要创建自定义类型(更像是对象复制,而不是继承。。)
缺点:
原型引用属性会被所有实例共享,因为是用整个父类对象来充当了子类原型对象,所以这个缺陷无可避免无法实现代码复用(新对象是现取的,属性是现添的,都没用函数封装,怎么复用)
P.S.这东西和继承有很大关系吗?为什么尼古拉斯把它也列为实现继承的一种方式?关系不大,但有一定关系
六、寄生式
寄生式是一种模式,并不是只能用来继承
function beget(obj){ // 生孩子函数 beget:龙beget龙,凤beget凤。
var F = function(){};
F.prototype = obj;
return new F();
}
function Super(){
this.val = 1;
this.arr = [1];
}
function getSubObject(obj){
// 创建新对象
var clone = beget(obj); // 核心
// 增强
clone.attr1 = 1;
clone.attr2 = 2;
//clone.attr3...
return clone;
}
var sub = getSubObject(new Super());
alert(sub.val); // 1
alert(sub.arr); // 1
alert(sub.attr1); // 1
注意:beget函数并不是必须的,换言之,创建新对象 -> 增强 -> 返回该对象,这样的过程叫寄生式继承,新对象是如何创建的并不重要(用beget生的,new出来的,字面量现做的。。都可以)
优点:
还是不需要创建自定义类型
缺点:
无法实现函数复用(没用到原型,当然不行)
P.S.剧情解析:有缺陷的寄生式继承 + 不完美的组合继承 = 完美的寄生组合式继承,不妨回去找找看哪里用到了寄生