每一个构造函数都有prototype属性 (是属性),这个prototype属性会指向一个对象,这个对象就是原型,然后每一个原型对象都会有一个constructor属性,指向它的构造函数。通常来说Person.prototype.constructor=Person。
原型链:每一个对象都有一个__proto__(dunder)属性,(obj.proto==Function.prototype),实例对象的__proto__属性所指向的对象,等于其构造函数的prototype属性所指向的对象,这个对象就是原型,原型上的属性和方法被实例所共享。当我们访问一个对象的属性或方法时,如果这个对象没有,就会顺着原型链向上查找。
一、构造函数继承
重点:用.call()和.apply()将父类构造函数引入子类函数(在子类函数中做了父类函数的自执行)
特点:1、只继承了父类构造函数的属性,没有继承父类原型的属性。
2、解决了原型链继承缺点1、2、3。
3、可以继承多个构造函数属性(call多个)。
4、在子实例中可向父实例传参。
缺点:1、只能继承父类构造函数的属性,不能继承父类原型上的属性。
2、因为不能继承父类原型链上的方法,所有要继承的函数只能定义在父类构造函数上,无法实现构造函数的复用。(每次 用每次都要重新调用)
3、每个新实例都有父类构造函数的副本,臃肿。
function person (name,age) {
this.name = name
this.age = age
this.test =function(){ console.log('test')}
}
person.prototype.sayholle = function () {
console.log(this.name+' holle'+ this.age)
}
function child (sex,name,age) {
this.sex = sex
person.call(this,name,age)
}
let p1 = new child('woman','rose','13')
let p2 = new child('man','jack','13')
console.log(p1.test===p2.test) //false 这是函数方法不能复用的意思
二、原型链继承
让子类构造函数的prototype指向父类的实例对象
function Parent2() {
this.name = 'parent2';
this.play = [1, 2, 3];
}
function Child2(age) {
this.age = age;
}
Child2.prototype = new Parent2();//new Child2().__proto__===Child2.prototype
var s1 = new Child2();
var s2 = new Child2();
console.log(s1.play, s2.play);//(3) [1, 2, 3] (3) [1, 2, 3]
s1.play.push(4);
console.log(s1.play, s2.play);//(4) [1, 2, 3, 4] (4) [1, 2, 3, 4]
特点:1、实例可继承的属性有:子类、父类构造函数的属性,子类、父类原型的属性。
缺点:1、新实例无法向父类构造函数传参。
2、不能多继承。
3、所有新实例都会共享父类构造函数中的属性,一个实例修改了属性,其他实例的属性也会改变。(但是子类构造函数中的属性是独立的)
三、组合继承
function Person (name,age) {
this.name = name
this.age = age
}
Person.prototype.sayholle = function () {
console.log(this.name, this.age)
}
function Child (sex,name,age) {
this.sex = sex
Person.call(this,name,age)
}
Child.prototype = new Person();
//或者Child.prototype = Object.create(Person.prototype),这两条语句的效果是一样的,都使得 Child.prototype.__proto__==Person.prototype 为true
/* 重新设置一下constructor 不设置也没有影响,严谨的角度上来说还是设置一下*/
/* 不设置的话,__proto__ 上时没有 constructor */
/* 正常来讲constructor是指向自身的 */
child.prototype.constructor = child;
let p1 = new child('man','czklove','13')
let p2 = new child('man','czklove1','16')
p1.sayholle(); // czklove 13
特点:组合继承,既能达到对父类属性的继承,也能继承父类原型上的方法,父类属性继承也不会在所有子类的实列上共享。
唯一缺点,子类原型上有父类构造函数的属性,也就是多了一份属性
寄生组合继承
寄生组合继承是,让子类型的原型对象等于父类型的原型对象的副本。优点:解决了组合继承的缺点,避免了在subtype.prototype上创建多余的属性(也就是组合继承中,父类构造器中的属性,不会被添加到原型上),与此同时,原型链还能保持不变,因此,寄生组合式继承被认为是引用类型最理想的继承范式。
function Parent(name) {
this.name = name
}
Parent.prototype.sayName = function() {
console.log('my name is ' + this.name)
return this.name
}
function Child(name, age) {
Parent.call(this, name)
this.age = age
}
var prototype = Object(Parent.prototype)
prototype.constructor = Child
Child.prototype= prototype
var child = new Child('Child', 18)
ES6类继承与ES5继承的区别?
ES6使用语法 class B extends A继承,ES6类继承是ES5函数继承的语法糖,把ES6的继承通过Babel转义成ES5之后,发现它是通过object.create方法实现了(1)B.proto = A。表示子类B继承父类A的静态属性。通过Object.setPropertypeOf()方法实现(2)B.prototype.proto = A.prototype 。这与组合继承相同。 (一) ES6继承同时存在两条继承链。ES5是没有第一条原型链的。
(二) ES6的子类必须在constructor方法中调用super()方法,super()代表调用父类的构造函数,因为子类没有自己的this对象,所以子类要先继承父类的this。但ES5 的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面(Parent.apply(this))。
(三) ES6 可以自定义原生数据结构(比如Array、String等)的子类,这是 ES5 无法做到的。
class A {
}
class B {
}
// B 的实例继承 A 的实例
Object.setPrototypeOf(B.prototype, A.prototype);
// B 继承 A 的静态属性
Object.setPrototypeOf(B, A);
const b = new B();
Object.setPrototypeOf = function (obj, proto) {
obj.__proto__ = proto;
return obj;
}
Object.setPrototypeOf(B.prototype, A.prototype);
// 等同于
B.prototype.__proto__ = A.prototype;
Object.setPrototypeOf(B, A);
// 等同于
B.__proto__ = A;