目录

js的继承你了解透彻了嘛

实现继承的方式有很多,下面我们来写常用的几种(包括但不限于原型链继承、构造函数继承、组合继承、寄生组合继承、ES6继承):

原型链继承

原型链继承通过修改子类的原型为父类的实例,从而实现子类可以访问到父类构造函数以及原型上的属性或者方法。

js 复制代码
// 原型链继承
function Parent () {
   this.name = 'kobe'
}

Parent.prototype.getName = function () {
    return this.name
}

Parent.prototype.info = {
    age: 20,
    
}

function Child() {}

Child.prototype = new Parent() 
let child = new Child()
console.log('child----', child)  // Child {}
var child2 = new Child()
console.log('child.info === child2.info', child.info === child2.info) // true
child.getName() // kobe
child2.getName() // kobe
child.info.age = 20
console.log('child1.info.age---', child.info.age) // 20
console.log('child2.info.age---', child2.info.age) // 20 我们很惊奇的发现child2的age也被改掉了

上面的例子是修改的原型对象上的引用数据类型的属性,如果改成下面这样,就不会有影响

js 复制代码
// 原型链继承
function Parent () {
   this.name = 'kobe'
}

Parent.prototype.getName = function () {
    return this.name
}

Parent.prototype.info = {
    age: 20,
}

function Child() {}

Child.prototype = new Parent() 
let child = new Child()
console.log('child----', child)  // Child {}
var child2 = new Child()
console.log('child.info === child2.info', child.info === child2.info) // true
child.getName() // kobe
child2.getName() // kobe
child.info = {
    age: 18,
    num: 24
}
console.log('afterchild === child2', child.info === child2.info) // false
console.log('child1.info---', child.info) // 20 {age: 18, num: 24}
console.log('child2.info---', child2.info) // {age: 20}

因为我们刚开始child的info是引用类型,存的是相同的地址,后面直接给info了一个新对象,相当于生成了一个新对象的地址,两个info此时指向了不同的地址,互不干扰了

优点

实现逻辑简单

缺点

父类构造函数中的引用类型(比如对象/数组),会被所有子类实例共享。其中一个子类实例进行修改,会导致所有其他子类实例的这个属性值都会改变

构造函数继承

构造函数继承其实就是通过修改父类构造函数this实现的继承。我们在子类构造函数中执行父类构造函数,同时修改父类构造函数的this为子类的this。

我们直接看如何实现:

javascript 复制代码
function Parent(name) {
  this.name = [name]
}

function Child(name) {
    Parent.call(this, name)
}

let child = new Child('kobe')
child.name.push('jordan')

var child2 = new Child('james')
console.log('child---', child.name) // ['kobe', 'jordan']
console.log('child2---', child2.name) // ['james'] 属性互不影响,但是方法是能各写各的,方法不通用

优点

解决了原型链继承中构造函数引用类型共享的问题,同时可以向构造函数传参(通过call传参)

缺点

所有方法都定义在构造函数中,每次都需要重新创建,方法无法复用(对比原型链继承的方式,方法直接写在原型上,子类创建时不需要重新创建方法)

所以为了解决原型链继承和构造函数继承的问题,我们决定把二者优点合一

组合继承

同时结合原型链继承、构造函数继承就是组合继承了。

javascript 复制代码
function Parent () {
    this.name='kobe'
}

Parent.prototype.getName = function () {
    return this.name    
}

function Child () {
    Parent.call(this)
    this.num = 24
}

Child.prototype = new Parent() 
Child.prototype.constructor = Child
let child = new Child()
console.log(child) // {name: 'kobe', num: 24}
console.log('Child.prototype.__proto__ = Parent.prototype', Child.prototype.__proto__ === Parent.prototype) // true

此时child为:

可以看到它的对象上有name属性,原型对象上也有name属性

优点

同时解决了构造函数引用类型的问题,同时解决了方法无法共享的问题

缺点

父类构造函数被调用了两次(第一次是new Parent(), 第二次是Parent.call(this))。同时子类实例以及子类原型对象上都会存在name属性。虽然根据原型链机制,并不会访问到原型对象上的同名属性,但总归是不美。

寄生组合继承

寄生组合继承其实就是在组合继承的基础上,解决了父类构造函数调用两次的问题。我们来看下如何解决的: 第一种写法:

javascript 复制代码
function Parent () {
  this.name = 'kobe'
}

Parent.prototype.getName = function () {
  return this.name
}
function Child () {
  Parent.call(this)
  this.num = 24
}

clone(Child, Parent)
function clone (Child, Parent) {
  Child.prototype = Object.create(Parent.prototype)
  Child.prototype.constructor = Child
}

let child  = new Child()
console.log('child-----', child)

第二种写法:

javascript 复制代码
function Parent () {
  this.name = 'kobe'
}

Parent.prototype.getName = function () {
  return this.name
}
function Child () {
  Parent.call(this)
  this.num = 24
}

clone(Child, Parent)

// 这个函数的作用可以理解为复制了一份父类的原型对象
// 如果直接将子类的原型对象赋值为父类原型对象
// 那么修改子类原型对象其实就相当于修改了父类的原型对象
function clone2 (o) {
    function F() {}
    F.prototype = o
    return new F()   
}
function clone (Child, Parent) {
  Child.prototype = clone2(Parent.prototype)
  Child.prototype.constructor = Child
}

let child  = new Child()
console.log('child-----', child)

其实这两种的本质是一样的,都是把Parent.prototype给Child.prototype,而不是把Parent给Child.prototype 此时child为:

此时原型对象上已经没有同名的name属性了

优点

这种方式就解决了组合继承中的构造函数调用两次,构造函数引用类型共享,以及原型对象上存在多余属性的问题。是推荐的最合理实现方式(排除ES6的class extends继承)

缺点

没有啥特别的缺点

ES6继承

ES6提供了class语法糖,同时提供了extends用于实现类的继承,这是项目中最常见的继承方式。

使用class继承很简单,也很直观:

javascript 复制代码
class Parent {
    constructor (name) {
        this.name = name
    }
    getName () {
        return this.name
    }
}

class Child extends Parent {
    constructor (name) {
        super(name)
        this.num = 24
    }
}

const child1 = new Child('kobe')
const child3 = new Child('jordan')
console.log('child1', child1) // {name: 'kobe', num: 24}
console.log('child3', child3) // {name: 'jordan', num: 24}
console.log('child1.getName()', child1.getName()) // kobe
console.log('child3.getName()', child3.getName()) // jordan

补充:

Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__

javascript 复制代码
const person = {name :'kobe'}
const player = Object.create(person); // player.__proto__ === person

proto必填参数,是新对象的原型对象,如上面代码里新对象player__proto__指向person。注意,如果这个参数是null,那新对象就彻彻底底是个空对象,没有继承Object.prototype上的任何属性和方法,如hasOwnProperty()、toString()等。

技术沟通交流欢迎+V:yinzhixiaxue

本文是转载文章,点击查看原文
如有侵权,请联系 xyy@jishuzhan.net 删除
相关推荐
丁总学Java8 分钟前
解锁 vue-property-decorator 的秘密:Vue 2 到 Vue 3 的 TypeScript 之旅!✨
前端·vue.js·typescript
MandiGao9 分钟前
ECharts 3D地球(铁路线、飞线、标点、图标、文字标注等)
前端·vue.js·3d·echarts
一个处女座的程序猿O(∩_∩)O9 分钟前
Vue 过滤器深度解析与应用实践
前端·javascript·vue.js
招风的黑耳28 分钟前
Web元件库 ElementUI元件库+后台模板页面(支持Axure9、10、11)
前端·elementui·axure
雯0609~29 分钟前
CSS:使用内边距时,解决宽随之改变问题
前端·css
Dolphin_海豚39 分钟前
10 分钟带你入坑 electron
前端·javascript·electron
乐闻x1 小时前
性能优化:javascript 如何检测并处理页面卡顿
前端·javascript·性能优化
雯0609~1 小时前
vue3:八、登录界面实现-忘记密码
前端·javascript·vue.js
爱看书的小沐1 小时前
【小沐学Web3D】three.js 加载三维模型(React)
javascript·react.js·vue·webgl·three.js·opengl·web3d
烂蜻蜓1 小时前
深入理解 HTML 中的<div>和元素:构建网页结构与样式的基石
开发语言·前端·css·html·html5