js 对象继承

2017-03-29  本文已影响0人  pauljun
/*
    约定
*/

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.剧情解析:有缺陷的寄生式继承 + 不完美的组合继承 = 完美的寄生组合式继承,不妨回去找找看哪里用到了寄生

上一篇下一篇

猜你喜欢

热点阅读