2024年最全零基础学习JS--基础篇--使用类(2),2024年最新前端系统工程师面试

JavaScript 和 ES6

在这个过程你会发现,有很多 JS 知识点你并不能更好的理解为什么这么设计,以及这样设计的好处是什么,这就逼着让你去学习这单个知识点的来龙去脉,去哪学?第一,书籍,我知道你不喜欢看,我最近通过刷大厂面试题整理了一份前端核心知识笔记,比较书籍更精简,一句废话都没有,这份笔记也让我通过跳槽从8k涨成20k。

JavaScript部分截图

如果你觉得对你有帮助,可以戳这里获取:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

}
// 静态块
static {
// 静态初始化代码
}
// 字段、方法、静态字段、静态方法、静态块都可以使用私有形式
#myPrivateField = “bar”;
}


如果你用过早于 ES6 的版本,你可能更熟悉使用函数作为构造函数。上面的模式大致可以转换为以下函数构造器:



function MyClass() {
this.myField = “foo”;
// 构造函数体
}
MyClass.myStaticField = “bar”;
MyClass.myStaticMethod = function () {
// myStaticMethod 体
};
MyClass.prototype.myMethod = function () {
// myMethod 体
};

(function () {
// 静态初始化代码
})();


###### 构造一个类:


在声明一个类之后,你可以使用 [new]( ) 运算符来创建它的实例。



const myInstance = new MyClass();
console.log(myInstance.myField); // ‘foo’
myInstance.myMethod();


典型函数构造器可以使用 `new` 来构造,也可以不使用 `new` 来调用。然而,对于类的调用则必须使用 `new`,否则会导致错误。



const myInstance = MyClass(); // TypeError: Class constructor MyClass cannot be invoked without ‘new’



#### 构造函数


类最重要的工作之一就是作为对象的“工厂”。在类中,实例的创建是通过[构造函数]( )来完成的。


例如,我们创建一个名为 `Color` 的类,它代表了一个特定的颜色。用户通过传入一个 [RGB (en-US)]( )") 三元组来创建颜色。



class Color {
constructor(r, g, b) {
// 将 RGB 值作为 this 的属性
this.values = [r, g, b];
}
}


打开你的浏览器的开发者工具,将上面的代码粘贴到控制台中,然后创建一个实例:



const red = new Color(255, 0, 0);
console.log(red);


你应该会看到如下输出:



Object { values: (3) […] }
values: Array(3) [ 255, 0, 0 ]


你已经成功创建了一个 `Color` 实例,该实例有一个 `values` 属性,它是一个包含了你传入的 RGB 值的数组。这与下面的代码几乎是等价的:



function createColor(r, g, b) {
return {
values: [r, g, b],
};
}


每一次调用 `new` 都将创建一个新的实例。



const red = new Color(255, 0, 0);
const anotherRed = new Color(255, 0, 0);
console.log(red === anotherRed); // false


在类的构造函数里,`this` 的值指向新创建的实例。你可以赋予它新的属性,或者读取已有的属性(尤其是方法——我们将在下一节中介绍)。


`this` 的值将自动作为 `new` 的结果返回。不建议从构造函数中返回任何值——因为如果你返回一个非原始类型的值,它将成为 `new` 表达式的值,而 `this` 的值将被丢弃。你可以在 new 运算符的[描述]( )中阅读更多关于 `new` 的内容。



class MyClass {
constructor() {
this.myField = “foo”;
return {};
}
}

console.log(new MyClass().myField); // undefined



#### 实例方法


如果一个类只有构造函数,那么它与一个只创建普通对象的 `createX` 工厂函数并没有太大的区别。然而,类的强大之处在于它们可以作为“模板”,自动将方法分配给实例。


例如,对于 `Date` 实例,你可以用一系列方法来获取日期的不同部分,例如[年份]( )、[月份]( )、[星期几]( )等等。你也可以通过 `setX` 方法来设置这些值,例如 [setFullYear]( )。


对于我们的 `Color` 类,我们可以添加一个方法来获取红色值:



class Color {
constructor(r, g, b) {
this.values = [r, g, b];
}
getRed() {
return this.values[0];
}
}

const red = new Color(255, 0, 0);
console.log(red.getRed()); // 255


如果你使用方法,它将在所有实例之间共享。一个函数可以在所有实例之间共享,且在不同实例调用时其行为也不同,因为 `this` 的值不同。你也许好奇这个方法存储在*哪里*——它被定义在所有实例的原型上,即 `Color.prototype`,详情参阅[继承与原型链]( )。


相似的,我们也可以添加一个 `setRed` 方法来设置红色值:



class Color {
constructor(r, g, b) {
this.values = [r, g, b];
}
getRed() {
return this.values[0];
}
setRed(value) {
this.values[0] = value;
}
}

const red = new Color(255, 0, 0);
red.setRed(0);
console.log(red.getRed()); // 0; 此时也即黑色



#### 私有字段


你或许会好奇:为什么我们要费心使用 `getRed` 和 `setRed` 方法,而不是直接访问实例上的 `values` 数组呢?



class Color {
constructor(r, g, b) {
this.values = [r, g, b];
}
}

const red = new Color(255, 0, 0);
red.values[0] = 0;
console.log(red.values[0]); // 0


在面向对象编程中,有一个叫做“封装”的哲学。这是说你不应该访问对象的底层实现,而是使用抽象方法来与之交互。例如,如果我们突然决定将颜色表示为 [HSL]( ) 而不是 RGB:



class Color {
constructor(r, g, b) {
// values 现在是一个 HSL 数组!
this.values = rgbToHSL([r, g, b]);
}
getRed() {
return this.values[0];
}
setRed(value) {
this.values[0] = value;
}
}

const red = new Color(255, 0, 0);
console.log(red.values[0]); // 0; 不再是 255,因为 HSL 模型下纯红色的 H 分量为 0


用户对 `values` 数组代表 RGB 值的假设不再成立,这可能会打破他们的代码逻辑。因此,如果你是一个类的实现者,你应该隐藏实例的内部数据结构,以保持 API 的简洁性,并防止在你做了一些“无害的重构”时,用户代码不至于崩溃。在类中,这是通过[私有字段]( )来实现的。


私有字段是以 `#`(井号)开头的标识符。井号是这个字段名的必要部分,这也就意味着私有字段永远不会与公共属性发生命名冲突。为了在类中的任何地方引用一个私有字段,你必须在类体中*声明*它(你不能在类体外部创建私有字段)。除此之外,私有字段与普通属性几乎是等价的。



class Color {
// 声明:每个 Color 实例都有一个名为 #values 的私有字段。
#values;
constructor(r, g, b) {
this.#values = [r, g, b];
}
getRed() {
return this.#values[0];
}
setRed(value) {
this.#values[0] = value;
}
}

const red = new Color(255, 0, 0);
console.log(red.getRed()); // 255


在我们将 `values` 字段私有化之后,我们可以在 `getRed` 和 `setRed` 方法中添加一些逻辑,而不仅仅是简单信息传递。例如,我们可以在 `setRed` 中添加一个检查逻辑,以确保它是一个有效的 R 值:



class Color {
#values;
constructor(r, g, b) {
this.#values = [r, g, b];
}
getRed() {
return this.#values[0];
}
setRed(value) {
if (value < 0 || value > 255) {
throw new RangeError(“无效的 R 值”);
}
this.#values[0] = value;
}
}

const red = new Color(255, 0, 0);
red.setRed(1000); // RangeError:无效的 R 值


如果我们暴露 `values` 属性,我们的用户就会很容易地绕过这个检查,直接给 `values[0]` 赋值,从而创建一个无效的颜色。但是通过良好封装的 API,我们可以使我们的代码更加健壮,防止下游的逻辑错误。


类方法可以读取其他实例的私有字段,只要它们属于同一个类即可。



class Color {
#values;
constructor(r, g, b) {
this.#values = [r, g, b];
}
redDifference(anotherColor) {
// #values 不一定要从 this 访问:
// 你也可以访问属于同一个类的其他实例的私有字段。
return this.#values[0] - anotherColor.#values[0];
}
}

const red = new Color(255, 0, 0);
const crimson = new Color(220, 20, 60);
red.redDifference(crimson); // 35


然而,若 `anotherColor` 并非一个 `Color` 实例,`#values` 将不存在(即使另一个类有一个同名的私有字段,它也不是同一个东西,也不能在这里访问)。访问一个不存在的私有字段会抛出错误,而不是像普通属性一样返回 `undefined`。如果你不知道一个对象上是否存在一个私有字段,且你希望在不使用 `try`/`catch` 来处理错误的情况下访问它,你可以使用 [in]( ) 运算符。



class Color {
#values;
constructor(r, g, b) {
this.#values = [r, g, b];
}
redDifference(anotherColor) {
if (!(#values in anotherColor)) {
throw new TypeError(“Color instance expected”);
}
return this.#values[0] - anotherColor.#values[0];
}
}


**备注:** 请记住,`#` 是一种特殊的标识符语法,你不能像字符串一样使用该字段名。`"#values" in anotherColor` 会查找一个名为 `"#values"` 的属性,而不是一个私有字段。


方法、[getter 与 setter]( ) 也可以是私有的。当你需要类内部做一些复杂的事情,但是不希望代码的其他部分调用时,它们就很有用。



#### getter字段


`color.getRed()` 和 `color.setRed()` 允许我们读取和写入颜色的红色值。如果你熟悉像 Java 这样的语言,你会对这种模式非常熟悉。然而,在 JavaScript 中,使用方法来简单地访问属性仍然有些不便。*getter 字段*允许我们像访问“实际属性”一样操作某些东西。



class Color {
constructor(r, g, b) {
this.values = [r, g, b];
}
get red() {
return this.values[0];
}
set red(value) {
this.values[0] = value;
}
}

const red = new Color(255, 0, 0);
red.red = 0;
console.log(red.red); // 0


这就像是对象有了一个 `red` 属性——但实际上,实例上并没有这样的属性!实例只有两个方法,分别以 `get` 和 `set` 为前缀,而这使得我们可以像操作属性一样操作它们。


解释一下:


在JavaScript类中,我们可以定义getter和setter方法来控制对象属性的读取和设置。在这个例子中,`red` 是 `Color` 类的一个实例对象,它具有一个名为 `red` 的属性。


当我们使用赋值操作符(`=`)来给对象的属性赋值时,实际上会调用属性的 setter 方法。在这里,`red.red = 0` 这行代码执行了 `red` 对象的 `red` 属性的 setter 方法,将红色分量值设置为 `0`。


因此,在这个例子中,赋值操作 `red.red = 0` 调用的是 `red` 对象的 `red` 属性的 setter 方法,而不是直接修改属性值或数组。


如果一个字段仅有一个 getter 而没有 setter,它将是只读的。



class Color {
constructor(r, g, b) {
this.values = [r, g, b];
}
get red() {
return this.values[0];
}
}

const red = new Color(255, 0, 0);
red.red = 0;
console.log(red.red); // 255



#### 公共字段


我们已经见过了私有字段,对应地,还有公共字段。公共字段使得实例可以获得属性,且它们常常独立于构造函数的参数。



class MyClass {
luckyNumber = Math.random();
}
console.log(new MyClass().luckyNumber); // 0.5
console.log(new MyClass().luckyNumber); // 0.3


公共字段几乎等价于将一个属性赋值给 `this`。例如,上面的例子也可以转换为:



class MyClass {
constructor() {
this.luckyNumber = Math.random();
}
}



#### 静态属性


在上面的 `Date` 例子中,我们还遇到了 [Date.now()]( )") 方法,它返回当前日期。这个方法不属于任何日期实例——它属于类本身。


[静态属性]( )是一组在类本身上定义的特性,而不是在类的实例上定义的特性。这些特性包括:


* 静态方法


### JavaScript 和 ES6



在这个过程你会发现,有很多 JS 知识点你并不能更好的理解为什么这么设计,以及这样设计的好处是什么,这就逼着让你去学习这单个知识点的来龙去脉,去哪学?第一,书籍,我知道你不喜欢看,我最近通过刷大厂面试题整理了一份前端核心知识笔记,比较书籍更精简,一句废话都没有,这份笔记也让我通过跳槽从8k涨成20k。

![JavaScript部分截图](https://i-blog.csdnimg.cn/blog_migrate/9a07268f0d462781925c33d9877b4343.png)


**[如果你觉得对你有帮助,可以戳这里获取:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】](https://bbs.csdn.net/forums/4304bb5a486d4c3ab8389e65ecb71ac0)**



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值