关注前端小讴,浏览更多原创技术文章

继承

  • 面向对象语言反对 2 种继承形式:接口继承实现继承
  • JS 函数没有签名(不用提前申明变量的类型),只反对实现继承,依附原型链

相干代码 →

原型链

  • 子类型构造函数的原型,被重写为超类型构造函数的实例
function SuperType() {  this.property = true}SuperType.prototype.getSuperValue = function () {  return this.property}function SubType() {}SubType.prototype = new SuperType() // SubType的原型 = SuperType的实例,SubType原型被重写 → SubType 继承了 SuperTypeconsole.log(SubType.prototype.__proto__) // SuperType原型,SuperType实例的[[Prototype]]指向SuperType原型console.log(SubType.prototype.__proto__.constructor) // SuperType构造函数,SuperType原型的constructor指向SuperType构造函数
  • 超类型实例属性和办法,均存在于子类型的原型
  • 子类型的实例可拜访超类型原型上的办法,办法仍存在于超类型的原型中
var instance = new SubType()console.log(instance.property) // true,SubType继承了property属性console.log(SubType.prototype.hasOwnProperty('property')) // true,property是SuperType的实例属性,SubType的原型已被重写为SuperType的实例console.log(instance.getSuperValue()) // true,SubType继承了getSuperValue()办法console.log(SubType.prototype.hasOwnProperty('getSuperValue')) // false,getSuperValue是SuperType的原型办法,不存在于SubType的实例中console.log(SuperType.prototype.hasOwnProperty('getSuperValue')) // true
  • 调用子类型构造函数创立实例后,因为子类型的原型被重写

    • 子类实例的[[Prototype]]指向超类实例(本来指向子类原型)
    • 子类实例的constructor指向重写子类原型的构造函数,即超类构造函数(本来指向子类构造函数)
  • 谁(哪个构造函数)重写了原型,(实例和原型的)constructor 就指向谁
console.log(instance.__proto__) // SuperType实例,SubType的原型SubType.prototype已被SuperType的实例重写console.log(instance.constructor) // SuperType构造函数,constructor指向重写原型对象的constructor,即new SuperType()的constructorconsole.log(instance.constructor === SubType.prototype.constructor) // true,都指向SuperType构造函数
  • 实现了原型链后,代码读取对象属性的搜寻过程:

    • 1.搜寻对象实例自身 -> 有属性 → 返回属性值 -> 完结
    • 2.对象实例自身无属性 -> 搜寻原型对象 → 有属性 → 返回属性值 -> 完结
    • 3.原型对象无属性 -> 一环一环向上搜寻原型链 → 有/无属性 → 返回属性值/undefined → 完结

默认原型

  • 所有援用类型都默认继承Object,所有函数的默认原型都是 Object 实例
  • 默认原型外部的[[Prototype]]指针,指向Object的原型即Object.prototype
  • Object.prototype上保留着constructor、hasOwnProperty、isPrototypeOf、propertyIsEnumerable、toString、valueOf、toLocaleString等默认办法,在实例中调用这些办法时,其实调用的是 Object 原型上的办法
console.log(SuperType.prototype.__proto__) // {},SuperType的默认原型是Object实例,Object实例的[[Prototype]]指向Object原型console.log(SuperType.prototype.__proto__ === Object.prototype) // true,都指向Object原型console.log(SuperType.prototype.__proto__.constructor) // Object构造函数console.log(Object.keys(SuperType.prototype.__proto__)) // [],Object原型上可枚举的办法console.log(Object.getOwnPropertyNames(SuperType.prototype.__proto__)) // [ 'constructor','__defineGetter__','__defineSetter__','hasOwnProperty','__lookupGetter__','__lookupSetter__','isPrototypeOf','propertyIsEnumerable','toString','valueOf','__proto__','toLocaleString' ],Object原型上的所有办法

原型与继承关系

  • instanceof操作符,测试实例原型链中呈现过的构造函数
  • instanceof具体含意:判断一个构造函数的 prototype 属性所指向的对象,是否存在于要检测对象(实例)原型链
console.log(instance instanceof Object) // true,instance是Object的实例console.log(instance instanceof SuperType) // true,instance是SuperType的实例console.log(instance instanceof SubType) // true,instance是SubType的实例
  • isPrototypeOf()办法,测试实例原型链上的原型
  • isPrototypeOf()具体含意:判断一个对象(原型对象)是否存在于要检测对象(实例)原型链
console.log(Object.prototype.isPrototypeOf(instance)) // true,Object.prototype是instance原型链上的原型console.log(SuperType.prototype.isPrototypeOf(instance)) // true,SuperType.prototype是instance原型链上的原型console.log(SubType.prototype.isPrototypeOf(instance)) // true,SubType.prototype是instance原型链上的原型

对于办法

  • 在子类型原型增加或重写超类型办法的代码,肯定要放在替换原型语句之后
SubType.prototype.getSubValue = function () {  // 给子类原型增加新办法  return false}SubType.prototype.getSuperValue = function () {  // 在子类原型中重写超类原型的办法  return false}var instance2 = new SubType()console.log(instance2.getSubValue()) // falseconsole.log(instance2.getSuperValue()) // false,办法被重写var instance3 = new SuperType()console.log(instance3.getSuperValue()) // true,不影响超类型原型中的办法
  • 通过原型链实现继承时,不能应用对象字面量创立原型办法,因为这样会重写原型链,导致继承关系生效
function SubType2() {}SubType2.prototype = new SuperType() // 继承SubType2.prototype = {  // 对象字面量重写原型,继承关系生效(子类原型被重写为Object实例)  someFunction: function () {    return false  },}var instance4 = new SubType2()console.log(instance4.getSuperValue()) // error,对象字面量重写了原型,继承关系已生效

原型链的问题

  • 子类实例援用类型的属性进行批改(非从新定义)时,会对超类实例的援用类型属性造成影响
function SuperTypePro(name) {  this.nums = [1, 2, 3] // 超类属性,援用类型  this.name = name // 超类属性,原始类型}SuperTypePro.prototype.getSuperNums = function () {  return this.nums}function SubTypePro() {}SubTypePro.prototype = new SuperTypePro() // 继承var instance5 = new SubTypePro()instance5.nums.push(4) // 在子类实例中,批改(非从新定义)继承的援用类型属性console.log(instance5.nums) // [1,2,3,4]var instance6 = new SubTypePro()console.log(instance6.nums) // [1,2,3,4],超类实例受到影响var instance7 = new SubTypePro()instance7.nums = [] // 在子类实例中,从新定义(笼罩)继承的援用类型属性console.log(instance7.nums) // []console.log(instance6.nums) // [1,2,3,4],超类实例不受影响
  • (在不影响所有对象实例的状况下)创立子类型实例时,无奈给超类型构造函数传递参数
var person = new SuperTypePro('Simon') // 创立超类型实例console.log(person.name) // 'Simon'var person2 = new SubTypePro('Simon') // 创立子类型实例,参数传递无意义console.log(person2.name) // undefined

盗用构造函数

  • 在子类构造函数外部,通过apply()call()超类构造函数作用域绑定给子类的实例 this,再调用超类构造函数
function SuperTypeBorrow() {  this.nums = [1, 2, 3]}function SubTypeBorrow() {  console.log(this) // SubTypeBorrow构造函数外部的this,指向SubTypeBorrow的实例  SuperTypeBorrow.call(this) // 将超类的作用域绑定给this,即子类的实例}var instance8 = new SubTypeBorrow()console.log(instance8.nums) // [ 1, 2, 3 ]instance8.nums.push(4)console.log(instance8.nums) // [ 1, 2, 3, 4 ]var instance9 = new SubTypeBorrow()console.log(instance9.nums) // [ 1, 2, 3 ],超类不受影响

传递参数

  • 能够在子类构造函数中,向超类构造函数传递参数
  • 为确保超类构造函数不会重写子类的属性,先调用超类构造函数,再增加子类中定义的属性
function SuperTypeParam(name) {  this.name = name}function SubTypeParam() {  SuperTypeParam.call(this, 'Nicholas') // 继承,先调用超类型构造函数  this.age = 29 // 再增加子类型中定义的属性}var instance10 = new SubTypeParam()console.log(instance10.name, instance10.age) // Nicholas 29

盗用构造函数的问题

  • 构造函数模式存在的问题 —— 办法都在超类构造函数中定义,每个办法都会在实例上创立一遍,函数没有复用,且超类原型中定义的办法,在子类中不可见

组合继承

  • 又称伪经典继承,应用原型链继承原型上的属性和办法,通过盗用构造函数继承实例属性
  • 既通过超类原型上定义的办法实现了函数复用,又保障每个实例有本人的属性
/* 超类型构造函数 */function SuperTypeMix(name) {  this.name = name  this.nums = [1, 2, 3]}SuperTypeMix.prototype.sayName = function () {  console.log(this.name)}/* 子类型构造函数 */function SubTypeMix(name, age) {  SuperTypeMix.call(this, name) // 盗用构造函数继承,继承实例属性  this.age = age}SubTypeMix.prototype = new SuperTypeMix() // 原型链继承,继承原型办法SubTypeMix.prototype.sayAge = function () {  console.log(this.age) // 子类型原型增加办法(须在替换原型语句之后)}var instance11 = new SubTypeMix('Nicholas', 29)instance11.nums.push(4)console.log(instance11.nums) // [ 1, 2, 3, 4 ],盗用构造函数继承而来,属性保留在超类型实例和子类型原型中instance11.sayName() // 'Nicholas',原型链继承而来,办法保留在超类型原型中instance11.sayAge() // 29,非继承,办法保留在子类型原型中var instance12 = new SubTypeMix('Greg', 27)console.log(instance12.nums) // [ 1, 2, 3]instance12.sayName() // 'Greg'instance12.sayAge() // 27
  • 组合继承也有本人的有余,其会调用 2 次超类构造函数

    • 第一次,是在重写子类原型时,超类实例属性赋给子类原型
    • 第二次,是在调用子类构造函数创立子类实例时,超类实例属性赋给子类实例
/* 超类构造函数 */function SuperTypeMix(name) {  this.name = name  this.nums = [1, 2, 3]}/* 子类构造函数 */function SubTypeMix(name) {  SuperTypeMix.call(this, name) // 盗用构造函数继承,继承属性(创立子类实例时,第二次调用超类构造函数,子类实例继承超类实例属性)}SubTypeMix.prototype = new SuperTypeMix() // 原型链继承,继承办法(第一次调用超类构造函数,子类原型曾经继承了超类实例和原型中的办法和属性)
  • 调用 2 次超类型构造函数影响效率,且:

    • 子类原型子类实例上,都继承并蕴含了超类实例属性
    • 子类原型上的超类实例属性会被子类实例的同名属性笼罩,因而子类原型上的是不必要
    • 从子类实例删除继承自超类实例的属性,属性仍存在于子类原型中,依然能够被拜访到
var instance11 = new SubTypeMix('Nicholas') // 创立子类实例instance11.nums.push(4)console.log(SubTypeMix.prototype) // SuperTypeMix { name: undefined, nums: [ 1, 2, 3 ], sayAge: [Function] },子类原型(被重写为超类实例)console.log(instance11) // SuperTypeMix { name: 'Nicholas', nums: [ 1, 2, 3, 4 ], age: 29 },子类实例delete instance11.nums // 从子类实例中删除(继承自超类实例的)属性console.log(instance11) // SuperTypeMix { name: 'Nicholas', age: 29 },子类实例console.log(instance11.nums) // [ 1, 2, 3 ],依然能够(从子类原型中)拜访到该属性

原型式继承

  • 创立一个函数,接管一个参数对象(必传)

    • 在函数外部创立长期构造函数
    • 将传入的对象作为这个构造函数的原型
    • 返回这个构造函数的新实例
  • 从实质上讲,该函数对传入其中的对象执行了一次浅复制
function object(o) {  function F() {} //函数外部创立长期构造函数  F.prototype = o // 将传入的对象作为这个构造函数的原型  return new F() // 返回这个构造函数的新实例}
  • 传入的对象作为另一个对象的根底,是函数返回的新对象的原型,其属性值(根本类型值 & 援用类型值)被新对象所共享
  • 返回的新对象相当于传入的对象创立的正本
var person = {  name: 'Nicholas',}var anotherPerson = object(person)console.log(anotherPerson.name) // 'Nicholas',来自personconsole.log(anotherPerson.hasOwnProperty('name')) // falseanotherPerson.name = 'Greg' // 笼罩同名属性console.log(anotherPerson.hasOwnProperty('name')) // trueconsole.log(anotherPerson.name) // 'Greg',来自正本console.log(person.name) // 'Nicholas',来自person
  • ES5 的Object.create()办法规范化原型式继承,接管 2 个参数

    • 参数一:用作新对象原型的对象,必传
    • 参数二:为新对象定义额定属性的对象,非必传
  • 不传第二个参数时Object.create()办法与后面提到的object()函数的行为雷同
var anotherPerson2 = Object.create(person)console.log(anotherPerson2.name) // 'Nicholas',来自person
  • 第二个参数与 Object.defineProperties()——定义对象属性办法——的第二个参数格局雷同,通过描述符定义要返回的对象的属性
var anotherPerson3 = Object.create(person, {  name: { value: 'Greg' }, // 描述符定义对象的属性,若有同名属性则笼罩})console.log(anotherPerson3.name) // 'Greg',来自正本
  • 无需创立构造函数、只是想让一个对象与另一个对象放弃相似的状况下,可应用原型式继承
  • 原型模式创建对象,作为原型的对象的援用类型属性始终被作为原型的对象和正本共享,批改(非从新定义)正本中援用类型的值,会对作为原型的对象的援用类型属性造成影响
var person2 = {  nums: [1, 2, 3],}var anotherPerson4 = Object.create(person2)anotherPerson4.nums.push(4) // 援用类型属性被批改,非从新定义console.log(anotherPerson4.nums) // [1, 2, 3, 4],来自personconsole.log(person2.nums) // [1, 2, 3, 4],作为原型的援用类型属性受到影响

寄生式继承

  • 原型式继承严密相干,其思路与寄生构造函数工厂模式相似:

    • 创立一个仅用于封装继承过程的函数,接管一个参数,参数是作为原型的对象
    • 在函数外部,调用原型式继承封装的函数,返回一个实例对象,再以某种形式加强这个实例对象
    • 最初返回这个实例对象
function createAnother(original) {  var clone = Object.create(original) // 进行原型式继承,返回一个空实例  console.log(clone) // {},空实例,其原型是orginal对象  clone.sayHi = function () {    console.log('Hi') // 给返回的实例对象增加办法(每个实例从新创立办法)  }  return clone}var person3 = {  name: 'Nicholas',}var anotherPerson5 = createAnother(person3)console.log(anotherPerson5.name) // 'Nicholas'console.log(anotherPerson5.hasOwnProperty('name')) // false,name属性保留在作为原型的对象person3上anotherPerson5.sayHi() // 'Hi'console.log(anotherPerson5.hasOwnProperty('sayHi')) // true,sayHi办法保留在返回的实例对象上console.log(anotherPerson5) // { sayHi: [Function] }
  • 次要思考对象而不是自定义类型和构造函数的状况下,可应用原型式继承
  • 构造函数模式存在的问题 —— 办法都在寄生式继承的封装函数中定义,无奈做到办法复用而升高了效率

寄生组合式继承

  • 原型链的混成模式:

    • 不通过调用超类构造函数给子类原型赋值(重写),只需超类原型的正本
    • 应用寄生式继承来继承超类的原型,再将后果指定给子类的原型
    • 外围:子类的原型继承超类的原型
// 封装:原型链的混成模式function inherit(subType, superType) {  // 1.创建对象,继承超类的原型  var superPrototype = Object.create(superType.prototype) // superPrototype的原型是超类原型  console.log(superPrototype.__proto__) // 指向superType.prototype超类原型  console.log(superPrototype.__proto__ === superType.prototype) // true  console.log(superPrototype.constructor) // 此时constructor指向超类构造函数  // 2.让constructor指向子类构造函数  superPrototype.constructor = subType  // 3.将创立的对象赋给子类的原型  subType.prototype = superPrototype  console.log(subType.prototype.__proto__ === superType.prototype) // true,子类原型继承超类原型}
  • 应用盗用构造函数继承实例属性,通过原型链的混成模式继承原型办法
/* 超类 */function SuperTypeMixParasitic(name) {  this.name = name  this.nums = [1, 2, 3]}SuperTypeMixParasitic.prototype.sayName = function () {  console.log(this.name)}/* 子类 */function SubTypeMixParasitic(name, age) {  SuperTypeMixParasitic.call(this, name) // 盗用构造函数,继承属性(只调用1次超类构造函数)  this.age = age}inherit(SubTypeMixParasitic, SuperTypeMixParasitic) // 原型链的混成模式,继承办法SubTypeMixParasitic.sayAge = function () {  console.log(this.age)}
  • 寄生组合式继承是援用类型最现实的继承范式

    • 只调用 1 次超类构造函数,不会在子类原型上创立多余的属性
    var instance13 = new SubTypeMixParasitic('Nicholas', 29)instance13.nums.push(4)console.log(instance13.nums) // [ 1, 2, 3, 4 ],盗用构造函数继承而来,属性保留在子类实例([ 1, 2, 3, 4 ])和超类实例([ 1, 2, 3 ])中console.log(SubTypeMixParasitic.prototype) // SubTypeMixParasitic { constructor: { [Function: SubTypeMixParasitic] sayAge: [Function] } },子类原型不含多余属性,只继承超类原型的办法,且constructor指向子类构造函数
  • 原型链放弃不变
console.log(SubTypeMixParasitic.prototype.constructor) // SubTypeMixParasitic构造函数console.log(instance13.__proto__ === SubTypeMixParasitic.prototype) // trueconsole.log(SubTypeMixParasitic.prototype.__proto__) // SuperTypeMixParasitic原型console.log(  SubTypeMixParasitic.prototype.__proto__ === SuperTypeMixParasitic.prototype) // trueconsole.log(SubTypeMixParasitic.prototype.__proto__.constructor) // SuperTypeMixParasitic构造函数console.log(SubTypeMixParasitic.prototype.__proto__.__proto__) // Object原型console.log(  SubTypeMixParasitic.prototype.__proto__.__proto__ === Object.prototype) // trueconsole.log(SubTypeMixParasitic.prototype.__proto__.__proto__.constructor) // Object构造函数
  • 能失常应用instanceofisPrototypeOf()——因为constructor仍旧指向子类型构造函数
console.log(instance13 instanceof SubTypeMixParasitic) // instance13是SubTypeMixParasitic的实例console.log(instance13 instanceof SuperTypeMixParasitic) // instance13是SuperTypeMixParasitic的实例console.log(SubTypeMixParasitic.prototype.isPrototypeOf(instance13)) // true,SubTypeMixParasitic.prototype是instance13原型链上的原型console.log(SuperTypeMixParasitic.prototype.isPrototypeOf(instance13)) // true,SuperTypeMixParasitic.prototype13是instance原型链上的原型

总结 & 问点

  • 什么是函数签名?为什么 JS 函数没有签名?JS 反对哪种形式的继承?其依附是什么?
  • 原型链继承的原理是什么?超类型实例上的属性和办法保留在哪些地位?超类型原型上的办法呢?
  • 通过原型链实现继承时,调用子类型构造函数创立实例后,因为子类型的原型被重写,实例的[[Prototype]]和 constructor 指针产生了怎么的变动?为什么?
  • 通过原型链实现继承后,代码读取对象属性的搜寻过程是什么?
  • 所有援用类型都默认继承自什么?所有函数的默认原型都是什么?默认原型外部的[[Prototype]]指向哪里?
  • 在实例中调用 toString()、valueOf()等罕用办法时,理论调用的是哪里的办法?
  • 有哪些办法能够确定原型和实例的关系?其别离含意和用法是什么?
  • 通过原型链实现继承时,为什么给子类原型增加或笼罩超类办法必须在替换原型语句之后?为什么不能应用对象字面量创立子类原型办法?
  • 独自应用原型链实现继承有哪些局限?
  • 盗用构造函数继承的原理是什么?相比原型链继承有什么劣势?其毛病又是什么?
  • 组合继承的原理是什么?作为最罕用的继承模式,其有哪些劣势和毛病?
  • 原型式继承的原理是什么?在什么状况下能够应用这种继承形式?其又有什么毛病?
  • 寄生式继承的原理是什么?在什么状况下能够应用这种继承形式?其又有什么毛病?
  • 请用代码残缺展现寄生组合式继承的过程,并说说为什么它是“援用类型最现实的继承范式”?