各位观众老爷,大家好!今天咱们来聊聊 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
不会被提升,所以你必须先声明,后使用。 就像let
和const
声明的变量一样。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
当参数传递呢? 这样做的好处在于:
- 动态创建类: 你可以根据不同的条件,创建不同的类。
- 解耦: 降低代码之间的依赖关系,让代码更灵活。
- 配置化: 可以通过配置来决定使用哪个类,而不是写死在代码里。
- 高阶函数: 可以编写接受类作为参数的高阶函数,实现更强大的功能。
举个例子,假设你要做一个游戏,游戏里有不同的角色,每个角色的属性和行为都不一样。 你可以把每个角色的类作为参数传递给一个工厂函数,动态创建角色。
实战演练:把 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
的用法。 记住,编程的乐趣在于不断学习和实践,只有多写代码,才能真正掌握这些技巧。
下次再见!