2024年零基础学习JS--基础篇--使用类,前端自学教程

总结

秋招即将开始,校招的朋友普遍是缺少项目经历的,所以底层逻辑,基础知识要掌握好!

而一般的社招,更是神仙打架。特别强调,项目经历不可忽视;几乎简历上提到的项目都会被刨根问底,所以项目应用的技术要熟练,底层原理必须清楚。

这里给大家提供一份汇集各大厂面试高频核心考点前端学习资料。涵盖 HTML,CSS,JavaScript,HTTP,TCP协议,浏览器,Vue框架,算法等高频考点238道(含答案)

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

资料截图 :

高级前端工程师必备资料包

每一次调用 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()") 方法,它返回当前日期。这个方法不属于任何日期实例——它属于类本身。

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

  • 静态方法
  • 静态字段
  • 静态 getter 与 setter

可见,我们之前见过的所有类的特性都有其静态版本。例如,对于我们的 Color 类,我们可以创建一个静态方法,它检查给定的三元组是否是有效的 RGB 值:

class Color {
  static isValid(r, g, b) {
    return r >= 0 && r <= 255 && g >= 0 && g <= 255 && b >= 0 && b <= 255;
  }
}

Color.isValid(255, 0, 0); // true
Color.isValid(1000, 0, 0); // false

静态属性与实例属性的区别在于:

  • 它们有 static 前缀,且
  • 它们不能从实例中访问。
console.log(new Color(0, 0, 0).isValid); // undefined

有一个特殊结构叫做静态初始化块 (en-US)"),它是一个在类第一次加载时运行的代码块。

class MyClass {
  static {
    MyClass.myStaticProperty = "foo";
  }
}

console.log(MyClass.myStaticProperty); // 'foo'

扩展与继承

类的一个关键特性(除了私有字段)是继承,这意味着一个对象可以“借用”另一个对象的大部分行为,同时覆盖或增强某些部分的逻辑。

例如,假定我们需要为 Color 类引入透明度支持。我们可能会尝试添加一个新的字段来表示它的透明度:

class Color {
  #values;
  constructor(r, g, b, a = 1) {
    this.#values = [r, g, b, a];
  }
  get alpha() {
    return this.#values[3];
  }
  set alpha(value) {
    if (value < 0 || value > 1) {
      throw new RangeError("Alpha 值必须在 0 与 1 之间");
    }
    this.#values[3] = value;
  }
}

然而,这意味着每个实例——即使是大多数不透明的实例(那些 alpha 值为 1 的实例)——都必须有额外的 alpha 值,这并不是很优雅。此外,如果特性继续增长,我们的 Color 类将变得非常臃肿且难以维护。

所以,在面向对象编程中,我们更愿意创建一个派生类。派生类可以访问父类的所有公共属性。在 JavaScript 中,派生类是通过 extends 子句声明的,它指示它扩展自哪个类。

下面是父类代码:

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

下面是派生类代码:

总结

秋招即将开始,校招的朋友普遍是缺少项目经历的,所以底层逻辑,基础知识要掌握好!

而一般的社招,更是神仙打架。特别强调,项目经历不可忽视;几乎简历上提到的项目都会被刨根问底,所以项目应用的技术要熟练,底层原理必须清楚。

这里给大家提供一份汇集各大厂面试高频核心考点前端学习资料。涵盖 HTML,CSS,JavaScript,HTTP,TCP协议,浏览器,Vue框架,算法等高频考点238道(含答案)

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

资料截图 :

高级前端工程师必备资料包

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值