IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> JavaScript知识库 -> 随时记|JS继承 -> 正文阅读

[JavaScript知识库]随时记|JS继承

一直对继承这个云里雾里的,看了B站视频结合文档记录一下~

Part1:预备知识

在此之前,你必须掌握以下知识,温故知新啦~

1. 构造函数的属性

举一个简单的构造函数的例子:

function Person(name) {
    this.name = name;      // 实例基本属性(该属性,强调私有,不共享)
    this.age = [18];       // 实例引用属性(该属性,强调私有,不共享)
    this.say = function() {   // 实例引用属性(该属性,强调复用,需要)
        console.log(`你好,我的名字是${name}.`)
    }
}

注意:数组和方法都属于"实例引用属性",但是数组强调私有、不共享的。方法需要复用、共享。在构造函数中,一般很少又数组形式的引用属性,大部分都是:基本属性+方法

2. 什么是原型对象

每个函数都有prototype【显式原型】属性,它就是原型对象,通过函数实例化出来的对象有个__proto__【隐式原型】属性,指向原型对象

function Person(name) {
    this.name = name;      // 实例基本属性(该属性,强调私有,不共享)
    this.age = [18];       // 实例引用属性(该属性,强调私有,不共享)
    this.say = function() {   // 实例引用属性(该属性,强调复用,需要)
        console.log(`你好,我的名字是${name}.`)
    }
}


let a = new Person('Katrina');
a.__proto__ = Person.prototype;    // 函数实例化对象的__proto__属性指向原型对象(这里需要知道new的过程)

// prototype的结构如下
Person.prototype = {
	constructor:Person,
	... 其他属性和方法
}

补充:new的过程及实现

  1. 新建一个空对象obj
  2. 链接到原型:将obj[[prototype]]属性指向构造函数的原型,即obj.[[prototype]] = construc.prototype (不懂的可以看图理解一下【图来源于b站大佬】)
  3. 绑定this:把构造函数内部的this绑定到新建的obj上,执行构造函数【显然这里需要用call、apply、bind来更改this指向】
  4. 返回新的对象
function create() {
	let obj = {};   // 1. 新建空对象
	let constructor = Array.prototype.shift.call(arguments); // 获取构造函数
	// 2. 链接到原型
	obj.__proto__= constructor.prototype;
	// 3. 绑定this
	let res = constructor.apply(obj,arguments);
	// 4. 返回新的对象
	return typeof === 'object' ? res : obj;
}

3. 原型对象的作用

原型对象的用途是为每个实例对象存储共享的方法和属性,它仅仅是一个普通的对象而已,并且所有的实例是共享同一个原型对象,因此有别于实例方法或属性,原型对象仅一份,而实例有很多份,且实例属性和方法是独立的。在构造函数中:为了属性(实例基本属性)的私有性以及方法(实例引用属性)的复用共享,提倡:

  1. 将属性封装在构造函数中
  2. 将方法定义在原型对象上
function Person(name) {
    this.name = name;      // 实例基本属性(该属性,强调私有,不共享)
}

Person.prototype.say = function() {  
    console.log('hello')
}

Part2:6种JS继承方式

1. 原型链继承

  • 核心:将父类实例作为子类原型
  • 优点:方法复用
    1. 由于方法定义在父类的原型上,复用了父类构造函数的方法
  • 缺点:
    1. 创建子类实例的时候,不能传父类的参数(比如name)
    2. 子类实例共享了父类构造函数的引用属性
    3. 无法实现多继承
function Person(name) {
    this.name = name || '无名氏';    // 实例基本属性(该属性,强调私有,不共享)
    this.arr = [1];   // 实例引用属性(该属性,强调私有,不共享)
}


Person.prototype.say = function() {
    console.log('hello');
};

/*
    有一个Student函数,让Student继承Person
    1. Student的原型指向Person,把Person的实例赋值给Student的prototype
    2. 修正constructor的指向
*/
function Student(grade) {
    this.grade = grade;
};


Student.prototype = new Person(); // 1. Student的原型指向Person,此时Student.prototype.constructor==Person
Student.prototype.constructor = Student; // 2. 修正constructor的指向


let s1 = new Student();
let s2 = new Student();

// 优点:共享了Person的say方法
s1.say();  // hello
s2.say();  // hello
console.log(s1.say()==s2.say())  // true

// 缺点1:不能向父类构造函数传参
/*
    在创建实例对象s1和s2的时候,我们不能把Person的name在new Student中传进入
*/
console.log(s1.name) // 无名氏
console.log(s1.name) // 无名氏
console.log(s1.name == s2.name) // true


// 缺点2:子类实例共享了父类构造函数的引用属性
s1.arr.push(2);
// 修改了s1的arr属性,s2的arr属性也会变,因为两个实例的原型上(Child.prototype)有了父类构造函数的实例属性arr
console.log(s2.arr);   // [ 1, 2 ]

// 注意:修改s1的name属性,是不会影响到s2.name的,因为设置s1.name相当于在子类实例新增了name属性

2. 借用构造函数

  • 核心:借用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类
  • 优点:实例之间独立
    1. 创建子类实例,可以向父类构造函数传参数
    2. 子类实例不共享父类构造函数的引用属性
    3. 可实现多继承(通过多个call或者apply继承多个父类)
  • 缺点:
    1. 父类方法不能复用
      由于方法在父构造函数中定义,导致方法不能复用(因为每次创建子类实例都要创建一次方法)
    2. 子类实例,继承不了父类原型上的属性(因为没有用到原型)
function Person(name) {
    this.name = name || '无名氏';    // 实例基本属性(该属性,强调私有,不共享)
    this.arr = [1];   // 实例引用属性(该属性,强调私有,不共享)
    this.say = function() {
        console.log('hrllo');
    };
};

function Student(name, grade) {
    Person.call(this, name);  // 修改this指向,传入参数为name
    this.grade = grade;
}

let s1 = new Student('Katrina','研一');
let s2 = new Student('Yang', '研二');

// 优点1:可以向父类构造函数传参
console.log(s1.name, s2.name);  // Katrina, Yang

// 优点2: 不共享父类构造函数传参
s1.arr.push(2);
console.log(s2.arr);  // [1]


// 缺点1:不能复用
console.log(s1.say === s2.say);  // false 说明s1和s2的say方法是独立的,不是共享的

// 缺点2:不能继承父类原型上的方法
Person.prototype.walk = function() {
    console.log('walk');
};

s1.walk();   // s1.walk is not a function

3. 组合继承

  • 核心:通过调用父类构造函数,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用
  • 优点:
    1. 保留构造函数的优点:创建子类实例,可以向父类构造函数传参数
    2. 保留原型链的优点:父类的方法定义在父类的原型上,可以实现方法复用
    3. 不共享父类的引用属性
  • 缺点:
    1. 由于调用了2次父类方法,要记得修复Student.prototype.constructor的指向
function Person(name) {
    this.name = name || '无名氏';    // 实例基本属性(该属性,强调私有,不共享)
    this.arr = [1];   // 实例引用属性(该属性,强调私有,不共享)
};

Person.prototype.say  = function() {   // 需要复用
    console.log('hrllo');
};

function Student(name, grade) {
    Person.call(this, name);  // 核心 第二次
    this.grade = grade;
}

Student.prototype = new Person();  // 核心  第一次
Student.prototype.constructor = Student;  // 修改指向 

let s1 = new Student('Katrina','研一');
let s2 = new Student('Yang', '研二');

// 优点1:可以向父类构造函数传参
console.log(s1.name, s2.name);  // Katrina, Yang

// 优点2:可复用父类原型上的方法
console.log(s1.say === s2.say);  // ture

// 优点3: 不共享父类构造函数传参
s1.arr.push(2);
console.log(s2.arr);  // [1]


// 缺点1:由于调用了2次父类的构造方法,会存在一份多余的父类实例属性

4. 实例继承

function Person(name) {
    this.name = name || '无名氏';    // 实例基本属性(该属性,强调私有,不共享)
    this.arr = [1];   // 实例引用属性(该属性,强调私有,不共享)
    this.say = function() {
        console.log('hello');
    };
};

function Student(name,grade){
    let instance = new Person();
    instance.name = name || '无名氏';
    this.grade = grade;
    return instance
};

let s1 = new Student();  
s1.say();  // hello
console.log(s1 instanceof Person);  // true
console.log(s1 instanceof Student); // false

5. 拷贝继承

function Person(name) {
    this.name = name || '无名氏';    // 实例基本属性(该属性,强调私有,不共享)
    this.arr = [1];   // 实例引用属性(该属性,强调私有,不共享)
    this.say = function() {
        console.log('hello');
    };
};

function Student(grade){
    let person = new Person();
    for (let p in person) {
        Student.prototype[p] = person[p];
    };

    this.grade = grade;
};

let s1 = new Student();  // 缺点:不能传递父类参数
s1.say();  // hello
console.log(s1 instanceof Person);  // false
console.log(s1 instanceof Student); // true

6. 寄生组合继承

function Person(name) {
    this.name = name || '无名氏';    // 实例基本属性(该属性,强调私有,不共享)
    this.arr = [1];   // 实例引用属性(该属性,强调私有,不共享)
};

Person.prototype.say = function() {
    console.log('hello');
}

function Student(name,grade){
    Person.call(this,name);  // 核心
    this.grade = grade;
};


// 核心:通过创建中间对象,子类原型和父类原型,就会隔离开
Student.prototype = Object.create(Person.prototype);

// 这里是修复构造函数指向的代码
Student.prototype.constructor = Student;

let s1 = new Student('Katrina','研一');
let s2 = new Student('Yang','研二');
  JavaScript知识库 最新文章
ES6的相关知识点
react 函数式组件 & react其他一些总结
Vue基础超详细
前端JS也可以连点成线(Vue中运用 AntVG6)
Vue事件处理的基本使用
Vue后台项目的记录 (一)
前后端分离vue跨域,devServer配置proxy代理
TypeScript
初识vuex
vue项目安装包指令收集
上一篇文章      下一篇文章      查看所有文章
加:2022-02-28 15:20:07  更:2022-02-28 15:20:48 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 9:43:24-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码