JS `Class Expression` 作为参数传递:动态创建和使用类

各位观众老爷,大家好!今天咱们来聊聊 JavaScript 里的一个有点意思的话题:Class Expression 作为参数传递,以及如何动态创建和使用类。 听起来有点绕是吧?别怕,咱们用大白话一点点掰扯清楚。

啥是 Class Expression?它跟 Class Declaration 有啥区别?

在 ES6 引入了 class 关键字,让 JavaScript 也能像其他面向对象语言一样写类了。但是,JavaScript 的 class 跟传统的面向对象语言的类还是有点区别的。

首先,咱们得区分 Class Declaration (类声明) 和 Class Expression (类表达式)。

  • Class Declaration (类声明): 就像你平时写函数一样,先声明,后使用。

    class MyClass {
      constructor(name) {
        this.name = name;
      }
      greet() {
        console.log(`Hello, ${this.name}!`);
      }
    }
    
    const instance = new MyClass("Alice");
    instance.greet(); // 输出: Hello, Alice!

    注意,Class Declaration 有个特点:它会被提升 (hoisting),也就是说,你可以在声明之前使用它,只要在真正执行的时候声明还在就行。

  • Class Expression (类表达式): 就像匿名函数一样,把类赋值给一个变量。

    const MyClass = class {
      constructor(name) {
        this.name = name;
      }
      greet() {
        console.log(`Hello, ${this.name}!`);
      }
    };
    
    const instance = new MyClass("Bob");
    instance.greet(); // 输出: Hello, Bob!

    Class Expression 不会被提升,所以你必须先声明,后使用。 就像 letconst 声明的变量一样。

    Class Expression 还可以有名字,这个名字只在类内部可见,通常用于递归或者调试。

    const MyClass = class NamedClass {
      constructor(name) {
        this.name = name;
      }
      greet() {
        console.log(`Hello, ${this.name} from ${NamedClass.name}!`); // NamedClass 在这里可用
      }
    };
    
    const instance = new MyClass("Charlie");
    instance.greet(); // 输出: Hello, Charlie from NamedClass!
    console.log(typeof NamedClass); // 输出: undefined (在外部不可见)
特性 Class Declaration Class Expression
提升 (Hoisting) 支持 不支持
命名 必须 可选
使用场景 常规类定义 动态创建、匿名类

为啥要把 Class Expression 当参数传递?

现在问题来了,为啥要把 Class Expression 当参数传递呢? 这样做的好处在于:

  1. 动态创建类: 你可以根据不同的条件,创建不同的类。
  2. 解耦: 降低代码之间的依赖关系,让代码更灵活。
  3. 配置化: 可以通过配置来决定使用哪个类,而不是写死在代码里。
  4. 高阶函数: 可以编写接受类作为参数的高阶函数,实现更强大的功能。

举个例子,假设你要做一个游戏,游戏里有不同的角色,每个角色的属性和行为都不一样。 你可以把每个角色的类作为参数传递给一个工厂函数,动态创建角色。

实战演练:把 Class Expression 当参数传递

咱们来写个例子,看看怎么把 Class Expression 当参数传递。

// 角色工厂函数
function createCharacter(CharacterClass, name, health, attack) {
  return new CharacterClass(name, health, attack);
}

// 战士类
const Warrior = class {
  constructor(name, health, attack) {
    this.name = name;
    this.health = health;
    this.attack = attack;
  }

  attackTarget(target) {
    console.log(`${this.name} attacks ${target.name} for ${this.attack} damage!`);
    target.health -= this.attack;
    console.log(`${target.name} health is now ${target.health}`);
  }
};

// 法师类
const Mage = class {
  constructor(name, health, mana, magicAttack) {
    this.name = name;
    this.health = health;
    this.mana = mana;
    this.magicAttack = magicAttack;
  }

  castSpell(spell, target) {
    if (this.mana >= spell.manaCost) {
      console.log(`${this.name} casts ${spell.name} on ${target.name} for ${spell.damage} damage!`);
      target.health -= spell.damage;
      this.mana -= spell.manaCost;
      console.log(`${target.name} health is now ${target.health}`);
      console.log(`${this.name} mana is now ${this.mana}`);
    } else {
      console.log(`${this.name} does not have enough mana to cast ${spell.name}!`);
    }
  }
};

// 定义法术
const Fireball = {
  name: "Fireball",
  damage: 50,
  manaCost: 30,
};

// 创建角色
const warrior = createCharacter(Warrior, "Arthur", 100, 20);
const mage = createCharacter(Mage, "Merlin", 80, 50, 60);

// 角色互动
warrior.attackTarget(mage);
mage.castSpell(Fireball, warrior);

在这个例子里,createCharacter 函数接受一个 Class Expression 作为参数,根据传入的类,动态创建角色。 这样,你就可以根据不同的需求,创建不同类型的角色,而不需要修改 createCharacter 函数本身。

更高级的用法:高阶函数和 Class Expression

Class Expression 还可以和高阶函数结合使用,实现更强大的功能。 比如,你可以写一个高阶函数,用来给类添加一些通用的方法。

// 给类添加方法的函数
function addMethodToClass(CharacterClass, methodName, method) {
  CharacterClass.prototype[methodName] = method;
}

// 定义一个通用的治疗方法
const heal = function(amount) {
  this.health += amount;
  console.log(`${this.name} heals for ${amount} health. Health is now ${this.health}`);
};

// 给 Warrior 类添加 heal 方法
addMethodToClass(Warrior, "heal", heal);

// 给 Mage 类添加 heal 方法
addMethodToClass(Mage, "heal", heal);

// 创建角色
const warrior = createCharacter(Warrior, "Arthur", 50, 20);
const mage = createCharacter(Mage, "Merlin", 30, 50, 60);

// 使用 heal 方法
warrior.heal(30);
mage.heal(20);

在这个例子里,addMethodToClass 函数接受一个 Class Expression 和一个方法作为参数,把这个方法添加到类的原型上。 这样,你就可以给不同的类添加通用的方法,而不需要在每个类里都写一遍。

动态创建类的更多姿势

除了直接传递 Class Expression,你还可以使用 eval() 函数或者 new Function() 构造函数来动态创建类。

注意: eval()new Function() 有一定的安全风险,要谨慎使用,避免执行恶意代码。

// 使用 eval() 动态创建类
function createClassWithEval(className, constructorCode, methodCode) {
  const classDefinition = `
    class ${className} {
      constructor(${constructorCode}) {
        ${constructorCode}
      }
      ${methodCode}
    }
    return ${className};
  `;
  return eval(classDefinition);
}

// 使用 new Function() 动态创建类
function createClassWithFunction(className, constructorCode, methodCode) {
  const constructor = new Function(constructorCode, 'this.name = name;');
  const method = new Function(methodCode, 'console.log(`Hello, ${this.name}!`);');

  const DynamicClass = class {
      constructor(name) {
          constructor.apply(this, arguments);
      }

      greet() {
          method.apply(this);
      }
  };

  return DynamicClass;
}

// 创建一个名为 "DynamicClass" 的类
const DynamicClassFromEval = createClassWithEval("DynamicClass", "name", "greet() { console.log(`Hello, ${this.name} from eval!`); }");
const DynamicClassFromFunction = createClassWithFunction("DynamicClass", "name", "console.log(`Hello, ${this.name} from Function!`);");

// 创建实例并使用
const evalInstance = new DynamicClassFromEval("Eve");
evalInstance.greet(); // 输出: Hello, Eve from eval!

const functionInstance = new DynamicClassFromFunction("Finn");
functionInstance.greet(); // 输出: Hello, Finn from Function!

这些方法可以让你更灵活地创建类,但也要注意安全性和性能问题。

总结

Class Expression 作为参数传递,是一种强大的 JavaScript 技巧,可以让你动态创建类、解耦代码、配置化应用。 掌握这种技巧,可以让你写出更灵活、更可维护的代码。

技巧 优点 缺点 适用场景
传递 Class Expression 简单易懂,代码结构清晰,易于维护。 需要提前定义类,灵活性相对较低。 需要根据不同条件创建不同类型的对象,但类的结构基本确定。
使用 eval() 灵活性高,可以根据任意字符串动态创建类。 安全风险高,容易执行恶意代码,调试困难,性能较差。 极少数情况下,需要根据完全动态的数据创建类,并且对性能和安全性要求不高。
使用 new Function() 灵活性较高,可以根据字符串动态创建类,相对 eval() 更安全,性能略好。 代码可读性较差,调试困难,仍然存在一定的安全风险。 需要根据动态数据创建类,并且对安全性有一定要求。
高阶函数添加方法 可以给类添加通用的方法,提高代码复用率,降低代码冗余。 需要理解原型链的概念,代码结构相对复杂。 需要给多个类添加相同的方法,或者需要在运行时动态修改类的行为。

希望今天的讲座能帮大家更好地理解 Class Expression 的用法。 记住,编程的乐趣在于不断学习和实践,只有多写代码,才能真正掌握这些技巧。

下次再见!

发表回复

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