JS - 相等还是不相等

17 阅读8分钟

题目描述

请你编写一个名为 expect 的函数,用于帮助开发人员测试他们的代码。它应该接受任何值 val 并返回一个包含以下两个函数的对象。

  • toBe(val) 接受另一个值并在两个值相等( === )时返回 true 。如果它们不相等,则应抛出错误 "Not Equal" 。
  • notToBe(val) 接受另一个值并在两个值不相等( !== )时返回 true 。如果它们相等,则应抛出错误 "Equal" 。

 

示例 1:

输入: func = () => expect(5).toBe(5)
输出: {"value": true}
解释: 5 === 5 因此该表达式返回 true

示例 2:

输入: func = () => expect(5).toBe(null)
输出: {"error": "Not Equal"}
解释: 5 !== null 因此抛出错误 "Not Equal".

示例 3:

输入: func = () => expect(5).notToBe(null)
输出: {"value": true}
解释: 5 !== null 因此该表达式返回 true.

题解

JavaScript 对象和函数返回

在 JavaScript 中,函数可以返回对象,对象是相关数据和函数(通常称为属性和方法)的集合。

以下是一个返回对象的函数示例:

function createPerson(name, age) {
  return {
    name: name,
    age: age,
    greet: function() {
      console.log(`Hello, my name is ${this.name} and I'm ${this.age} years old.`);
    }
  };
}

let person = createPerson("Alice", 25);
person.greet(); // "Hello, my name is Alice and I'm 25 years old."

JavaScript 对象和有限方法链

JavaScript 对象是一种重要的结构,用于将相关数据和函数组合在一起。它们可以容纳包括函数在内的各种数据类型,当函数位于对象内部时,就被视为对象的方法。

在当前的问题中,expect 函数返回一个对象。该对象包含 toBe 和 notToBe 方法。尽管这些方法可以以类似链式的方式连续调用,但这代表了一种受限制的方法链,因为它们不会返回原始对象以供进一步链式调用,而这正是 JavaScript 编程中方法链的一个关键特征。

全方法链是 JavaScript 中的常见模式,允许在单个语句中调用多个方法。此模式是通过每个方法返回一个对象来实现的,该对象可以是原始对象(对于可变对象)或新对象(对于不可变对象)。全方法链提高了代码的可读性和简洁性,在许多 JavaScript 库中都是首选的模式。

以下是完整方法链的示例:

let arr = [5, 2, 8, 1];

let result = arr.sort().reverse().join("-");

console.log(result); // "8-5-2-1"

在此示例中,sort() 方法对数组进行排序,然后 reverse() 方法反转排序后的数组,最后 join("-") 方法将元素连接成一个以 「-」 为分隔符的字符串。每个方法都返回一个数组,允许直接在结果上调用下一个方法。

在 expect 函数的上下文中,方法链的一种受限形式允许开发人员在单行中无缝使用 toBe 和 notToBe 方法,如下所示:

expect(5).toBe(5); // 返回 true 或抛出错误
expect(5).notToBe(3); // 返回 true 或抛出错误

expect 函数返回一个包含 toBe 和 notToBe 方法的对象。这些方法不会返回原始对象,而是返回 true 或根据比较结果抛出错误。因此,expect 函数提供了一种受限制的方法链;尽管如此,它在许多情况下仍然非常有用。

JavaScript 错误处理

在 JavaScript 中,错误处理主要通过使用 throw 语句和 try...catch 块来实现。使用 throw 语句允许你创建自定义错误消息,这对于调试代码非常有用。根据你想要实现的目标,可以使用不同的方式使用 throw 语句。

抛出字符串:

你可以在 JavaScript 中抛出一个字符串。它将在 catch 块中被捕获为错误消息。

function checkName(name) {
  if (name === '') {
    throw "Name can't be empty!";
  }
  return name;
}

try {
  console.log(checkName(''));
} catch (error) {
  console.error(error); // "Name can't be empty!"
}

抛出 Error 实例:

一种更常见和推荐的方法是抛出一个 Error 实例。这允许在错误中包含比如堆栈跟踪的附加元数据,有助于调试。

function divide(numerator, denominator) {
  if (denominator === 0) {
    throw new Error("Cannot divide by zero!");
  }
  return numerator / denominator;
}

try {
  console.log(divide(5, 0));
} catch (error) {
  console.error(error.message); // "Cannot divide by zero!"
}

抛出聚合错误:

有时候,你可能希望同时抛出多个错误。这在处理 Promise 时特别有用。JavaScript 有一个内置的 AggregateError 对象,可以在这类情况下使用。AggregateError 对象可以接受一个错误对象的可迭代集合和一个可选的消息作为参数。

let error1 = new Error("First Error");
let error2 = new Error("Second Error");

try {
  throw new AggregateError([error1, error2], "Two errors occurred.");
} catch (error) {
  if (error instanceof AggregateError) {
    console.error(error.message); // "Two errors occurred."
    for (let e of error.errors) {
      console.error(e.message); // 打印 "First Error""Second Error"
    }
  }
}

方法 1:对象工厂

概述

在这个问题中,我们被要求实现一个名为 expect 的函数,用于对代码执行一些基本的测试。该函数应该接受任何值,并返回一个包含两个方法的对象:toBe 和 notToBe

toBe 方法应该接受另一个值,并使用严格相等运算符(===) 将这两个值进行比较,如果它们严格相等 (===),则返回 true。如果它们不相等,则应抛出一个带有 "Not Equal" 消息的错误。

同样,notToBe 方法应该接受另一个值,并使用严格不相等运算符 (!==) 将这两个值进行比较,如果它们不完全相等 (!==) ,则返回 true。如果它们相等,则应抛出一个带有 "Equal" 消息的错误。

算法

  1. 从 expect 返回一个包含 toBe 和 notToBe 方法的对象。
  2. 在 toBe 方法中,将输入值与传递给 expect 的值进行比较。如果它们相等,返回 true。如果不相等,则抛出一个带有 「Not Equal」 消息的错误。
  3. 类似地,在 notToBe 方法中,将输入值与传递给 expect 的值进行比较。如果它们不相等,返回 true。如果它们相等,则抛出一个带有「Equal」消息的错误。

实现

这个解决方案可以通过给定的函数表达式或构造一个额外的 ES6 类来实现。

实现 1:函数表达式

在这个实现中,expect 函数充当工厂函数,创建一个包含 toBe 和 notToBe 两个方法的对象。这两个方法分别使用严格相等 ( === ) 或不等 ( !== ) 运算符将 val 与传递给 expect 的值进行比较,根据比较的结果返回 true 或抛出错误。

var expect = function(val) {
  return {
    toBe: (val2) => {
      if(val !== val2) throw new Error("Not Equal");
      return true;
    },
    notToBe: (val2) => {
      if(val === val2) throw new Error("Equal");
      return true;
    }
  };
};
type ToBeOrNotToBe = {
  toBe: (val: any) => boolean;
  notToBe: (val: any) => boolean;
};

const expect = (val: any): ToBeOrNotToBe => {
    return {
        toBe: (val2: any): boolean => {
            if(val !== val2) throw new Error("Not Equal");
            return true;
        },
        notToBe: (val2: any): boolean => {
            if(val === val2) throw new Error("Equal");
            return true;
        }
    };
};

实现 2:使用 ES6 类

在这个代码中,expect 是一个函数,返回一个新的 Expect 类的实例。该实例保留传递给 expect 的值,并在其 toBe 和 notToBe 方法中使用该值。

class Expect {
  constructor(val) {
    this.value = val;
  }

  toBe(compareVal) {
    if (this.value !== compareVal) {
      throw new Error("Not Equal");
    }
    return true;
  }

  notToBe(compareVal) {
    if (this.value === compareVal) {
      throw new Error("Equal");
    }
    return true;
  }
}

function expect(val) {
  return new Expect(val);
}
class Expect<T> {
    private val: T;

    constructor(val: T) {
        this.val = val;
    }
    
    toBe(val2: T): boolean {
        if (this.val !== val2) {
            throw new Error("Not Equal");
        }
        return true;
    }
    
    notToBe(val2: T): boolean {
        if (this.val === val2) {
            throw new Error("Equal");
        }
        return true;
    }
}

function expect<T>(val: T): Expect<T> {
    return new Expect(val);
}

复杂性分析

时间复杂度:O(1),函数只创建一个包含两个方法的对象,不执行任何迭代或递归操作。

空间复杂度:O(1),函数始终创建一个包含两个方法的对象,不管输入值的大小或复杂性如何。因此,函数使用的内存量不会随着输入大小而变化,导致常数空间复杂度。

面试技巧:

  • JavaScript 中函数返回对象或其他函数是什么意思?

    当一个函数返回对象或其他函数时,它使用了高阶函数和工厂函数。高阶函数是通过接受其他函数作为参数或返回它们来操作其他函数的函数。另一方面,工厂函数是返回对象实例的函数。这个概念在函数式编程中非常重要,提供了一种封装和重用代码的方式。

  • 方法链在 JavaScript 中是如何工作的,什么时候使用它可能会更有效?

    方法链是 JavaScript 中常见的一种模式,允许在单个语句中调用多个方法。这是因为每个方法返回一个对象,该对象可以是原始对象(对于可变对象)或新对象(对于不可变对象)。方法链使代码更易读和简洁,在执行多个转换或操作时特别有用。

  • 在 JavaScript 中,== 和 === 有什么区别?

    == 运算符是抽象相等运算符,如果两个变量的类型不同,它将尝试进行类型转换。另一方面,=== 运算符是严格相等运算符,不进行类型转换,仅在两个变量的值和类型都相同时返回 true。

  • 如何在 JavaScript 函数中处理错误?

    在 JavaScript 函数中处理错误可以通过使用 try...catch...finally 块来实现。try 块包含可能引发错误的代码,catch 块在 try 块中发生错误时执行,finally 块在 try 和 catch 块之后始终执行,无论结果如何。另一种处理错误的方式是使用错误优先的回调,这是 Node.js 中常见的模式,其中回调函数的第一个参数保留给错误对象。

  • 为什么我们要抛出错误,而不是在 toBe 和 notToBe 方法中直接返回 false?

    抛出错误可以提供更多关于出错原因的信息,并允许你在代码的更高层使用 try...catch语句捕获错误。相比之下,简单地返回 false 只会告诉你值不相等,而不会提供任何进一步的上下文或信息。