JavaScript继承学习笔记【新手必看】
作者:jingxian 发布时间:2024-07-26 00:10:20
JavaScript作为一个面向对象语言(JS是基于对象的),可以实现继承是必不可少的,但是由于本身并没有类的概念,所以不会像真正的面向对象编程语言通过类实现继承,但可以通过其他方法实现继承。实现继承的方法很多,下面就只是其中的几种。
一. 原型链继承
function Person() { //被继承的函数叫做超类型(父类,基类)
this.name='mumu';
this.age='18';
}
Person.prototype.name='susu';//当属性名相同时需就近原则,先在实例里面查找,没找到再到原型里找
function Worker(){ //继承的函数叫做子类型(子类,派生类)
this.job='student';
}
Worker.prototype=new Person();//通过原型链继承,超类型实例化后的对象实例,赋值给子类的原型属性
var p2=new Worker();
console.log(p2.name);
console.log(p2 instanceof Object);//ture 所有的构造函数都继承自Object
以上实现继承关键在于:Worker.prototype=new Person(); 将Worker的原型成为Person的一个实例,通过原型链继承。
注意:在使用原型链实现继承时,不能使用对象字面量创建原型方法,因为这样会中断关系而重写原型链。
原型链继承问题:
1.出现引用共享问题,他们还是共用一个空间,子类会影响父类
function Person() {
this.bodys=['eye','foot'];
}
function Worker(){
}
Worker.prototype=new Person();
var p1=new Worker();
p1.bodys.push('hand');
var p2=new Worker();
console.log(p1.bodys);
console.log(p2.bodys);
2.在创建子类型的实例时,不能像超类型的构造函数中传递参数。
那么如何解决原型链的两个问题呢?那就继续看下面的继承方式吧~
二. 借用构造函数继承(也叫对象冒充,伪造对象或经典继承)
function Person(name,age){
this.name=name;
this.age=age;
this.bodys=['eye','foot'];
}
Person.prototype.showName=function(){
console.log(this.name);
}
function Worker(name,age,job){
Person.call(this,name,age);
this.job=job;//子类添加属性
}
var p1=new Worker('mumu','18','学生');
p1.bodys.push('hand') ;
var p2=new Worker();
console.log(p1.name);
console.log(p2.bodys);
console.log(p1.showName());
简单分析下以上使用借用构造函数的原理:Person.call(this,name,age);这句代码调用父级构造函数,继承父级属性,使用call方法调用Person构造函数改变函数执行时候的this, 这里的this-> new出来的一个Worker对象 构造函数伪装方法:把Worker传给上面的Person。
当引用类型放在构造函数里面的时候就不会被共享,所以p2不受影响。
这里借用构造函数继承方式就解决了原型链不能传递参数以及引用类型共享的问题。
小知识:call()和apply()方法可以改变函数执行的作用域, 简言之就是改变函数中this指向的内容。
call()和apply()都接受两个参数:第一个是在其中运行函数的作用域,另一个是传递的参数。
call和apply的区别就是参数的不同.
call中的参数必须是一个个枚举出来的.
apply中的参数必须是数组或者是arguments对象
那么问题来了:为什么p1.showName()结果是错误的呢?----因为借用构造函数继承方式只能继承构造函数里的属性和方法。这里也就发现了借用构造函数的一个问题。
注意:由于把方法都放在构造函数里,每次我们实例化就会分配内存空间给它造成资源的浪费,所以一般我们都是把方法放在原型里,属性放在构造函数里。
借用构造函数继承问题:
因为借用构造函数只能继承构造函数里的属性和方法,在超类型的原型中定义的方法对子类而言是不可见的,所以就相当于没有了原型。结果所有的方法都只能在构造函数里定义,因此就没有函数复用了。
那么如何解决借用构造函数所产生的问题呢?那就要看下面这种继承方式了
三. 组合继承(伪经典继承)
function Person(name,age){
this.name=name;
this.age=age;
}
Person.prototype.showName=function(){
console.log(this.name);
}
function Worker(name,age,job){
Person.call(this,name,age);//借用构造函数
this.job=job;
}
Worker.prototype=new Person();//原型链继承
var p1=new Worker('mumu','18','学生');
console.log(p1.age);
p1.showName();
组合继承:将原型链与借用构造函数结合。
思路:通过使用原型链实现原型上的属性和方法继承,借用构造函数实现实例属性的继承
以上的例子Person.call(this,name,age);借用构造函数继承了属性
Worker.prototype=new Person();原型链继承了方法 , 避免了两者的缺点,融合了它们的优点,成为最常用的继承模式。
组合继承的问题:
调用两次超类型构造函数,一次是在创建子类型原型时,另一次是在子类型的构造函数内部。
要解决这个问题就要用到寄生组合式继承方式了。
四. 原型式继承
function object(proto) {
function F() {}
F.prototype = proto;
return new F();
}
var person = {
name: 'mumu',
friends: ['xiaxia', 'susu']
};
var anotherPerson = object(person);
anotherPerson.friends.push('wen');
var yetAnotherPerson = object(person);
anotherPerson.friends.push('tian');
console.log(person.friends);//["xiaxia", "susu", "wen", "tian"]
console.log(anotherPerson.__proto__)//Object {name: "mumu", friends: Array[4]}
简单分析下:function object(proto)是一个临时中转函数,里面的参数proto表示将要传递进入的一个对象,F()构造函数是临时新建的对象,用来存储传递过来的对象,F.prototype = proto;将对象实例赋值给F构造函数的原型对象,最后返回传递过来的对象的对象实例。原型式继承还是会共享引用类型的属性。
五. 寄生式继承
//临时中转函数
function object(proto) {
function F() {}
F.prototype = proto;
return new F();
}
//寄生函数
function create(proto){
var f=object(proto);
f.love=function(){
return this.name;
}
return f;
}
var person = {
name: 'mumu',
friends: ['xiaxia', 'susu']
};
var anotherPerson = create(person);
console.log(anotherPerson.love());寄生组合式继承
六. 寄生组合式继承
function object(proto) {
function F() {}
F.prototype = proto;
return new F();
}
//寄生函数
function create(Person,Worker){
var f=object(Person.prototype);//创建对象
f.constructor=Worker;//调整原型构造指针,增强对象
Worker.prototype=f;//指定对象
}
function Person(name,age){
this.name=name;
this.age=age;
}
Person.prototype.showName=function(){
console.log(this.name);
}
function Worker(name,age,job){
Person.call(this,name,age);
this.job=job;
}
create(Person,Worker);//寄生组合式继承
var p1=new Person('mumu','18','学生');
p1.showName();
这种方法也是现在实现继承方法中最完美的,也是最理想的。


猜你喜欢
- 本文实例讲述了python中管道用法。分享给大家供大家参考。具体如下:#!coding=utf-8import multiprocessin
- 下面就来介绍一下这些在后台辛勤工作的进程们。系统检测器(System Monitor,SMON)、进程监视器(Process Monitor
- kali添加开机自启采用systemd的方法,kali默认是没有rc.local的,需要自己创建。本方法也适用于ubuntu 18.04 6
- 事务特性1、原子性(Atomicity):事务开始后所有操作,要么全部做完,要么全部不做,不可能停滞在中间环节。2、一致性(Consiste
- 一、推箱子1)代码展示import osimport sysimport cfgimport pygamefrom modules impo
- 以下插件是我在项目中经常使用的jQuery插件,不见得是最好的,但是我目前接触到的jQuery插件中最适合我的。01. jQuery.Fle
- 用游标,和WHILE可以遍历您的查询中的每一条记录并将要求的字段传给变量进行相应的处理==================DECLARE @
- 微信小程序 滚动选择器(时间日期)详解微信小程序自己封装了很多控件,用起来确实很方便,如果这是Android里面,还需要自己去定
- 前段时间写了个比较简单的批量水印添加的python实现方式,将某个文件夹下面的图片全部添加上水印。今天正好有时间就做了一个UI应用的封装,这
- 在mac下载安装prometheus在https://prometheus.io/download/下载prometheus放到自定义的位置
- 1、何为计算属性:大白话讲就是计算出来的结果保存在属性当中,可以想象为缓存。<!DOCTYPE html><html la
- 这两天在整理一些文章,但是文件夹中每个文章没有序号会看起来很乱,所以想着能不能用Python写一个小脚本。于是乎,参考了多方资料,简单写了下
- 1.因为oracle 10g暂时没有与win7兼容的版本,我们可以通过对安装软件中某些文件的修改达到安装的目地。 a)打开“\ORACLE1
- 基于signal模块实现signal包负责在Python程序内部处理信号,典型的操作包括预设信号处理函数,暂停并等待信号,以及定时发出SIG
- 前言matplotlib画图例默认的位置是在图中的各个角落,但有时图例位置会遮挡住图像而不符合我们的需求,需要对图例位置进行调整。代码如下:
- 1.说明redis作为一个缓存数据库,在各方面都有很大作用,Python支持操作redis,如果你使用Django,有一个专为Django搭
- 我就废话不多说了,直接上代码吧!#全0和全1矩阵v1 = tf.Variable(tf.zeros([3,3,3]), name="
- 简介mplcursors包也可以为matplotlib提供交互式的数据光标(弹出式注释框),它的灵感来源于mpldatacursor包,可以
- 一、使用PyChram的正则首先,小编讲的不是爬取ip,而是讲了解PyCharm的正则,这里讲的正则不是Python的re模块哈!而是PyC
- flask响应错误处理及errorhandler应用@app.errorhandler(404)def page_not_found(err