面试题:你知道如何完美的将 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 的实例对象,如果不是就是用了别的调用方式了,我们直接抛出错误就好了。

总结 🌸🌸🌸

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

相关推荐
I_Am_Me_3 分钟前
【JavaEE进阶】 JavaScript
开发语言·javascript·ecmascript
雯0609~10 分钟前
网页F12:缓存的使用(设值、取值、删除)
前端·缓存
℘团子এ13 分钟前
vue3中如何上传文件到腾讯云的桶(cosbrowser)
前端·javascript·腾讯云
学习前端的小z19 分钟前
【前端】深入理解 JavaScript 逻辑运算符的优先级与短路求值机制
开发语言·前端·javascript
程序猿进阶33 分钟前
堆外内存泄露排查经历
java·jvm·后端·面试·性能优化·oom·内存泄露
前端百草阁42 分钟前
【TS简单上手,快速入门教程】————适合零基础
javascript·typescript
彭世瑜43 分钟前
ts: TypeScript跳过检查/忽略类型检查
前端·javascript·typescript
FØund40443 分钟前
antd form.setFieldsValue问题总结
前端·react.js·typescript·html
Backstroke fish44 分钟前
Token刷新机制
前端·javascript·vue.js·typescript·vue
zwjapple44 分钟前
typescript里面正则的使用
开发语言·javascript·正则表达式