今天在工作中,偶然看到公司项目中的一段代码,引发了我对 JavaScript 特性的重新思考。这段代码将整套 CRUD 操作以及工作流配置进行了高度封装,尤其是在一个表单页面的渲染逻辑上体现得尤为明显。
具体场景是这样的:页面包含多个 tab 选项卡,用于控制不同表单组件的展示。每个表单组件都与特定的工作流相关联,并通过配置化方式实现动态渲染。每一个组件本质上是一个具有多个属性的实例对象,能够根据设定的条件决定是否渲染。在这个过程中,我一度陷入思维误区——为何一个组件既能作为可调用的函数,又能像普通对象一样拥有各种属性?
// 1. 你写的组件本质上是一个函数
const BasicForm = (props) => {
return <div>Hello World</div>;
};
// 2. 在 JS 里,函数本身就是对象
console.log(typeof BasicForm); // "function"
console.log(BasicForm instanceof Object); // true ?
// 3. 所以可以给函数添加属性
BasicForm.myCustomProp = '我是属性';
BasicForm.anotherProp = 123;
经过一番梳理后,我才意识到自己忽略了 JavaScript 中最基本却极为重要的特性之一:函数的本质是对象。
那么问题来了:为什么同一个实体既可以当作组件使用,又可以作为对象来访问其属性?
// 比如,你现在的组件:
const BasicForm = (props) => { /* 组件逻辑 */ };
// 执行 FormAttWrapper 后:
BasicForm.groupName = 'xxx';
BasicForm.formName = '规划评审';
// 现在 BasicForm 同时拥有:
// 1. 函数能力:可以被调用 BasicForm(props)
// 2. 对象能力:可以访问属性 BasicForm.groupName
下面来看一个更具体的示例说明这一现象:
// 例子1:普通函数 + 属性
function sayHello(name) {
console.log(`Hello ${name}`);
}
// 给它加属性
sayHello.language = '中文';
sayHello.version = '1.0';
console.log(sayHello.language); // "中文"
sayHello('张三'); // "Hello 张三"
// 例子2:React 组件也是函数
const MyComponent = (props) => {
return <div>{props.text}</div>;
};
// 给组件加元数据
MyComponent.displayName = '我的组件';
MyComponent.defaultProps = { text: '默认文本' };
// 函数调用和属性访问互不干扰
console.log(MyComponent.displayName); // "我的组件"
const element = <MyComponent text="测试" />; // 正常渲染
我们可以通过类比来理解这个机制:
第一步:原本的 BasicForm 是一个纯函数形式:
BasicForm = function(props) { return jsx; }
第二步:执行了 FormAttWrapper 高阶函数处理之后,向该函数添加了一系列静态属性:
Object.assign(BasicForm, {
groupName: 'xxx',
formName: '规划评审',
isStart: false,
allowQuery: true
});
第三步:此时的 BasicForm 实际上变成了这样一个复合结构:
BasicForm = {
// 函数本体(仍可被调用)
[Function: BasicForm],
// 同时附加了多个自定义属性
groupName: 'xxx',
formName: '规划评审',
isStart: false,
allowQuery: true
}
这就像一个人,比如定义如下函数:
const person = (name) => console.log(`我叫${name}`);
除了“说话”这种行为(即函数调用),这个人还可以拥有身份证、职业、年龄等静态信息。例如:
person.job = '程序员';
console.log(person.job); // 输出:程序员
当我们调用 person('皇帝') 时,是在执行函数功能;而访问 person.job 时,则是在读取其作为对象所携带的属性信息。两者并行不悖。
最终结论如下:
在 JavaScript 中,函数属于“一等公民”(first-class object),这意味着它们不仅可以被调用,还能像普通对象一样拥有属性和方法。因此,你的组件之所以既能被 React 正常调用以进行 UI 渲染,又能在其他地方作为配置对象被访问,正是基于这一语言层面的核心特性——函数本身就是对象,调用与属性存取互不影响。