前言:JavaScript首先在ES6没有出来之前,利用一个叫原型的一系列机制来用一段很长的代码来实现类的继承,说白了就是在函数里面默认给你加个一个叫原型的对象属性,再利用一系列指向来完成继承。在ES6之后,才有了形式上的类class及其对象,以及一个单词extends就搞定的继承,虽说搞定,但这里面的机制还是原型相关知识,记录学习一下。
不管是原型也好,新加的class也罢,就一个目的——为了实现面向对象。从两个体系来展开详述。
体系一:原型+原型链
隐式原型__proto__
每个对象里面都有__proto__属性,这个叫对象原型,这个玩意指向构造函数的prototype对象
__proto__对象原型的意义就在于为对象的查找机制提供一个方向,或者说一条路线,但是它是一个非标准属性,因此实际开发中,不可以使用这个属性,它只是内部指向原型对象 prototype
比如说,有一个Person类,有一个Student类,Student类继承自Person类,Student有一个实例student_1,那么student_1会有__proto__对象属性,并且展开该__proto__,会发现里面是Person类的方法和属性
显式原型prototype
每个构造函数里面都有一个属性,这个属性叫prototype,指向另一个对象(有什么用?在后面原型链就会发现有用了),并且这个属性是一个对象,叫做构造函数原型
这样可以解决一个问题,就是创建不同实例,这些事例所用的方法都是同一个内存下的方法,实现共享
constructor构造函数
- 对象原型( __proto__)和构造函数(prototype)原型对象里面都有一个属性 constructor 属性 ,constructor 我们称为构造函数,因为它指回构造函数本身。
- constructor 主要用于记录该对象引用于哪个构造函数,它可以让原型对象重新指向原来的构造函数。
- 一般情况下,对象的方法都在构造函数的原型对象中设置。如果有多个对象的方法,我们可以给原型对象采取对象形式赋值,但是这样就会覆盖构造函数原型对象原来的内容,这样修改后的原型对象 constructor 就不再指向当前构造函数了。此时,我们可以在修改后的原型对象中,添加一个 constructor 指向原来的构造函数。
instanceof
a instanceof b
判断是true还是false
看a的constructor是什么
当b为 a的constructor或a的__proto__的constructor……
结果都是true
原型链和成员查找机制
原型体系中的继承
call方法可以改变一个函数的指向
- 继承父构造函数里面的属性
// 1. 父构造函数
function Father(uname, age) {
// this 指向父构造函数的对象实例
this.uname = uname;
this.age = age;
}
// 2 .子构造函数
function Son(uname, age, score) {
// this 指向子构造函数的对象实例
// 3.使用call方式实现子继承父的属性
Father.call(this, uname, age);
this.score = score;
}
var son = new Son('刘德华', 18, 100);
console.log(son);
- 继承方法
// 1. 父构造函数
function Father(uname, age) {
// this 指向父构造函数的对象实例
this.uname = uname;
this.age = age;
}
Father.prototype.money = function() {
console.log(100000);
};
// 2 .子构造函数
function Son(uname, age, score) {
// this 指向子构造函数的对象实例
Father.call(this, uname, age);
this.score = score;
}
// Son.prototype = Father.prototype; 这样直接赋值会有问题,如果修改了子原型对象,父原型对象也会跟着一起变化
Son.prototype = new Father();
// 如果利用对象的形式修改了原型对象,别忘了利用constructor 指回原来的构造函数
Son.prototype.constructor = Son;
// 这个是子构造函数专门的方法
Son.prototype.exam = function() {
console.log('孩子要考试');
}
var son = new Son('刘德华', 18, 100);
console.log(son);
体系二:类(class)【ES6】
在 ES6 中新增加了类的概念,可以使用 class 关键字声明一个类,之后以这个类来实例化对象。类抽象了对象的公共部分,它泛指某一大类(class)对象特指某一个,通过类实例化一个具体的对象。其创建的方式和之前学过的语言很类似。
在 ES6 中类没有变量或是函数提升概念,所以必须先定义类,才能通过类实例化对象,这也是类和函数最大的不同。
原型方法和原型字段声明
原型方法:
- 构造函数方法
- get set
- 静态方法:通常作为类的工具函数
- 普通方法
原型字段:
- 私有字段
- 公有字段
class Tooltip {
#test1 = 111; // 私有成员
test2 = 222; // 公有成员
static test3 = 333; // 静态成员(类内部不能访问 相当于类的属性)
static fun1(a, b) {
// 静态方法(类内部不能访问 相当于类的属性)
return a + b;
}
constructor() {
console.log('private member test1: ', ++this.#test1);
console.log('public member test2: ', ++this.test2);
console.log('origin class: ', this);
}
}
class TextTooltip extends Tooltip {
constructor(quill, options) {
super(quill, options.bounds);
}
}
this.textToolTip = new TextTooltip(quill, options);
console.log(this.textToolTip,
Tooltip.test3,
TextTooltip.test3,
TextTooltip.fun1(333, 333));
export default TextTooltipToolbar;
继承
// 奥特曼类
class Aoteman {
// 构造函数里面放共有属性、方法
constructor(name, age) {
this.name = name;
this.age = age;
}
Ability(abi) {
console.log(this.name + " can " + abi);
}
}
// 假 继承 奥特曼
class Jia extends Aoteman {
constructor (x, y) {
// 继承父类的构造函数
super(x,y);
// 方便写自己的函数
this.x = x;
this.y = y;
}
// 自己内部的函数
Chuiniu() {
console.log(this.x + " chuiniu");
}
}
// 创建迪迦对象
let dijia = new Aoteman('dijia', 100);
console.log(dijia);
dijia.Ability("X-ray");
let dijia_jia = new Jia("dijia_jia", 50);
dijia_jia.Ability("xxx");
dijia_jia.Chuiniu();
多重继承
JavaScript对象的继承只能继承一个父类,因为继承的根本实现是基于原型链的,所以继承不能同时指向多个对象,所以实现多重继承有两个思路:
- 父类链式继承
class Parent1 extends Parent2 {}
class Parent2 extends Parent3 {}
class Child extends Parent1 {}
- 实现Mixin来继承
function mixin(...mixins) {
class Mixin {
constructor(...args) {
mixins.forEach(
mixin => copyProperties(this, new mixin(...args)) // 拷贝实例属性
)
}
}
mixins.forEach(
mixin => {
copyProperties(Mixin, mixin); // 拷贝静态属性
copyProperties(Mixin.prototype, mixin.prototype); // 拷贝原型属性
}
)
return Mixin;
}
function copyProperties(target, source) {
for (let key of Reflect.ownKeys(source)) {
if (['constructor', 'prototype', 'name'].indexOf(key) < 0) {
let desc = Object.getOwnPropertyDescriptor(source, key);
Object.defineProperty(target, key, desc);
}
}
}
class Child extends mixin(Parent1, Parent2, Parent3) {}
对象的各种创建方式
字面量
属性和方法的调用:
属性可以用"."和["属性名"]来调用
方法用"."来调用
模式工厂
new一个空对象,let obj = new Object();
然后利用"."的方式给其添加属性和方法
内置的Object()构造函数
构造函数
构造函数:是一种特殊的函数,主要用来初始化对象,即为对象成员变量赋初始值,它总与 new 运算符一起使用。我们可以把对象中一些公共的属性和方法抽取出来,然后封装到这个函数里面。
function 构造函数名(形参1,形参2,形参3) {
this.属性名1 = 参数1;
this.属性名2 = 参数2;
this.属性名3 = 参数3;
this.方法名 = 函数体;
}
let obj = new 构造函数名(实参1,实参2,实参3)
- 构造函数约定首字母大写。
- 函数内的属性和方法前面需要添加 this ,表示当前对象的属性和方法。
- 构造函数中不需要 return 返回结果。
- 当我们创建对象的时候,必须用 new 来调用构造函数。
原型模式
下面详述什么是原型
对象的内置方法
defineProperty
Object.defineProperty
设置或修改对象中的属性
interface PropertyDescriptor {
configurable?: boolean;
enumerable?: boolean;
value?: any;
writable?: boolean;
get?(): any;
set?(v: any): void;
}
删除对象属性
- 直接用delete,暴力简单
- 也可以参考:如何优雅的删除对象中的指定属性?
assign()
- Object.assign后者覆盖前者,再返回前者
- 常用于深拷贝浅拷贝数据,如果对象的值是引用类型的数据,那么是浅拷贝https://www.yuque.com/wztlink1013/blog/lwi9wr
对象的遍历
https://blog.csdn.net/loveoobaby/article/details/127165722
https://blog.csdn.net/loveoobaby/article/details/127165722
for...in
语句用于对数组或者对象的属性进行循环操作。
其语法如下:
for (变量 in 对象名字) {
// 在此执行代码
}
- 语法中的变量是自定义的,它需要符合命名规范,通常我们会将这个变量写为 k 或者 key。书里面建议用const来加持。
for (let k in obj) {
console.log(k); // 这里的 k 是属性名
console.log(obj[k]); // 这里的 obj[k] 是属性值
}
Object.keys(obj)
获取属性名
var obj = {
id: 1,
pname: '小米',
price: 1999,
num: 2000
};
var result = Object.keys(obj)
console.log(result)//[id,pname,price,num]
评论区