讲js继承之前,想一想什么是继承? 生活中有很多例子,比方说继承财产,继承女朋友的前男友的前女友 ヽ(ー_ー)ノ ,这些和js继承差不多,但是有一个不一样的地方,就是继承过后,原先的人就没有了,js继承过后原先还有原先的属性。
最简单的继承 : 原型链继承(我最简单,也重要,也常见,不要因为简单看不起我,我很傲娇的 o(´^`)o)
原型链继承抓住了一个重要核心: 我指向的构造函数的实例就能访问原型的方法(没错,不需要理由谁叫我是你爸爸,我的方法给你用 ( ̄ェ ̄;)
(爸爸更新的技术儿子享受就行了,爸爸添加的方法给儿子用)
function Wife( ){ this.say : function(){ console.log( ' 今天你也很美,比月亮还美 ' ) } } // 生一个儿子呗 var son = new Wife() // 找到老公并且给孩子他爹发明一个方法fn,有了这个方法可以说Hello了 Wife.prototype.fn = function() { cosole.log('Hello') } son.fn() // 'Hello'
(幸福生活没过多久,老公出轨,老婆和老公离婚,老婆不想找另一个男的,厉害了自己造一个,有鼻子有眼的,让他的心一直指向老婆constructor)
function Wife( ){ this.say : function(){ console.log( ' 今天你也很美,比月亮还美 ' ) } } // 生一个儿子呗 var son = new Wife() // 新造一个老公 Wife.prototype = { fn : function(){ console.log('老婆你最美') }, say : '老婆最棒', // 把心永远向着老婆 constructor : Wife } son.fn() // '老婆你最美'
吸功大法:混入式继承 + 原型链继承 (在原型上定义一个混入式继承的方法)
function Wife(){ name : ' 仙女 ', age : 20 } Wife.prototype.extend(obj){ for( var key in obj ){ if( obj.hasOwnProperty(key) ){ this[ key ] = obj [ key ] } } } var wugong1 = { 'qiankun': '乾坤大挪移' } var wugong2 = { 'xuanming': '玄冥神掌' } var son = new Wife(); son.extend(wugong1) son.extend(wugong2) console.log(son) // 添加了wugong1,wugong2,this指向发生了改变,指向了son这个实例对象,所以是son添加了,而不是原型添加了wugong1,wugong2
(为什么不说混入继承,混入继承就是遍历一个对象,把对象的属性一个一个赋给另一个对象,有需求的话注意深浅拷贝)
构造函数借用 (缺点 : 方法必须也在构造函数里才能借用而借用过来的方法也在构造函数里,方法最好在原型上)
function Animal(name){ this.name = name; this.try = function (){ console.log('叫声') } } function Cat(){ Animal.call(this,name); // 自己的属性 this.age = 3; } var cat = new Cat(); console.log(cat) // 有自己的age属性 有继承构造函数的name ,try方法
既然上述方法有方法继承的缺陷,那么不妨试试组合继承(结合原型继承【处理方法继承】 + 构造函数借用【处理属性继承】,缺陷是执行两次超类构造函数)
// 组合继承 简单的说通过借用在构造函数里继承属性、用被继承的实例指向继承的原型来继承方法 // 组合继承不能继承被继承原型里的方法,也就是方法要写在构造函数里 function Animal(name) { this.name = name; this.color = '我是毛色'; } function Cat(name) {
// new的时候执行一次超类构造函数 Animal.call(this, name); //只继承属性 this.cry = '喵'; }
// 又执行一次超类(被继承类)构造函数 Cat.prototype = new Animal(); // 把被继承的构造函数实例对象充当继承构造函数的原型,根据属性查找原则,不用担心获取的和原型的冲突 Cat.prototype.constructor = Cat; // 因为原型覆盖了,所以要加上constructor Cat.prototype.action = function() { // 可以在原型上新建方法 console.log('猫步'); }; var cat = new Cat('波斯'); console.log(cat);// Cat {name: "波斯", color: "我是毛色", cry: "喵"} console.log(cat.__proto__);// Animal {name: undefined, color: "我是毛色", constructor: Cat, action: ƒ}
道格拉斯(可不是尼古拉斯【红宝书作者】)的原型式继承【从原型直接到实例唯一的不足就是属性不能从原型上继承,因为不同实例不好改属性值,只适合继承方法】
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <title></title> <style></style> </head> <body> <script> var obj = { name: 'zs', friends: ['云云', '雪雪', '雨雨'] }; function object(o) { function F() {} F.prototype = o; // 直接就不管构造函数与原型的联系,直接由原型继承 return new F(); } var example1 = object(obj); console.log(example1.friends.push('薇薇')); // 此举会改变obj这个对象,因为F.prototype = o是浅拷贝,导致以后实例受影响,这不算缺点 var example2 = object(obj); console.log(example2.friends); // ['云云','雪雪','雨雨','薇薇'] </script> </body> </html>
最骚的继承 : 经典继承 var newObj = Object.create( obj ) newObj是obj.contructor的实例,不是构造函数,也就是直接__proto__逆操作,骚的一P(其实就是原型继承的强化版)
Es5规范了上述的原型继承,给object取名create,并挂到Object上,唯一的不同之处在于还有第二个可选参数是个对象{ 属性名 : { value : 属性值 } },当属性名不存在添加,已经存在原型上则为覆盖。【话说浅拷贝的问题依然存在】
寄生继承(原型继承基础上用另一个函数把原型继承的实例增强后返回出去,通过增加的方法还不是在原型上,所以复用性不高)
var obj = { name: 'zs', friends: ['云云', '雪雪', '雨雨'] }; function object(o) { function F() {} F.prototype = o; return new F(); } function Parasitic(o) { var newExample = object(o); // 增强这个实例 newExample.sayHi = function() { console.log('sayHi'); }; return newExample; } console.log(new Parasitic(obj));
最牛的继承 : 寄生组合继承
function object(o){ function Fn(){} Fn.prototype = o; return new Fn() } function SuperType(name){ this.name = name; this.colors = ['red','green','blue'] } SuperType.prototype.sayHi()= function() { console.log('我是超类'); } function SubType(name,age){ SuperType.call(this,name) this.age = age; } function ParaComponent(SubType,SuperType){ var p = object(SuperType.prototype); p.consructor = SubType; SubType.prototype = p; } ParaComponent(SubType,SuperType); SubType.prototype.sayAge = function(){ console.log('sayAge'); }