哇哦~今天迎来的是掘金后台编写文章的新页面,不知道这个页面是需要通过写文章才能打开的,还是新上的啊哈哈啊哈
本篇由来
其实自己平时在总结上是非常注重的,但是一般是总结的比较随意,会存放在自己的笔记软件中,推荐使用“语雀”,划分目录和页面的美观交互深得我心,当然,适合自己的一个笔记软件才是最重要的;接下来的半个月内,我也将重新回顾自己总结的知识点和项目中遇到的困难点,并整理到掘金文章中
切入主题 对象
谈起对象,其实是一个很需的概念,在大学课程java学习中就留下了一个非常清晰的词语“面向对象”编程,对于对象而言,不仅仅是去创建、去继承,与之相呼应的还有一些理论性的名词,这些名词在javascript中可能是不全体现的,更多体现在java语言程序中;
理论名词介绍
我突然想起大学考试卷子填写概念的场景了 🤯🤯🤯
对象
所谓对象,本质上就是指事物(包括人和物)在程序设计语言中的表现形式。这里的事物可以是任何的东西(如客观存在的对象,或者较为抽象的概念);
比如 小狗,我们将其理解成对象,它是具有某些明确的特征的(体重、颜色、名字等),除此之外,还能够执行某些动作(汪汪叫、吃饭、睡觉等),在javascript语言中这些明确的某些特征就是属性,能够执行的行为就是方法。
类
类就是具备某些共同特征的实体的集合,它是一种抽象的数据类型,它是对所具有相同特征实体的抽象。在面向对象的程序设计语言中,类是对一类“事物”的属性与行为的抽象。
比如:“老人”、“儿童”、“青年“、“中年人“是关于人的年龄层段的划分,他们的共有的某些特征,其实可以抽成“人”,
继承
在传统的OOP环境中,继承通常指的是类与类之间的关系,但由于javascript中不存在类,因此它的继承只能发生在对象之间;
当一个对象继承自另一个对象时,通常会往其中加入新的方法,以扩展被继承的老对象。通常将这一过程称之为“B继承自A”或“B扩展自A”。另外对于新对象来说,它可以根据自己的需要,从继承的那组方法中选择几个来重新定义。这样做并不会改变对象的接口,因为其方法名是相同的,只不过当调用新对象时,该方法的行为与之前不同了
封装
封装主要用于阐述对象中所包含的内容。封装概念通常由两部分组成:
- 相关的数据(用于存储属性)
- 基于这些数据所能做的事(所能调用的方法)
封装的目的是将信息隐藏,即方法与属性的可见性。一般而言,封装包括封装数据和封装实现
在许多语言的对象系统中,封装数据是由语法解析来实现的,这些语言提供了
public、private、protected这些关键字来限定方法和属性的可见性,这种限定分类定义了对象用户所能访问的层次; 但javascript并没有提供对这些关键字的支持,只能依赖变量的作用域来实现封装特性, 而且只能模拟出public 和 private这两种封装性。除了ECMAScript6中提供的let之外,一般通过函数来创建作用域:不过typescript就另当别论了;
多态
多态的实际含义是:同一个操作作用于不同的对象上面,可以产生不同的解释和不同的执行结果。换句话说,给不同的对象发送同一个消息的时候,这些对象会根据这个信息分别给出不同的反馈; 自己停留理解不够深刻,就不班门弄斧了,直接推荐链接阅读
理论总结
- 对象:marry是一个学生(学生是一个对象)
- 属性:marry是女生,黑头发、高个子
- 方法:marry能学习、吃饭、睡觉
- 类:marry是Person类的一个实例
- 原型对象:marry是一个由Person对象扩展而来的新对象
- 封装:marry对象包含了数据和基于这些数据的方法
- 继承:marry,xiaoli,xiaotong都是分别扩展自Person对象的新对象
- 多态:可以随时调用:marry,xiaoli,xiaotong这三个对象各自的talk方法,它们都可以正常工作,尽管这些方法会产生不同的结果。marry注重页面设计,xiaoli注重页面实现,xiaotong注重页面测试
javascript继承实现
javascript的继承方式也有很多种,日常常用的方法也是非常多,并且很多书籍中,也有介绍继承的方法,本文参考《javascript 高级程序设计(第三版)》
原型链继承
在我们了解的JavaScript中,对于原型链的使用是非常多的,被创建的对象可以通过原型链访问上级的对象,而原型链继承的方式正式运用了此访问关系;
原型链继承实现
function Super() {
this.value = true;
}
Super.prototype.getValue = function () {
return this.value;
}
function Sub() { }
console.log("super", new Super())
//Sub继承super
Sub.prototype = new Super();
//将sub的构造函数指向 Sub
Sub.prototype.constructor = Sub;
//创建实例
var instance = new Sub();
console.log('sub', instance)
console.log(instance.getValue())
而sub、Super以及instance之间关系是:
原型链继承存在问题
原型链最主要的问题在于包含引用类型值的原型属性会被所有实例共享,而这也正是为什么要在构造函数中,而不是在原型对象中定义属性的原因。
在通过原型来实现继承时,原型实际上会变成另一个类型的实例。于是,原先的实例属性也就顺理成章地变成了现在的原型属性了;
构造函数包含引用类型
function Super1() {
this.colors = ['red', 'blue', 'green'];
}
function Sub1() { };
//Sub继承了Super
Sub1.prototype = new Super1();
var instance1 = new Sub1();
//查看instance1中的属性
console.log('instance1',instance1)
instance1.colors.push('black');
console.log('instance1.colors',instance1.colors);//'red,blue,green,black'
var instance2 = new Sub1();
console.log('instance2 通过Sub1构造出来的',instance2.colors);//'red,blue,green,black'
- 原型链继承方式,对于复杂数据类型是引用关系,不是每个构造函数中单独享有;
- 创建子类型的是互殴,不能向超类型的构造函数中传递参数,在很多的情况下,其实是需要传递参数场景的
原型继承
借助原型可以基于已有的对象来创建新对象,同时不必因此创建自定义类型;
具体的实现方式;
原型继承实现
function object(o) {
function F() { };
F.prototype = o;
return new F();
}
var superObj = {
init: function (value) {
this.value = value;
},
getValue: function () {
return this.value;
}
}
var subObj = object(superObj);
console.log('subObj',subObj)
subObj.init('sub');
console.log('subObj.getValue',subObj.getValue());//'sub'
与原型链继承的关系;它们的一个重要区别是父类型的实例对象不再作为子类型的原型对象;
这种方式更类似于Object.create()的实现
function Super(){
this.value = 1;
}
Super.prototype.value = 0;
function Sub(){};
Sub.prototype = Object.create(Super.prototype);
Sub.prototype.constructor = Sub;
//创建子类型的实例对象
var instance = new Sub;
console.log(instance.value);//0
function F(){};
F.prototype = Super.prototype;
Sub.prototype = new F();
由上面代码看出,子类的原型对象是临时类F的实例对象,而临时类F的原型对象又指向父类的原型对象;所以,实际上,子类可以继承父类的原型上的属性,但不可以继承父类的实例上的属性;
原型继承和原型链继承都是共享父例引用类型的值;
查看引用类型引用问题
function object(o) {
function F() { };
F.prototype = o;
return new F();
}
var superObj = {
colors: ['red', 'blue', 'green']
};
var subObj1 = object(superObj);
subObj1.colors.push("black");
var subObj2 = object(superObj);
subObj2.colors.push("white");
console.log('superObj.colors',superObj.colors);
console.log('subObj1.colors',subObj1.colors);
借用构造函数
借用构造函数,非常深刻明了了,其实是借助super的属性,即在子类型构造函数的内部调用超类型构造函数,通过使用apply()和call()方法在新创建的对象上执行构造函数;
借用构造继承实现
//借用构造函数
function Super2() {
console.log("Super2执行了")
this.colors = ['red', 'blue', 'green'];
}
function Sub2() {
//继承了Super
Super2.call(this);
}
var instance2 = new Sub2();
instance2.colors.push('black');
console.log('instance2.colors',instance2.colors);// ['red','blue','green','black']
var instance3 = new Sub2();
console.log('instance3.colors',instance3.colors);// ['red','blue','green']
此时我们的引用类型也不会存在冲突啦,这个主要的原因还是因为当我们Sub2中执行的时候,Super2的当前执行作用域在Sub2中,形成了内部的变量关系;
借用构造函数的缺点
Super2中的原型上的方法是无法继续使用的- 不符合我们继承的实现目的
寄生组合式继承
借用构造函数能够帮助我们解决对象引用问题,原型链继承能够帮助我们解决调用原型方法问题,如果将这两种方式组合在一起;岂不美哉~
原型链继承+借用构造函数继承=既可以获取构造函数中的方法,也能够断引用;
组合继承(combination inheritance)有时也叫伪经典继承,指的是将原型链和借用构造函数的技术组合到一块,从而发挥二者之长的一种继承模式。其背后的思路是使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。这样,既通过在原型上定义方法实现了函数复用,又能够保证每个实例都有它自己的属性;
寄生组合式继承实现
var count = 0; //用于计数
function Super(name) {
console.log('Super==count',count++)
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
Super.prototype.sayName = function () {
console.log('Super==this.name',this.name);
};
function Sub(name, age) {
console.log('Sub==count',count++)
// 第二次调用Super(),Sub.prototype又得到了name和colors两个属性,并对上次得到的属性值进行了覆盖
Super.call(this, name);
this.age = age;
}
//继承方法
// 第一次调用Super(),Sub.prototype得到了name和colors两个属性
Sub.prototype = new Super();
Sub.prototype.constructor = Sub;
Sub.prototype.sayAge = function () {
console.log('Sub===this.age',this.age);
}
var instance1 = new Sub("bai", 29);
instance1.colors.push("black");
console.log('instance1.colors',instance1.colors);//['red','blue','green','black']
instance1.sayName();//"bai"
instance1.sayAge();//29
console.log('Super',new Super())
寄生组合式优缺点
- 优点
- 定义在
Super中引用类型数据独立 - 能够向构造函数
Super中传入自定义的参数
- 定义在
- 缺点
Super调用次数问题,继承时候调用一次,实例化Sub后再次调用了Super- 每次实例化
Sub的时候都会调用Super.call
寄生式继承
寄生组合式继承带来的影响需要调用两次Super。寄生组合式继承与组合继承相似,都是通过借用构造函数来继承不可共享的属性,通过原型链的混成形式来继承方法和可共享的属性。只不过把原型继承的形式变成了寄生式继承。使用寄生组合式继承可以不必为了指定子类型的原型而调用父类型的构造函数
寄生式继承只继承了父类型的原型属性,而父类型的实例属性是通过借用构造函数的方式来得到的;也就是 我们借用Super的构造函数,但是去复制Super的原型函数;
寄生式继承实现
function Super(name) {
console.log("Super === 执行")
this.name = name;
this.colors = ["red", "blue", "green"];
}
Super.prototype.sayName = function () {
console.log("this.name",this.name)
return this.name;
};
function Sub(name, age) {
//第一次 借用构造函数
Super.call(this, name);
this.age = age;
}
//继承的方法 就是拷贝原型
if (!Object.create) {
Object.create = function (proto) {
function F() { };
F.prototype = proto;
return new F();
}
}
// 利用复制原型的方式进行操作 将Super.prototype的原型方法通过实例化给Sub
Sub.prototype = Object.create(Super.prototype);
Sub.prototype.constructor = Sub;
var instance1 = new Sub("bai", 29);
instance1.colors.push("black");
console.log('instance1.colors',instance1.colors);//['red','blue','green','black']
instance1.sayName();//"bai"
var instance2 = new Super("hu", 27);
console.log("instance2.colors",instance2.colors);//['red','blue','green']
instance2.sayName();//"hu"
这三者的链式关系
参考文档
-
未被记录的参考文章 ps:好久之前整理的,查过很多文档博客内容,但是没有一一记录下来,我还是很尊重知识来源的的🥳🥳