【重学前端】004-JavaScript:我们真的需要模拟类吗
文章目录
一、曾经的“模拟面向对象”
1、“模拟面向对象”
思维导图
早期情况概述
早期的 JavaScript 程序员一般有过使用 JavaScript **“模拟面向对象”**的经历;
实际上,JavaScript 本身就是正统的面向对象语言!
描述对象的方式 = 基于类(Java等) + 基于原型(JavaScript等);
基于类的描述方式更成功,导致人们错误地认为这样的方式才是面向对象。
公司政治原因
这模仿得不够彻底啊!如果 Java 的写法都能在这里使用,那对于 Java 程序员岂不是一件大好事!
如果 JavaScript 也是 sun 公司写的就好了,或者直接使用 Java 写前后端,岂不妙哉!
就像 TypeScript 的语法,和微软自家的 C# 就很像!
当然,JavaScript 本身也有很多实用而 Java 没有的特性!
由于公司的一些政治原因,JavaScript 在推出之时,被要求去模仿 Java ,因此有了 new
、this
等语言特性,使其看起来更像 Java !
2、ES6 提供了 class 关键字
ES6 提供了 class
这个关键字来定义类,尽管其仍然是基于原型运行时系统的模拟,但修正了一些常见的“坑”,统一了社区方案,这对语言的有很大好处!
二、什么是原型
0、思维导图
1、顺应人类自然思维的产物
原型是顺应人类自然思维的产物。
比如“照猫画虎”,这就是一种基于原型的思维!
再比如电影中的“外星人”,他们和人类很相似,这也是一种基于原型的思维!人们想象不出来外星人到底是什么样子,就在自身的基础上创造出“外星人”的样子!这就是基于原型的思维!
再比如电影、电视剧、小说中的故事,我们常说主人公的原型就是谁谁谁,我认为这也是基于原型的思维!
说了这么多,原型的含义也没啥复杂的了!多举例子是表达的妙诀!
2、基于“原型”描述对象
此时,我们就更倾向于描述一个东西像什么,比如老虎像大猫!
创建对象的方式:复制过来,改一改!
JavaScript 实现复制操作的两种方式:
引用方式: 新对象持有一个原对象的引用;
真复制: 直接复制原对象,新对象与原对象无任何关系!
3、基于“类”描述对象
比较基于“原型”,我们是把这一类对象抽象成一个模型,然后基于这个模型来描述具体的对象!比如学生这一类对象,我们把他们共有的状态和行为抽象成一个学生类,比如都有姓名、学号、年龄、性别等等,然后每一个具体的学生根据类这个模型创建出来,这就达到了描述对象的效果!
基于“类”比基于“原型”更抽象一点!
创建对象的方式:基于类模型,创建!
三、JavaScript 的原型
1、对原型系统的 2 条概括
抛开模拟 Java 类的复杂语法设施,原型系统非常简单!
- 如果所有对象都有私有字段[[prototype]],就是对象的原型;
- 读一个属性,如果对象本身没有,则会继续访问对象的原型,直到原型为空或者找不到为止。
2、访问和操作原型
这个原型在 ES6 以来提供了一系列内置函数,以便更为直观地访问和操作原型。
3、三个方法
Object.create
:根据指定的原型创建新对象,原型可以是 null ;Object.getPrototypeOf
:获得一个对象的原型;Object.setPrototyoeOf
:设置一个对象的原型。
创建新对象
代码演示
// 创建一个对象作为原型
var 父亲 = {
名字: "刘备",
年龄: 63,
民族: "汉族",
说: function () {
console.log("我是刘备");
},
};
console.log(父亲);
父亲.说();
// 创建一个新对象
var 儿子 = Object.create(父亲);
console.log(儿子);
儿子.名字 = "刘禅";
儿子.年龄 = 16;
儿子.说 = function () {
console.log("我是刘禅");
};
console.log(儿子);
儿子.说();
console.log(儿子.民族); // 注意:汉族
运行结果
如果对象本身没有,则会继续访问对象的原型,直到原型为空或者找不到为止!
PS D:\MyFile\VSCodeProjects\study-js> node .\zibo.js
{ '名字': '刘备', '年龄': 63, '民族': '汉族', '说': [Function: 说] }
我是刘备
{}
{ '名字': '刘禅', '年龄': 16, '说': [Function (anonymous)] }
我是刘禅
汉族
获得一个对象的原型
方法说明
Object.getPrototypeOf(obj) :返回指定对象的原型(内部[[Prototype]]属性的值)。
obj:要返回其原型的对象。
返回值:给定对象的原型。如果没有继承属性,则返回 null
代码演示
// 创建一个对象作为原型
var 父亲 = {
名字: "刘备",
年龄: 63,
民族: "汉族",
说: function () {
console.log("我是刘备");
},
};
console.log(父亲);
父亲.说();
// 获得对象的原型
console.log(Object.getPrototypeOf(父亲));
// 创建一个新对象
var 儿子 = Object.create(父亲);
console.log(儿子);
儿子.名字 = "刘禅";
儿子.年龄 = 16;
儿子.说 = function () {
console.log("我是刘禅");
};
console.log(儿子);
儿子.说();
console.log(儿子.民族); // 注意:汉族
// 获得对象的原型
console.log(Object.getPrototypeOf(儿子));
运行结果
PS D:\MyFile\VSCodeProjects\study-js> node .\zibo.js
[Object: null prototype] {}
{ '名字': '刘备', '年龄': 63, '民族': '汉族', '说': [Function: 说] }
设置一个对象的原型
方法说明
Object.setPrototypeOf(obj1, obj2):将 obj1 的原型设置为 obj2
代码演示
// 创建一个对象作为原型
var 父亲 = {
名字: "刘备",
年龄: 63,
民族: "汉族",
说: function () {
console.log("我是刘备");
},
};
// 获得对象的原型
console.log(Object.getPrototypeOf(父亲));
// 创建一个新对象
var 儿子 = {};
// 设置对象的原型
Object.setPrototypeOf(儿子, 父亲);
// 获得对象的原型
console.log(Object.getPrototypeOf(儿子));
运行结果
PS D:\MyFile\VSCodeProjects\study-js> node .\zibo.js
[Object: null prototype] {}
{ '名字': '刘备', '年龄': 63, '民族': '汉族', '说': [Function: 说] }
四、ES6 中的类
1、class 关键字
ES6 加入了新特征 class ,在任何场景下都推荐使用 ES6 语法来定义类,令 function 回归原本的函数语义。
ES6 中引入了 class 关键字,并且在标准中删除了所有 [[class]] 相关的私有属性描述,类的概念正式从属性升级成语言的基础设施,从此,基于类的编程方式成为了 JavaScript 的官方编程范式。
2、基本写法
代码示例
在现有的类语法中,getter/setter 和 method 是兼容性最好的。
class Rectangle {
constructor(height, width) {
this.height = height;
this.width = width;
}
// Getter
get area() {
return this.calcArea();
}
// Method
calcArea() {
return this.height * this.width;
}
}
console.log(new Rectangle(3, 4).area); // 12
说明
类的写法实际上也是由原型运行时来承载的,逻辑上 JavaScript 认为每个类是有共同原型的一组对象,类中定义的属性和方法被写在原型对象上。
3、类的继承
代码示例
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(this.name + ' makes a noise.');
}
}
class Dog extends Animal {
constructor(name) {
super(name); // 调用父类构造函数并传入 name 参数
}
speak() {
console.log(this.name + ' barks.');
}
}
let d = new Dog('Mitzie');
d.speak(); // Mitzie barks.
说明
使用 extends 关键字自动设置了 constructor,并且会自动调用父类的构造函数,这是一种更少坑的设计。
一些激进的观点认为,class 关键字和箭头运算符可以完全替代旧的 function 关键字,它更明确地区分了定义函数和定义类两种意图,这是有一定道理的。