JS-深入理解继承(非class方式与class继承)

从一个面试题目入手: 如何实现一个类?

1. 类的三种属性:

  • 公有属性:使用__proto__访问的属性
  • 私有属性: 实例自带的属性
  • 静态方法 (es7中有了静态属性)

2. 利用构造函数的方式模拟类

// 父类
function Parent() {
    // 构造函数中的this, 通过new调用的,那么this指代的是实例
    // 私有属性
    this.name = 'parent'
}

// 公有
Parent.prototype.eat = function () {
    console.log('eat')
}

// 子类
function Child() {  
    this.age = 9
}

// 子类的公有属性
Child.prototype.smoking = function () {  
    console.log('smoking')
}

let parent = new Parent()  // 父类实例
let child = new Child()  // 子类实例
console.log(child.__proto__.constructor)   // 指向构造函数Child

// 注意:
console.log(child.__proto__.constructor.age)  // 这样是拿不到age属性的,因为child.__proto__.constructor得到的是构造函数,而age是定义在实例上的

继承方式:

  • 继承私有属性:即child可以获取到parent的name属性,调用构造函数 Parent.call(this), 如上上述代码所示。
  • 继承公有属性:即继承parent原型上的属性,child既要保留自己的属性smoking又可以继承父类的eat。
function Parent() {
    this.name = 'parent'
}

Parent.prototype.eat = function () {
    console.log('eat')
}

function Child() {
    this.age = 9
    Parent.call(this)  // 继承私有属性
}

Child.prototype.smoking = function () {
    console.log('smoking')
}

Child.prototype = Parent.prototype  // 子类的原型指向了父类的原型

let parent = new Parent()
let child = new Child()

console.log(child.eat())
// console.log(child.smoking())  // 子类原本在原型上的属性就丢失了

// 给子类设置一个新的属性后,父类上也能访问到这个属性的值, 父类上修改,子类上也会修改,  没有继承关系了,这种方式是不对的!!!
Child.prototype.a = 100
console.log(parent.a)

正确继承父类的公有属性的方式是:

function Parent() {
    this.name = 'parent'
}

Parent.prototype.eat = function () {
    console.log('eat')
}

function Child() {
    this.age = 9
    Parent.call(this)  // 继承私有属性
}

Child.prototype.smoking = function () {
    console.log('smoking')
}

Child.prototype.__proto__ = Parent.prototype    
// 可以实现,但是一般不会直接操作__proto__, 以后可以使用下面的这种等价写法
// Object.setPrototypeOf(Child.prototype, Parent.prototype)
let parent = new Parent()
let child = new Child()
child.eat()
console.log(Child.prototype.constructor)   // [Function: Child]

另一种方法: Object.create(), 这种方式就是只实现公有属性的继承,Child.prototype = Object.create(Parent.prototype),这种方式是如何实现的呢?代码如下:

// 如何实现的呢?
function Parent() {
    this.name = 'parent'
}

Parent.prototype.eat = function () {
    console.log('eat')
}

function Child() {
    this.age = 9
    Parent.call(this) // 继承私有属性
}

Child.prototype.smoking = function () {
    console.log('smoking')
}

// 简单实现Object.create()
function create(parentPrototype) {  
    function Fn() {  }
    Fn.prototype = parentPrototype  // Fn上只会有父类的公有属性
    return new Fn()
}
Child.prototype = create(Parent.prototype)

let child = new Child()
console.log(child.constructor)  // [Function: Parent]  , 存在一个问题就是子类的constructor指向变成了父类

如何解决上述问题?如果使用Object.create()来实现的话是通过传入参数对象来实现的:

Child.prototype = Object.create(Parent.prototype, {
    constructor: {value: Child}
})   // 将constructor指回Child
console.log(child.constructor)

如何自己实现上述的constructor指向问题?
首先来看Object.defineProperty()的用法:

let a = {}
Object.defineProperty(a, 'name', {
    enumerable: true,  // 表示这个属性可以被枚举出来,是可见的
    configurable: true,  // 是否可以被删除
    // writable: true, // 是否可以修改, 跟set冲突  写一个就行
    get() {
        console.log('get')
        return 'xy'
    },
    set(value) {
        console.log('设置值')
    }
})
console.log(a.name)
a.name = 'milly'
// 以上这种方式,在做双向绑定的时候会用到

现在来实现刚才create中的constructor指向问题:

function create(parentPrototype, props) {
    function Fn() { }
    Fn.prototype = parentPrototype 
    let fn = new Fn()
    for (let key in props) {
        Object.defineProperty(fn, key, {
            ...props[key],
            enumerable: true
        })
    }
    return fn
}

Child.prototype = Object.create(Parent.prototype, {
    constructor: { value: Child }
})   

let child = new Child()
console.log(child.constructor)  // Child

还有一种既可以继承方式:
Child.prototype = new Parent() // 基本不用, 因为new出来的实例上既有父类的私有又有父类的公有

3. class创建类

首先使用class创建一个类:

class Child {
    constructor () {
       this.age = 9  // 私有属性
    }
    // 注意: 之间没有逗号

    static a() {  // 属于类上的方法,通过类来调用
        return 1
    }
    smoking() {  // 公有, 原型上的方法
        console.log('sssss')
    }
 }

let child = new Child()
child.smoking()
console.log(Child.a())  // 通过类,调用静态方法

继承的写法:

class Parent {
    constructor() {
        this.name = 'parent'
    }
    static b() {
        return 2
    }
    eat () {
        console.log('eat')
    }
}


class Child extends Parent{  // 要求继承父亲的私有和公有
    constructor() {
        super()  // 等价于Parent.call(this)  继承父类的私有属性
        this.age = 9 
    }
    // 注意: 之间没有逗号

    static a() { // 属于类上的方法,通过类来调用
        return 1
    }
    smoking() { // 公有, 原型上的方法
        console.log('sssss')
    }
}

let child = new Child()
child.smoking()
child.eat() // 继承公有属性
console.log(child.name)  // 继承私有属性

// 在父类上有一个静态方法b, 在子类上能否被继承呢? ---
// 一定要明确: 静态方法一定是属于类的,而不是实例的
console.log(Child.b())  // 可以继承
// 所以,类可以继承公有、私有和静态方法

父类的构造函数中返回一个引用类型的时候, 会把这个引用类型作为子类的this。

class Parent {
    constructor() {
        this.name = 'parent'
        return {xx: 'xy'}
    }
    static b() {
        return 2
    }
    eat() {
        console.log('eat')
    }
}


class Child extends Parent { // 要求继承父亲的私有和公有
    constructor() {
        super() // 等价于Parent.call(this)  继承父类的私有属性
        this.age = 9
    }
    // 注意: 之间没有逗号

    static a() { // 属于类上的方法,通过类来调用
        return 1
    }
    smoking() { // 公有, 原型上的方法
        console.log('sssss')
    }
}

let child = new Child()
console.log(child) // { xx: 'xy', age: 9 } , 这样的话就没有父类的name属性了

小结:class创建类需要注意的点

  • class类只能new
  • es6只支持静态方法,没有静态属性, 静态方法是类上的方法,通过类来调用
  • 类可以继承公有属性、 私有属性和静态方法
  • 父类的构造函数中返回一个引用类型的时候,会把这个引用类型作为子类的this

4. 如何实现一个类?

  1. 实现一个没有继承关系的
    实现思路:
  • 首先,使用立即执行函数搭起一个类的框架:
let Parent = (function () {
  // 写逻辑
  function P() {
   
  }
  return P;
})();
  • 判断是不是通过new调用的:
    如何判断呢? 返回函数P中的this如果是window,那就是直接调用的,但如果是P的实例,就是new出来的
//  类的调用检测, 检测实例是不是new出来的
function _classCallCheck(instance, constructor) {
 if (!(instance instanceof constructor)) {
   throw new Error('Class constructor Child cannot be invoked without "new"');
 }
}
// 类的实现
let Parent = (function () {
 // 写逻辑
 function P() {
  _classCallCheck(this, P)   // 类调用检查
 }
 return P;
})();

Parent();  // 报错
let p = new Parent();  // 正常
  • 私有属性很好设置,直接复制过来就可以了
//  类的调用检测, 检测实例是不是new出来的
function _classCallCheck(instance, constructor) {
 if (!(instance instanceof constructor)) {
   throw new Error('Class constructor Child cannot be invoked without "new"');
 }
}
// 类的实现
let Parent = (function () {
 // 写逻辑
 function P() {
  _classCallCheck(this, P)   // 类调用检查
  this.name = 'parent'
 }
 return P;
})();

let p = new Parent();  // 正常
  • 设置公有方法和静态方法,这两者的区别在于,如果是给P的原型添加方法就是添加的公有方法,如果是给P类添那么添加的就是静态方法:
function _classCallCheck(instance, constructor) {
  if (!(instance instanceof constructor)) {
    throw new Error('Class constructor Child cannot be invoked without "new"');
  }
}

// 创建类
function _createClass(constructor, protoProperties, staticProperties) {
  // constructor  --- 构造函数
  // protoProperties  --- 原型方法的描述, 数组类型
  // staticProperties --- 静态方法的描述, 数组类型
  if (protoProperties.length > 0) {
  		// 将属性添加到构造函数的原型上 
      defineProperties(constructor.prototype, protoProperties);
  }
  if (staticProperties.length > 0) {
  		// 将属性添加到构造函数上 
    defineProperties(constructor, staticProperties);
  }
}

let Parent = (function () {
  function P() {
   _classCallCheck(this, P)   // 类调用检查
   this.name = 'parent'
  }
  
  // 创建类
  _createClass(
    P,
    [
      {
        key: "eat",
        value: function () {
          console.log("eat");
        }
      }
    ],
    [
      {
        key: "b",
        value: function () {
          return 2
        }
      }
    ]
  );

  return P;
})();
  • 接下来就是defineProperties()方法的实现,这里就要用到Object.defineProperty()去给目标对象添加方法了:
defineProperties (target, properties) {
	// target:  构造函数或者是构造函数的原型
	// properties:  对象数组
	for (let i=0; i<properties.length; i++) {
		Object.defineProperty(target, properties[i].key, {
			...properties[i],
			configurable: true,
			enumerable: true,
			writable:true
		})
	}
}
  • 现在来测试这个类,基本可以访问了:
let p = new Parent();
p.eat()
console.log(Parent.b())
  1. 手写实现类的继承
// 子类继承父类
function _inherits(subClass, superClass) {
    // 继承公有属性
    subClass.prototype = Object.create(superClass.prototype, {
        constructor: {value:subClass}
    })
    
    // 继承父类的静态方法: 子类的链,连接到父类上,就可以调用父类的方法了
    Object.setPrototypeOf(subClass, superClass)
}

接下来创建子类:

let Child = (function (Parent) {
    // 实现继承父类的公有属性和静态方法
    _inherits(C, Parent)
    function C() {  
        _classCallCheck(this, C)  // 类型检测
        let obj = Parent.call(this)  // 需要处理,父类返回一个引用类型的情况
        let that = this
        if (typeof obj === 'object') {
            that = obj
        }
        // 设置子类的私有属性
        that.age = 9
        return that
    }
    return C
})(Parent);

到这里一个简单的继承就实现了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值