广告
返回顶部
首页 > 资讯 > 前端开发 > JavaScript >分享JavaScript 中的几种继承方式
  • 727
分享到

分享JavaScript 中的几种继承方式

2024-04-02 19:04:59 727人浏览 泡泡鱼
摘要

目录一、原型链1.1 原型链的问题二、盗用构造函数2.1 基本思想2.2 可向父类构造函数传参2.3 盗用构造函数的问题三、组合继承(伪经典继承)3.1 基本思想3.2 组合继承的问

前言:

说到javascript中的继承,与之密切相关的就是原型链了,JavaScript中的继承主要是通过原型链实现的。但是简单的原型链继承方式也存在一定的缺陷,在此借着《JavaScript高级程序设计(第四版)》一书,聊聊JavaScript中的几种继承方式

一、原型链

ECMA-262 把原型链定义为ECMAScript的主要继承方式,其基本思想就是通过原型继承多个引用类型的属性和方法。

在此回顾一下原型、构造函数、实例之间的关系:

每个构造函数都有一个原型对象,原型有一个属性指回构造函数,实例有一个内部指针指向原型。
有关原型和原型链的知识这里先不多说了,这里来谈谈原型链的一些问题。

1.1 原型链的问题

  • 原型链主要问题出现在原型中包含引用值的时候。因为原型上的属性会在所有属性之间共享,对于原型上的引用值,实例继承的是指向该对象的引用,所以在实例中修改该属性时,会影响原型上的属性。
function Father() {
    this.colors = ['red'];
}
function Son() {}
Son.prototype = new Father();
let son1 = new Son();
console.log(son1.colors);  // ['red']
son1.colors.push('green');
console.log(son1.colors);  // ['red', 'green']
console.log(son1.hasOwnProperty('colors'));  // false
let son2 = new Son();
console.log(son2.colors);  // ['red', 'green']
console.log(Son.prototype.colors);  // ['red', 'green']

如上代码,构造函数的原型为new Father(),原型包含引用值属性colorsSon对象实例自身并没有colors属性,而是继承自原型,所以向colors中添加"green"影响到的原型上的colors。这就导致son2访问colors属性时值为['red', 'green']
所以,若原型上属性为引用值时,在实例中对该属性修改时会影响原型属性。

但是需要注意下面这种情况:

function Father() {
    this.colors = ['red'];
}
function Son() {}
Son.prototype = new Father();
let son1 = new Son();
console.log(son1.colors);  // ['red']
son1.colors = [];
console.log(son1.colors);  // []
console.log(son1.hasOwnProperty('colors'));  // true
let son2 = new Son();
console.log(son2.colors);  // ['red']
console.log(Son.prototype.colors);  // ['red']

代码中son1.colors = []并不是修改原型属性colors[],而是在为实例son1添加新的属性colors

  • 原型链的另一个问题是,子类型在实例化时不能给父类型的构造函数传参。即不能在不影响其他对象实例的情况下传递参数给父类的构造函数。

那上面的代码来说就是,在创建Son对象实例的时候,不能指定colors的值。

综上所述:由于引用值和传参问题,原型链一般不会被单独使用。

二、盗用构造函数

为了解决原型包含引用值所导致的问题,出现了一种叫作"盗用构造函数"(constructor stealing)的技术。

2.1 基本思想

在子类构造函数中调用父类构造函数。主要是通过callapply来实现。

function Father() {
    this.colors = ['red'];
}
function Son() {
    // 在此通过call()调用父类构造函数
    Father.call(this);
}
let son1 = new Son();
console.log(son1.colors);  // ['red']
// 说明colors 是实例的自身属性
console.log(son1.hasOwnProperty('colors'));  // true
son1.colors.push('green');
console.log(son1.colors);  // ['red', 'green']
let son2 = new Son();
console.log(son2.colors);  // ['red']

new运算符调用构造函数的过程可知,会将函数中的this指向新创建的实例。所以Father.call(this);相当于实例调用了Father方法,然后添加了自身属性colors。所以后续son1.colors.push('green');并不会影响到其他实例。

2.2 可向父类构造函数传参

盗用构造函数的另外一个优点在于,可以在子类构造函数中向父类构造函数传参。

如下代码:

function Father(name) {
    this.name = name;
}
function Son(name) {
    Father.call(this, name);
}
let son = new Son('dali');
console.log(son);  // Son {name: 'dali'}

2.3 盗用构造函数的问题

盗用构造函数的主要问题如下:

  • 所有方法必须在构造函数中定义,所以方法不能重用。(即:即使功能相同的方法,每个实例上对应的该方法不是同一个函数对象)
function Father() {
    this.foo = function() {}
}
function Son() {
    Father.call(this);
}
let son1 = new Son();
let son2 = new Son();
console.log(son1.foo === son2.foo);  // false
  • 子类不能访问到父类原型上的方法。因为子类仅仅只是调用父类构造函数,并没有设置原型指向父类实例。子类和父类之间并没有建立原型关系。
let son = new Son();
console.log(son instanceof Father)  // false

综上所述:单独使用盗用构造函数也是不可行的。

三、组合继承(伪经典继承)

3.1 基本思想

组合继承综合了原型链和盗用构造函数,使用原型链继承原型上的属性和方法,通过盗用构造函数继承实例属性。

function Father(name) {
    this.name = name;
    this.colors = ['red'];
}
Father.prototype.sayHello = function() {
    console.log('hello');
}
function Son(name) {
    // 继承属性
    Father.call(this, name);
}
// 构建原型链,继承方法
Son.prototype = new Father();
let son1 = new Son('dali');
console.log(son1);  // {name: 'dali', ['red']}
son1.colors.push('green');
console.log(son1);  // {name: 'dali', ['red', 'green']}
let son2 = new Son('haha');
console.log(son2);  // {name: 'haha', ['red']}
// 每个实例都有自身的 colors 属性
console.log(son1.colors === son2.colors)  // false
// 实例间共享sayHello方法
console.log(son1.sayHello === son2.sayHello)  // true

通过调用父类构造函数,每个实例都有“自身”的原型属性(例如:colors),所以通过引用修改对应的对象时,不会影响其他实例,因为每个实例的引用值属性指向的对象不同。此外,通过原型链也实现了所以实例之间共享同一方法。

3.2 组合继承的问题

虽然组合继承弥补了原型链和盗用构造函数的不足,但是组合继承也存在效率问题:

  • 父类的构造函数会被调用两次
    • 一次时在创建子类原型的时候被调用
    • 另一次是实例化子类对象时在子类构造函数中被调用
  • 子类原型上存在不必要的属性
console.log(Son.prototype);  // Father {name: undefined, colors: Array(1)}

紧接着上述代码,我们可以看到子类构造函数的原型对象上有namecolors属性,但是每个Son对象实例上都有自身的namecolors属性,并不是继承自原型。所以,子类构造函数的原型对象上有namecolors属性是多余的。

  • 子类构造函数原型(prototype)上的constructor属性丢失
console.log(Son.prototype.constructor === Son)  //  false

修改构造函数的原型都会出现这种问题。

四、原型式继承

4.1 基本思想

function object(o) {
    function F();
    F.prototype = o;
    return new F();
}

其实就是在创建一个对象时,指定该对象的原型。

4.2 Object.create()

在ECMAScript 5 中增加了Object.create()方法,对原型式继承进行了规范化

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

(1)语法

Object.create(proto,[propertiesObject])
  • proto: 新创建对象的原型对象
  • propertiesObject: 可选。需要传入一个对象,该对象的属性类型参照Object.defineProperties()的第二个参数。如果该参数被指定且不为 undefined,该传入对象的自有可枚举属性(即其自身定义的属性,而不是其原型链上的枚举属性)将为新创建的对象添加指定的属性值和对应的属性描述符。
  • 返回值:一个新对象,带着指定的原型对象和属性。

(2)示例

o = Object.create(Object.prototype, {
  // foo会成为所创建对象的数据属性
  foo: {
    writable:true,
    configurable:true,
    value: "hello"
  },
  // bar会成为所创建对象的访问器属性
  bar: {
    configurable: false,
    get: function() { return 10 },
    set: function(value) {
      console.log("Setting `o.bar` to", value);
    }
  }
});

(3)手动实现

function objectCreate(proto, propertiesObject=undefined){
    // 构造函数
    function F() {
    }
    // 构造函数原型 prototype 链接到proto对象
    F.prototype = proto;

    // 创建对象
    const obj = new F();
    // 若参数 propertiesObject 被指定且不为 undefined
    if (propertiesObject !== undefined) {
        // 新创建的对象添加指定的属性值和对应的属性描述符。
        Object.defineProperties(obj, propertiesObject);
    }
    return obj;
}

五、寄生式继承

5.1 基本思想

创建一个实现继承的函数,以某种方式增强对象,然后返回这个对象。

function createAnother(original) {
    // 通过调用函数创建一个新对象
    let clone = Object(original);
    // 以某种方式增强这个对象
    clone.sayHi = function() {
        console.log('hi');
    };
    
    // 返回增强的对象
    return clone;
}

个人理解:寄生式继承就是通过一个函数,以当前对象为基础,创建一个新的对象,并为新的对象添加新的方法。

let obj = {};
let anotherObj = createAnother(obj);
anotherObj.sayHi();  // hi

5.2 寄生式继承

与盗用构造函数类似,寄生式继承中给对象新增的函数不能被重用。

六、寄生式组合继承

针对第三节中组合继承存在的问题,可以通过寄生式组合继承来解决。

6.1 基本思想

不通过调用父类构造函数给子类原型赋值,而是得到父类原型的一个副本。即使用寄生式继承来继承父类原型,然后将返回的新对象赋值给子类原型。

function inheritPrototype(subType, SuperType) {
    // 创建对象
    let prototype = Object(SuperType.prototype);
    // 增强对象(防止修改原型导致constructor丢失)
    prototype.constructor = subType;
    // 赋值对象
    subType.prototype = prototype
}
  • subType:子类构造函数
  • SuperType:父类构造函数

如上代码:

  • 首先创建一个父类原型的副本
  • 在副本上添加constructor属性,防止在修改原型时丢失了constructor属性
  • 最后修改子类构造函数的原型,实现继承
function Father(name) {
    this.name = name;
    this.colors = ['red'];
    console.log('父类构造函数调用了');
}
Father.prototype.sayHello = function() {
    console.log('hello');
}
function Son(name) {
    // 继承属性
    Father.call(this, name);
}
// 寄生式继承原型
inheritPrototype(Son, Father)

// 父类构造函数只在实例化时调用一次
let son = new Son('dali');  // 父类构造函数调用了

// 子类构造函数中不存在不必要的属性
console.log(Son.prototype)  // {sayHello: ƒ, constructor: ƒ}
// 子类构造函数的 constructor 属性未丢失
console.log(Son.prototype.constructor === Son)  // true

如上代码,寄生式组合继承解决了组合继承存在的一些问题。综上,寄生式组合继承可以算是引用类型继承的最佳模式。

但是,关于寄生式组合需要注意的一点是:寄生式继承函数在创建对象副本时,如果使用的是Object()函数,对于Object()函数如果给定值是一个已经存在的对象,则会返回这个已经存在的值(相同地址)。所以函数中prototype.constructor = subType;会修改父类原型上的constructor属性。

console.log(Father.prototype.constructor)  // ƒ Son(name) {// 继承属性 Father.call(this, name);}
console.log(Father.prototype.constructor === Father)  // false

但是,这并不会影响父类对象实例的创建

console.log(new Father('haha'))  // Father {name: 'haha', colors: Array(1)}

到此这篇关于分享JavaScript 中的几种继承方式的文章就介绍到这了,更多相关js继承方式内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: 分享JavaScript 中的几种继承方式

本文链接: https://www.lsjlt.com/news/149735.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • 分享JavaScript 中的几种继承方式
    目录一、原型链1.1 原型链的问题二、盗用构造函数2.1 基本思想2.2 可向父类构造函数传参2.3 盗用构造函数的问题三、组合继承(伪经典继承)3.1 基本思想3.2 组合继承的问...
    99+
    2022-11-13
  • JavaScript中常见的几种继承方式
    目录原型继承内存图分析盗用构造函数继承分析组合继承原型链继承寄生式继承寄生组合式继承原型继承 function Parent(name) { this.name = name }...
    99+
    2022-11-13
  • 浅谈JavaScript的几种继承实现方式
    目录当前需求: 实现 Student 继承自 Person构造函数Person构造函数Student希望满足的条件功能利用原形链实现方法的继承方式1: 子类原型指向父类原型方式2 子...
    99+
    2023-05-17
    JavaScrip 继承
  • 好程序员分享JavaScript六种继承方式详解
    好程序员分享JavaScript六种继承方式详解,继承是面向对象编程中又一非常重要的概念,JavaScript支持实现继承,不支持接口继承,实现继承主要依靠原型链来实现的 原型链 首先得要明白什么是原型链,在一篇文章看懂...
    99+
    2023-06-03
  • c++继承的实现方式有哪几种
    在C++中,有三种继承的实现方式:公有继承、私有继承和保护继承。 公有继承: 公有继承是最常用的继承方式。使用关键字"publi...
    99+
    2023-10-26
    c++
  • JavaScript实现继承的7种方式总结
    目录什么是继承实现继承的6种方式1. 原型链继承2. 构造函数继承3. 组合继承(伪经典继承)4. 原型式继承5. 寄生式继承6.寄生组合式继承7.class 类继承什么是继承 用官...
    99+
    2023-05-14
    JavaScript实现继承方式 JavaScript继承方式 JavaScript继承
  • JavaScript中六种面试常考继承方式总结
    目录原型链继承盗用构造函数组合继承原型式继承寄生式继承寄生式组合继承js的几种继承方式在我们面试的时候经常会被问到,所以深入理解js几种继承方式以及它们的优缺点是非常有必要的。 原型...
    99+
    2023-02-13
    JavaScript继承方式 JavaScript继承
  • js中有哪三种继承方式
    这篇文章主要介绍js中有哪三种继承方式,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!1.js原型(prototype)实现继承代码如下<body>  <s...
    99+
    2022-10-19
  • 分享JavaScript 类型判断的几种方法
    目录一、JavaScript 基本类型1、原始数据类型2、引用数据类型3、两种数据类型的存储方式二、Javascript 数据类型判断的几种方法1、typeof2、instanceo...
    99+
    2022-11-13
  • JavaScript继承的三种方法实例
    继承 1. 什么是继承 继承: 首先继承是一种关系,类(class)与类之间的关系,JS中没有类,但是可以通过构造函数模拟类,然后通过原型来实现继承。 继承也是为了数据共...
    99+
    2022-11-12
  • JavaScript实现继承的6种常用方式总结
    目录1.原型链继承2.借用构造函数继承3.组合继承(经典继承)4.原型式继承方法一:借用构造函数方法二:Object.create()5.寄生式继承6.寄生组合式继承7.ES6、Cl...
    99+
    2022-11-13
  • JavaScript中的继承方式有哪些
    这篇文章将为大家详细讲解有关JavaScript中的继承方式有哪些,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。类式继承(构造函数)JS中其实是没有类的概念的,所谓的类也...
    99+
    2022-10-19
  • JavaScript中有哪些继承方式
    这期内容当中小编将会给大家带来有关JavaScript中有哪些继承方式,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。原型链首先得要明白什么是原型链,在一篇文章看懂prot...
    99+
    2022-10-19
  • 分享Pytestfixture参数传递的几种方式
    目录1.背景2.fixture中参数传递的几种方式1)fixture中的函数返回2)与@pytest.mark.parametrize的结合3)fixture中的方法嵌套传递4)测试...
    99+
    2022-11-10
  • python 下载文件的几种方式分享
    目录1 、一般同步下载2、 使用流式请求,requests.get方法的stream3 、异步下载文件4、 异步拆分下载文件5、注意1 、一般同步下载 示例代码: import...
    99+
    2022-11-12
  • JavaScript中多种组合继承的示例分析
    这篇文章主要为大家展示了“JavaScript中多种组合继承的示例分析”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“JavaScript中多种组合继承的示例分析...
    99+
    2022-10-19
  • js继承的6种方式详解
    原型链继承 原型链继承是ECMAScript的主要继承方式。其基本思想就是通过原型继承多个引用类型的属性和方法。什么是原型链?每个构造函数都会有一个原型对象,调用构造函数创...
    99+
    2022-11-12
  • javascript 几种常用继承方法和信用盘平台搭建
    1.原型链继承(最简单)核心 (实现思路):信用盘平台搭建【企鹅21717-93408】用父类的实例充当子类原型对象 function Person(name) {this.name = name;this...
    99+
    2022-10-18
  • javascript的继承方式有哪些
    这篇文章主要讲解了“javascript的继承方式有哪些”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“javascript的继承方式有哪些”吧! ...
    99+
    2022-10-19
  • JavaScript的继承方式是什么
    本文小编为大家详细介绍“JavaScript的继承方式是什么”,内容详细,步骤清晰,细节处理妥当,希望这篇“JavaScript的继承方式是什么”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。JavaScript中...
    99+
    2023-06-30
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作