继承是获取已经存在的对象已有属性和方法的一种方式,ECMAScript 依靠原型连实现继承。
就近继承原则
function F(name) {
this.name = name;
this.toString = function() {
console.log('我是构造函数上的方法')
}
}
F.prototype.toString = function() {
console.log('我是构造函数原型上的方法')
};
var fun = new F()
fun.toString() // 我是构造函数上的方法
对象的方法去访问属性时,先查找有没有对应的实例属性,有则使用;
若没有,就去该对象的原型对象上查找,有则使用;
若没有,就接着去原型对象的原型对象上查找,有则使用;
若没有,就继续沿原型链查找;
直到搜索到 Object.prototype
为止,如果还是没有找到就返回 undefined
或者报错。
可以通过删除实例属性的方式,打破就近原则,去访问原型链上的属性。
function Box() {}
Box.prototype.name = 'lee'
var box1 = new Box()
box1.name = 'jack'
alert(box1.name) // jack
delete box1.name
alert(box1.name) // lee
继承实现方式
-
原型链继承
核心就是让子类的原型为父类的一个实例对象。// 父类 function Person() { this.name = 'lxx' this.friends = ['aaa','bbb','ccc'] } Person.prototype.run = function() { consolee.log(this.name + ' running~') } // 子类 function Student() { this.sno = 110 } // 实现继承 Student.prototype = new Person() var stu1 = new Student() stu1.sno // 110 stu1.friends // ['aaa','bbb','ccc'] stu1.run() // lxx running~ stu1.friends.push('ddd') var stu2 = new Student() stu2.sno // 110 stu2.friends // ['aaa','bbb','ccc','ddd'] stu2.run() // lxx running~
缺点:父类所有引用类型属性被所有子类共享,修改一个子类的引用属性,其他子类也会受影响;子类在实例化时不能给父类构造函数传参;继承的属性打印看不到。
-
盗用构造函数继承
在子类构造函数中调用父类构造函数,使用call()
和apply()
方法以新创建的对象为上下文执行构造函数。// 父类 function Person(name,firends) { this.name = name; this.friends = friends this.run = function() { console.log(this.name + ' running~') } } Person.prototype.eating = function() { console.log(this.name + ' eating~') } // 子类 function Student(name,friends,sno) { Person.call(this, name, friends) this.sno = sno } var stu1 = new Student('lxx',['aaa','bbb'],1) stu1 // {name: 'lxx', friends: Array(2), sno: 1, run: ƒ} stu1.run() // lxx running~ stu1.friends.push('ddd') stu1.friends // ['aaa','bbb','ddd'] stu1.eating() // stu1.eating is not a function var stu2 = new Student('xxx',['ccc'],2) stu2 // {name: 'xxx', friends: Array(1), sno: 2, run: ƒ} stu2.run() // xxx running~ stu2.friends // ['ccc'] stu2.eating() // stu2.eating is not a function stu1.run === stu2.run // false
优点:可以在子类构造函数中向父类构造函数传参;父类的引用类型属性不会共享。 缺点:必须在构造函数中定义方法,且方法不能重用;子类不能访问父类原型上的方法。
-
组合式继承
原型链继承+盗用构造函数继承。基本思路是使用原型链继承方式继承原型上的属性和方法,而通过盗用构造函数方式继承实例属性。// 父类 function Person(name,friends) { this.name = name this.friends = friends } Person.prototype.run = function (){ console.log(this.name + ' running~') } // 子类 function Student(name,friends,sno) { // 通过盗用父类构造函数构造属性,this是Student的实例对象 Person.call(this, name, friends) // 第一次调用父类构造函数 this.sno = sno } // 通过原型链继承父类方法 Student.prototype = new Person() // 第二次调用父类构造函数 // 修复子类 constructor 指向 Student.prototype.constructor = Student var stu1 = new Student('lxx',['aaa','bbb','ccc'],1) stu1.name // lxx stu1.friends.push('ddd') stu1.friends // ["aaa", "bbb", "ccc", "ddd"] var stu2 = new Student('xxx',['ddd'],2) stu2.name // xxx stu2.friends // ["ddd"]
💣 盗用父类构造函数构造属性时,要把
call
放置到最前面,因为父类构造函数和子类构造函数如果发生属性重名,父会覆盖子。💣 为什么明明是
new Student()
出来的实例,结果constructor
确指向Person
?因为 Student() 中盗用了构造函数 Person(),所以要修复指向。实例的方法继承自原型对象上的同一个方法,实现了方法的重用。
stu1.run === stu2.run // true
缺点:调用多次父类构造函数,第一次在子类构造函数内部,第二次在创建子类构造函数原型对象时;子类实例的原型对象上会多出一些没有必要的属性。
-
寄生式继承
var personObj = { running() { console.log('running') } } function createStudent(name) { var stu = Object.create(personObj) stu.name = name stu.studying = function () { console.log('studying') } return stu } var s1 = createStudent('xxx') s1.running() var s2 = createStudent('lxx') s2.running()
缺点:创建的对象没有明确的类型;
createStudent
中的函数重复创建。 -
👍 寄生组合式继承
业界公认,最有效最完整的继承方式
红色代码为第三方函数(原型继承函数)创建方式一:直接创建构造函数
// 父类
function Person(name,age) {
this.name = name
this.age = age
}
// 子类
function Student(num, name, age) {
// 一、盗用父类构造函数构造属性
Person.call(this, name, age)
this.num = num
}
// 二、借助原型链继承和寄生式继承来继承方法
// 1、创建一个第三方构造函数
function Temp() {}
// 2、把该构造函数的原型对象指向父构造函数的原型对象
Temp.prototype = Person.prototype
// 3、创建一个实例
var temp = new Temp()
// 4、设置子构造函数的原型对象为一个实例对象
Student.prototype = temp
// 5、修复constructor指向
Student.prototype.constructor = Student
// 这里一定要放在以上步骤后面,放在前面原型对象被修改了,无法访问!
Student.prototype.run = function() {
console.log('running~')
}
var stu = new Student('001','lxx',18)
console.log(stu)
stu.run()
寄生组合式继承,第三方函数(原型继承函数)创建方式二:Object.create()
function Person(name,age,friends) {
this.name = name
this.age = age
this.friends = friends
}
Person.prototype.running = function () {
console.log('running')
}
function Student(name,age,friends,sno,score) {
// 盗用父类构造函数构造属性
Person.call(this,name,age,friends)
this.sno = sno
this.score = score
}
// 以Person.prototype 为原型创建一个新对象,并赋值给Student.prototype
Student.prototype = Object.create(Person.prototype)
// 修复constructor指向
Object.defineProperty(Student.prototype,'constructor',{
value:Student,
enumerable:false,
writable:true,
configurable:true
})
Student.prototype.studying = function () {
console.log('studying')
}
var s1 = new Student('lxx',25,['aaa','bbb'],100,100)
console.log(s1)
s1.running() // running
s1.studying() // studying
var s2 = new Student('xxx',29,['xxx','yyy'],110,80)
s1.friends.push('ccc')
s2.friends.push('zzz')
👍 寄生组合式继承,封装原型继承函数
function inheritPrototype(subtype,supertype) {
subtype.prototype = Object.create(supertype.prototype)
Object.defineProperty(subtype.prototype,'constructor',{
value:subtype,
enumerable:false,
writable:true,
configurable:true
})
}
function Person(name,age,friends) {
this.name = name
this.age = age
this.friends = friends
}
Person.prototype.running = function () {
console.log('running')
}
Person.prototype.eating = function () {
console.log('eating')
}
function Student(name,age,friends,sno,score) {
Person.call(this,name,age,friends)
this.sno = sno
this.score = score
}
// 使用封装的继承函数
inheritPrototype(Student,Person)
Student.prototype.studying = function () {
console.log('studying')
}
var s1 = new Student('lxx',25,['aaa','bbb'],100,100)
console.log(s1)
s1.eating() // eating
s1.running() // running
s1.studying() // studying
var s2 = new Student('xxx',29,['xxx','yyy'],110,80)
s1.friends.push('ccc')
s2.friends.push('zzz')
原型继承函数除了 Object.create()
方式,还有其他方式:
原型继承函数
将某个对象作为新创建对象的原型。
var obj = {
name:'lxx',
age:25
}
// 方式一
function createObject(o) {
function Foo() {}
Foo.prototype = o
var newObj = new Foo()
return newObj
}
var info = createObject(obj)
console.log(info) // {}
console.log(info.__proto__) // obj
// 方式二
function createObject(o) {
var newObj = {}
Object.setPrototypeOf(newObj,o)
return newObj
}
var info = createObject(obj)
console.log(info) // {}
console.log(info.__proto__) // obj
// 方式三 ES6
var info = Object.create(obj)
console.log(info) // {}
console.log(info.__proto__) // obj