各位观众老爷,今天咱们来聊聊 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 之后,来了个类表达式,直接把类当成表达式来用。 这就像把做饭的步骤写在一张纸上,然后直接把这张纸(菜谱)交给厨师,厨师照着菜谱做菜。 你可以把这张纸(菜谱)存在变量里,也可以直接交给厨师(函数)去使用。
类表达式的两种姿势:
类表达式有两种形式:
- 匿名类表达式: 就像一个没有名字的幽灵,创建的时候不知道叫啥,只能在使用的时候给它起个名字。
const MyClass = class { // 注意这里没有类名
constructor(name) {
this.name = name;
}
greet() {
console.log(`Hello, ${this.name}!`);
}
};
const myInstance = new MyClass("李四");
myInstance.greet(); // 输出:Hello, 李四!
- 命名类表达式: 创建的时候就自带名字,但这个名字只能在类内部使用,外部依然需要用你赋给它的变量名来访问。有点像艺名,只有圈内人知道,圈外人还是得叫你大名。
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, 孙八!
类表达式的应用场景:
- 模块化开发: 在模块化开发中,可以使用类表达式来定义模块,并将其导出。
// 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, 周九!
- 工厂模式: 可以使用类表达式来创建工厂函数,根据不同的参数,创建出不同的类的实例。
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
- 策略模式: 可以使用类表达式来定义不同的策略,并将其作为参数传递给函数,根据不同的策略,执行不同的操作。
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 中一个非常强大的工具,掌握它可以让你编写更加灵活、可维护的代码。 但是,也要注意代码的可读性和安全性,避免过度使用和滥用。 记住,代码不仅要能运行,还要能让人读懂。 这样才能成为一个优秀的程序员。
好了,今天的讲座就到这里。 希望大家能够学有所获,并在实际项目中灵活运用类表达式。 咱们下回再见!