引言
正如上篇所提到的,有些人认为JavaScript并不是真正的面向对象语言,在经典的面向对象语言中,您可能倾向于定义类对象,然后您可以简单地定义哪些类继承哪些类,JavaScript使用了另一套实现方式,继承的对象函数并不是通过复制而来,而是通过原型链继承(通常被称为 原型式继承 —— prototypal inheritance)。
继承的方式
说到继承,首先得明白继承的是什么东西。为了方便理解,我个人把属性(包括方法)分为共有属性和私有属性。私有属性比如名字或者身份证之类的,是每个人独有的。共有属性是能共用的属性或者方法,比如爱好属性,吃饭方法。
私有属性的继承(构造函数+call)
这个比较简单,私有属性用利用构造函数和call或者apply
const Person = function (name) { this.name = name }const Students = function (name) { Person.call(this,name) }const xm = new Students('小明')console.log(xm) //Students {name: "小明"}
公有属性的继承
这里里面坑有点多,大家听我娓娓道来。
通过原型链实现公有属性继承肯定没错,但是我们设计的时候有个原则 子类需要有自己的原型,父类也必须要有自己的原型,子实例在自己的原型上找不到属性的时候才会到父原型上去找
子类.prototype = 父类.prototype 这样肯定不行,虽然能继承父类原型的方法,但是子类的原型和父类的原型是同一个,给子类原型添加方法的时候,相当于给父类的原型也添加了一个方法。so我们应该有一个缓冲
。
子类实例---->子类原型------->中间对象------->父类原型 //沿着箭头能访问,表现上符合我们的设计原则
最常见的是使用父类的实例当这个中间对象。
Children.prototype = new Parent()
但是了这么做有个不好的地方。会实例化一次父类。如果父类特别复杂,比如axios,那么会带来很多额外的开销。
我们看一下中间对象有什么作用,实际上只起了一个隔离和原型重定向的作用。完全可以用一个空对象实现这个功能
//实现缓冲var fn = function() {}fn.prototype = Parent.prototypeChildren.prototype = new fn()
实际上,这个就是Oject.create()的实现
//Oject.create()Object.create = function(obj){ var fn = funcion(){} fn.prototype = obj reurturn new fn() }
终极继承解决方案
现在既要继承私有属性,又要继承公有属性。
const Person = function (name) { this.name = name } Person.prototype.eat = function () { console.log('i am hungry,i want to eat!') } const Student = function (name) { Person.call(this,name) } Student.prototype = Object.create(Person.prototype) //注意这里!原型重定向的后遗症 const xm = new Student ('小明') xm.name //'小明' xm.eat() //i am hungry,i want to eat! console.log(xm) //Student {name: "小明"} // name: "小明" // __proto__: Person // __proto__: //这一层是个空对象,只有一个__proto__属性指向Person的原型 // eat: ƒ () // constructor: ƒ (name) // __proto__: Object
但是这里 xm.constructor.name // Person
这里因为原型重定向后没有重置construtor,xm本身没有construtor只能找我们创建的空对象,空对象没有construtor所以继续往上找,找到了Person.prototype上的construtor为 Person
所以解决思路很简单,在改写原型链的时候重置一下constructor就行了
...var temObj = Object.create(Person.prototype)temObj.constructor = StudentStudent.prototype =temObj... xm.constructor.name // Student ok搞定
new干了啥
既然new在“类”的创建里面必须使用,那么我们就说一下new到底干了啥事情
1.创建一个对象o继承构造函数
2.让构造函数的this变为o,并执行构造函数,将返回值设置为k
3.如果k
//仿写newfunction new1(func) { var o = Object.create(func.prototype) var k = func.apply(o,arguments[1]) return typeof k === 'object'? k: o }const x = new1(Student,['张三'])x.name //'张三'x.eat //'i am hungry,i want to eat!'
我们回过头再分析一下构造函数模式继承
const Person = function (name) { this.name = name }const Students = function (name) { Person.call(this,name) //this是student实例 }const xm = new Students('小明') //分析这里干了什么console.log(xm) //Students {name: "小明"}
1.让空对象o继承Students(o能访问Students的原型)
2.student执行,执行Person的代码,this是o,并且传入name, o.name='小明'返回的k是undefined
3.返回o,也就是返回{name:'小明'}
今天先写到这,未完待续....