ES5中的继承
时间:2022-07-27
本文章向大家介绍ES5中的继承,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。
面向对象的三大特性是:封装、继承、多态。其中继承是最难理解的,也是最重要的部分。 JS中本身没有专门继承的语法,它是使用各种代码的模拟来实现的。即使ES6有了正真的继承语法,其本质也是ES5中继承的语法糖。目前ES5继承最被人津津乐道的就是尼古拉斯的著名书籍《JavaScript高级程序设计》中记录的6中方法。本文也是摘自这本本书的这部分的核心内容,并整理给大家呈现出来。
- 原型链继承
原型链继承是最为经典的一种继承,其继承方式就是子类的原型指向父类的实例。下面我们来看一个这种继承的例子:
function Person(name){ this.name = name; this.hobbies = ["coding","running"]; } Person.prototype.sayName = function(){ console.log("我是:" + this.name); }; function Man(name){ this.name = name; } Man.prototype = new Person();//原型继承 var lufei = new Man("海贼王的男人"); lufei.sayName();// 我是:海贼王的男人 我们可以看到子类的对象
lufei
已经拥有父类的方法,说明继承成功了。上面最重要的一行代码是第14行,印证了子类的原型指向父类的实例。原型继承其实就是利用原型链来实现的,如果在子类中没有找到某个属性和方法就会去子类的原型中去找,如果父类的实例没有又会去父类的原型去找,直到找到Object的原型为止。 原型继承是最简单最长用的一种继承方式,但是它有自己的缺点: 缺点1:父类中引用类型的属性,会被子类共享。 如上例中的hobbies属性,每个子类的实例都指向了同一个hobbies属性。如果某个子类不重写hobbies并且给他添加了一个值,那么所有的没有重写hobbies的子类的这个值都将会改变(同一个对象)。 缺点2:创建子类的时候,无法调用父元素的构造函数。 如上例中的this.name = name;
在父类中已有相同的代码无法做到复用。 - 借用构造函数实现继承
借用构造函数实现的继承是解决原型继承的缺点而出现的,他的核心思想就是子类通过call(或者apply)调用父类的构造函数。请看下面的例子:
function Person(){ this.hobbies = ["coding","running"]; } function Man(){ Person.call(this); // 借用构造函数的继承 } var lufei = new Man(); var nami = new Man(); lufei.hobbies.push("eat meat"); console.log(lufei.hobbies); // ["coding", "running", "eat meat"] console.log(nami.hobbies); // ["coding", "running"] 右上可知,子类的两个对象
lufei
和nami
都拥有了父类的属性,所以继承成功。我们可以看到子类可以调用父类的构造方法,同时父类的引用属性也不再共享。 这种方法的缺点: 缺点1:父类原型中的属性和方法无法继承。 缺点2:对每个子类对象来说,父类中的函数属性都是不同的函数,代码无法复用。 - 组合继承
组合继承又称为伪经典继承,他的核心思想就是原型继承和借用构造函数的继承合二为一。请看下面的例子:
function Person(name){ this.name = name; this.hobbies = ["coding","running"]; } Person.prototype.sayName = function (){ console.log("我是:" + this.name); } function Man(name){ Person.call(this,name); // 借用构造函数的继承 //这里可以写其他的子类独有的属性 } Man.prototype = new Person();// 原型继承部分 Man.prototype.constructor = Man;// 修复构造器的指向 var lufei = new Man("路飞"); var nami = new Man("娜美"); lufei.hobbies.push("eat meat"); console.log(lufei.hobbies); // ["coding", "running", "eat meat"] console.log(nami.hobbies); // ["coding", "running"] //子类拥有父类原型上的方法 lufei.sayName(); // 我是:路飞 nami.sayName(); // 我是:娜美 // 父类的属性方法可以复用 console.log(lufei.sayName === nami.sayName);// true 右上可知,子类的两个对象
lufei
和nami
都拥有了父类的属性,所以继承成功。组合继承是最常用的继承方式之一,但是我们可以看到子类可以调用父类的构造方法,同时父类的引用属性也不再共享。 缺点:调用了两次父类构造函数,比较消耗内存。 一次在第10行,一次再第14行。 - 原型式继承
要解决组合继承的缺点,我们不得不先说一下原型式继承,它是道格拉斯提出的一种继承方式,其核心思想就是借助原型,用已有的对象创建对象。。请看下面的例子:
function object(o){// 通过原型创建对象的方法 function F(){} F.prototype = o; return new F(); } var person = { name: "人", sayName: function (){ console.log("我是:" + this.name); } }; var lufei = object(person); lufei.name = "路飞"; lufei.sayName();// 我是:路飞 右上可知,子类的对象
lufei
拥有了父类的方法,继承成功。但是我们每次得自己写一个类似于上面的object
方法。ES5考虑到这个问题,把这个方法规范化了,就是大名鼎鼎的Object.create()
方法,其本质就是上面的object
函数。这个函数接受2个参数,一个是要复制的对象,一个是Object.defineProperties()
函数第二个参数相同的结构。所以上面第13行和第14行可以改写为: var lufei = Object.create(person,{ name : {//这个对象是属性描述符里面那样的对象 value:"路飞" } }); 这种方式比较方便,它跳过了创建子类这一步,直接创建了子类对象。 缺点1:子类自己独特的属性或方法,是无法复用的。 缺点2:没有子类的概念,直接创建了子类对象。 - 寄生式继承
寄生式继承解决了子类对象拥有自己的属性和方法的问题,其核心思想就是在原型式继承的外面再包装一层,使得返回的对象可以添加自己的属性和方法。。下面这个例子我们直接使用ES5的
Object.create
了,你可以理解成原型式继承里面的object
函数: var person = { name: "人", sayName: function (){ console.log("我是:" + this.name); } }; function createObject(obj){// 创建子类对象一个方法 var newObj = Object.create(obj);// 原型式继承 newObj.sayHello = function (){ // 这里给每个子对象添加方法 console.log("大家好!!!") } // ... 这里可以添加其他的属性或方法 return newObj; } var lufei = createObject(person); lufei.name = "路飞"; lufei.sayName();// 我是:路飞 lufei.sayHello();// 大家好!!! var nami = createObject(person); nami.name = "娜美"; nami.sayName();// 我是:娜美 nami.sayHello();// 大家好!!! 由上可知,子类的对象lufei
和nami
即拥有了父类的方法也拥有了子类自己独特的方法sayHello
,继承成功。但是它也是直接创建了子对象的。 缺点:没有子类的概念,直接创建了子类对象。 - 寄生组合式继承 寄生组合式过借用构造函数来继承属性,通过原型链的混成形式来继承方法。其核心思想就是使用寄生式继承来继承父类的原型,然后再将结果指定给子类的原型;子类的构造函数借助构造函数来继承父类。。请看下面的例子: function inheritPrototype(subType, superType){ var prototype = Object.create(superType.prototype); // 寄生式继承来继承父类的原型 prototype.constructor = subType; // 修复子类构造函数的指向 subType.prototype = prototype; // 修复子类原型的指向 } function Person(){ this.hobbies = ["coding","running"]; } function Man(){ Person.call(this); // 借助构造函数继承属性 } inheritPrototype(Man, Person); var lufei = new Man(); var nami = new Man(); lufei.hobbies.push("eat meat"); console.log(lufei.hobbies); // ["coding", "running", "eat meat"] console.log(nami.hobbies); // ["coding", "running"] 寄生组合式继承被誉为引用类型最理想的继承方式,也是最重要的一种继承方式。 缺点:除了代码比较多外,没有其他的缺点了!
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 如何使用 Ktor 快速开发 Web 项目
- 神经网络架构搜索——二值可微分搜索(BATS)
- UEFI 原理与编程 1 - UEFI开发环境EDK2搭建
- 【Unity】瞎做个宝石迷阵吧!(1)——构建场景
- VUE组件传值案例讲解
- 【JAVA】来写个JAVA的HelloWorld吧!
- 如何发布自己的项目到Maven中央仓库?
- 《闲扯Redis八》Redis字典的哈希表执行Rehash过程分析
- 为什么说在Android中请求权限从来都不是一件简单的事情?
- 小知识:如何赋予用户查看所有存储过程和触发器的权限
- ZCU106使用VCU TRD的MIPI的例子
- 一款功能简约到可怜的SQL 客户端
- Kotlin---data class
- sqlmap的使用方法
- 从0开始做播放器---音频播放有杂音且音调异常