🧩 深入浅出讲解:analyzeScriptBindings —— Vue 如何分析 <script> 里的变量绑定


一、这段代码是干什么的?

Vue 组件有两种写法:

类型 示例 特点
普通 <script> export default { data(){...}, props:{...} } 传统写法
<script setup> 顶层直接写 const count = ref(0) Vue3 新写法

而 Vue 编译器在处理 .vue 文件时,需要知道:

每个变量来自哪里?它是 props 吗?data 吗?methods 吗?

这段源码的作用就是:

👉 当我们用"普通写法"时,分析出每个变量的"来源类型"。


二、运行结果长什么样?

假设我们有个组件:

javascript 复制代码
export default {
  props: ['title'],
  data() {
    return { count: 0 }
  },
  methods: {
    inc() { this.count++ }
  }
}

经过这段分析函数后,会得到这样的结果对象:

yaml 复制代码
{
  title: 'props',
  count: 'data',
  inc: 'options',
  __isScriptSetup: false
}

这就告诉 Vue:

  • title 来自 props;
  • count 来自 data;
  • inc 是 methods;
  • 不是 <script setup>。

三、从头到尾一步步看源码逻辑

Step 1️⃣:找到 export default { ... }

arduino 复制代码
export function analyzeScriptBindings(ast: Statement[]): BindingMetadata {
  for (const node of ast) {
    if (
      node.type === 'ExportDefaultDeclaration' &&
      node.declaration.type === 'ObjectExpression'
    ) {
      return analyzeBindingsFromOptions(node.declaration)
    }
  }
  return {}
}

🧠 意思:

  • AST 是整段脚本的语法树。

  • 遍历每个语句,找到:

    arduino 复制代码
    export default { ... }
  • 然后调用 analyzeBindingsFromOptions() 来分析里面的对象内容。


Step 2️⃣:创建结果对象并标记类型

php 复制代码
const bindings: BindingMetadata = {}
Object.defineProperty(bindings, '__isScriptSetup', {
  enumerable: false,
  value: false,
})

📘 这一步干嘛?

  • 初始化一个结果对象;
  • 加一个隐藏属性 __isScriptSetup=false,告诉系统"这是普通 script"。

Step 3️⃣:逐个分析对象里的属性

例如:

javascript 复制代码
export default {
  props: ['foo'],
  data() { return { msg: 'hi' } },
  methods: { sayHi() {} }
}

程序就会循环每个属性(props、data、methods...),判断它是哪种类型。


Step 4️⃣:不同类型的属性,分别分析

(1) props 分析

vbnet 复制代码
if (property.key.name === 'props') {
  for (const key of getObjectOrArrayExpressionKeys(property.value)) {
    bindings[key] = BindingTypes.PROPS
  }
}

🧠 支持两种写法:

  • props: ['foo', 'bar']
  • props: { foo: String }

结果:

css 复制代码
{ foo: 'props', bar: 'props' }

(2) inject 分析

vbnet 复制代码
else if (property.key.name === 'inject') {
  for (const key of getObjectOrArrayExpressionKeys(property.value)) {
    bindings[key] = BindingTypes.OPTIONS
  }
}

对应:

vbnet 复制代码
inject: ['token']

👉 结果 { token: 'options' }


(3) methods / computed 分析

python 复制代码
else if (
  property.value.type === 'ObjectExpression' &&
  (property.key.name === 'computed' || property.key.name === 'methods')
)

📘 当 methods: { sayHi(){} } 或 computed: { total(){} } 时,

把每个函数名记录下来:

css 复制代码
{ sayHi: 'options', total: 'options' }

(4) data / setup 分析

python 复制代码
else if (
  property.type === 'ObjectMethod' &&
  (property.key.name === 'setup' || property.key.name === 'data')
)

这时要进入函数体里查找 return 的内容:

javascript 复制代码
data() {
  return { count: 0 }
}
setup() {
  return { foo: ref(0) }
}

📘 分析结果:

  • data 返回的变量 → BindingTypes.DATA
  • setup 返回的变量 → BindingTypes.SETUP_MAYBE_REF

四、辅助函数们(简化理解)

1️⃣ 获取对象的键名

css 复制代码
function getObjectExpressionKeys(node) {
  // 从 { foo: 1, bar: 2 } 中提取出 ['foo', 'bar']
}

2️⃣ 获取数组的键名

css 复制代码
function getArrayExpressionKeys(node) {
  // 从 ['foo', 'bar'] 中提取出 ['foo', 'bar']
}

3️⃣ 自动判断是对象还是数组

javascript 复制代码
export function getObjectOrArrayExpressionKeys(value) {
  // 根据类型选择上面的函数
}

五、整体运行逻辑图

markdown 复制代码
AST语法树
   ↓
找到 export default {}
   ↓
进入 analyzeBindingsFromOptions()
   ↓
循环每个属性:
   - props → PROPS
   - inject → OPTIONS
   - methods/computed → OPTIONS
   - data → DATA
   - setup → SETUP_MAYBE_REF
   ↓
返回 BindingMetadata

六、为什么这么做?

因为 Vue 在模板编译时,需要知道哪些名字是:

  • 响应式变量(data、setup)
  • 只读输入(props)
  • 普通函数(methods)

这样模板里写的:

css 复制代码
<p>{{ count }}</p>

才能被编译成正确的访问代码:

复制代码
_ctx.count

或者:

复制代码
_props.title

七、你可以怎么用它?

如果你想做:

  • 自定义 Vue 编译工具;
  • 分析 .vue 文件中定义的变量;
  • 或者写一个 ESLint 规则来检测组件结构;

就可以直接复用这段逻辑,让它帮你快速"读懂" Vue 组件结构。


八、潜在问题

问题 说明
不支持动态 key [foo]: value 这种会被忽略
不识别 TS 类型 如果写 props: { foo: String as PropType<number> } 不会处理
无法分析复杂 setup 返回逻辑 例如条件 return 不被识别

✅ 总结一句话

这段代码的作用就是让编译器"看懂"一个普通 Vue 组件里的变量来源,区分哪些是 props、data、methods、setup 返回的。


本文部分内容借助 AI 辅助生成,并由作者整理审核。

相关推荐
程序人生8889 分钟前
PDF 转 Word 版式错乱、表格丢失?DocConverter Web 格式互转引擎的保真实践
前端·人工智能·opencv·机器学习·pdf·word
粥里有勺糖1 小时前
安利一下最近用的桌面“Agent” | T3 Code
前端·github·ai编程
xy34531 小时前
Axure 9.0 动态面板基本操作
前端·ui·html·axure·原型·产品设计
不停喝水1 小时前
【前端转全栈java速通课】 项目实战④7-11节 操作数据库-登录-注册-修改密码-注销-mubatis-plus简化crud-
java·前端·数据库
一水鉴天2 小时前
统一对象拓扑(UOT)设计全链定稿:从三件事分离到三继承 之1 20261010(豆包“查看聊天内容”继续)
前端·css
栖凤2 小时前
Java 的 try-catch 在 Agent 里失效了,我用了 5 个模式才兜住
java·前端·python
粥里有勺糖2 小时前
视野修炼第136期 | 前端小恐龙"Deno"被收购
前端·github·ai编程
修炼的dance3 小时前
页面塞了13.88 MB字体,导出的PDF只有71 KB
前端
qq_369173633 小时前
如何将 AI 生成的 HTML 网页发布成在线链接?不用自己搭建服务器
前端·人工智能·html·效率工具·html 发布
风花一世月3 小时前
⚡ 我把一座含氢能源园区搬进了浏览器(五类能流实时算守恒,在线直接玩)
前端·人工智能