js继承是一种允许我们在已有类的基础上创建新类的机制,为子类提供了更高的灵活性,可以重用父类的方法和属性。
// 提供父类(继承谁,提供继承的属性)
function Person(name) {
this.name = name || 'sun'
this.work = function(){
console.log(`${
name}正在加班`)
}
this.works = ['文档', '代码']
}
Person.prototype.end = function(){
console.log(`${
name}下班了`)
}
function People(){
}
People.prototype = new Person() // 继承父类
//重写People.prototype的constructor属性,使其执行自己的构造函数People
People.prototype.constructor = People
// 创建实例
let people = new People()
people.name = 'du'
console.log(people.name) // du
let people1 = new People()
console.log(people1.name) // sun
但是存在一些问题(首先我们要清楚js中引用类型数据的存储方式)
通过改变原型指向实现了继承。这导致了一个问题,因为我们改变原型指向的同时,直接初始化了属性,这样继承过来的属性的值都是一样的了。这是个问题,如果我们想要改变继承过来的值,只能重新调用对象的属性进行重新赋值,这又导致我们上边的初始化失去了意义
let people2 = new People()
people2.works.push('测试')
console.log(people2.works) // ['文档', '代码', '测试']
let people3 = new People()
console.log(people3.works) // ['文档', '代码', '测试']
People.prototype = new Person('小李') // 继承父类
// 这个很好理解,此时当我们通过new People()创建实例时会发现,所有的实例的name属性的值都是一样的
在子类型构造函数的内部调用父类构造函数,通过使用call()和apply()方法可以在新创建的对象上执行构造函数。
function Person(name = 'sun') {
this.works = ['文档', '代码']
this.work = function(){
console.log(`${
name}正在加班`)
}
}
function People(name) {
Person.call(this, name)
// Person.apply(this, arguments)
}
let people = new People('sun')
people.works.push('测试')
console.log(people.works) // ["文档", "代码", "测试"]
let people1 = new People('张')
people1.works.push('运行')
console.log(people1.works) // ["文档", "代码", "运行"]
自己运行代码之后就能发现此时已经解决了原型链继承的通病:
重点在于Person.call(this, name)这句话。call()或apply(),实际上是在新创建的People实例的环境下调用了People构造函数,改变了Person内部this的指向,此时我们打印people和people1都会发现work和works属性都变成了自己独有的属性,而不是在_proto_上
但是同样的有一个问题:
从代码可以看到我们从每次通过new Peope()创建实例的时候,this.work这个方法都是打印谁正在加班,作用都是一样的,所示是不是很浪费内存,这是就可以考虑将这种共有同样作用的属行放在原型的,所以引出了原型链、构造函数组合继承
用原型链实现对原型属性和方法的继承,用借用构造函数继承来实现对实例属性的继承
function Person(name = 'sun') {
this.works = ['文档', '代码']
}
// 将共有的方法放在原型上
Person.prototype.work = function(){
console.log(`${
this.name}正在加班`)
}
function People(name, age){
Person.call(this, name)
// Person.apply(this, arguments)
this.age = age
}
// 重点:
//构造函数方式是不能继承原型属性/方法的 (原型中定义的方法和属性对于子类是不可见的)。
//原型链继承的方式就可以继承原型的属性/方法
People.prototype = new Person()
//重写People.prototype的constructor属性,使其执行自己的构造函数People
//People.prototype.constructor = People
let people = new People('sun', 23)
console.log(people)
console.log(people.age) // 23
console.log(people.works) // ["文档", "代码"]
let people1 = new People('张', 25)
console.log(people1)
console.log(people1.age) //25
console.log(people1.works) // ["文档", "代码"]
当我们执行代码的时候就可以发现,当注释掉这段代码 People.prototype = new Person() 直接创建实例的时候,打印people和people1时候,利用原型链的知识层层往上寻找work属性并未找到,但是执行了 People.prototype = new Person() 之后就找到了
原来构造函数方式是不能直接继承原型属性/方法 (原型中定义的方法和属性对于子类是不可见的)。原型链继承的方式就可以继承原型的属性和方法
People.prototype = new Person()
// 创建实例时调用下面的代码
let people = new People('sun', 23)
Person.call(this, name)
用一个函数包装一个对象,然后返回这个函数的调用,这个函数就变成了个可以随意增添属性的实例或对象object.create()就是这个原理。类似于复制一个对象,用函数来包装。
一句话描述:创建对象的原型=传入的对象
缺点:
function createObj(o) {
function F(){
}
F.prototype = o;
return new F();
}
let person = {
name: 'sun',
age: 24,
work: ['文档', '测试']
}
let person1 = createObj(person)
let person2 = createObj(person)
person1.name = 'liu'
console.log(person1.name) // liu, 此时的name为自身的属性
console.log(person2.name) // sun 此时的name为原型上的属性
person1.worl.push('测试')
console.log(person1) // ["文档", "测试", "测试"]
console.log(person2) // ["文档", "测试", "测试"]
缺点很明显,跟原型链有着同样问题:引用类型的属性值始终都会共享相应的值。
就是给原型式继承外面套了个壳子
function createObj(obj){
let newObj = Object.create(obj)
newObj.work = ['文档','工作']
newObj.sayHello = function() {
console.log('hello')
}
return newObj
}
let obj = {
name: 'sun',
}
let person1 = createObj(obj)
person1.work.push('测试')
console.log(person1.work) // ["文档", "工作", "测试"]
let person2 = createObj(obj)
console.log(person2.work) // ["文档", "工作"]
确定很明显,sayHello每次创建对象都会被执行。
解决组合继承里实例上和其原型上存在相同的属性
function Person(name = 'sun') {
this.name = name
this.works = ['文档', '代码']
}
// 将共有的方法放在原型上
Person.prototype.work = function(){
console.log(`${
this.name}正在加班`)
}
function People(name){
Person.call(this, name)
// Person.apply(this, arguments)
}
// 组合继承,看上面第三点(原理及存在的问题)
//People.prototype = new Person()
// 寄生组合继承
function createObj(o) {
function F(){
}
F.prototype = o;
return new F();
}
function instantiation(People, Person) {
// 继承原型上的属性和方法
People.prototype = createObj(Person.prototype)
//重写People.prototype的constructor属性,使其执行自己的构造函数People(修复实例,不然还是会指向Person)
People.prototype.constructor = People
}
instantiation(People, Person)
let people1 = new People('李四')
console.log(people1) //看下面的打印结果
从截图我们可以看到我们可以看到新的实例people1的属性并没有和其_proto_的属性重复,并且constructor属性的指向也没有混乱