资讯专栏INFORMATION COLUMN

基础学习 - 在JS 中的继承

Taste / 1467人阅读

摘要:继承方法原型链继承父类型子类型子类型的原型为父类型的一个实例对象这种继承方式把设置直接设置为对象子类就可以通过原型链访问父级所有的属性和方法了。但是缺点很明显,在创建子类的时候会调用调用两次父类的构造函数。

起因

最近在使用node-jsonwebtoken中发现了下面这个代码,感觉挺好看,于是就打算探索一些相关代码:

代码地址,点击这里

var JsonWebTokenError = function (message, error) {
  Error.call(this, message);
  if(Error.captureStackTrace) {
    Error.captureStackTrace(this, this.constructor);
  }
  this.name = "JsonWebTokenError";
  this.message = message;
  if (error) this.inner = error;
};

JsonWebTokenError.prototype = Object.create(Error.prototype);
JsonWebTokenError.prototype.constructor = JsonWebTokenError;

module.exports = JsonWebTokenError;

等会再来分析这个段代码.

找到MDN中关于继承部分(继承在原型链中是高级部分教程)如下:


在JavaScript中继承都是通过原型链来实现的。下面就来谈谈在JS 中继承

什么是继承?

继承是面向对象的软件的当中一个概念。在面向对象还有两个特征分别是多态、分装。继承是可以让自雷拥有父类的属性和方法或者重新定义、追加属性和方法等。

wikipedia)
继承方法 原型链继承
//父类型
       function Parent(name, age) {
           this.name = name,
           this.age = age,
           this.play = [1, 2, 3]
           this.setName = function () { }
       }
       Parent.prototype.setAge = function () { }
       //子类型
       function Child(price) {
           this.price = price
           this.setScore = function () { }
       }
       Child.prototype = new Parent() // 子类型的原型为父类型的一个实例对象
       var s1 = new Child(15000)
       var s2 = new Child(14000)
       console.log(s1,s2)

这种继承方式把 Child.prototype 设置直接设置为 Parent 对象, 子类就可以通过原型链访问父级所有的属性和方法了。后续若要增加新的属性,必须在 Child.prototype = new Parent() 后面,否则则会被覆盖.

在上面示例中需要说明的事,在使用父类属性的时候会遵守JS的数据类型变化规则, 原始值(Primitive values) 不可突变(not mutation), 对象则会进行突变的。这个JS 存储类型决定。原始值每次返回一定是一个新的值,而对象则代表是内存中一个区域地址,地址不变,具体在代码中表现是即使内部数据不同,但是他们依旧相等。这个则设计JS中深浅拷贝问题,后续再涉及。
使用子类构造函数来继承
function Parent(name, age) {
    this.name = name,
    this.age = age,
    this.setName = function () {}
  }
  Parent.prototype.setAge = function () {}
  function Child(name, age, price) {
    Parent(this, name, age)  // 相当于: this.Parent(name, age)
    /*this.name = name
    this.age = age*/
    this.price = price
  }
  var s1 = new Child("Tom", 20, 15000)

这种方式首先在初始化 Child 之前,会对 Child 进行一个初始化,我们这里涉及这个关于 new 知识点, 在 new 之前会将 this 初始化为 Child.prototype 这个对象,这里使用 Function.prototype.call 来调用Parent,其实就是类似如下代码:

const ChildPrototype = {
    Parent: function(name, age) {
        //...
    }
}

这里就获得了Parent中使用 this 初始化的属性和方法, 这里不能够获取Parent原型链的数据。使用这种方式有如下优劣:

优点

解决了原型链继承,子类访问父类引用属性的问题

子类初始化时候可以向父类传参

实现多继承(call多个父类对象)

缺点

子类实例使用instanceof 并不等于父类实例

不能继承父类原型链上的方法

每次初始化过程中都需要进行父类函数初始化,性能不佳

这里使用 Function.prototype.call 方式可以看这里
组合继承(原型链+子类构造函数) - 1
function Parent(name, age) {
    this.name = name,
    this.age = age,
    this.setAge = function () { }
}
Parent.prototype.setAge = function () {
    console.log("111")
}
function Child(name, age, price) {
    Parent.call(this, name, age)
    this.price = price
    this.setScore = function () { }
}
Child.prototype = new Parent()
Child.prototype.contructor = Child
Child.prototype.sayHello = function () { }
var s1 = new Child("Tom", 20, 15000)
console.log(s1)

这里的话融合了原型链继承和构造函数的的优点。但是缺点很明显,在创建子类的时候会调用调用两次父类的构造函数。
在上面中 Child.prototype.contructor = Child 这里需要进行通过原型链继承修复。这里主要修复之前几个继承问题

父类引用共享

子类可以获取父类所有属性和方法

组合继承 - 2
function Parent(name, age) {
        this.name = name,
        this.age = age,
        this.setAge = function () { }
}
Parent.prototype.setAge = function () {
    console.log("111")
}
function Child(name, age, price) {
    Parent.call(this, name, age)
    this.price = price
    this.setScore = function () { }
}
Child.prototype = Parent.prototype
Child.prototype.sayHello = function () { }
var s1 = new Child("Tom", 20, 15000)
console.log(s1)

这里通过子类的 prototype 重新赋值给 Parent.prototype, 就可以继承到父类的所有原型链上的属性,例如 setAge, 在子类函数申明中,通过 Parent.call 继承父类本身的属性和方法。这种方式就可以解决两次调用构造函数问题, 但是这里也有一个问题就是,子类构造函数Child.prototype.constructor 被父类构造函数覆盖,Parent.prototype.construtorChild.prototype.constructor 指向都是 Parent.prototype.constructor

组合继承 - 3
function Parent(name, age) {
        this.name = name,
        this.age = age,
        this.setAge = function () { }
}
Parent.prototype.setAge = function () {
    console.log("111")
}
function Child(name, age, price) {
    Parent.call(this, name, age)
    this.price = price
    this.setScore = function () { }
}
Child.prototype = Object.create(Parent.prototype)
Child.prototype.construct = Child
Child.prototype.sayHello = function () { }
var s1 = new Child("Tom", 20, 15000)
console.log(s1)

这里和上面一种区别在于通过创建使用 Object.create() 来把 Parent.prototype 设置为 Child.prototype.__proto__ 上, 也就说现在结构会变成如下图:

通过这种方式的继承,目前来看是最完美的一种方式,也解决之前很多问题。

ES6 的 extends 关键字
class Parent {
    //调用类的构造方法
    constructor(name, age) {
        this.name = name
        this.age = age
    }
    //定义一般的方法
    showName() {
        console.log("调用父类的方法")
        console.log(this.name, this.age);
    }
}
let p1 = new  Parent("kobe", 39)
console.log(p1)
//定义一个子类
class Child extends Parent {
    constructor(name, age, salary) {
        super(name, age)//通过super调用父类的构造方法
        this.salary = salary
    }
    showName() {//在子类自身定义方法
        console.log("调用子类的方法")
        console.log(this.name, this.age, this.salary);
    }
}
let s1 = new Child("wade", 38, 1000000000)
console.log(s1)
s1.showName()

这种方式是目前es6的方式,缺点就是兼容性,不是所有浏览器都完美兼容es6.不过有点也很明显和其他语言保持了一致的继承语法,简单易懂。

实际JS 都是通过原型链继承,所以这里最终会编译成如下代码:

这是Babel编译的结果,可以看到还是通过原型链来实现的。


"use strict";

function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }

function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); }

function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn"t been initialised - super() hasn"t been called"); } return self; }

function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }

function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }

function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }

function _instanceof(left, right) { if (right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance]) { return right[Symbol.hasInstance](left); } else { return left instanceof right; } }

function _classCallCheck(instance, Constructor) { if (!_instanceof(instance, Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

var Test = function Test() {
  _classCallCheck(this, Test);
};

var Test1 =
/*#__PURE__*/
function (_Test) {
  _inherits(Test1, _Test);

  function Test1() {
    _classCallCheck(this, Test1);

    return _possibleConstructorReturn(this, _getPrototypeOf(Test1).apply(this, arguments));
  }

  return Test1;
}(Test);

看下面图示:


这里代码和组合继承3是类似的哦

更多查看:

https://www.quora.com/What-is...
https://segmentfault.com/a/11...

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/104322.html

相关文章

  • 新上课程推荐:TypeScript完全解读(总26课时)

    摘要:本套课程包含两大部分,第一部分是基础部分,也是重要部分,参考官方文档结构,针对内容之间的关联性和前后顺序进行合理调整。 showImg(https://segmentfault.com/img/bVbpBA0?w=1460&h=400); 讲师简介: iview 核心开发者,iview-admin 作者,百万级虚拟渲染表格组件 vue-bigdata-table 作者。目前就职于知名互...

    caozhijian 评论0 收藏0
  • javascript基础篇:关于js面向对象的理解

    摘要:关于中面向对象的理解面向对象编程它是一种编程思想我们的编程或者学习其实是按照类实例来完成的学习类的继承封装多态封装把实现一个功能的代码封装到一个函数中一个类中以后再想实现这个功能,只需要执行这个函数方法即可,不需要再重复的编写代码。 关于js中面向对象的理解 面向对象编程(oop) 它是一种编程思想 (object-oriented programming ), 我们的编程或者学习其...

    roadtogeek 评论0 收藏0
  • javascript基础篇:关于js面向对象的理解

    摘要:关于中面向对象的理解面向对象编程它是一种编程思想我们的编程或者学习其实是按照类实例来完成的学习类的继承封装多态封装把实现一个功能的代码封装到一个函数中一个类中以后再想实现这个功能,只需要执行这个函数方法即可,不需要再重复的编写代码。 关于js中面向对象的理解 面向对象编程(oop) 它是一种编程思想 (object-oriented programming ), 我们的编程或者学习其...

    newtrek 评论0 收藏0
  • javascript基础学习

    摘要:预解释变量和函数的预解释只发生在当前的作用于中中内存的分类栈内存用来提供一个代码指定的环境作用域全局作用域和局部作用域堆内存用来存储引用类型的值对象存储的是键值对,函数储存的是字符串函数执行的时候形成一个私有作用域有形参给形参赋值,形参也 预解释 变量和函数的预解释只发生在当前的作用于中 js中内存的分类 栈内存:用来提供一个js代码指定的环境 —>作用域(全局作用域和局部作用域...

    魏明 评论0 收藏0

发表评论

0条评论

Taste

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<