首先要明白两点:
1、非方法属性每一个子类实例须要独立
2、方法属性每一个子类实例须要共享
为何?
若是非方法属性为引用类型,且非方法属性共享,在一个实例中改变,其余实例中就会作出改变,这样每一个实例就会相互影响,而方法属性通常是不须要进行改变的,只是对方法调用。chrome
方法跟属性分别能够定义在构造函数内部跟prototype上。函数
继承的目的是子类继承父类的方法跟属性,换句话说一些类的相同的方法属性须要共享,将这些须要共享的方法属性抽取到一个地方,这就是父类。this
代码主要来自于红宝书4spa
每一个函数都有个prototype
属性,每一个对象都有__proto__
属性(在chrome中表现如此,prototype也是如此) 如图,属性的查找会从当前层级依次向原型链上查找,直到查找到原型链的顶端null,具体可参考js proto
既然属性的查找是按照原型链向上查找的,且继承就是继承父类的属性跟方法,那么就能够利用这个特性,进行继承。prototype
function SuperType() { this.property = true; } SuperType.prototype.getSuperValue = function () { return this.property; }; function SubType() { this.subproperty = false; } // 继承SuperType SubType.prototype = new SuperType(); SubType.prototype.getSubValue = function () { return this.subproperty; }; let instance = new SubType(); console.log(instance.getSuperValue()); // true 能够正确调用父类的方法,拿到父类的属性
原型虽然实现了继承,可是仍是有缺点的
劣势:code
function SuperType() { this.colors = ["red", "blue", "green"]; } function SubType() {} // 继承SuperType SubType.prototype = new SuperType(); let instance1 = new SubType(); instance1.colors.push("black"); console.log(instance1.colors); // "red,blue,green,black"; let instance2 = new SubType(); console.log(instance2.colors); // "red,blue,green,black";
为何非方法属性不写在prototype上?对象
由于prototype上的属性的共享的,在一个实例上改了该属性,其余实例的该属性也会被改掉。blog
为何方法不写在构造函数内部?继承
方法写在子类内部:每次实例化构造函数,方法都是新的;方法只是用来调用,不须要修改,因此实例共享就好了。
方法写在父类内部:不一样的子类继承父类都须要实例化父类;方法只是用来调用,不须要作修改,因此实例共享就好了,包括子类实例。若是子类须要修改父类方法,直接在子类中定义相同方法名,进行覆盖就好了。ip
为了解决父类中属性为引用类型致使子类实例化后,引用属性共享的问题,跟父类构造函数没法传参的问题。引入了“盗用构造函数“方式实现继承。思路是在子类构造函数中调用父类构造函数。
function SuperType() { this.colors = ["red", "blue", "green"]; } function SubType() { // 继承SuperType SuperType.call(this); } let instance1 = new SubType(); instance1.colors.push("black"); console.log(instance1.colors); // "red,blue,green,black"; let instance2 = new SubType(); console.log(instance2.colors); // "red,blue,green";
instance1 instance2两个实例就不会相互影响。
function SuperType(name) { this.name = name; } function SubType(name) { // 继承SuperType并传参 SuperType.call(this, name); // 实例属性 this.age = 29; } let instance = new SubType("geek"); console.log(instance.name); // "geek"; console.log(instance.age); // 29
动态传递参数到父类构造函数
劣势:
function SuperType(name) { this.name = name; } SuperType.prototype.say = function () { console.info("hello"); }; function SubType(name) { // 继承SuperType并传参 SuperType.call(this, name); // 实例属性 this.age = 29; } let instance = new SubType("geek"); console.log(instance.name); // "geek"; console.log(instance.age); // 29 instance.say() // 获取不到该函数
经过 new 实例化后,实例才能拿到prototype上的方法,a.__proto__===Animal.prototype
,因此instance.say()
不存在
每次实例化子类,都会调用父类构造函数,其内部定义的方法都是新的,占用了没必要要的内存,没有实现方法的共享。
组合继承兼顾原型链继承跟盗用构造函数的优势,这样既能够把方法定义在原型上以实现重用,又能够看让每一个实力都有本身的属性。
function SuperType(name) { this.name = name; this.colors = ["red", "blue", "green"]; } SuperType.prototype.sayName = function () { console.log(this.name); }; function SubType(name, age) { // 继承属性,绑定上下文为SubType的实例 SuperType.call(this, name); this.age = age; } // 继承方法 SubType.prototype = new SuperType(); SubType.prototype.sayAge = function () { console.log(this.age); }; let instance1 = new SubType("Nicholas", 29); instance1.colors.push("black"); console.log(instance1.colors); // "red,blue,green,black" instance1.sayName(); // "Nicholas"; instance1.sayAge(); // 29 let instance2 = new SubType("Greg", 27); console.log(instance2.colors); // "red,blue,green"; instance2.sayName(); // "Greg"; instance2.sayAge(); // 27
能够传递参数到父类构造函数
两个实例中的引用类型不会相互影响
实例能够调用父类的方法,且实现方法的共享
组合继承也保留了 instanceof 操做符和isPrototypeOf() 方法识别合成对象的能力。
劣势:
SuperType会被调用两次,SubType实例跟原型链上都有name跟colors属性。
不定义构造函数经过原型实现对象以前的继承。
function object(o) { function F() {} F.prototype = o; return new F(); }
返回新对象,让其原型指向O
let person = { name: "Nicholas", friends: ["Shelby", "Court", "Van"], }; let anotherPerson = object(person); anotherPerson.name = "Greg"; anotherPerson.friends.push("Rob"); let yetAnotherPerson = object(person); yetAnotherPerson.name = "Linda"; yetAnotherPerson.friends.push("Barbie"); console.log(person.friends); // "Shelby,Court,Van,Rob,Barbie";
父对象中的引用属性会在子对象中共享,致使相互污染。
ES5引入了Object.create()
规范了原型式继承。
let person = { name: "Nicholas", friends: ["Shelby", "Court", "Van"], }; let anotherPerson = Object.create(person); anotherPerson.name = "Greg"; anotherPerson.friends.push("Rob"); let yetAnotherPerson = Object.create(person); yetAnotherPerson.name = "Linda"; yetAnotherPerson.friends.push("Barbie"); console.log(person.friends); // "Shelby,Court,Van,Rob,Barbi
使用Object.create后anotherPerson.__proto__===person
成立,因此anotherPerson能够拿到person的属性,可是一样存在父对象属性共享的问题,改了父对象的属性,其余的子对象都跟着改变。
劣势:
父对象的引用类型会在实例中共享,这样就会相互污染。
寄生式继承跟原型式继承很相似,用工厂函数在对返回的新对象包一层,给新对象赋值一些属性
工厂函数的定义:
function createAnother(original) { let clone = object(original); // 经过调用函数建立一个新对象; clone.sayHi = function () { // 以某种方式加强这个对象; console.log("hi"); }; return clone; // 返回这个对象 }
使用:
let person = { name: "Nicholas", friends: ["Shelby", "Court", "Van"], }; let anotherPerson = createAnother(person); anotherPerson.sayHi(); // "hi"
定义在新对象上的sayHi方法,每次调用新对象都是新的,没法实现共享。
劣势:
父对象的引用类型会在实例中共享,这样就会相互污染。
方法没法实现共享
上面提到,组合继承的缺点就是父类构造函数会被调用两次,一次是在子类的构造函数中,另外一次在建立子类原型时调用。继承就是要继承父类的属性跟方法,组合继承实现了这个目标,可是怎么避免重复调用父类构造函数。
先看下组合继承:
function SuperType(name) { this.name = name; this.colors = ["red", "blue", "green"]; } SuperType.prototype.sayName = function () { console.log(this.name); }; function SubType(name, age) { SuperType.call(this, name); // 第二次调用,将父类的属性绑定到子类的实例中 SuperType(); this.age = age; } SubType.prototype = new SuperType(); // 第一次调用SuperType(); SubType.prototype.constructor = SubType; SubType.prototype.sayAge = function () { console.log(this.age); };
由图可见s的原型链上依然有name跟colors属性。这也是不须要的。怎么解决这两个问题?
父类的属性是须要的,父类的原型上的方法是须要的,重复的父类属性不须要,由上图可见重复的父类属性是因为实例化父类给子类原型形成的,咱们不去实例化父类,而是将父类的原型传递给子类的原型就好了,结合原型式继承特色能够作到
function SuperType(name) { this.name = name; this.colors = ["red", "blue", "green"]; } SuperType.prototype.sayName = function () { console.log(this.name); }; function SubType(name, age) { SuperType.call(this, name); // 将父类的属性绑定到SubType实例中 this.age = age; } SubType.prototype = Object.create(SuperType.prototype); // 将子类的prototype关联到父类的prototype上 SubType.prototype.sayAge = function () { console.log(this.age); };
使用Object.create解决了父类构造函数调用两次,父类属性重复的问题,可是子类constructor并无出如今原型链中
下面作出改造:
SubType.prototype = Object.create(SuperType.prototype, { constructor: { value: SubType, // 修正 constructor 指向 writable: true, configurable: true, }, });
SuperType的constructor出现了,其实constructor并没什么用,只是个约定罢了,参考贺老的解释JavaScript 中对象的 constructor 属性的做用是什么?instanceof操做符和 isPrototypeOf() 方法正常有效。寄生式组合继承能够算是引用类型继承的最佳模式