记录-对象有哪些继承方式

yizhihongxing

这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助

记录-对象有哪些继承方式

1. 原型链

温故而知新:

构造函数、原型和实例的关系:
  每个构造函数都有一个原型对象,原型有一个属性指回构造函数,实例有一个内部指针指向原型。

思考:如果原型是另一个类型的实例呢?
  那就意味着这个原型本身有一个内部指针指向另一个原型,相应的另一个原型也有一个指针指向另一个构造函数。这样就在实例和原型之间构造了一条原型链。这就是原型链的基本思想。
实现原型链涉及如下代码模式:

function SuperType(){
    this.property = true;
}

SuperType.prototype.getSuperValue = function(){
    return this.property;
}

function SubType(){
    this.subproperty = false;
}

// 继承SuperType
SubType.prototype = new SuperType()

SubType.prototype.getSubValue = function(){
    return this.subproperty;
}

let instance = new SubType()
console.log(instance.getSuperValue()); //true

 以上的代码定义了两个类型:SuperType和SubType。这两个类型分别定义了一个属性和方法。两个类型的主要区别是:SubType通过创建SuperType的实例并将其赋值给自己的原型SubType.prototype实现了对SuperType的继承。这个赋值重写了SubType最初的原型,将其替换成SuperType的实例。这意味着SuperType实例可以访问的所有属性和方法也会存在于SubType.prototype。这样实现继承后,紧接着又给SuperType的实例添加了一个新的方法。最后又创建了SubType的实例并调用了它继承的getSuperValue方法。

默认原型

  默认情况下,所有引用类型都继承自Object,这也是通过原型链实现的。任何函数默认的原型都是一个Object的实例,这意味着这个实例有一个内部指针指向Object.prototype。这也是为什么自定义类型能够继承包括toString()、valueOf在内部的所有默认方法的原因。
  SubType继承SuperType,而SuperType继承Object。在调用instance.toString时,实际上调用的是保存在Object.prototype上的方法。

原型与继承的关系

  原型与继承的关系可以通过两种方式来确定:

使用instanceof操作符:

如果一个实例的原型中出现过相应的构造函数,则instanceof返回true

    console.log(instance instanceof  Object); //true
    console.log(instance instanceof  SuperType); //true
    console.log(instance instanceof  SubType); //true

从技术上讲,instance是Object、SuperType、SubType的实例,因为instance的原型链中包含这些构造函数的原型。结果就是instanceof对所有这些构造函数都返回true。

使用isPrototypeOf()方法

原型链中的每个原型都可以调用这个方法,这要原型链中包含这个原型,这个方法就会返回true

    console.log(Object.prototype.isPrototypeOf(instance)); //true
    console.log(SuperType.prototype.isPrototypeOf(instance)); //true
    console.log(SubType.prototype.isPrototypeOf(instance)); //true

关于方法

子类有时需要覆盖父类的方法,或者增加父类没有的方法。为此,这些方法必须在原型赋值之后再添加到原型上。来看下面的例子:

function SuperType(){
    this.property = true;
}

SuperType.prototype.getSuperValue = function(){
    return this.property;
}

function SubType(){
    this.subproperty = false;
}

// 继承SuperType
SubType.prototype = new SuperType()

// 新方法 
SubType.prototype.getSubValue = function(){
    return this.subproperty;
}

// 覆盖已有的方法
SubType.prototype.getSuperValue = function(){
    return false;
}

let instance = new SubType()
console.log(instance.getSuperValue()); //false

以上代码新增两个方法。第一个方法getSubValue()是SubType的新方法。而第二个方法getSuperValue()是原型链上已经存在但在这里被遮蔽的方法。;后面在SubType实例上调用getSuperValue()时调用的是这个方法。而SuperValue的实例仍然会调用最初的方法。重点在于上述两个方法都是在把原型赋值为SuperType的实例之后定义的。
  另一个要理解的重点,以对象字面量方式创建原型方法,会破环之前的原型链。因为这样相当于重写原型链。

   function SuperType(){
        this.property = true;
    }

    SuperType.prototype.getSuperValue = function(){
        return this.property;
    }

    function SubType(){
        this.subproperty = false;
    }

    // 继承SuperType
    SubType.prototype = new SuperType()

    // 通过对象字面量添加新的方法,会导致上一行无效
    SubType.prototype = {
        getSubValue(){
            return this.subproperty;
        },
        someOtherMethod(){
            return false;
        }
    }

    let instance = new SubType()
    console.log(instance.getSuperValue()); //instance.getSuperValue is not a function

子类的原型在被赋值为SuperType的实例后,又被一个对象字面量覆盖了。覆盖后的原型是一个Object的实例,而不再是SuperType的实例。因此,之前的原型链就断了。SubType和SuperType之间也没有关系了。

原型链的额问题

原先的实例属性变成了原型属性
  原型包含的引用值会在所有实例间共享,在使用原型实现继承时,原型实际上变成了另一个类型的实例。

    function SuperType(){
        this.colors= ["red","blue","green"]
    }
    function SubType() { 

    }
    // 继承SuperType
    SubType.prototype = new SuperType()

    let instance1  = new SubType()
    instance1.colors.push("black");
    console.log(instance1.colors); //["red", "blue", "green", "black"]

    let instance2 = new SubType()
    console.log(instance2.colors); //["red", "blue", "green", "black"]

SuperType构造函数定义了一个colors属性,其中包含一个数组(引用值)。每个SuperType的实例都会有自己的colors属性,包含自己的数组。但是当SubType通过原型继承SuperType后,SubType.prototype变成了SuperType的一个实例,因而也获得了自己的colors属性。这类似于创建了SubType.prototype.colors属性。最终结果是,SubType的所有实例都会共享这个colors属性。这一点通过instance1.colors上的属性也能反应到instance2.colors上就可以看出。

子类型在实例化时不能给夫类型的构造函数传参
  我们无法在不影响所有对象实例的情况下把参数传进父类的构造函数。再加上原型中包含引用值的问题,就导致原型链基本不会被单独使用。

2. 盗用构造函数

  盗用构造函数是用来解决原型包含引用值导致的继承问题。基本思路:在子类构造函数中调用父类构造函数。因为函数是在特定上下文中执行代码的简单对象,所以可以使用apply()和call()方法以新创建的对象上下文执行构造函数。

function    SuperType(){
    this.colors = ["red","blue","green"];
}

function SubType(){
    // 继承SuperType
    /* *********** */
    SuperType.call(this);
    /* *********** */
}

let instance1 = new SubType();
instance1.colors.push("black");
console.log(instance1.colors); // ["red", "blue", "green", "black"]

let instance2 = new SubType()
console.log(instance2.colors); //["red", "blue", "green"]

以上代码展示了盗用构造函数的调用。通过使用call()或者apply()方法,SuperType构造函数在为SubType的实例创建的新对象的上下文中执行了。这相当于新的SubType对象上运行了SuperType函数中的所有初始化代码。结果就是每个实例都会有自己的colors属性。

1. 优点

  可以在子类构造函数中向父类构造函数传参

function SuperType(name){
    this.name = name;
}

function SubType(){
    // 继承SuperType并传参
    SuperType.call(this,"Nicholas");
    // 实例属性
    this.age = 29
}

let instance = new SubType()
console.log(instance.name); //Nicholas
console.log(instance.age); //29

 SuperType构造函数接受一个参数name,然后将它赋值给一个属性。在SubType构造函数中调用SuperType构造函数时传入这个参数,实际上会在SubType的实例上定义name属性。为确保SuperType构造函数不会覆盖SubType定义的属性,可以在调用父类构造函数之后再给子类实例添加额外的属性。

2. 缺点

必须在构造函数中定义方法,因此函数不能重用。
子类不能访问父类原型上定义的方法,因此所有类型只能使用构造函数模式。
由于存在以上问题,所以盗用构造函数基本上也不能单独使用。

3. 组合继承  

组合继承也叫伪经典继承,综合了原型链和盗用构造函数,将两者的优点集中起来。基本思路是:使用原型链继承原型上的属性和方法,通过盗用构造函数继承实例属性。这样既可以把方法定义在原型上以实现重用,又可以让每个实例都有自己的属性。

/* 组合继承 */

function SuperType(name){
    this.name = name;
    this.colors = ["red", "blue", "green"]
}
SuperType.prototype.sayName = function(){
    console.log(this.name);
}
function SubType(name,age){
    // 继承属性
    SuperType.call(this,name)
    
    this.age = age
}

// 继承方法  
SubType.prototype = new SuperType()
SubType.prototype.sayAge = function(){
    console.log(this.age);
}

let instance1 = new SubType("Nicholas",29)
instance1.colors.push("blcak");
console.log(instance1.colors); //["red", "blue", "green", "blcak"]
instance1.sayName() //Nicholas
instance1.sayAge(); //29

let instance2 = new SubType("Greg",27)
console.log(instance2.colors); //["red", "blue", "green"]
instance2.sayName(); //Greg
instance2.sayAge(); //27

SuperType构造函数定义了两个属性name和colors,而它的原型上也定义了一个方法叫sayName()。SubType构造函数调用了SuperType构造函数,传入name参数,然后又定义了自己的属性age。此外,SubType.prototype也被赋值为SuperType的实例。原型赋值之后,又在这个原型上添加了新方法sayAge()。这样就可以创建两个SubType实例,让这两个实例都有自己的属性,包括colors,同时还共享相同的方法。
  组合继承弥补了原型链和盗用构造函数的不足,是JavaScript中使用最多的继承模式。而且组合继承也保留了instanceof操作符和isPrototypeOf()方法识别合成对象的能力。
缺点:
  父类构造函数会被调用两次,一次是在创建子类原型时调用,一次是在子类构造函数中调用。
  本质上,子类原型最终是要包含超类对象的所有实例属性,子类构造函数只要在执行时重写自己的原型就行了。

4. 原型式继承

  2006年Douglas Crockford的文章:《JavaScript中的原型式继承》中介绍了一种不涉及严格意义上构造函数的继承方法。作者的出发点是即使不自定义类型也可以通过原型实现对象之间的信息共享。文章给出了一个函数:

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

  这个object方法会创建一个临时构造函数,将传入的对象赋值给这个构造函数的原型,然后返回这个临时类型的一个实例。本质上,object()是对传入对象执行了一次浅复制。

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

let person = {
    name:"Nicholas",
    friends:["Shelby",'Court','Van']
};
let anotherPerson = object(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");

let yetAnotherPerson = object(person);
yetAnotherPerson.name = 'Linda';
yetAnotherPerson.friends.push("Barbie")

console.log(person.friends); //["Shelby", "Court", "Van", "Rob", "Barbie"]

ECMAScript通过增加Object.create()方法将原型式继承的概念规范化。这个方法接受两个参数:作为新对象原型的对象,给新对象定义额外属性的对象(可选)。在只有一个参数时,Object.create()与这里的object()方法效果类似。

let person = {
    name:"Nicholas",
    friends:["Shelby",'Court','Van']
}
let anotherPerson = Object.create(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");

let yetAnotherPerson = Object.create(person);
yetAnotherPerson.name = "Linda"
yetAnotherPerson.friends.push("Barbie")

console.log(person.friends); //["Shelby", "Court", "Van", "Rob", "Barbie"]

Object.create()的第二个参数与Object.defineProperties()的第二个参数一样:每个新增参数属性都通过各自的描述符来描述。以这种方式添加的属性会遮蔽原型对象上的同名属性。

let person = {
    name:"Nicholas",
    friends:["Shelby",'Court','Van']
}
let anotherPerson = Object.create(person,{
    name:{
        value:'Greg'
    }
});
console.log(anotherPerson.name); //Greg

原型式继承非常适合不需要单独创建构造函数,但仍然需要在对象间共享信息的场合。但要记住,属性中包含的引用值始终会在相关对象间共享,跟使用原型模式时一样的。

5. 寄生式继承

  寄生式继承背后的思路类似于寄生构造函数和工厂模式:创建一个实现继承的函数,以某种方式增强对象,然后返回这个对象。基本的寄生继承模式如下:

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

createAnother()函数接受一个参数,就是新对象的基准对象。这个对象original会被传给object()函数,然后将返回的新对象赋值给clone对象添加一个新方法sayHi()。最后返回这个对象。

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

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

let person = {
    name:"Nicholas",
    friends:["Shelby",'Court','Van']
}

let anotherPerson = createAnother(person);
anotherPerson.sayHi(); //Hi
 

 这个例子基于person对象返回了一个新对象。新返回的anotherPerson对象具有person的所有属性和方法,还有一个新方法叫sayHi()
  寄生式继承同样适合主要关注对象,而不在乎类型和构造函数的场景。object()函数不是寄生式继承所必需的,任何返回新对象的函数都可以在这里使用。
==注意:==通过寄生式继承给对象添加函数会导致函数难以重用,与构造函数模式类似。

6. 寄生式组合继承

  通过盗用构造函数继承属性,但使用混合式原型链继承方法。基本思路是不通过调用父类构造函数给子类原型赋值,而实取得父类原型的一个副本。说到底就是使用寄生式继承来继承父类原型,然后将返回的新对象赋值给子类原型。

function inheritPrototype(subType,superType){
    let prototype = object(superType.prototype); //创建对象
    prototype.constructor = subType; //增强对象
    subType.prototype = prototype; //赋值对象
}

这个inheritPrototype()函数实现了寄生式组合继承的核心逻辑。这个函数接受到两个参数:子类构造函数和父类构造函数。这个函数内部,第一步是创建一个父类原型的一个副本。然后,给返回的prototype对象设置constructor属性,解决由于重写原型导致默认constructor丢失的问题。最后将新创建的对象赋值给子类型的原型。调用inheritPrototype()就可以实现前面例子中的子类型原型赋值:

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

function inheritPrototype(subType,superType){
    let prototype = object(superType.prototype); //创建对象
    prototype.constructor = subType; //增强对象
    subType.prototype = prototype; //赋值对象
}

function SuperType(name){
    this.name = name;
    this.colors =["red", "blue", "green"];
}

SuperType.prototype.sayName = function(){
    console.log(this.name);
}

function SubType(name,age){
    SuperType.call(this,name);
    this.age = age;
}

inheritPrototype(SubType,SuperType);

SubType.prototype.sayAge = function(){
    console.log(this.age);
}

这里只调用了一次SuperType构造函数,避免了SubType.prototype上不必要也用不到的属性,因此可以说这个例子效率更高。而且,原型链仍然保持不变,因此,instanceof操作符和isPrototypeOf()方法正常有效。寄生式组合继承可以说是引用类型继承的最佳方式。

本文转载于:

https://blog.csdn.net/qq_39200185/article/details/115134869

如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。

 记录-对象有哪些继承方式

原文链接:https://www.cnblogs.com/smileZAZ/p/17371915.html

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:记录-对象有哪些继承方式 - Python技术站

(0)
上一篇 2023年5月3日
下一篇 2023年5月5日

相关文章

  • javascript实现数字时钟特效

    下面是实现数字时钟特效的完整攻略。 一、准备工作 在开始实现之前,我们需要先搭建一个简单的HTML框架,并且引入jQuery库和一个字体库。 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> &l…

    JavaScript 2023年5月27日
    00
  • MVVM 双向绑定的实现代码

    MVVM(Model-View-ViewModel)是一种经典的设计模式,其最大的优势是可以实现双向绑定。在MVVM中,Model表示数据模型,View表示用户界面,ViewModel是连接Model和View的纽带。在MVVM中,数据流是单向的,即从Model流向View,而ViewModel则起到了一个桥梁的作用。这种单向数据流的机制本质上就实现了数据与…

    JavaScript 2023年6月11日
    00
  • 5种方法告诉你如何使JavaScript 代码库更干净

    5种方法告诉你如何使JavaScript 代码库更干净 在开发 JavaScript 代码时,保持代码库干净是非常重要的。这可以提高代码的可读性和可维护性,使代码更易于维护。下面是几种方法可以帮助你保持 JavaScript 代码库的干净。 1. 使用严格模式 JavaScript 的 “use strict” 指令可以让代码在严格模式下执行。这种模式可以避…

    JavaScript 2023年6月11日
    00
  • JavaScript中Function函数与Object对象的关系

    在JavaScript中,函数和对象是密切相关的。每个函数都是一个对象,并且可以像对象一样使用和传递。在此过程中,函数在对象中的重要性很大。下面将详细讲解Function函数和Object对象之间的关系。 Function与Object 每一个函数都是一个Function类型的对象,因此具有Function所继承的方法和属性。函数的定义可以采用最简单的字面量…

    JavaScript 2023年5月27日
    00
  • JavaScript实现简单表单验证案例

    当编写一个表单的时候,为了保证提交的数据正确性以及安全性,我们需要对用户输入的数据进行验证。而 JavaScript 可以提供灵活的表单验证功能,使得表单数据的验证更加方便和高效。 以下是一个可用于表单验证的基本攻略: 获取表单元素对象 通过使用 JavaScript的 DOM 操作获取表单元素对象,以便于后续的获取用户输入和验证数据。 示例: let in…

    JavaScript 2023年6月10日
    00
  • JavaScript整除运算函数ceil和floor的区别分析

    下面我来为你讲解一下“JavaScript整除运算函数ceil和floor的区别分析”。 1. 序言 在 JavaScript 中,Math.ceil() 和 Math.floor() 都是用于实现上取整和下取整操作的函数。在实际开发过程中,这两个函数经常被用来计算数据的精度。但是这两个函数之间还是有一些微小的差异,接下来我们将会逐一解释它们之间的区别。 2…

    JavaScript 2023年6月11日
    00
  • JavaScript高级程序设计 阅读笔记(十二) js内置对象Math

    以下是对JavaScript高级程序设计中Math对象的详细讲解: 什么是Math对象 Math对象是JavaScript内置的一个全局对象,提供了许多数学计算相关的方法和常量。通过调用Math对象提供的方法和属性,我们可以进行数值的运算、随机数的生成等操作。 常用方法 Math.abs() Math.abs() 方法用于返回一个数的绝对值,即该数与 0 的…

    JavaScript 2023年5月27日
    00
  • js数组转json并在后台对其解析具体实现

    下面是详细讲解“js数组转json并在后台对其解析具体实现”的完整攻略。 什么是JSON JSON是JavaScript Object Notation(JavaScript对象标记语言)的缩写,它是一种轻量级的数据交换格式,易于人和计算机解读和编写。在JavaScript中,它通常用于数据传输,尤其是在与服务器进行数据交互时。 js数组转换成JSON的方法…

    JavaScript 2023年5月27日
    00
合作推广
合作推广
分享本页
返回顶部