继承是为了复用对象。下面是几种继承方法:
-
原型链继承
function A(name) { this.name = name; this.city = ['Guangzhou', 'Shenzhen']; }; A.prototype.getName = function () { return this.name }; function B(sex) { this.sex = sex; } B.prototype = new A('Bobo'); var c = new B('male'); c.getName(); // Bobo // 缺陷 c.city.push('Shanghai'); var d = new B('jojo'); d.city; // [ 'Guangzhou', 'Shenzhen', 'Shanghai' ]优势:可以函数共享。
缺陷:当上一个实例修改了原型中的
引用类型的值,会影响到下一个实例。 -
构造函数继承
function A (name) { this.city = ['Guangzhou', 'Shenzhen']; this.name = name; } A.prototype.getName = function() { return this.name; }; function B(name, sex) { A.call(this, name); this.sex = sex; } var c = new B('coco', 'male'); // c.getName(); // 无法共享方法 c.name; // coco c.sex; // male c.city.push('Shanghai'); var d = new B('jojo', 'female'); d.city; // [ 'Guangzhou', 'Shenzhen' ]为什么叫构造函数继承呢,是因为对于新new对象(比如例子的c)来说,B就是他的构造函数,在B内进行继承,所以叫构造函数继承。
构造函数继承,就是在子类函数里面使用
call,apply来改变this指向,让它指向父类,改变上下文环境。由于父类方法是挂载在prototype所以子类无法获取到挂载的方法。优势:属性是实例借助构造函数自己生成的,所以各个实例的属性是各自独立的。
缺陷:无法共享方法。
-
结合继承
function A (name) { this.city = ['Guangzhou', 'Shenzhen']; this.name = name; } A.prototype.getName = function() { return this.name; }; function B(name, sex) { A.call(this, name); this.sex = sex; } B.prototype = new A(); // A原型上的属性其实是没有必要的 var c = new B('dodo', 'male'); c.getName(); // dodo c.city.push('Shanghai'); var d = new B('eoeo', 'female'); d.city; // [ 'Guangzhou', 'Shenzhen' ]显而易见地,结合继承结合了原型继承和构造函数继承的优点。
缺陷:子类原型上有一份多余的父类实例属性,父类构造函数被调用了2次,生成了2份,子类实例上的那一份屏蔽了子类原型上的。
-
寄生结合继承(圣杯模式)
// 写法1 function inherit(c, p) { function f() {} f.prototype = p.prototype; c.prototype = new f(); c.prototype.constructor = c; // uber是超类,储存这个目标是继承于谁,可写可不写 c.prototype.uber = p.prototype; } // 写法2 var inherit2 = (function(c, p){ var F = function(){}; return function(c, p) { F.prototype = p.prototype; c.prototype = new F(); c.uber = p.prototype; c.prototype.constructor = c; } })(); function A (name) { this.city = ['Guangzhou', 'Shenzhen']; this.name = name; } A.prototype.getName = function() { return this.name; }; function B(name, sex) { this.sex = sex; this.name = name; } inheirt(B, A); var c = new B('fofo', 'male'); c.name; // fofo c.sex; // male // c.city; // 没有这个属性 c.getName(); // fofo创建一个中间对象f,将父类的
prototype指向中间对象f的prototype;然后子类实现f的原型继承,再矫正子类的constructor,这样不会造成原型链的混乱。从而隔开了子类修改原型对父类造成的影响。但是无法继承父类的属性(比如父类的city属性);关键点在于
__proto__===constructor.prototype的理解。对象会循着__proto__向上寻找属性,如果自身没有的话。这样当A.prototype = new B()的时候,var c = new A()当c执行一个方法或者查找一个属性没有找到的时候,c.__proto__ -> A.prototype(new B()) -> A.prototype.__proto__ -> B.prototype -> ... -> null; 所以这样是可以实现原型链查找,这也是继承的体现之处。