继承
1、原型链
概念
构造函数或构造器具有prototype属性,对象具有__proto__属性,这就是之前学习的原型。
如果构造函数或对象A,A的原型指向构造函数或对象B,B的原型再指向构造函数或对象C,以此尖推,最终的构造函数或对象的原型指向Object的原型。由此形成一条链状结构,被称之为原型链。
按照上述的描述,在B中定义的属性或方法,可以直接在A中使用并不需要定义。这就是继承,它允许每个对象来访问其原型链上的任何属性或方法。
原型链是ECMAScript标准中指定的默认实现继承的方式。
//1、定义构造函数A function A(){ this.a = 'a'; } // 通过构造函数方式创建对象a var a = new A(); console.log(a.a)//a //2、定义构造函数B function B(){ this.b = 'b'; } // 将B原型指向对象a B.prototype = a; //通过构造函数方式创建对象b var b = new B(); console.log(b.b)//b console.log(b.a)//a //3、定义构造函数C function C(){ this.c = 'c'; } // 将C的原型指向对象b C.prototype = b; // 通过构造函数方式创建对象c var c = new C(); console.log(c.c)//c console.log(c.b)//b console.log(c.a)//a
解析图如下:
2、只继承于原型
出于对效率的考虑,尽可能地将属性和方法添加到原型上。可以采取以下方式:
不要为继承关系单独创建新对象。
尽量减少运行时的方法搜索。
示例代码如下:
function A() {}; // 将自有属性改为原型属性 A.prototype.a='a' function B() {}; // 将B的原型指向A的原型 B.prototype = A.prototype; // 为对象B使用原型新增属性b B.prototype.b='b'; function C() { this.c='c'; }; // 将C的原型指向B的原型 C.prototype = B.prototype; var c=new C(); console.log(c.c) console.log(c.b) console.log(c.a)
原型链的问题
原型链虽然很强大,用它可以实现JavaScript中的继承,但同时也存在着一些问题。
原型链实际上是在多个构造函数或对象之间共享属性和方法
创建子类的对象时,不能向父级的构造函数传递任何参数。
综上所述,在实际开发中很少会单独使用原型链。
function A() {}; // 将自有属性改为原型属性 A.prototype.a='a' function B() {}; // 将B的原型指向A的原型 B.prototype = A.prototype; // 为对象B使用原型新增属性b B.prototype.b='b'; function C() {}; // 将C的原型指向B的原型 C.prototype = B.prototype; C.prototype.c = 'c'; // 实现多个构造函数原型链之间的属性共享 var c=new C(); console.log(c.c) console.log(c.b) console.log(c.a) var b = new B(); console.log(b.c) console.log(b.b) console.log(b.a) var a= new A(); console.log(a.c) console.log(a.b) console.log(a.a)
3、原型式继承(1)
所谓原型式继承,就是定义一个函数,该函数中创建一个临时性的构造函数,将作为参数传入的对象作为这个构造函数的原型,最后返回这个构造函数的实例对象。
根据原型式继承所总结的object()函数实现继承,如下代码示例:
/* 定义一个函数 - 用于实现对象之间的继承 * 参数 * obj: 表示继承关系中的父级对象 * prop : 对象格式,表示继承关系中的子级对象的属性和方法 */ function fn(obj,prop) { // 定义一个临时的 构造函数 function Fun() { // 遍历对象的属性和方法 for (var attrName in prop){ this[attrName] = prop[attrName]; } } // 将函数的参数作为构造函数的原型 Fun.prototype = obj; // 将构造函数创建的对象进行返回 return new Fun(); } var obj = { name : '猪猪侠' } // 调用函数 var result = fn(obj,{ age:18, sayMe : function () { console.log('我叫猪猪侠'); } }); console.log(result.age); result.sayMe();
注:这种原型式继承要求必须具有一个对象可以作为另一个对象的基础。
原型式继承(2)
也可以利用Obiect 的create()方法替代自定义的object()函数,从而实现规范化。
// 利用Object.create()方法实现继承 var obj = { name:'猪猪侠' } var newobj = Object.create(obj,{ age : { value:18 }, sayMe : { value : function () { console.log('我叫猪猪侠'); } } }); console.log(newobj.age);//18 newobj.sayMe();//我叫猪猪侠
4、借助构造函数方式实现继承
无论是原型链还是原型式继承,都具有相同的问题。想要解决这样的问题的话,可以借助构造函数(也可以叫做伪造对象或经典继承)。
这种方式实现非常简单,就是在子对象的构造函数中调用父对象的构造函数。具体可以通过调用apply()和call()方法实现。
apply()和call()方法都允许传递指定某个对象的this。对于继承来讲,可以实现在子对象的构造函数中调用父对象的构造函数时,将子对象的this和父对象的this绑定在一起。
示例代码如下:
// 定义父级对象的构造函数 function Parent() { this.parent = 'parent'; } // 定义子级对象的构造函数 function Child(){ // 调用父级对象的构造函数 -> 使用apply()方法或call()方法 Parent.call(this); this.child = 'child'; } // 创建子级对象 var child = new Child(); console.log(child);
上述示例代码 分析如下:
5、组合方式继承
组合继承,也叫做伪经典继承,指的是将原型链或原型式继承和借助构造函数的技术组合在一起,发挥二者长处的一种继承方式。
具体实现的思路就是:
使用原型链或原型式继承实现对原型的属性和方法的继承。
通过借助构造函数实现对实例对象的属性的继承。
这样,既通过在原型上定义方法实现了函数的重用,又可以保证每个对象都有自己的专有属性。
示例代码如下所示:
function Parent(){ // 构造函数的自有属性 this.name = '猪猪侠' } // 构造函数的原型属性 Parent.prototype.age = 20; function Child() { // 继承父级构造函数的自有属性 Parent.call(this); this.job = '主角'; } // 继承父级构造函数中的原型属性 Child.prototype = Parent.prototype; var child = new Child(); // 打印子级上的job console.log(child.job);//主角 // 打印父级上的age console.log(child.age);//20 // 打印父级上的name console.log(child.name)//猪猪侠