热门标签 | 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"]


推荐阅读
  • 基于Node.js的高性能实时消息推送系统通过集成Socket.IO和Express框架,实现了高效的高并发消息转发功能。该系统能够支持大量用户同时在线,并确保消息的实时性和可靠性,适用于需要即时通信的应用场景。 ... [详细]
  • 本文将继续探讨 JavaScript 函数式编程的高级技巧及其实际应用。通过一个具体的寻路算法示例,我们将深入分析如何利用函数式编程的思想解决复杂问题。示例中,节点之间的连线代表路径,连线上的数字表示两点间的距离。我们将详细讲解如何通过递归和高阶函数等技术实现高效的寻路算法。 ... [详细]
  • 重要知识点有:函数参数默许值、盈余参数、扩大运算符、new.target属性、块级函数、箭头函数以及尾挪用优化《深切明白ES6》笔记目次函数的默许参数在ES5中,我们给函数传参数, ... [详细]
  • 解决Bootstrap DataTable Ajax请求重复问题
    在最近的一个项目中,我们使用了JQuery DataTable进行数据展示,虽然使用起来非常方便,但在测试过程中发现了一个问题:当查询条件改变时,有时查询结果的数据不正确。通过FireBug调试发现,点击搜索按钮时,会发送两次Ajax请求,一次是原条件的请求,一次是新条件的请求。 ... [详细]
  • 本文详细介绍了 PHP 中对象的生命周期、内存管理和魔术方法的使用,包括对象的自动销毁、析构函数的作用以及各种魔术方法的具体应用场景。 ... [详细]
  • 检查在所有可能的“?”替换中,给定的二进制字符串中是否出现子字符串“10”带 1 或 0 ... [详细]
  • 在PHP中如何正确调用JavaScript变量及定义PHP变量的方法详解 ... [详细]
  • 技术分享:使用 Flask、AngularJS 和 Jinja2 构建高效前后端交互系统
    技术分享:使用 Flask、AngularJS 和 Jinja2 构建高效前后端交互系统 ... [详细]
  • 利用 JavaScript 和 Node.js 验证时间的有效性
    本文探讨了如何使用 JavaScript 和 Node.js 验证时间的有效性。通过编写一个 `isTime` 函数,我们可以确保输入的时间格式正确且有效。该函数利用正则表达式匹配时间字符串,检查其是否符合常见的日期时间格式,如 `YYYY-MM-DD` 或 `HH:MM:SS`。此外,我们还介绍了如何处理不同时间格式的转换和验证,以提高代码的健壮性和可靠性。 ... [详细]
  • 在对WordPress Duplicator插件0.4.4版本的安全评估中,发现其存在跨站脚本(XSS)攻击漏洞。此漏洞可能被利用进行恶意操作,建议用户及时更新至最新版本以确保系统安全。测试方法仅限于安全研究和教学目的,使用时需自行承担风险。漏洞编号:HTB23162。 ... [详细]
  • C++ 异步编程中获取线程执行结果的方法与技巧及其在前端开发中的应用探讨
    本文探讨了C++异步编程中获取线程执行结果的方法与技巧,并深入分析了这些技术在前端开发中的应用。通过对比不同的异步编程模型,本文详细介绍了如何高效地处理多线程任务,确保程序的稳定性和性能。同时,文章还结合实际案例,展示了这些方法在前端异步编程中的具体实现和优化策略。 ... [详细]
  • 本文详细探讨了使用纯JavaScript开发经典贪吃蛇游戏的技术细节和实现方法。通过具体的代码示例,深入解析了游戏逻辑、动画效果及用户交互的实现过程,为开发者提供了宝贵的参考和实践经验。 ... [详细]
  • 本文详细探讨了JavaScript中数组去重的各种方法,并通过实际代码示例进行了深入解析。文章首先介绍了几种常见的去重技术,包括使用Set对象、过滤方法和双重循环等。每种方法都附有具体的实现代码,帮助读者更好地理解和应用这些技术。此外,文中还讨论了不同方法在性能上的优劣,为开发者提供了实用的参考。 ... [详细]
  • 在处理木偶评估函数时,我发现可以顺利传递本机对象(如字符串、列表和数字),但每当尝试将JSHandle或ElementHandle作为参数传递时,函数会拒绝接受这些对象。这可能是由于这些句柄对象的特殊性质导致的,建议在使用时进行适当的转换或封装,以确保函数能够正确处理。 ... [详细]
  • 在JavaWeb开发中,文件上传是一个常见的需求。无论是通过表单还是其他方式上传文件,都必须使用POST请求。前端部分通常采用HTML表单来实现文件选择和提交功能。后端则利用Apache Commons FileUpload库来处理上传的文件,该库提供了强大的文件解析和存储能力,能够高效地处理各种文件类型。此外,为了提高系统的安全性和稳定性,还需要对上传文件的大小、格式等进行严格的校验和限制。 ... [详细]
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社区 版权所有