JS继承和原型链,ES6新继承(class,extends,super)

本文探讨JavaScript的继承机制,包括封装、继承和多态的概念。深入讲解了JS中的继承方式,如混入式、替换原型式和混合式继承,并解析了原型链的工作原理。此外,还介绍了ES6引入的`class`、`extends`和`super`关键字,以及它们在类继承中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

特征

  1. 封装:把需要复用的代码 放入对象的方法中

  2. 继承:一个对象拥有另一个对象的所有成员(JS重点)

  3. 多态:一个对象在不同情况下的多种状态

继承(JS)

子对象 继承 父对象所有的成员

js中常用的三种继承方式

假设有两个对象,一个是父亲对象,一个是儿子对象,父亲有房子属性(或其他属性),儿子通过继承的方式获得父亲的房子属性(或其他属性)。

1. 混入式继承

遍历父对象成员添加给子对象(forin),一般用于继承一个子对象,如果子对象一多,代码冗余(有几个继承就要写几次forin循环了)。

//父对象
let father = {
    house: {
        address: 'xxxxx小区',
        price: 'xxxx¥'
    }
}
//子对象
let son = {
    worker: '销售员' //本身的属性
}
/*
    混入试继承:遍历父对象成员添加给子对象
*/
for (let key in father) {
    son[key] = father[key];
}
console.log(son); //son中除了自己的属性(儿子的工作),还有了爸爸的房子(继承过来的)

2. 替换原型继承

将父对象作为子对象的原型(适用于多个继承),可以实现多个子对象继承,但是会覆盖子对象默认的原型

//父对象
let father = {
    house: {
        address: 'xxxxx小区',
        price: 'xxxx¥'
    }
}
//第二种方式:
//替换原型继承:将父对象作为子对象的原型
function Son(work) {
    this.work = work;
}
//子对象默认的原型
Son.prototype.hobby = function() {
    console.log('乒乓球');
}
//将父对象作为子对象的原型
Son.prototype = father; //这里就会把Son中的默认原型hobby给替换成了father中的属性
​
let son1 = new Son('销售员');
let son2 = new Son('程序员');
console.log(son1.house, son2.house);

3.混合式继承

混入式 + 替换原型

*遍历父对象所有成员,添加给对象的原型

//第三种方式 
//混合式继承  :  混入式+替换原型
function Son(work) {
    this.work = work;
}
//子对象默认的原型
Son.prototype.hobby = function() {
    console.log('乒乓球');
}
​
//遍历父对象所有成员,添加给子对象原型
for (let key in father) {
    Son.prototype[key] = father[key];
}
​
let son1 = new Son('销售员');
let son2 = new Son('程序员');
console.log(son1.hobby(), son2.house);

原型链

原型链

每一个对象都有自己的原型,而原型也是对象,也会有自己的原型(链),以此类推形成一个链式结构---原型链。

原型

  • 所有引用类型都有一个proto(隐式原型)属性,属性值是一个普通的对象

  • 所有函数都有一个prototype(原型)属性,属性值是一个普通的对象

  • 所有引用类型的proto属性指向它构造函数的prototype

对象访问原型链的规则

先看自己有没有,自己有优先访问自己的,没有再去从原型对象中去找,还没有就从原型对象的对象中去找,以此类推,到终点也没有找到的话,属性会返回undefined,方法就会报错。

每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个原型对象的指针。

构造函数:是用来创建对象的函数,本质上也是函数

原型对象:当我们每次创建一个函数的时候,函数对象都会有一个prototype属性,这个属性是一个指针,指向它的原型对象

实例对象:通过构造函数创建实例对象

class Person {
    //构造函数
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }
}
​
//原型对象
Person.prototype.type = '灵长类'
Person.prototype.eat = function() {
    console.log('杂食性');
}
​
//实例对象
let p1 = new Person('warm', 19);
//找实例对象p1的原型
console.log(p1.__proto__.constructor); //用的是谁的构造函数[class Person] p1.__proto__.constructor(p1的原型是什么) -->__proto__的意思是谁的原型 constructor原型是什么
console.log(p1.__proto__ == Person.prototype); //p1的原型是不是Person的原型  true
//检查p1的原型的原型
console.log(p1.__proto__.__proto__.constructor); //[Function: Object]
console.log(p1.__proto__.__proto__ === Object.prototype);
//检查p1的原型的原型的原型
console.log(p1.__proto__.__proto__.__proto__.constructor);//null不是没有了而是给个终点 不然这得找哪儿去了都 所以null代表原型链的终点
​
​
/* 关于下面输出的解释帮助理解 */
console.log(p1.name); //p1中有的属性
console.log(p1.type); //p1没有 但是他指向的原型(Person)里面有
p1.eat(); //原型有
p1.toString(); //这里虽然不打印 但是也不报错  原型链  虽然p1没有 p1的原型也没有 但是p1的原型里面还有一个原型(原型中还有原型)可能里面的原型有
p1.learn(); //p1没有,原型链里面也没有 报错

内置对象原型链

所有对象的原型链都会最终指向Object

小技巧:当你想使用某个内置对象的函数,但是你又记不太清这个函数名,这个时候你可以打印他的内置原型链,去查看原型中的函数。

Array

数组对象,具体看代码

let arr = [1, 2, 3, 4, 5, 6];
​
//1.查看arr对象的原型
console.log(arr.__proto__.constructor); //Array 证明什么的操作其实就是 new Array(1,2,3,4,5,6);
console.log(arr.__proto__ === Array.prototype); //true 两个都指向同一个原型链
​
//2.查看arr对象的原型的原型
console.log(arr.__proto__.__proto__.constructor); //Object 
console.log(arr.__proto__.__proto__ === Object.prototype); //true
​
//看接下来分析
arr.reverse();
//Array只有一个属性 length 但是他的原型中有许多方法(revers()等)
//js中所有的方法都被写到原型中 不写到Array中是因为不需要每创建一次数组 执行一编这么多函数 直接写原型里面会省下很多事

Date

//日期对象
let date = new Date();
/*
注意 : js中有少部分的对象,无法通过log来打印的,一旦使用log会自动转成字符串显示
    *这三种:日期对象Date  函数对象function  Dom对象
    如果想要看这三个对象的内存,则需要使用 console.dir()
*/
console.log(date); //浏览器页面时没有Prototype的
console.dir(date); //浏览器会有Prototype的
​
//小技巧 -- 当你忘记你需要用的那个方法的具体单词 你可以通过打印原型链 去查看你需要的那个方法
// console.dir(date);就是这个可以去浏览器查看原型中的方法
​
//1.查看date对象的原型
console.log(date.__proto__.constructor); //Date 证明什么的操作其实就是 new Array(1,2,3,4,5,6);
console.log(date.__proto__ === Date.prototype); //true 两个都指向同一个原型链
​
//2.查看date对象的原型的原型
console.log(date.__proto__.__proto__.constructor); //Object 
console.log(date.__proto__.__proto__ === Object.prototype); //true

String

//两种方法  两者打印会有区别
//1.console.log(str.__proto)
let str1 = 'abc';
console.log(str1.__proto__); //这里只能调用原型才能打印出来
//2.console.log(str);
let str2 = new String('abc'); //str = 'abc'这两个功能是一样 但是你要想直接查原型链 建议使用 new String('abc');这种方式
console.log(str2);
​
//1.查看String对象的原型
console.log(str2.__proto__.constructor); //String
console.log(str2.__proto__ === String.prototype); //true 两个都指向同一个原型链
​
//2.查看String对象的原型的原型
console.log(str2.__proto__.__proto__.constructor); //Object 
console.log(str2.__proto__.__proto__ === Object.prototype); //true

DOM对象原型链

DOM对象结构设计 : 一共是有七层的原型链结构


<body>
    <div class="box">我是div</div>
    <p class="pp">
        我是标签p
    </p>
    <script>
        let box = document.querySelector(".box");
        let pp = document.querySelector(".pp");
        console.log(box); //log打印不出来原型链哦
​
        //一共七层层
        console.dir(pp);
        console.dir(box); //本身的原型
        console.dir(pp.__proto__); //HTMLParagraphElement pp的第一层 之后的都是一样
        console.log(box.__proto__); //HTMLDivElement div的第一层 之后的都是一样
        console.log(box.__proto__.__proto__); //HTMLElement
        console.log(box.__proto__.__proto__.__proto__); //Element
        console.log(box.__proto__.__proto__.__proto__.__proto__); //Node
        console.log(box.__proto__.__proto__.__proto__.__proto__.__proto__); //EventTarget
        console.log(box.__proto__.__proto__.__proto__.__proto__.__proto__.__proto__); //Object
        console.log(box.__proto__.__proto__.__proto__.__proto__.__proto__.__proto__.__proto__); //终点null
    </script>
</body>

函数也是对象类型

点语法证明(对象才有点语法)

console.log打印代码区的内容

console.dir打印对象区的内容

函数中其实有代码区和对象区一分为二,但是我们一般使用函数来存储代码。

//1.使用点语法证明(对象才有点语法)
function fn() {
    console.log('这是一个函数');
}
fn.sex = '男';
fn.age = 19;
console.dir(fn); //log只能打印代码 所以要看属性dir打印 这里可以看到函数里面多了age,sex的属性 而且函数也有原型链
console.log(fn.sex);
console.log(fn.age);

查看函数的原型链(对象区)

//查看函数的原型链
console.log(fn.__proto__.constructor);
console.log(fn.__proto__ === Function.prototype);
​
//查看函数原型的原型
console.log(fn.__proto__.__proto__.constructor);
console.log(fn.__proto__.__proto__ === Object.prototype);

 

js中所有的函数都是被function构造函数创建的

常见的创建函数的方式(具名函数,匿名函数...)底层都是用Function创建的。

//2.js中所有的函数都是被function构造函数创建的
//其底层:
let fn2 = new Function('console.log(112233)'); //无参返回
let fn3 = new Function('a', 'b', 'return a+b') //有参返回
fn2();
console.log(fn3(1, 2));

instanceof运算符

关于MDN官网instanceof:instanceof - JavaScript | MDNhttps://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/instanceof

  1. 与原型链相关的关键字:instanceof

  1. instanceof运算符 语法: 对象 instanceof 构造函数

  1. 检测右边构造函数的原型在不在左边实例对象的原型链中 返回值为Boolean类型

let arr = [1, 2, 3, 4];
console.log(arr instanceof Array); //true
console.log(arr instanceof Object); //true

ES6类与继承

class关键字--声明一个类函数

其实仔细看前面的一些构造函数的建造就是使用了class类中的构建。

类函数:其实就是构造函数

类函数的好处

  1. 把构造函数和原型包在一个大括号内部,阅读性会高

  2. 类函数必须使用new来调用,语法要求更有严谨性,不使用new直接报错

class必须要有一个函数叫constructor();

//ES5
//构造函数
function Person(name, age) {
    this.name = name;
    this.age = age;
}
//添加原型对象
Person.prototype.show = function() {
    console.log('这是ES5的添加原型对象');
}
​
​
//ES6
class Person2 {
    //构造函数
    constructor(name, age) {
            this.name = name;
            this.age = age;
        }
        //添加原型对象
    show() {
        console.log('这就是新的ES6原型对象创建方法');
    }
}
let p1 = new Person2('warm', 19);
console.log(p1);

show已经是原型对象之一了。

extend关键字--替换原型的方式继承

作用:继承

原理:替换原型的方式继承,但是不会覆盖原来的原型

语法:

class 子改造函数 父构造函数

//原理是替换原型方式继承  但是不会覆盖原来的原型
class Father {
    constructor(housr) {
        this.housr = housr;
    }
    show() {
        console.log('father的show方法');
    }
}
// console.log(f1);
class Son extends Father {
    learn() {
        console.log('学法律');
    }
}
let s1 = new Son('xxxx小区');
console.log(s1);

看图:

不会覆盖是因为,他覆盖的其实是son原型的原型里面去覆盖了,他没有覆盖son的原型,而是覆盖了son原型的原型。

super关键字

作用:子类中调用父类的方法

原理:函数上下文调用

语法 super() super只能用于extends中

当子类继承父类,如果子类想要自己写构造函数,就必须先调用父类的super();

class Father {
    constructor(housr) {
        this.housr = housr;
    }
    show() {
        console.log('father的show方法');
    }
}
class Son extends Father {
    constructor(name, age, hobby, housr) {
            super(housr); //当子类继承父类,如果子类想要自己写构造函数,就必须先调用父类的super
            this.name = name;
            this.age = age;
            this.hobby = hobby;
​
        }
        //原型
    learn() {
        console.log('学法律');
    }
}
​

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值