您的位置:首页 > Web前端 > JavaScript

js继承详解-值类型、引用类型

2017-08-01 20:21 465 查看
原型继承

原型继承是js中最通用的继承方式,不用实例化对象,通过直接定义对象,并被其他对象引用,这样形成的一种继承关系,其中引用对象被称为原型对象。

优点:从instanceof关键字来看,实例既是父类的实例,又是子类的实例,看起来似乎是最纯粹的继承。

缺点:例如:Sub.prototype.name = “sub”;无法实现多重继承。

function A(){
this.color = 'red';
this.name = ['1','2'];
}
function B(){

}
B.prototype=new A();

var a = new A();
var b1 = new B();
var b2 = new B();

//color值类型的继承
a.color = "a";
b1.color = 'b1';
b2.color = 'b2'

console.log("原型继承");
console.log(a.color);  //a
console.log(b1.color);  //b1
console.log(a.color);  //a
console.log(b2.color);  //b2

//引用类型继承  赋给的是内存中的地址
console.log(b1.name);  //["1", "2"]
b1.name.push('b1');
console.log(b1.name);  //["1", "2", "b1"]
console.log(b2.name);  // ["1", "2", "b1"]

b2.name.push('b2');
console.log(b1.name);  // ["1", "2", "b1", "b2"]
console.log(b2.name);  //  ["1", "2", "b1", "b2"]

b2.name = ['1','2','b2']   //直接等于是可以更改不影响另一个
console.log(b1.name); // ["1", "2", "b1", "b2"]
console.log(b2.name);  // ["1", "2", "b2"]


构造继承: 解决原型链的缺点

优点:可以实现多重继承,可以把子类特有的属性设置放在构造器内部。

缺点:使用instanceof发现,对象不是父类的实例。

这种继承方式,所有的属性和方法都要在构造函数中定义,比如我们这里也要绑定之前的sayA()方法并继承,

就只能写在A的构造函数里面,而写在A prototype的的方法,没法通过这种方式继承,

而把所有的属性和方法都要在构造函数中定义的话,就不能对函数方法进行复用.

function A() {
this.name = "a"
this.color = ['red','green'];
}
function B(){
//“借用”|就体现在这里,子类型B借用了父类型A的构造函数,从而在这里实现了继承
A.call(this);
}

//生成两个个B的实例
var b1 = new B();
var b2 = new B();
//观察color属性
console.log(b1.name)//a
console.log(b2.name)//a
console.log(b1.color)//['red','green']
console.log(b2.color)//['red','green']
//改变b1的name和color属性
b1.name = 'b'
b1.color.push('black')

//重新观察属性
console.log(b1.name)//b
console.log(b2.name)//a
console.log(b1.color)//['red','green','black']
console.log(b2.color)//["red", "green"]


在这里我们没有采用原型链,而是利用call()方法来实现在子类型的构造函数中借用父类型的构造函数,完成了继承,

这样继承的结果就是:

b1,b2都分别拥有自己的name和color属性(可以直接console.log(b1)查看对象的属性),

也就是b1和b2完全独立的。这就解决了之前的第一个问题,而且传递参数的问题其实也可以解决,再稍微改一下A函数:

//这里name改成传递参数的
function A(name) {
this.name = name
this.color = ['red','green'];
}
function B(name){
//在这里我们接受一个参数,并且通过call方法传递到A的构造函数中
A.call(this,name);
}

//生成两个个B的实例
var b1 = new B('Mike');
var b2 = new B('Bob');
//观察属性
console.log(b1.name)//Mike
console.log(b2.name)//Bob
console.log(b1.color)//['red','green']
console.log(b2.color)//['red','green']


组合继承

学习了原型链的继承和借用构造函数的继承后,我们可以发现,这两种方法的优缺点刚好互补:

原型链继承可以把方法定义在原型上,从而复用方法

借用构造函数继承法可以解决引用类型值的继承问题和传递参数问题

function A(name){
this.name = name;
this.color = ['green','yellow'];
}
A.prototype.sayA = function (){
console.log('from A');
}
function B(name,age){
A.call(this,name);
this.age = age;
}
B.prototype = new A();
B.prototype.sayB = function(){
console.log('from B');
}

var b1 = new B('xiamin',21);
var b2 = new B('xiaoming',20);

console.log(b1); //{name: "xiamin", color: Array(2), age: 21}
console.log(b2); //{name: "xiaoming", color: Array(2), age: 20}
b1.sayA(); //from A
b2.sayA(); //from A
b2.sayB(); //from B


最终实现的效果就是,b1和b2都有各自的属性,同时方法都定义在两个原型对象上,

这就达到了我们的目的:

属性独立,方法复用,这种继承的理解相对简单,因为就是把前两种继承方式简单的结合一下,

原型链负责原型对象上的方法,call借用构造函数负责让子类型拥有各自的属性。

组合继承是js中最常用的继承方式

原型式继承

原型式继承与之前的继承方式不太相同,原理上相当于对对象进行一次浅复制,

浅复制简单的说就是:把父对像的属性,全部拷贝给子对象。但是我们前面说到,由于引用类型值的赋值特点,

所以属性如果是引用类型的值,拷贝过去的也仅仅是个指针,拷贝完后父子对象的指针是指向同一个引用类型的原型式继承目前可以通过Object.create()

方式来实现,

Object.create()接收两个参数:

第一个参数是作为新对象的原型的对象

第二个参数是定义为新对象增加额外属性的对象(这个是可选属性)

var A = {
name:'A',
color:['red','green']
}
var B = Object.create(A);
B.name = 'B';
B.color.push('yellow');

console.log(A.name);
console.log(B.name);
console.log(A.color);
console.log(B.color);
// 在这个例子中,我们只传入第一个参数,所以B和C都是对A浅复制的结果,
// 由于name是值类型的,color是引用类型的,所以ABC的name值独立,color属性指向同一个对象。接下来举个传递两个参数的例子:
var A = {
name:'A',
color:['red','green'],
sayA:function(){
console.log('from A');
}
};
var B = Object.create(A,{
name:{
value:'B'
}
});
console.log(B);
B.sayA();


寄生式继承

寄生式继承和原型继承联系紧密,思路类似于工厂模式,即创建一个只负责封装继承过程的函数,在函数中根据需要增强对象,最后返回对象

function createA(name){
var obj = Object(name);
obj.say = function(){
console.log('from 0');
}
return obj;
}

var A ={
name:'A',
color:['red','green'],
}
var B = createA(A);
console.log(B);
B.say();


寄生组合式继承

我们在之前说过,最常用的继承方式就是组合继承,但是看似完美的组合继承依然有缺点:

子类型会两次调用父类型的构造函数,一次是在子类型的构造函数里,另一次是在实现原型链的步骤

function A(name) {
this.name = name
this.color = ['red','green'];
}
A.prototype.sayA = function(){
console.log("form A")
}
function B(name,age){
//第二次调用了A
A.call(this,name);
this.age = age;
}

//我们一直默认A是父类型,B是子类型
function inheritPrototype(B,A){
//复制一个A的原型对象
var pro  = Object(A.prototype);
//改写这个原型对象的constructor指针指向B
pro.constructor = B;
//改写B的prototype指针指向这个原型对象
B.prototype = pro;
}
//第一次调用了A
//  B.prototype = new A();    B是A的实例,new时候会把属性继承
inheritPrototype(B,A);

B.prototype.sayB = function(){
console.log("form B")
}

var b1 = new B('Mike',12);
var b2 = new B('Bob',13);
console.log(b1.name);
//里面没有name了
console.log(B.prototype)//{sayA: function, sayB: function, constructor: function}


这个函数很简短,只有三行,函数内部发生的事情是:我们复制一个A的原型对象,

然后把这个原型对象替换掉B的原型对象。为什么说这样就代替了 B.prototype = new A();,

不妨思考一下,我们最初为什么要把B的prototype属性指向A的一个实例?无非就是想得到A的prototype的一个复制品,

然后实现原型链。而现在我们这样的做法,同样达到了我们的母的目的,而且,此时B的原型对象上不会再有A的属性了,

因为它不是A的实例。因此,只要把将上面的 B.prototype = new A();,替换成inheritPrototype(B,A),就完成了寄生组合式继承。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息