继承是面向对象语言中最显著的一个特征。
它是从已有的类中派生出新的类,新的类能吸收已有类(基类、父类)的数据(特征和行为),并拓展属于自己的新的能力。
原生JavaScipt案例合集
JavaScript +DOM基础
JavaScript 基础到高级
Canvas游戏开发
类:具有相同特征和行为的集合。
比如:人类有姓名、年龄、性别、身高、体重等属性,吃饭、睡觉、走路等等行为,所以人可以划为一类。
人类这个大的范围太广了,我们还可以进行细分,根据不同的划分标准可以划分出不同的小类:
按照肤色:黄种人 白种人 黑种人
按照国籍:中国人、法国人、美国人、俄罗斯等
按照工种:教师、学生、医生、工人、农民等等
…
在传统的JS中不存在类的概念,我们使用构造函数模拟类,并通过一些方式实现类与类之间的继承。
对象:从类中拿出的具体特性和行为的个体。
比如:张三丰 年龄23 性别男 黄种人 国籍中华人民共和国 是一个医生
类和对象关系:
类是对象的抽象化;
对象是类的具体化。
传统JS中提供了几种继承的方式:类式继承(原型继承)、构造函数式继承(apply和call方法)、组合式继承(前面两种组合)、寄生式继承(类式继承的优化)、寄生组合式继承(寄生继承和组合继承的结合)
类式继承也叫原型继承,将子类的原型指向父类实例化对象。
//定义父类
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需要优化。
构造函数式继承也叫对象冒充继承。利用 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,"我是一只小小小鸟,怎么飞也飞不高!!");
这种方式对 父类和子类有一些共同的参数,进行了优化;
当调用父类原型中的方法时,报错。
说明,这种方式不是真正的继承。
但是,如果是在父类函数中自带的本地属性和方法,可以直接调用。
原型继承 + 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,"我是一只小小小鸟,怎么飞也飞不高!!");
这种方式解决的问题:父子类参数相同问题,实现真正的继承可以调用父类原型中的方法。
依旧存在的问题:在实例化对象的原型上,存在空的属性。
封装一个函数,解决原型上空参数问题。
//定义父类
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)]
解决的问题:空参数
依旧存在的问题:父子参数重复
这是我们建议的最终形态,优化程度较高。
//定义父类
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)]
优化了:空参数问题 父子重复参数的问题
//定义父类
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, "我是一只小小小鸟,怎么飞也飞不高!!");
// 通过关键字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)]
把父类的所有属性和方法,拷贝进子类
// 定义一个父类的构造函数
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会引发父构造函数原型的改动。
因为对象属于复杂数据类型,而复杂数据类型的赋值是引用的赋值,跟简单数据类型的赋值是不一样的。
而拷贝之后修改子构造函数原型不会引发父构造函数原型的变化。
宿主环境就是 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”]
/**
// 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
``