热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

JavaScript中的对象继承

文章目录1.原型链继承2.借用构造函数(经典继承)3.组合继承4.原型式继承5.寄生式继承6.寄生组合式继承1.原型链继承套路定义父类型构造函数给父类型的原型添加方法定义子类型的构

文章目录

      • 1.原型链继承
      • 2.借用构造函数(经典继承)
      • 3.组合继承
      • 4.原型式继承
      • 5.寄生式继承
      • 6.寄生组合式继承


1.原型链继承


  1. 套路

    • 定义父类型构造函数
    • 给父类型的原型添加方法
    • 定义子类型的构造函数
    • 创建父类型的对象赋值给子类型的原型
    • 将子类型原型的构造属性设置为子类型给子类型原型添加方法
    • 创建子类型的对象: 可以调用父类型的方法
  2. 关键

    子类型的原型为父类型的一个实例对象

//父类型
function Supper() {this.supProp = '父亲的原型链'
}
//给父类型的原型上增加一个[showSupperProp]方法,打印自身subProp
Supper.prototype.showSupperProp = function () {console.log(this.supProp)
}//子类型
function Sub() {this.subProp = '儿子的原型链'
}// 子类型的原型为父类型的一个实例对象
Sub.prototype = new Supper()
// 让子类型的原型的constructor指向子类型
// 如果不加,其构造函数找的[`new Supper()`]时从顶层Object继承来的构造函数,指向[`Supper()`]
Sub.prototype.constructor = Sub
//给子类型的原型上增加一个[showSubProp]方法,打印自身subProp
Sub.prototype.showSubProp = function () {console.log(this.subProp)
}var sub = new Sub()sub.showSupperProp() //父亲的原型链
sub.showSubProp() //儿子的原型链
console.log(sub)
/**
Sub {subProp: "儿子的原型链"}
subProp: "儿子的原型链"
__proto__: Supper
constructor: ƒ Sub()
showSubProp: ƒ ()
supProp: "父亲的原型链"
__proto__: Object
*/

问题:

1.引用类型的属性被所有实例共享,包含引用类型的属性值始终都会共享相应的值

​ 因为两个实例使用的是同一个原型对象,内存空间是共享的

举例:

function Parent () {this.names = ['kevin', 'daisy'];
}function Child () {}Child.prototype = new Parent();var child1 = new Child();child1.names.push('xiaofeixia');console.log(child1.names); // ["kevin", "daisy", "xiaofeixia"]var child2 = new Child();console.log(child2.names); // ["kevin", "daisy", "xiaofeixia"]

  1. 某些属性其实是保存在父类型的实例对象上的;我们通过直接打印对象是看不到这个属性的;

3.无法传递参数

2.借用构造函数(经典继承)

套路:

  • 定义父类型构造函数
  • 定义子类型构造函数
  • 在子类型构造函数中调用父类型构造

  1. 关键:
    • 在子类型构造函数中通用call()调用父类型构造函数
  2. 作用:

​ 能借用父类中的构造方法,但是不灵活

​ 3.缺点:方法都在构造函数中定义,每次创建实例都会创建一遍方法。

function Person(name, age) {this.name = namethis.age = age
}
function Student(name, age, price) {//此处利用call(),将 [Student]的this传递给Person构造函数Person.call(this, name, age) // 相当于: this.Person(name, age)/*this.name = namethis.age = age*/this.price = price
}var s = new Student('Tom', 20, 14000)
console.log(s.name, s.age, s.price)
//在[`Student`]中利用[`Person.call(this, name, age)`]改变了其this指向,所以可以实现此效果

父类原型对象中一旦存在父类之前自己定义的方法,那么子类将无法继承这些方法

相比原型链继承方式,父类的引用属性不会被共享,优化了第一种继承方式的弊端,但是只能继承父类的实例属性和方法,不能继承原型属性或者方法

所有的子类实例事实上会拥有两份父类的属性
一份在当前的实例自己里面(也就是person本身的),另一份在子类对应的原型对象中(也就是
person.__proto__里面);
当然,这两份属性我们无需担心访问出现问题,因为默认一定是访问实例本身这一部分的;

3.组合继承

优点:融合原型链继承和构造函数的优点,是 Javascript 中最常用的继承模式。

  1. 利用原型链实现对父类型对象的方法继承
  2. 利用super()借用父类型构建函数初始化相同属性

function Person(name, age) {this.name = namethis.age = age
}
Person.prototype.setName = function (name) {this.name = name
}function Student(name, age, price) {Person.call(this, name, age) // 为了得到属性this.price = price
}
Student.prototype = new Person() // 为了能看到父类型的方法
Student.prototype.constructor = Student //修正constructor属性
Student.prototype.setPrice = function (price) {this.price = price
}var s = new Student('Tom', 24, 15000)
s.setName('Bob')
s.setPrice(16000)
console.log(s.name, s.age, s.price)

4.原型式继承

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

就是 ES5 Object.create 的模拟实现,将传入的对象作为创建的对象的原型。

也可以直接借助Object.create方法实现普通对象的继承

利用一个空对象作为中介,将某个对象直接赋值给空对象构造函数的原型。

缺点:

​ 包含引用类型的属性值始终都会共享相应的值,

var person = {name: 'kevin',friends: ['daisy', 'kelly']
}var person1 = createObj(person);
var person2 = createObj(person);person1.name = 'person1';
console.log(person2.name); // kevinperson1.firends.push('taylor');
console.log(person2.friends); // ["daisy", "kelly", "taylor"]

最终的结果:person1,person2 对象的原型指向了person对象;

注意:修改person1.name的值,person2.name的值并未发生改变,并不是因为person1person2有独立的 name 值,而是因为person1.name = 'person1',给person1添加了 name 值,并非修改了原型上的 name 值。

代码2:

let parent4 = {name: "parent4",friends: ["p1", "p2", "p3"],getName: function() {return this.name;}};let person4 = Object.create(parent4);//借助Object.create方法实现普通对象的继承person4.name = "tom";person4.friends.push("jerry");let person5 = Object.create(parent4);person5.friends.push("lucy");console.log(person4.name); // tomconsole.log(person4.name === person4.getName()); // trueconsole.log(person5.name); // parent4console.log(person4.friends); // ["p1", "p2", "p3","jerry","lucy"]console.log(person5.friends); // ["p1", "p2", "p3","jerry","lucy"]

这里主要借助Object.create方法实现普通对象的继承

5.寄生式继承

创建一个仅用于封装继承过程的函数,该函数在内部以某种形式来做增强对象,最后返回对象。

function createObj (o) {var clone = object.create(o);clone.sayName = function () {console.log('hi');}return clone;
}

缺点:跟借用构造函数模式一样,每次创建对象都会创建一遍方法。

代码2:

let parent5 = {name: "parent5",friends: ["p1", "p2", "p3"],getName: function() {return this.name;}
};function clone(original) {let clone = Object.create(original);clone.getFriends = function() {return this.friends;};return clone;
}let person5 = clone(parent5);console.log(person5.getName()); // parent5
console.log(person5.getFriends()); // ["p1", "p2", "p3"]

寄生式继承在上面继承基础上进行优化,利用这个浅拷贝的能力再进行增强,添加一些方法

6.寄生组合式继承

现在我们来回顾一下之前提出的比较理想的组合继承
组合继承是比较理想的继承方式, 但是存在两个问题:

  • 问题一: 构造函数会被调用两次: 一次在创建子类型原型对象的时候, 一次在创建子类型实例的时候.

  • 问题二: 父类型中的属性会有两份: 一份在原型对象中, 一份在子类型实例中.

  • 事实上, 我们现在可以利用寄生式继承将这两个问题给解决掉.

需要先明确一点: 当我们在子类型的构造函数中调用父类型.call(this, 参数)这个函数的时候, 就会将父类型中的属性和方法复制一份到了子类型中. 所以父类型本身里面的内容, 我们不再需要.

  • 这个时候, 我们还需要获取到一份父类型的原型对象中的属性和方法.
  • 能不能直接让子类型的原型对象= 父类型的原型对象呢?
  • 不要这么做, 因为这么做意味着以后修改了子类型原型对象的某个引用类型的时候, 父类型原生对象的引用类型也会被修改.
  • 我们使用前面的寄生式思想就可以了.

//定义寄生式核心函数
function inheritPrototype(subType, superType){var prototype = Object.create(superType.prototype); // 创建对象,创建父类原型的一个副本prototype.constructor = subType; // 增强对象,弥补因重写原型而失去的默认的constructor 属性subType.prototype = prototype; // 指定对象,将新创建的对象赋值给子类的原型
}// 父类初始化实例属性和原型属性
function SuperType(name){this.name = name;this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){alert(this.name);
};// 借用构造函数传递增强子类实例属性(支持传参和避免篡改)
function SubType(name, age){SuperType.call(this, name);this.age = age;
}// 将父类原型指向子类
inheritPrototype(SubType, SuperType);// 新增子类原型属性
SubType.prototype.sayAge = function(){alert(this.age);
}var instance1 = new SubType("xyc", 23);
var instance2 = new SubType("lxy", 23);instance1.colors.push("2"); // ["red", "blue", "green", "2"]
instance1.colors.push("3"); // ["red", "blue", "green", "3"]


推荐阅读
  • ECMA262规定typeof操作符的返回值和instanceof的使用方法
    本文介绍了ECMA262规定的typeof操作符对不同类型的变量的返回值,以及instanceof操作符的使用方法。同时还提到了在不同浏览器中对正则表达式应用typeof操作符的返回值的差异。 ... [详细]
  • PHP反射API的功能和用途详解
    本文详细介绍了PHP反射API的功能和用途,包括动态获取信息和调用对象方法的功能,以及自动加载插件、生成文档、扩充PHP语言等用途。通过反射API,可以获取类的元数据,创建类的实例,调用方法,传递参数,动态调用类的静态方法等。PHP反射API是一种内建的OOP技术扩展,通过使用Reflection、ReflectionClass和ReflectionMethod等类,可以帮助我们分析其他类、接口、方法、属性和扩展。 ... [详细]
  • node.jsurlsearchparamsAPI哎哎哎 ... [详细]
  • 使用nodejs爬取b站番剧数据,计算最佳追番推荐
    本文介绍了如何使用nodejs爬取b站番剧数据,并通过计算得出最佳追番推荐。通过调用相关接口获取番剧数据和评分数据,以及使用相应的算法进行计算。该方法可以帮助用户找到适合自己的番剧进行观看。 ... [详细]
  • vue使用
    关键词: ... [详细]
  • Java序列化对象传给PHP的方法及原理解析
    本文介绍了Java序列化对象传给PHP的方法及原理,包括Java对象传递的方式、序列化的方式、PHP中的序列化用法介绍、Java是否能反序列化PHP的数据、Java序列化的原理以及解决Java序列化中的问题。同时还解释了序列化的概念和作用,以及代码执行序列化所需要的权限。最后指出,序列化会将对象实例的所有字段都进行序列化,使得数据能够被表示为实例的序列化数据,但只有能够解释该格式的代码才能够确定数据的内容。 ... [详细]
  • PHP中的单例模式与静态变量的区别及使用方法
    本文介绍了PHP中的单例模式与静态变量的区别及使用方法。在PHP中,静态变量的存活周期仅仅是每次PHP的会话周期,与Java、C++不同。静态变量在PHP中的作用域仅限于当前文件内,在函数或类中可以传递变量。本文还通过示例代码解释了静态变量在函数和类中的使用方法,并说明了静态变量的生命周期与结构体的生命周期相关联。同时,本文还介绍了静态变量在类中的使用方法,并通过示例代码展示了如何在类中使用静态变量。 ... [详细]
  • 不同优化算法的比较分析及实验验证
    本文介绍了神经网络优化中常用的优化方法,包括学习率调整和梯度估计修正,并通过实验验证了不同优化算法的效果。实验结果表明,Adam算法在综合考虑学习率调整和梯度估计修正方面表现较好。该研究对于优化神经网络的训练过程具有指导意义。 ... [详细]
  • 在编写业务代码时,常常会遇到复杂的业务逻辑导致代码冗长混乱的情况。为了解决这个问题,可以利用中间件模式来简化代码逻辑。中间件模式可以帮助我们更好地设计架构和代码,提高代码质量。本文介绍了中间件模式的基本概念和用法。 ... [详细]
  • JDK源码学习之HashTable(附带面试题)的学习笔记
    本文介绍了JDK源码学习之HashTable(附带面试题)的学习笔记,包括HashTable的定义、数据类型、与HashMap的关系和区别。文章提供了干货,并附带了其他相关主题的学习笔记。 ... [详细]
  • 本文介绍了在wepy中运用小顺序页面受权的计划,包含了用户点击作废后的从新受权计划。 ... [详细]
  • 模板引擎StringTemplate的使用方法和特点
    本文介绍了模板引擎StringTemplate的使用方法和特点,包括强制Model和View的分离、Lazy-Evaluation、Recursive enable等。同时,还介绍了StringTemplate语法中的属性和普通字符的使用方法,并提供了向模板填充属性的示例代码。 ... [详细]
  • Netty源代码分析服务器端启动ServerBootstrap初始化
    本文主要分析了Netty源代码中服务器端启动的过程,包括ServerBootstrap的初始化和相关参数的设置。通过分析NioEventLoopGroup、NioServerSocketChannel、ChannelOption.SO_BACKLOG等关键组件和选项的作用,深入理解Netty服务器端的启动过程。同时,还介绍了LoggingHandler的作用和使用方法,帮助读者更好地理解Netty源代码。 ... [详细]
  • 用Vue实现的Demo商品管理效果图及实现代码
    本文介绍了一个使用Vue实现的Demo商品管理的效果图及实现代码。 ... [详细]
  • 本文介绍了GTK+中的GObject对象系统,该系统是基于GLib和C语言完成的面向对象的框架,提供了灵活、可扩展且易于映射到其他语言的特性。其中最重要的是GType,它是GLib运行时类型认证和管理系统的基础,通过注册和管理基本数据类型、用户定义对象和界面类型来实现对象的继承。文章详细解释了GObject系统中对象的三个部分:唯一的ID标识、类结构和实例结构。 ... [详细]
author-avatar
少钧13
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有