【JavaScript高级】继承

一、继承

继承是面向对象语言中最显著的一个特征。

它是从已有的类中派生出新的类,新的类能吸收已有类(基类、父类)的数据(特征和行为),并拓展属于自己的新的能力。

原生JavaScipt案例合集
JavaScript +DOM基础
JavaScript 基础到高级
Canvas游戏开发

类:具有相同特征和行为的集合。

比如:人类有姓名、年龄、性别、身高、体重等属性,吃饭、睡觉、走路等等行为,所以人可以划为一类。

人类这个大的范围太广了,我们还可以进行细分,根据不同的划分标准可以划分出不同的小类:

​ 按照肤色:黄种人 白种人 黑种人

​ 按照国籍:中国人、法国人、美国人、俄罗斯等

​ 按照工种:教师、学生、医生、工人、农民等等

​ …

在传统的JS中不存在类的概念,我们使用构造函数模拟类,并通过一些方式实现类与类之间的继承。

对象:从类中拿出的具体特性和行为的个体。

比如:张三丰 年龄23 性别男 黄种人 国籍中华人民共和国 是一个医生

类和对象关系:

​ 类是对象的抽象化;

​ 对象是类的具体化。

传统JS中提供了几种继承的方式:类式继承(原型继承)、构造函数式继承(apply和call方法)、组合式继承(前面两种组合)、寄生式继承(类式继承的优化)、寄生组合式继承(寄生继承和组合继承的结合)

1.1 类式继承

类式继承也叫原型继承,将子类的原型指向父类实例化对象。

//定义父类
function People(name,age,sex){
    this.name = name;
    this.age = age;
    this.sex = sex;
}

People.prototype.eat = function(){
    return "是人就得吃饭,持不同的饭,长不同的身体!!"
}

//定义一个子类
function Doctor(name,age,sex,hospital,des){
    this.name = name;
    this.age = age;
    this.sex = sex;

    this.hospital = hospital;
    this.des = des;
}


//子类的原型 指向 父类的原型
// Doctor.prototype = People.prototype;
//子类的原型 指向 父类的实例化对象
Doctor.prototype = new People();
//子类改变原型指向后 原型链发生紊乱  需要手动修正
Doctor.prototype.constructor = Doctor;
//改变子类的原型后 再去通过原型自定义新的方法
Doctor.prototype.sleep = function(){
    return "是人就得睡觉!管他睡觉打呼噜还是流口水..."
}

//通过父类和子类分别实例化对象
var f = new People("张三丰",123,"男");
var doc = new Doctor("扁鹊",1000,"男","春秋蔡国","扁鹊见蔡桓公,望闻问切");

观察上面控制台信息,还是有一些地方需要优化:

​ 子类的构造函数指向了父类需要优化为指向子类自己的构造函数(上面代码中解决);

​ 父类和子类有一些共同的参数,需要优化;

​ 原型中有空的参数undefined需要优化。

1.2 构造函数式继承

构造函数式继承也叫对象冒充继承。利用 apply 和 call 方法

//定义父类
function Animal(name,age){
    this.name = name;
    this.age = age;
}

Animal.prototype.sayHi = function(){
    return "动物名称:" + this.name + ",动物的年龄:" + this.age;
}

//定义子类
function Bird(name,age,des){
    //利用apply或call改变调用对象
    Animal.apply(this,arguments);

    this.des = des;
}

Bird.prototype.fly = function(){
    return "我想飞的更高..."
}

var bird = new Bird("鹧鸪鸟",4,"我是一只小小小鸟,怎么飞也飞不高!!");

这种方式对 父类和子类有一些共同的参数,进行了优化;

当调用父类原型中的方法时,报错。

说明,这种方式不是真正的继承。

但是,如果是在父类函数中自带的本地属性和方法,可以直接调用。

1.3 组合式继承

原型继承 + aplly继承

//定义父类
function Animal(name,age){
    this.name = name;
    this.age = age;

    this.sayHello = function(){
        return "动物是多种多样,猜我是谁!!!";
    }
}

Animal.prototype.sayHi = function(){
    return "动物名称:" + this.name + ",动物的年龄:" + this.age;
}

//定义子类
function Bird(name,age,des){
    //利用apply或call改变调用对象
    Animal.apply(this,arguments);

    this.des = des;
}

//子类的原型指向父类的实例
Bird.prototype = new Animal();
//修正构造函数
Bird.prototype.constructor = Bird;

Bird.prototype.fly = function(){
    return "我想飞的更高..."
}

var bird = new Bird("鹧鸪鸟",4,"我是一只小小小鸟,怎么飞也飞不高!!");

这种方式解决的问题:父子类参数相同问题,实现真正的继承可以调用父类原型中的方法。

依旧存在的问题:在实例化对象的原型上,存在空的属性。

1.4 寄生式继承

封装一个函数,解决原型上空参数问题。

//定义父类
function Animal(name,age){
    this.name = name;
    this.age = age;
}

Animal.prototype.sayHi = function(){
    return "动物名称:" + this.name + ",动物的年龄:" + this.age;
}

//定义子类
function Bird(name,age,des){
    this.name = name;
    this.age = age;

    this.des = des;
}

//定义一个函数
function extend(F,S){
    var Fn = function () {  };
    Fn.prototype = F.prototype;
    S.prototype = new Fn();
    S.prototype.constructor = S;
}
extend(Animal,Bird);

Bird.prototype.fly = function(){
    return "我想飞的更高..."
}

var bird = new Bird("鹧鸪鸟",4,"我是一只小小小鸟,怎么飞也飞不高!!");

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-23tjXSOd-1692924565703)(JavaScript中的继承.assets/image-20210220153309416.png)]

解决的问题:空参数

依旧存在的问题:父子参数重复

1.5 寄生组合式继承

这是我们建议的最终形态,优化程度较高。

//定义父类
function Animal(name,age){
    this.name = name;
    this.age = age;
}

Animal.prototype.sayHi = function(){
    return "动物名称:" + this.name + ",动物的年龄:" + this.age;
}

//定义子类
function Bird(name,age,des){
    Animal.call(this,name,age);

    this.des = des;
}

//定义一个函数
function extend(F,S){
    var Fn = function () {  };
    Fn.prototype = F.prototype;
    S.prototype = new Fn();
    S.prototype.constructor = S;
}
extend(Animal,Bird);

Bird.prototype.fly = function(){
    return "我想飞的更高..."
}

var bird = new Bird("鹧鸪鸟",4,"我是一只小小小鸟,怎么飞也飞不高!!");

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tff8isPI-1692924565703)(JavaScript中的继承.assets/image-20210220153325867.png)]

优化了:空参数问题 父子重复参数的问题

1.6 ES5中 Object.create() 对继承的优化

//定义父类
function Animal(name, age) {
    this.name = name;
    this.age = age;
}

// 父类原型中的方法
Animal.prototype.sayHi = function () {
    return "动物名称:" + this.name + ",动物的年龄:" + this.age;
}

//定义子类
function Bird(name, age, des) {
    // Animal.call(this, name, age);
    Animal.apply(this, arguments);
    this.des = des;
}

// ES5中 Object.create(obj) 优化继承
Bird.prototype = Object.create(Animal.prototype); //基于 Animal 的原型实例的对象

// 原型继承会造成结构的紊乱,将原型对象的构造函数手动改回到 Bird
Bird.prototype.constructor = Bird;

// 子类原型中的方法
Bird.prototype.fly = function () {
    return "我想飞的更高..."
}

var bird = new Bird("鹧鸪鸟", 4, "我是一只小小小鸟,怎么飞也飞不高!!");

1.7 ES6中的类和继承

// 通过关键字class定义父类 Animal
class Animal {
    // 构造函数,参数为对象的属性
    constructor(props) {
        this.name = props.name || 'Unknown';
        this.age = props.age || 'Unknown';
    }
    // 父类中的共有方法
    sayHi() {
        return "动物名称:" + this.name + ",动物的年龄:" + this.age;
    }
}

// 定义子类,并通过关键字extends继承父类
class Bird extends Animal {
    // 构造函数
    constructor(props, nativeAttri) { //props是继承过来的属性,nativeAttri是子类自己的私有属性
        // 通过关键字super调用实现父类的构造函数,相当于获取父类的this指向
        super(props);
        // 子类的私有属性
        this.species = nativeAttri.species;
        this.des = nativeAttri.des;
    }
    // 子类的私有方法
    fly() {
        return "我想飞的更高..."
    }
}

// 实例化 Bird 对象
var bird = new Bird({
    name: "鹧鸪鸟",
    age: 23
}, {
    species: "鸟类",
    des: "我是一只小小小鸟,怎么飞也飞不高!!"
})

控制台操作

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BusJNRz2-1692924565704)(JavaScript中的继承.assets/image-20210220172350618.png)]

1.8 拓展:构造函数中的拷贝继承

把父类的所有属性和方法,拷贝进子类

// 定义一个父类的构造函数
function Animal() {
}

// 把Animal所有不变的属性,都放到它的prototype对象上
Animal.prototype.species = "动物";

// 父类原型中的方法
Animal.prototype.sayHi = function () {
    return "动物名称:" + this.name + ",动物的年龄:" + this.age;
}

//定义子类
function Bird(name, age, des) {
    this.name = name;
    this.age = age;
    this.des = des;
}

// 拷贝继承(浅拷贝):把父对象的所有属性和方法,拷贝进子对象
function extend(Child, Parent) {
    // 获取父子构造函数的原型
    var p = Parent.prototype;
    var c = Child.prototype;

    // 遍历父构造函数原型的属性,并将其拷贝到子构造函数的原型上
    for (i in p) {
        c[i] = p[i]
    }

    //为子对象设一个uber属性,这个属性直接指向父对象的prototype属性
    c.uber = p;//uber是向上、上一层的意思,相当于在子对象上打开一条通道,可以直接调用父对象的方法;这句话只是为了实现继承的完备性,纯属备用性质
}
extend(Bird, Animal);

// 实例化一个Bird对象
var bird = new Bird("鹧鸪鸟", 4, "我是一只小小小鸟,怎么飞也飞不高!!");

不同于直接继承父构造函数的prototype
直接继承父构造函数的prototype,这时改变子构造函数的prototype会引发父构造函数原型的改动。
因为对象属于复杂数据类型,而复杂数据类型的赋值是引用的赋值,跟简单数据类型的赋值是不一样的。
而拷贝之后修改子构造函数原型不会引发父构造函数原型的变化。

三、内置构造函数

3.1 宿主环境所提供的构造函数

宿主环境就是 JS 代码执行的环境,目前所谓的宿主环境其实是浏览器环境

Image对象

var img = document.createElement(“img”);
var img = new Image();


### 3.2 ECMAScript核心语法提供的构造函数

Object    Array   String    RegExp    Date   Function  Number    Boolean   

// Object对象
// var obj = {};
// var obj = new Object();
var obj = Object();

//Array
// var arr = [];
// var arr = new Array();
var arr = Array();

//RegExp
// var patt = /[\u4e00-\u9fa5]/g;
// var patt = new RegExp(/[\u4e00-\u9fa5]/,“g”);
// var patt = new RegExp(“[0-9]”,“g”);
var patt = RegExp(/[0-9]/,“g”);

// Date
var date = new Date();//Tue May 19 2020 15:09:19 GMT+0800 (中国标准时间) “object”
// var date = Date();//“Tue May 19 2020 15:08:54 GMT+0800 (中国标准时间)” “string”

// String
// var str = “hello”; //“string”
// var str = new String(“hello”); //“object”
var str = String(“hello”); //“string”
var arr = str.split(“”);// (5) [“h”, “e”, “l”, “l”, “o”]
/**

  • var str = String(“hello”);
  • var str2 = new String(str);
  • str2.split(“”)
  • 调用完成返回切割的数组,移除str2
  • */

// Number
// var num = 10;
var num = Number(‘10’);//“number”
// var num = new Number(‘10’);//“object”

// Boolean
// var flag = true;
// var flag = Boolean(undefined);//“boolean”
var flag = new Boolean(undefined);//“object”

// Error
// var error = new Error(“除数不能为0”);
var error = Error(“除数不能为0”);

// Function
// var fn = function(){};//ƒ (){}
// function fn(){}// ƒ fn(){}
// var fn = new Function();//ƒ anonymous(){}
// var fn = Function();//ƒ anonymous(){}

//含有参数和返回值
// var fn = new Function(“a”,“b”,“c”,“return a + b + c”);
//等价于
// var fn = function(a,b,c){
// return a+b+c;
// }

// 返回一个对象 需要的是JSON串格式
var fn = new Function('return ’ + ‘{“name”:“张三丰”,“age”:23}’);
//等价于
// var fn = function(){
// return {
// name:“张三丰”,
// age:23
// }
// }

//总结:Object、Array、RegExp、Error、Function是安全类



### 3.3 内置构造函数之间的关系

JS中除了undefined之外所有的东西都可以看作是对象,函数也是对象;

所有的对象又都可以看作是构造函数Object的实例,Object构造函数也是函数;

所有的函数又是Function的实例;

var fn = new Function();
var arr = [];
var obj = new Object();

console.log(fn instanceof Function);//true
console.log(fn instanceof Array);//false
console.log(fn instanceof Object);//true

console.log(fn.constructor);//ƒ Function() { [native code] }
console.log(fn.constructor instanceof Function);//true
console.log(fn.constructor instanceof Object);//true

console.log(Function instanceof Function);//true
console.log(Function instanceof Object);//true

console.log(arr instanceof Array);//true
console.log(arr instanceof Function);//false
console.log(arr instanceof Object);//true
console.log(arr.constructor instanceof Object);//true
console.log(arr.constructor instanceof Function);//true

console.log(obj instanceof Object);//true
console.log(obj.constructor instanceof Object);//true
console.log(obj.constructor instanceof Function);//true
``

你可能感兴趣的:(前端开发,JavaScript,javascript,原型模式,继承,ES6,类)