JS如何实现继承?
JS的继承是基于JS类的基础上的一种代码复用机制。换言之,有了代码,我们就不需要复制之前写好的方法,只要通过简捷的方式 复用之前自己写的或同事写的代码。比如一个弹出层,我们需要在上面做一些修改。同事写好了一个,我们继承一下,对它的某个方法做了一些修改,或者新建一个方法,然后 再new出来就可以用。
它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。
通过继承创建的新类称为“子类”或“派生类”。
被继承的类称为“基类”、“父类”或“超类”。
在清楚继承的作用之后,下面我们来探讨一下JS中的几种继承实现的方式:
//混入式继承(拷贝)//obj2继承到obj1中的成员,可以直接将obj1中的成员拷贝到obj2中即可var obj1 = {name:"zs",age:10};var obj2 = {};// 将obj1中的成员拷贝到obj2中for (var key in obj1) {
obj2[key] = obj1[key];
}console.log(obj1);console.log(obj2);
最终得到的obj2中的成员和obj1中的成员完全一致,当然,我们需要清除的是,此时的obj1和obj2是两个不同的对象。
混入式继承方式看似很简单,但是存在共享数据安全的问题。
var obj1 = {name:"zs",age:10,car:{name:"mini"}};var obj2 = {};// 将obj1中的成员拷贝到obj2中for (var key in obj1) {
obj2[key] = obj1[key];
}//修改obj1对象中的car属性
obj1.car.name = "bus";
console.log(obj1);console.log(obj2);
当我们需要修改某些对象中的引用类型对象的时候,会造成其他相关的对象也被修改,这是我们不希望看到的。
原型式继承的实现
回想一下,当我们在访问一个对象中的成员的时候,首先是在当前对象中查找,如果找不到,就往上,在原型链中依次查找,如果在整个原型链中也不存在该成员,那么就返回undefined。
所以,我们想要在A对象中访问到B对象中的成员,除了将B中的成员添加到A中,如:混入式,我们也可以考虑将B中成员添加到A的原型链中,实现对象成员的共享。
function Animal() {
}
Animal.prototype.name="animal";function Person() {
}//修改Person的原型对象
Person.prototype= Animal.prototype;
Person.prototype.useTool = function () {
console.log("use fire");
}var p = new Person();console.log(p);var ani = new Animal();console.log(ani);
画图分析:
- 最初,Animal和Person的两个对象没有任何关系,所以各自只能访问各自的成员
- 现在,Person对象如果想要继承Animal对象,只需要将Person的原型对象修改为Animal的原型对象即可
这种方式实现的继承称之为原型继承,实现也是比较方便的,当时和混入式一样,存在数据共享的问题。
原型链继承的实现
原型式继承
在前面的课程中,我们讲了原型式的继承,这种继承方式是修改子类对象原型指向父类对象的原型,如前面的MyArray执行的是Array的原型对象。
这种方式存在问题是,只能继承父类对象原型上的成员,但无法继承父类对象中的成员。
function Animal() {
this.color="red";
}
Animal.prototype.weight=100;function Person() {
}
Person.prototype = Animal.prototype;var p = new Person();console.log(p);
Person.prototype = Animal.prototype;原型式的继承
此时创建出来的Person对象p可以访问Animal中的weight属性,但是无法访问color属性。看图理解
那么,如果我们既要继承原型对象中的成员,也要继承实例对象中的成员,就应该使用这一节中的原型链继承
function Animal() {
this.color="red";
}
Animal.prototype.weight=100;function Person() {
}
Person.prototype = new Animal();var p = new Person();console.log(p.color);//red
使用原型链继承方式能够继承到更多的成员。但是依然存在问题:
- 共享的问题
- 无法向构造器传参
复杂的原型链继承示例---
Object-Animal-Person-Boy
Object.create方法的基本使用
在Object的构造函数上存在一个create方法,该方法的作用是用来创建对象的。
该方法可以接收的参数有一下两种 null
创建一个空对象,这个空对象中连最基本的原型对象都没有的
对象
创建传递进来的对象,并设置该对象的原型对象为当前的参数
该方法的使用率比较低,要求大家知道存在这样的一种创建对象的方式即可。
call方法和apply方法的基本使用
call和apply方法作用:- 方法借用2. 设置方法中this的指向
var obj1 = {
name:"Neld",
age:10,
showInfo:function () {
console.log("name:"+this.name,"age:"+this.age);
}
}var obj2 = {
name:"Lily",
age:12
}
obj1.showInfo();//name:Neld age:10
obj2.showInfo();//obj2.showInfo is not a function
obj1对象中有showInfo方法,而obj2对象中没有,所以如果直接使用obj2调用showInfo方法的时候抛出错误信息。
如果我们临时需要使用obj2调用showInfo方法来打印出name和age属性的值,此时可以使用这里的call或者apply方法来实现。
obj1.showInfo.call(obj2);//name:Lily age:12
obj1.showInfo.apply(obj2);//name:Lily age:12
这就是把obj1中的showInfo方法借用给obj2使用。
同时我们观察到,在showInfo方法中使用到了this关键字,在obj2借用该方法的时候,其中的this已经指向了obj2对象,这就要达到修改方法中this关键字的指向的目的。
call和apply方法的作用是完全一样的,那么他们的区别是什么呢?继续往下分析。
var obj1 = {
name:"Neld",
age:10,
add : function (a, b) {
return a + b;
}
}var obj2 = {
name:"Lily",
age:12
}console.log(obj1.add.call(obj2, 1, 2));//3//console.log(obj1.add.apply(obj2, 1, 2));//CreateListFromArrayLike called on non-objectconsole.log(obj1.add.apply(obj2, [2, 2]));//4
在obj1中定义一个带有两个参数的方法,obj2中没有,问题一样,obj2也要使用到obj1中的add方法,此时使用call或者apply借用即可。
此时新的问题是,在调用add方法的时候参数应该如何传递?
call方法:
将this指向的对象作为第一个参数,其他参数依次传递即可
apply方法:
将this指向的对象作为第一个参数,其他参数封装到数组中传递
如果没有使用数组,程序报错。
以上就是call和apply的基本使用,这两个方法在后续的课程中会大量的使用到,所以必须引起重视。
借用构造函数继承说明
所谓借用构造函数,就是在子构造函数中调用父构造函数,达到继承并向父构造函数传参的目的。
function SuperClass(name,age) {
this.name = name;
this.age = age;
}
SuperClass.prototype.fun1 = function () {
console.log("name:",this.name,"age:",this.age);
}function SubClass(color) {
this.color = color;
}
SubClass.prototype = new SuperClass("xxx",10);//继承父构造函数并设置name和age的值
SubClass.prototype.fun2 = function () {
console.log("color:",this.color);
}var sub = new SubClass("red", "Neld", 10);var sub2 = new SubClass("red", "Lily", 12);console.log(sub);console.log(sub2);
上面是原型链的继承,这种方式存在一个问题是,在创建不同对象的时候,无法为其继承过来的成员赋值。
这里的sub和sub2两个对象的name和age属性值都是“xxx”和10,很明显是不满足我们需求的。
那么我们来看看借用构造函数是否能解决这个问题呢?
function SuperClass(name,age) {
this.name = name;
this.age = age;
}
SuperClass.prototype.fun1 = function () {
console.log("name:",this.name,"age:",this.age);
}function SubClass(color, name, age) {
this.newMethod = SuperClass;//①
this.newMethod(name, age);//②
this.color = color;
delete this.newMethod;//③
}//SubClass.prototype = new SuperClass();
SubClass.prototype.fun2 = function () {
console.log("color:",this.color);
}var sub = new SubClass("red", "Neld", 10);var sub2 = new SubClass("red", "Lily", 12);console.log(sub);console.log(sub2);
①、②、③处代码是实现借用构造函数的关键。下面一一作出解释:
①:将父对象的构造函数设置为子对象的成员
②:调用这个方法,想过类似于将父构造函数的代码放在子构造函数中来执行
function SubClass(color, name, age) {
this.newMethod = SuperClass;
this.name = name;//父构造函数中的代码
this.age = age;//父构造函数中的代码
this.color = color;
delete this.newMethod;
}
这样看应该更直观一点,执行之后就是在为当前创建出来的对象封装name和age属性。
③:在子构造函数中,newMethod仅仅为了在②调用父构造函数使用,用完之后也就没了价值,所以,直接删除该方法即可
可以看到,借用构造函数继承方式解决了原型链及继承的问题。
下面再看看另外一种借用构造函数的实现方式(使用call或apply):
function SubClass(color, name, age) {
//SuperClass.call(this,name,age);
SuperClass.apply(this,[name,age]);
this.color = color;
}
我们可以使用call或apply更简单快捷的实现和上面一样的效果。
以上就是借用构造函数继承(也要对象冒充)的两种实现方式。当然,这种继承方式都存在下面两个问题: - 如果父子构造函数存在相同的成员,那么子构造函数会覆盖父构造函数中的成员
- 不能继承原型链中的成员
组合继承的基本结构
基于原型链继承和借用构造函数继承两种方式存在的问题,我们提出一个解决方案---组合继承,就是两种一起使用。
function SubClass(color, name, age) {
//SuperClass.call(this,name,age);
SuperClass.apply(this,[name,age]);//继承构造函数中的成员
this.color = color;
}
SubClass.prototype = new SuperClass();//继承原型链上的成员
总结: ECMAScript 实现继承的方式不止一种。这是因为 JavaScript 中的继承机制并不是明确规定的,而是通过模仿实现的。这意味着所有的继承细节并非完全由解释程序处理。作为开发者,你有权决定最适用的继承方式。
绘制完整的原型链结构图
这一节重点探讨函数对象的原型链结构。完整的结构图如下:
继承总结起来就是用构造函数(call ,apply)继承不同的实例属性,通过父对象的原型指向子对象的原型实现相同方法的继承,但是有一点要注意,不能通过 sub.prototype = Super.prototype继承父对象的原型。因为这样,相当于父对象和子对象同时公用一个原型,对子对象做的修改,会影响到父对像,此时父对象的原型链已经被破坏,所以对于这种情况一定要注意!
JS如何实现继承?的更多相关文章
- 浅谈JS中的继承
前言 JS 是没有继承的,不过可以曲线救国,利用构造函数.原型等方法实现继承的功能. var o=new Object(); 其实用构造函数实例化一个对象,就是继承,这里可以使用Object中的所有属 ...
- JS创建对象、继承原型、ES6中class继承
面向对象编程:java中对象的两个基本概念:1.类:类是对象的模板,比如说Leader 这个是泛称领导,并不特指谁.2:实例:实例是根据类创建的对象,根据类Leader可以创建出很多实例:liyi,y ...
- js最好的继承机制:用对象冒充继承构造函数的属性,用原型prototype继承对象的方法。
js最好的继承机制:用对象冒充继承构造函数的属性,用原型prototype继承对象的方法. function ClassA(sColor) { this.color = sColor; } Class ...
- js模拟实现继承功能
<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8&quo ...
- js中实现继承的几种方式
首先我们了解,js中的继承是主要是由原型链实现的.那么什么是原型链呢? 由于每个实例中都有一个指向原型对象的指针,如果一个对象的原型对象,是另一个构造函数的实例,这个对象的原型对象就会指向另一个对象的 ...
- js怎么实现继承?
3. js怎么实现继承? 1. 使用原型prototype 这个问题其实之前总结过了……但是面试时候有点忘……主要思想是记得的,但是不会写,还是基础太不牢靠,写的太少了.一开始因为不知道怎么能继承父类 ...
- [js]js原型链继承小结
这是之前总结的, 发现有很多的毛病,就是重点不突出,重新翻看的时候还是得耗费很长时间去理解这玩意. js中的继承 js中什么是类 1,类是函数数据类型 2.每个类有一个自带prototype属性 pr ...
- js一种继承机制:用对象冒充继承构造函数的属性,用原型prototype继承对象的方法。
js一种继承机制:用对象冒充继承构造函数的属性,用原型prototype继承对象的方法. function ClassA(sColor) { this.color = sColor; } ClassA ...
- 【学习笔记】六:面向对象的程序设计——理解JS中的对象属性、创建对象、JS中的继承
ES中没有类的概念,这也使其对象和其他语言中的对象有所不同,ES中定义对象为:“无序属性的集合,其属性包含基本值.对象或者函数”.现在常用的创建单个对象的方法为对象字面量形式.在常见多个对象时,使用工 ...
- JS中的继承(上)
JS中的继承(上) 学过java或者c#之类语言的同学,应该会对js的继承感到很困惑--不要问我怎么知道的,js的继承主要是基于原型(prototype)的,对js的原型感兴趣的同学,可以了解一下我之 ...
随机推荐
- spring相关—AOP编程—切入点、连接点
1 切入点表达式 1.1 作用 通过表达式的方式定位一个或多个具体的连接点. 1.2 语法细节 ①切入点表达式的语法格式 execution([权限修饰符] [返回值类型] [简单类名/全类名] [方 ...
- RookeyFrame 整个运行流程
准备开始整理一下这个项目的整体框架,很久没研究这个框架了,心里还是念着的,今儿乘有时间弄一下. 一丁点建议: 先自己一个一个的搬作者的类库,这样就能很好的理解作者的项目结构 每搬一个类库都运行一下哦, ...
- luogu P1724 东风谷早苗
二次联通门 : luogu P1724 东风谷早苗 /* luogu P1724 东风谷早苗 模拟即可 手抖 Y-- 打成 Y++ WA了两发.. */ #include <cstring> ...
- Java动态调用脚本语言Groovy
Java动态调用脚本语言Groovy 2019-05-15 目录 0. pom.xml添加依赖1. 使用GroovyShell计算表达式2. 使用GroovyScriptEngine脚本引擎加载Gro ...
- js之select三级联动
效果图如下: 代码逻辑梳理:层层递进,比如选择了课程后,将对应的课程id保存,然后点击选择章时自动触发对应的时间,根据这个课程ID获取其下面的章信息.其它的如节等,同理. 代码说明:如下代码不规范,可 ...
- 论文阅读 | Recurrent Attentional Reinforcement Learning for Multi-label Image Recognition
源地址 arXiv:1712.07465: Recurrent Attentional Reinforcement Learning for Multi-label Image Recognition ...
- Leetcode 222:完全二叉树的节点个数
题目 给出一个完全二叉树,求出该树的节点个数. 说明: 完全二叉树的定义如下:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置. ...
- PHP系列 | Thinkphp3.2 上传七牛 bad token 问题 [ layui.upload 图片/文件上传]
前端代码 <div class="logo_out" id="upload-logo"></div> JS代码 /** * 上传图片 * ...
- mapdb的适用场景介绍
对于大部分系统来说,mapdb并无太大价值,而且增加了成本.但是如果一级缓存巨大例如数以十GB级别,或占据了整个JVM的1/2以上,mapdb的价值就会体现出来.正如其官网介绍: MapDB prov ...
- Openresty与Tengine
Tengine官方网站:http://tengine.taobao.org/index_cn.html OpenResty官方网站:http://openresty.org/ Openresty和Te ...