JS 类表达式:动态创建类或作为参数传递

各位观众老爷,今天咱们来聊聊 JavaScript 里的“类表达式”,这玩意儿听起来高大上,但其实就像变形金刚,能屈能伸,动态创建,还能当参数传递,简直是居家旅行、装X 必备!

开场白:类表达式,是啥玩意儿?

在 ES6 之前,咱们定义类都得用 class 关键字,规规矩矩的:

class MyClass {
  constructor(name) {
    this.name = name;
  }

  greet() {
    console.log(`Hello, ${this.name}!`);
  }
}

const myInstance = new MyClass("张三");
myInstance.greet(); // 输出:Hello, 张三!

但 ES6 之后,来了个类表达式,直接把类当成表达式来用。 这就像把做饭的步骤写在一张纸上,然后直接把这张纸(菜谱)交给厨师,厨师照着菜谱做菜。 你可以把这张纸(菜谱)存在变量里,也可以直接交给厨师(函数)去使用。

类表达式的两种姿势:

类表达式有两种形式:

  1. 匿名类表达式: 就像一个没有名字的幽灵,创建的时候不知道叫啥,只能在使用的时候给它起个名字。
const MyClass = class { // 注意这里没有类名
  constructor(name) {
    this.name = name;
  }

  greet() {
    console.log(`Hello, ${this.name}!`);
  }
};

const myInstance = new MyClass("李四");
myInstance.greet(); // 输出:Hello, 李四!
  1. 命名类表达式: 创建的时候就自带名字,但这个名字只能在类内部使用,外部依然需要用你赋给它的变量名来访问。有点像艺名,只有圈内人知道,圈外人还是得叫你大名。
const MyClass = class NamedClass { // 注意这里有类名 NamedClass
  constructor(name) {
    this.name = name;
  }

  greet() {
    console.log(`Hello, ${this.name}!`);
  }

  internalMethod() {
    console.log(`我是内部方法,我能叫自己的名字:${NamedClass.name}`); // 可以在内部使用 NamedClass
  }
};

const myInstance = new MyClass("王五");
myInstance.greet(); // 输出:Hello, 王五!
myInstance.internalMethod(); // 输出:我是内部方法,我能叫自己的名字:NamedClass

// console.log(NamedClass); // 报错!NamedClass is not defined

类表达式的骚操作:动态创建类

类表达式最牛的地方在于它可以动态创建类。 也就是说,你可以根据不同的条件,创建出不同的类。 就像一个魔术师,根据观众的要求,变出不同的东西。

function createClass(className, constructorCode, methodCode) {
  const classString = `
    class ${className} {
      constructor(${constructorCode}) {
        ${constructorCode}
      }

      ${methodCode}
    }
  `;

  // 使用 eval 动态创建类 (小心 eval!)
  return eval(classString);
}

// 创建一个 Person 类
const Person = createClass(
  "Person",
  "name, age",
  `greet() {
    console.log(`Hello, my name is ${this.name}, and I am ${this.age} years old.`);
  }`
);

const person = new Person("赵六", 30);
person.greet(); // 输出:Hello, my name is 赵六, and I am 30 years old.

注意: 上面的代码使用了 eval 函数,这玩意儿很强大,但也很危险。 它可以执行任何 JavaScript 代码,如果你的代码里包含用户输入,或者从不可靠的来源获取代码,那么 eval 可能会导致安全问题。 所以,除非万不得已,尽量不要使用 eval。 可以考虑使用 Function 构造函数来代替 eval, 虽然安全性有所提高,但同样需要谨慎使用。

function createClass(className, constructorParams, constructorBody, methodBody) {
  // 使用 Function 构造函数创建 constructor
  const constructorFunction = new Function(constructorParams, constructorBody);

  // 创建 class
  const MyClass = class {
    constructor(...args) {
      constructorFunction.apply(this, args);
    }

    greet() {
      methodBody.call(this);
    }
  };
  Object.defineProperty(MyClass, 'name', { value: className }); // 设置类名

  return MyClass;
}

// 创建一个 Person 类
const Person = createClass(
  "Person",
  "name, age",
  "this.name = name; this.age = age;",
  function() { console.log(`Hello, my name is ${this.name}, and I am ${this.age} years old.`); }
);

const person = new Person("赵七", 35);
person.greet(); // 输出:Hello, my name is 赵七, and I am 35 years old.

类表达式的另一个绝技:作为参数传递

类表达式可以像普通变量一样,作为参数传递给函数。 这就像把菜谱交给餐厅的服务员,服务员再把菜谱交给厨师,厨师照着菜谱做菜。

function createInstance(Class, ...args) {
  return new Class(...args);
}

const MyClass = class {
  constructor(name) {
    this.name = name;
  }

  greet() {
    console.log(`Hello, ${this.name}!`);
  }
};

const myInstance = createInstance(MyClass, "孙八");
myInstance.greet(); // 输出:Hello, 孙八!

类表达式的应用场景:

  1. 模块化开发: 在模块化开发中,可以使用类表达式来定义模块,并将其导出。
// module.js
const MyModule = class {
  constructor(name) {
    this.name = name;
  }

  greet() {
    console.log(`Hello from module, ${this.name}!`);
  }
};

export default MyModule;

// main.js
import MyModule from './module.js';

const myInstance = new MyModule("周九");
myInstance.greet(); // 输出:Hello from module, 周九!
  1. 工厂模式: 可以使用类表达式来创建工厂函数,根据不同的参数,创建出不同的类的实例。
function createAnimal(type, name) {
  switch (type) {
    case "dog":
      return new (class {
        constructor(name) {
          this.name = name;
          this.type = "dog";
        }

        bark() {
          console.log("Woof!");
        }
      })(name);
    case "cat":
      return new (class {
        constructor(name) {
          this.name = name;
          this.type = "cat";
        }

        meow() {
          console.log("Meow!");
        }
      })(name);
    default:
      throw new Error("Unknown animal type");
  }
}

const dog = createAnimal("dog", "旺财");
dog.bark(); // 输出:Woof!
console.log(dog.type); // 输出:dog

const cat = createAnimal("cat", "咪咪");
cat.meow(); // 输出:Meow!
console.log(cat.type); // 输出:cat
  1. 策略模式: 可以使用类表达式来定义不同的策略,并将其作为参数传递给函数,根据不同的策略,执行不同的操作。
function processData(data, Strategy) {
  const strategy = new Strategy();
  return strategy.execute(data);
}

const UppercaseStrategy = class {
  execute(data) {
    return data.toUpperCase();
  }
};

const LowercaseStrategy = class {
  execute(data) {
    return data.toLowerCase();
  }
};

const data = "Hello World";

const uppercaseResult = processData(data, UppercaseStrategy);
console.log(uppercaseResult); // 输出:HELLO WORLD

const lowercaseResult = processData(data, LowercaseStrategy);
console.log(lowercaseResult); // 输出:hello world

总结:

类表达式是 JavaScript 中一个非常强大的特性,它可以动态创建类,也可以作为参数传递,非常灵活。 在模块化开发、工厂模式、策略模式等场景中,都可以使用类表达式来简化代码,提高代码的可读性和可维护性。

类声明 vs 类表达式:

为了更好地理解类表达式,我们将其与类声明进行对比。

特性 类声明 类表达式
语法 class MyClass { ... } const MyClass = class { ... }; 或者 const MyClass = class NamedClass { ... };
提升 类声明会被提升 (hoisted),可以在声明之前使用。 类表达式不会被提升,必须在声明之后才能使用。
命名 必须有类名。 可以是匿名的或命名的。命名的类表达式的名称只能在类体内部使用。
使用场景 通常用于定义全局类或模块导出的类。 更常用于动态创建类、作为参数传递,以及在函数内部定义类。

高级技巧: Mixins

类表达式也可以与 Mixins 结合使用,以实现更复杂的代码复用。 Mixins 是一种将多个类的功能组合到一个类中的技术。

// 定义一些 Mixins
const LoggerMixin = (superclass) => class extends superclass {
  log(message) {
    console.log(`[LOG]: ${message}`);
  }
};

const SerializableMixin = (superclass) => class extends superclass {
  serialize() {
    return JSON.stringify(this);
  }
};

// 创建一个基础类
class BaseClass {
  constructor(id) {
    this.id = id;
  }
}

// 使用 Mixins 创建一个新类
const MyClass = LoggerMixin(SerializableMixin(BaseClass));

const myInstance = new MyClass(123);
myInstance.log("Creating instance"); // 输出:[LOG]: Creating instance
console.log(myInstance.serialize()); // 输出:{"id":123}

注意事项:

  • 虽然类表达式很灵活,但也要注意代码的可读性。 不要过度使用类表达式,导致代码难以理解。
  • 尽量避免使用 eval 函数,除非你非常清楚自己在做什么。 可以考虑使用 Function 构造函数来代替 eval
  • 在使用 Mixins 时,要注意 Mixins 之间的冲突。 如果两个 Mixins 定义了相同的方法,那么后应用的 Mixin 会覆盖先应用的 Mixin。

总结的总结:

类表达式是 JavaScript 中一个非常强大的工具,掌握它可以让你编写更加灵活、可维护的代码。 但是,也要注意代码的可读性和安全性,避免过度使用和滥用。 记住,代码不仅要能运行,还要能让人读懂。 这样才能成为一个优秀的程序员。

好了,今天的讲座就到这里。 希望大家能够学有所获,并在实际项目中灵活运用类表达式。 咱们下回再见!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注