继承父类:
// 父类
function Person(name) {
this.name = name
this.show = function() {
console.log('名字', this.name)
}
}
Person.prototype.age = 20
var p = new Person('aron')
一、原型链继承
// 原型链继承
function Son(name) {
this.name = name
}
// 子级(构造函数)元素的原型与父级(构造函数)原型相对
Son.prototype = new Person() // 重点代码
var s1 = new Son('张三')
console.log(s1 instanceof Person) // true
var s2 = new Son('王五')
console.log('子级', s1.age) // 20
console.log('子级', s1.show) // 父级的show函数
// 当Son.prototype = new Person('李四'),new Son()不传参时,打印undefined,
// 因为自己本身有name属性,但没赋值,继承时本身有该属性不会去父级原型中找,只有本身没有该属性才会去父级原型中找
// 实际的寻找过程是:s1(son的实例,相当于son的儿子)——————Son构造函数——————Person(父级)
console.log('子级', s1.name) // 张三
// 从下面测试可知,继承是指继承了父级的属性,当子级的该属性没有属性值时,会继承父级的属性和值,当子级有自己的值时,不会继承父级的值,
// 父级的实例修改自己的值不会影响子级的值,只有父级修改自己原型的值才会对子级有影响,当子级自己没有值时,会去父级的原型取最初始的值
p.age = 40
s2.age = 15
console.log('p', p.age) // 40
console.log(s2.age) // 15
实现思路:让子级构造函数的原型等于父级构造函数原型,因为我们知道new Person的内部过程就是把Person的原型赋值给其他对象
特点:子级的实例可继承的有:子级构造函数的属性,父级构造函数的属性,父级原型内的属性,另外,子级的实例与父级的实例是没有关系的,虽然这两者可能继承了父级同样的属性,但是修改本身属性不会影响对方,只有父级修改原型的值才会对子级有影响(就好比说,虽然你和你兄弟都继承了你父亲的黑头发,但是你兄弟去染个黄头发,是对你父亲和对你都没有任何关系的,改变的只是他自己,只有你父亲的基因库(原型对象)的属性发生了变化,才会对子级有影响,因为子级的属性都是从他那继承而来的)
缺点:1.子级的实例无法向父级传参(因为自己的实例是用子级的构造函数生成的,只能向子级构造函数传参)
2.会共享父级构造函数和原型里面的属性和方法,所以一修改父级构造函数和原型的值(Person.prototype.age =60,前提是子级的实例没有自己赋值,如:p.age = 40),子级都会受影响
上面说的有四个对象,分别是:父级构造函数、父级实例、子级构造函数、子级实例,他们之间的关系如下:
二、借用构造函数继承
// 借用构造函数继承
function Son2() {
// 重点代码
Person.call(this, 'james')
this.head = 'big'
}
var ss3 = new Son2()
console.log('ss3', ss3.age) // undefined
console.log('ss3', ss3.show) // function show
console.log('ss3', ss3.name) // james
console.log('ss3', ss3 instanceof Person) // false
实现思路:拷贝父级的作用域,把构造函数的属性复制到子类
特点:只继承父级构造函数的属性,不继承父级原型的属性(和原型没有关系),解决了向父级传参的问题(可以向父级传参)
缺点:只继承了父级构造函数的属性,每个子级实例都有父级构造函数的副本,代码内部臃肿
三、组合式继承(构造函数+原型链)
还有一种常用方法是结合了原型链继承和构造函数继承,如下:
// 组合式继承(构造函数+原型链)
function Son3(name) {
// 构造函数继承,继承父级构造函数的属性,解决传参问题
Person.call(this, name)
}
// 原型链继承,继承父级构造函数和原型中的属性,解决复用问题
Son3.prototype = new Person()
var ss4 = new Son3('小明')
console.log('——————————————')
console.log('ss4', ss4.name) // 小明
console.log('ss4', ss4.age) // 20
console.log('ss4', ss4.show) // function show
console.log('ss4', ss4 instanceof Person) // true
实现思路:既使用原型链继承也使用构造函数继承
特点:可以传参,复用,既可以继承原型属性,也可以继承构造函数中的属性
缺点:调用两次父类构造函数,耗内存
四、寄生组合式继承
上面三种是最基本的三种继承,其实还有很多基于以上三种的变种写法,如这种寄生式继承
// 寄生组合式(寄生+构造函数:寄生是在原型链基础之上外部加了一个函数包裹,)
// 寄生写法,box实际上是子级实例的另外一种写法,找一个宿主,写在宿主里面
// 本质是原型链继承
function box(obj) {
function A() {}
A.prototype = obj
return new A()
}
// 创建子级实例(ss5实际上是P的实例,继承了传入对象obj (Person的原型)的属性)
var ss5 = box(Person.prototype)
// 构造函数方式继承Person的构造函数属性
function Son4(name) {
Person.call(this, name)
}
// 重点部分:Son4继承了ss5实例
Son4.prototype = ss5
ss5.constructor = Son4
var ss6 = new Son4('LiLi')
console.log('ss6', ss6.name) // LiLi(继承的构造函数属性)
console.log('ss6', ss6.age) // 20(继承的原型上的属性)
console.log('ss6', ss6 instanceof Person) // true