【重学前端】004-JavaScript:我们真的需要模拟类吗

文章目录

  • ​​【重学前端】004-JavaScript:我们真的需要模拟类吗​​
  • ​​一、曾经的“模拟面向对象”​​
  • ​​1、“模拟面向对象”​​
  • ​​思维导图​​
  • ​​早期情况概述​​
  • ​​公司政治原因​​
  • ​​2、ES6 提供了 class 关键字​​
  • ​​二、什么是原型​​
  • ​​0、思维导图​​
  • ​​1、顺应人类自然思维的产物​​
  • ​​2、基于“原型”描述对象​​
  • ​​3、基于“类”描述对象​​
  • ​​三、JavaScript 的原型​​
  • ​​1、对原型系统的 2 条概括​​
  • ​​2、访问和操作原型​​
  • ​​3、三个方法​​
  • ​​创建新对象​​
  • ​​代码演示​​
  • ​​运行结果​​
  • ​​获得一个对象的原型​​
  • ​​方法说明​​
  • ​​代码演示​​
  • ​​运行结果​​
  • ​​设置一个对象的原型​​
  • ​​方法说明​​
  • ​​代码演示​​
  • ​​运行结果​​
  • ​​四、ES6 中的类​​
  • ​​1、class 关键字​​
  • ​​2、基本写法​​
  • ​​代码示例​​
  • ​​说明​​
  • ​​3、类的继承​​
  • ​​代码示例​​
  • ​​说明​​

一、曾经的“模拟面向对象”

1、“模拟面向对象”

思维导图

【重学前端】004-JavaScript:我们真的需要模拟类吗_前端

早期情况概述

早期的 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、思维导图

【重学前端】004-JavaScript:我们真的需要模拟类吗_javascript_02

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 关键字,它更明确地区分了定义函数和定义类两种意图,这是有一定道理的。