面试题:你知道如何完美的将 class 转换为 function 吗?

前言

在进行面试题的讲解之前,我们先简单了解一下ES6中的 class 类。

ES6 是 JavaScript 语言的一次重大更新,它引入了类(class)的概念,提供了一种新的基于原型的继承方式。类似 JavaScript 中实现面向对象编程(OOP)的一种方式,它使得代码更加模块化和易于理解。所以 class 其实是一个语法糖(指那些使得语言更加易于阅读、编写或理解的语言特性),其底层还是通过 构造函数 去创建的,它提供了一种更接近传统面向对象编程语言的语法来创建对象。

以下是ES6中类(class)的一些基本特性:

  1. 声明类 :使用class关键字来声明一个类。
js 复制代码
class Example {}
  1. 构造函数 :类中的constructor方法是一个特殊的方法,用于创建类的一个新实例时执行。如果没有显式定义,那么一个默认的constructor将被隐式添加。
js 复制代码
class Example {
    constructor(){
    
    }
}
  1. 实例属性 :类的属性通过在constructor中使用this关键字来定义。
js 复制代码
this.name = name;
  1. 方法:类可以包含方法,这些方法定义在类的主体中。
js 复制代码
class Example {
    constructor(name){
        this.name = name;
    }
    function() {
        console.log(this.name);
    }
}
  1. 继承 :使用extends关键字来实现类的继承。
js 复制代码
class Example2 extends Example {}
  1. 静态方法 :使用static关键字定义静态方法,直接通过类来调用的方法,其中的 this 指向类本身。
js 复制代码
class Example {
  static function() {
    return 'Ywis';
  }
}
  1. getter 和 setter:类提供了一种定义属性访问器和修改器的方法。
js 复制代码
class Example {
  constructor(name) {
    this.name = name;
  }
  get name() {
    return this.name;
  }
  set name(value) {
    this.name = value;
  }
}
  1. 类表达式:类也可以作为表达式使用,并且可以被赋值给变量。
js 复制代码
const Example = class {
  constructor(name) {
    this.name = name;
  }
};
  1. 私有方法和属性:尽管ES6没有直接支持私有方法和属性,但是可以使用闭包的特性来模拟。

面试题 🔥🔥🔥

在了解完 class 类之后就可以开始我们的面试题讲解了,先上题目:

js 复制代码
class Example {
    constructor(name){
        this.name = name;
    }
    function() {
        console.log(this.name);
    }
}

我们知道 class 类其实就是一个语法糖,本质上还是个普通的构造函数,所以这道题本质就是来考察我们对于类的理解是否准确。我相信绝大多数的小伙伴们都能回答出这个面试题,给出这样一份答卷:

js 复制代码
function Example(name){
    this.name = name;
}
Example.prototype.function = function () {
    console.log(this.name);
}

这样子做虽然算是完成了这道面试题,但却达不到让面试官满意的程度,小问题还是有很多的。接下来,我们来看看如何才能让面试官心满意足。

第一步

我们清楚在类和模块的内部,默认是严格模式,所以不需要使用 use strict 指定运行模式。只要你的代码写在类或模块之中,就只有严格模式可用。所以我们在转换成普通构造函数时,我们也要在第一行加上 use strict,即:

js 复制代码
`use strict`;
function Example(name){
    this.name = name;
}
Example.prototype.function = function () {
    console.log(this.name);
}

第二步

在 ES6 中类虽然本质上是一个函数,但它也有它自己的特点,我们只能通过 new 来调用,直接调用会出现这样的一个错误:

Example('Ywis')

TypeError: Class constructor Example cannot be invoked without 'new'

所以我们在转换时,也应该将这个错误给加上,应该怎么做呢?

我们知道类只能通过 new 来调用,那么它的 this 就默认指向类的实例。然后在构造函数中,当它是通过 new 来调用的时候,它的 this 就一定是指向这构造函数的实例,也就是一定满足 this instanceof Example,所以我们可以通过验证 this 的指向,来判断函数的调用方式是不是通过 new 来调用的,解决代码如下:

js 复制代码
`use strict`;
function Example(name){
    // 验证 this 的指向
    if(!(this instanceof Example)){
        throw new TypeError(
            `Class constructor Example cannot be invoked without 'new'`
        )
    }
    this.name = name;
}
Example.prototype.function = function () {
    console.log(this.name);
}

第三步

class类中,它有自己的方法,这些方法定义在类的主体中,也肯定是在原型上的,不过有一点需要注意的是:这些方法是不可以被枚举的。我们通过一段代码来理解:

js 复制代码
class Example {
    constructor(name){
        this.name = name;
    }
    function() {
        console.log(this.name);
    }
}

let example = new Example('Ywis')
for(let key in example){
    console.log(key); // name
}

我们先使用 new 关键字创建了 Example 类的一个新实例,然后使用 for...in 循环 遍历example对象的可枚举属性。由于Example类的实例中只有一个可枚举属性name,所以将打印出'name'

可是将一样的思路用于转换后的代码,我们会发现将打印出 'name' 'function'。所以我们就不能像这样 Example.prototype.function = ... 直接赋值了,我们可以这样改:

js 复制代码
Object.defineProperty(Example.prototype, 'function', {
    value: function(){
        console.log(this.name);
    },
    enumerable: false, // 不可枚举
})

这一步完成后,大伙们是不是觉得这个面试题已经近乎完美了?其实这里还有一个非常隐秘的加分点,让我们来看看。

第四步

在类自己的方法里面有一个这样的特点,这个方法本身不能使用 new 关键字来调用,如果这样做的话会报错:

new example.function()

example.function is not a constructor

它会告诉你,这个 example.function 它不是一个构造器,所以我们在转换后的代码中,也要进行一个和第二步 类似的操作,一样的通过判断 this 的指向来让它不能通过 new 调用。

js 复制代码
`use strict`;
function Example(name){
    // 不可通过 new 调用(验证 this 的指向)
    if(!(this instanceof Example)){
        throw new TypeError(
            `Class constructor Example cannot be invoked without 'new'`
        )
    }
    this.name = name;
}

Object.defineProperty(Example.prototype, 'function', {
    value: function(){
        // 不可通过 new 调用(验证 this 的指向)
        if(!(this instanceof Example)){
            throw new TypeError(
                `example.function is not a constructor`
            )
        }
        console.log(this.name);
    },
    enumerable: false, // 不可枚举
})

let example = new Example('Ywis')
new example.function()

正常情况下,这里的 this 应该指向 Example 的实例对象,如果不是就是用了别的调用方式了,我们直接抛出错误就好了。

总结 🌸🌸🌸

看到这里,恭喜大家又解决一道面试题,我相信各位小伙伴已经能够完美的应对面试官了。最后祝你也祝我在今后日子里能够登高望远,心向彼岸。

相关推荐
腾讯TNTWeb前端团队32 分钟前
helux v5 发布了,像pinia一样优雅地管理你的react状态吧
前端·javascript·react.js
uhakadotcom4 小时前
视频直播与视频点播:基础知识与应用场景
后端·面试·架构
范文杰4 小时前
AI 时代如何更高效开发前端组件?21st.dev 给了一种答案
前端·ai编程
拉不动的猪4 小时前
刷刷题50(常见的js数据通信与渲染问题)
前端·javascript·面试
拉不动的猪4 小时前
JS多线程Webworks中的几种实战场景演示
前端·javascript·面试
FreeCultureBoy5 小时前
macOS 命令行 原生挂载 webdav 方法
前端
uhakadotcom5 小时前
快速开始使用 n8n
后端·面试·github
uhakadotcom5 小时前
Astro 框架:快速构建内容驱动型网站的利器
前端·javascript·面试
uhakadotcom6 小时前
了解Nest.js和Next.js:如何选择合适的框架
前端·javascript·面试
uhakadotcom6 小时前
React与Next.js:基础知识及应用场景
前端·面试·github