ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

微信小程序代码构成详解:WXML、WXSS、JS、JSON与运行机制

微信小程序代码构成详解:WXML、WXSS、JS、JSON与运行机制 小程序开发入门最常见的误区就是以为“代码”就是写一堆JS逻辑。真正上手之后你会发现小程序的一个页面由四个文件共同构成缺一个都会出问题。这四个文件之间的关系、各自承担的职责以及它们如何在运行时协作才是理解小程序代码构成的核心。这篇文章我会从零拆解微信小程序的代码结构把WXML、WXSS、JS、JSON这四类文件全部讲透再深入全局配置、逻辑层与渲染层的协作机制以及组件化、工程化配置这些进阶内容。不管你是刚接触小程序的新手还是已经写过几个页面但总觉得代码结构混乱的开发者这篇都能帮你把底层的代码框架理顺。1. 小程序项目的基础骨架WXML、WXSS、JS、JSON到底在干什么1.1 四个核心文件各司其职微信小程序的代码构成从项目根目录往下看主要分为全局文件和页面文件两大部分。一个页面通常由四个同名的文件组成.wxml、.wxss、.js、.json。这四个文件分别对应结构、样式、逻辑、配置作用划分非常清晰。.wxml是页面的结构文件全称是 WeChat Markup Language语法上跟 HTML 类似但它使用的是小程序自己的组件标签比如view代替divtext代替spanimage代替img。WXML 里可以写数据绑定、条件渲染、列表渲染比如用{{userInfo.name}}把 JS 里的数据渲染到界面上用wx:for循环渲染一组列表。用生活中的类比来说WXML 就是房子的框架和毛坯结构决定了这面墙在哪里、门开在哪、房间有几间。.wxss是页面的样式文件全称 WeChat Style Sheets语法跟 CSS 几乎一致但增加了一个非常重要的响应式单位rpx。小程序规定屏幕宽度固定为 750rpx不管你在 iPhone SE 还是大屏安卓机上都可以用 rpx 来适配不同的屏幕尺寸。比如width: 375rpx在任何设备上都是屏幕宽度的一半。这个设计直接解决了一部分移动端适配的痛点是 WXSS 最大的特色。.js是页面的逻辑文件负责处理交互、请求数据、生命周期等逻辑。页面中的所有数据都定义在data对象里所有方法都写在 Page() 函数内。JS 文件通过setData方法把数据同步到视图层这是小程序数据单向流动的核心机制。.json是页面的配置文件用来设置页面的窗口表现比如导航栏标题、背景色、下拉刷新开关等。页面级 JSON 只能覆盖 window 相关的配置不能设置全局的 tabBar 或路由。这四个文件合在一起才算一个完整的页面。我在实际开发中见过不少人只改了 WXML 没改 JS数据写死导致页面无法复用或者改了 WXSS 忘了清除旧样式导致界面错乱。理解每个文件的边界职责能帮你避免一批类似的低级问题。1.2 页面与全局的层级关系小程序代码构成里除了每个页面自己的四个文件还有三个全局文件在根目录下app.js、app.json、app.wxss。这三个文件分别对应全局逻辑、全局配置、全局样式。app.js是项目的逻辑入口在小程序启动时执行。你可以在onLaunch生命周期里初始化一些全局数据比如从缓存里读取登录状态、获取用户信息、初始化第三方 SDK。需要注意的是app.js里定义的全局数据在页面里可以通过getApp()方法拿到。这个机制非常适合存放用户登录凭证、全局配置等跨页面共享的数据但也正因为是全局的要小心不要在里面存体积过大的对象否则可能加大内存压力。app.json是全局配置决定了整个小程序的行为。最核心的pages字段是一个数组第一项就是小程序的首页。这个数组里列出的所有路径系统会在编译时自动生成对应的页面目录这是很多新手容易忽略的——你新建了一个页面文件但忘了在pages里注册编译时页面根本访问不到报错信息还会让人摸不着头脑。app.json里还可以配置window全局窗口样式、tabBar底部导航栏、networkTimeout网络超时时间等非常关键。app.wxss是全局样式文件写在里面的样式会作用到所有页面。通常把一些通用的样式类和全局变量放在这里比如 CSS 变量自定义属性、公共的 reset 样式、通用的按钮样式等。页面的wxss只能影响当前页面不能跑到别的页面去。全局三件套加上页面的四件套构成了完整的层级关系。逻辑上全局的东西先加载页面再基于全局配置做个性化覆盖结构上全局文件在根目录页面文件在 pages 目录下路径关系清晰可查。2. 全局配置与页面配置app.json和页面json的权限划分2.1 全局配置的核心字段app.json是小程序代码构成里最容易被低估的文件。它看起来只是一个 JSON却能决定小程序到底长什么样、能不能跑起来。我挑几个高频使用的字段展开说说。pages字段是我在任何项目里第一个检查的字段。它必须是一个数组每一项是页面的路径字符串不需要写文件后缀。这里的第一项就是小程序启动后展示的首页。比如{ pages: [ pages/index/index, pages/detail/detail ] }如果新加了一个页面忘了在pages里注册运行时跳转会直接报错提示“页面路径不存在”或“页面文件未找到”。这个字段的顺序还影响 tabBar 页面的初始展示所以建议把核心页面放在数组前面。window字段用于配置导航栏和窗口样式。最常用的是navigationBarTitleText导航栏标题、navigationBarBackgroundColor导航栏背景色、navigationBarTextStyle导航栏文字颜色可选 black 或 white、backgroundColor窗口背景色、enablePullDownRefresh是否开启下拉刷新。这些配置全局生效但如果某个页面想单独改标题可以在页面的.json文件里覆盖。tabBar字段用于配置底部导航栏这是商城类小程序最常见的入口结构。list数组里最少 2 项、最多 5 项每项需要配置pagePath、text、iconPath、selectedIconPath。图标文件建议放在images目录下并且不推荐使用网络图片因为 tabBar 图标的网络地址在部分基础库版本上不生效。还有一个容易被忽略的字段是lazyCodeLoading推荐设置为requiredComponents这是官方推荐的按需注入代码模式。开启后小程序不会一次性加载所有页面和组件的 JS而是使用到哪个组件才加载哪个能显著减少启动时间。我实际测试过在页面数量超过 20 个的项目里这个配置能缩短冷启动耗时约 10%~20%。2.2 页面级配置的覆盖逻辑每个页面的.json文件是全局 window 配置的补充和覆盖层。比如全局app.json里设置了导航栏标题为“我的商城”但你在pages/detail/detail.json里设置了navigationBarTitleText: 商品详情那么打开详情页时标题会显示“商品详情”离开这个页面后其他页面依然显示“我的商城”。覆盖逻辑听起来简单但有个容易踩坑的地方页面json只能覆盖window涉及的窗口相关配置不能覆盖tabBar、pages等全局路由配置。有些新手试图在页面 json 里加tabBar: {}结果不生效因为 tabBar 只能在 app.json 里配。页面 json 另一个高频用法是开启或关闭当前页面的特定能力。比如enablePullDownRefresh: true只对这个页面生效navigationStyle: custom可以把导航栏改为自定义导航方便做沉浸式设计但要注意一旦自定义导航页面顶部就没有默认标题栏和胶囊按钮了需要自己用组件模拟。从代码构成的视角看全局配置和页面配置的关系就是“父子级继承 局部覆盖”。全局配置提供基础的样式和能力基线页面配置只写自己需要差异化的部分这样既能保证整体风格统一又保留单个页面的灵活度。这个设计跟 CSS 的继承与覆盖思路是一致的理解了这一点你在配置代码时就不会把大量重复字段写进页面 json。2.3 配置决策背后的小技巧配置写多了以后我总结出几个常用的决策习惯。首先是“能全局则全局能页面则页面”全局配置里只放稳定的共性设置不要把某个业务页里的临时需求塞进全局否则后续改的时候影响范围会很难控制。其次是“先注册再开发”每次新建页面第一步就把它加进app.json的pages数组然后再去编辑页面文件这样能避免频繁遇到路径不存在的报错。还有一个很多人不知道的小技巧app.json里支持配置style: v2这是使用新版组件样式的开关。设置后部分内置组件的默认样式会更新例如按钮默认边框会去掉视觉上更符合设计规范。如果你的项目从旧版本升级而来突然发现样式变了很多情况下是这个字段造成的。对于自定义导航栏页面 json 配置navigationStyle: custom后还需要配合一个状态栏高度的适配。常见做法是在app.js的onLaunch里用wx.getWindowInfo()获取状态栏高度和胶囊按钮的位置然后通过全局数据传给自定义导航组件动态计算内容区域的 padding-top。这一步如果不做自定义导航栏就会顶到状态栏下面字会被时间、电量遮挡看起来非常廉价。3. 逻辑层与渲染层小程序代码的双线程运行机制3.1 双线程架构原理要真正搞懂小程序代码的构成只看文件还不够还得理解它的运行机制。小程序跟传统网页最大的区别在于它的逻辑层和渲染层是分开的运行在两条独立的线程上。渲染层负责界面的渲染基于 WebView 技术实现逻辑层负责 JS 代码的执行运行在 JavaScriptCore 引擎iOS 和安卓的实现略有差异中。两层之间通过一套桥接机制通信这套机制封装成了我们接口不需要开发者直接操作底层协议。这个设计的好处有两个。第一是安全用户的 JS 逻辑代码运行在独立的逻辑层不能直接操作渲染层的 DOM这在一定程度上降低了恶意代码注入的风险。第二是性能逻辑层的计算不会直接阻塞渲染线程页面在滚动时JS 的耗时操作不容易导致界面卡死。但代价也很明显逻辑层和渲染层的通信是异步的每次调用setData都会有一层序列化和传输的开销。如果你在短时间内频繁调用setData传大对象就会明显感到页面卡顿这就是双线程架构的天然约束。我在做实时刷新类功能比如倒计时、股票行情时会刻意减少数据更新的频率和单次数据的体量本质就是为了避开这条通信链路的瓶颈。3.2 数据绑定与setData的秘密数据绑定是 WXML 和 JS 之间的桥梁。JS 里data对象定义的字段可以直接在 WXML 里用{{ }}引用。例如Page({ data: { productName: 智能手环, price: 299 } })在 WXML 中view{{productName}}/view view价格{{price}} 元/view这种绑定是单向的数据从逻辑层流向渲染层。如果想要界面改动后反向把值传给逻辑层需要通过事件绑定比如bindinput去监听输入框的input事件再把值回传给data。setData是小程序里传递数据的核心接口。它的签名是this.setData({ key: newValue })支持传数据的路径表达式比如this.setData({ list[0].name: 新名字 })。这个特性很实用修改深层嵌套对象的某个字段时不需要把整个对象重新传一遍能减少通信数据量。但setData有几个真实的坑。第一不要把 WXML 里没用到的大对象传过去数据照样会走完整的通信链路白白消耗性能。第二不要在setData的 callback 里做复杂操作它只是渲染完成后的通知不代表同步完成。第三频繁的小数据量更新可以合并成一次setData例如在循环里收集变更最后统一提交。实测下来把 10 次小数据量的 setData 合并成 1 次渲染耗时能降低一半以上。3.3 生命周期函数每个小程序页面都有生命周期函数它们是理解页面代码执行顺序的关键入口。onLoad是页面创建时触发接收上一个页面跳转传来的参数query适合做数据初始化和页面数据拉取。onShow是页面每次从后台切换到前台时触发适合做数据刷新比如商品价格更新、购物车角标数量刷新。onReady在页面初次渲染完成后触发此时可以操作渲染层的一些组件实例。onHide是页面被隐藏时触发比如跳转到下一个页面或切到后台。onUnload是页面销毁时触发适合清理定时器、解除事件监听。实际操作中最容易出问题的生命周期就是onLoad和onShow的顺序问题。很多人以为onShow在onLoad之后执行一次就不管了但实际上如果你从页面 A 跳到页面 B再返回 AA 的onShow会再次触发而onLoad不会。所以把每次返回都要刷新的逻辑写进onShow才是正确的把只需要创建时做一次的逻辑留在onLoad。另外一个常见的坑是定时器的清理。在页面里用setInterval做轮询或动画如果不在onUnload里清除页面销毁后定时器还在运行会持续消耗内存甚至引发报错。微信开发者工具通常不会直接报错发布到真机上后这类问题有时会被打包成各种诡异的性能问题排查起来很麻烦。页面组件的生命周期跟页面本身是独立的组件有自己的created、attached、ready、detached等生命周期。在使用自定义组件时要区分清楚“组件自身的生命周期”和“页面的生命周期”尽量不要在组件里直接依赖页面的生命周期来驱动自己的逻辑解耦的设计会让代码更健壮。4. 组件化与模块化从单页到多页面的代码组织4.1 自定义组件当页面开始变多、功能开始复用之后小程序的代码构成就需要从“页面思维”升级到“组件思维”。自定义组件是小程序实现代码复用的核心方式。定义一个组件需要创建一个目录目录下有四个同名文件跟页面结构一致.js、.json、.wxml、.wxss。组件的.json文件里需要声明component: true这样微信才确认它是个组件。在页面的 json 文件里通过usingComponents字段引入组件并起一个别名例如{ usingComponents: { price-tag: /components/price-tag/price-tag } }这样在页面的 WXML 里就能直接写price-tag ... /。组件的 JS 用Component()构造器定义跟页面的Page()类似但更严格。核心成员包括properties外部传入的属性相当于 Vue 的 props、data组件内部状态、methods组件的方法。properties里的值可以通过observer监听变化这个特性在根据外部数据动态更新组件内部状态时非常有用。组件内部的样式默认是隔离的也就是说页面 WXML 里加的 class 样式不会作用到组件内部组件内的 class 也不会泄露到页面。这个样式隔离机制能有效防止命名冲突但有时也会带来困惑你想通过页面单方面调整组件内部的视觉表现发现改不动需要借助styleIsolation配置或组件暴露的样式接口externalClasses。我在写公共组件时会额外定义一些用于外部调整样式的 class 名称比如custom-class如果用户想自定义尺寸可以传入这个 class灵活度会大很多。4.2 JS模块化小程序代码构成里的.js文件除了页面和组件还有大量独立的功能模块。模块化代码的复用靠的是 CommonJS 规范跟 Node.js 的模块机制一致。常见的做法是把可复用的工具函数、API 封装、常量配置放在单独的文件里通过module.exports导出在需要的页面里用require引入。例如// utils/format.js function formatPrice(price) { return price.toFixed(2); } module.exports { formatPrice };// pages/index/index.js const { formatPrice } require(../../utils/format.js); Page({ data: { price: formatPrice(199.9) } });模块化的核心价值是把代码按职责拆分。我建议至少分出utils/工具函数、api/网络请求接口封装、config/环境配置和常量三个目录。api/目录里的封装尤其重要小程序里每个请求都写明域名、URL、参数、回调逻辑会导致大量重复代码。把请求统一封装成一个 Promise 方法页面只需要关注业务逻辑代码会清晰很多。模块化过程中要特别注意路径引用的准确性。小程序对相对路径敏感../../多一层或少一层都会导致编译报错。在项目变大之后可以考虑用开发者工具或构建工具配置路径别名例如配置指向src目录能明显减少路径书写的心智负担。4.3 页面复用与分包策略当页面数量继续增长代码构成里就要引入“分包”的概念了。分包机制允许把一些不常用的页面打包到独立的子包中用户打开小程序时只下载主包的代码等到需要访问某个子包页面时再按需下载。这个设计能显著减少小程序的启动体积尤其是商城类、工具类小程序主包控制在 1~2MB 以内体验会好很多。分包配置很简单在app.json里加subpackages数组每个子包包含root和pages字段。比如{ subpackages: [ { root: packageA, pages: [ pages/activity/activity ] } ] }分包内的页面路径以/packageA/pages/activity/activity这样的形式访问。需要注意的是tabBar 页面必须放在主包里不能放进分包分包之间不能互相跳转只能通过主包中转。分包预下载规则可以通过preloadRule配置比如进入首页后预下载某个子包这个功能在秒开子包页面时很有效。从代码管理的角度看分包不仅是为了体积还是一种逻辑组织方式。把营销活动页放进独立子包把核心交易流程放在主包既方便版本迭代时独立发布也明确了不同模块的边界。我见过一些小项目页面总共才 10 来个没有分包即可但也应该在 pages 目录里按模块划分子目录为后续增长预留空间。5. 工具链与工程化配置构建与调试的关键文件5.1 project.config.json项目设置与上传配置project.config.json是微信开发者工具认识的工程配置文件它不参与小程序运行但直接影响开发体验。文件里主要包含 appid、编译设置、上传配置、本地配置等。appid决定了你要把代码上传到哪个小程序账号上。如果是测试项目可以用测试号但要想体验完整能力比如云开发、真机预览必须使用注册后生成的正式 AppID。很多新手在真机预览时报“appid 错误”多半是在这里填错了或用的是别人的 appid。setting对象里有几个高频配置项。urlCheck控制是否检查合法域名本地开发时经常要关闭它否则无法请求本机或测试环境的接口。es6控制是否将 ES6 转 ES5低版本基础库兼容时需要开启。minified控制在上传时是否压缩代码发布时建议开启。还有postcss用于样式兼容处理建议保持开启。compileType字段通常为miniprogram表示这是一个小程序项目。projectname是项目名称可以自定义。如果使用了 npm 构建还需要在工具里执行“构建 npm”并在project.config.json的setting里配置useCompilerPlugins: [typescript]等插件选项。工程化配置里还有一个容易被忽略的点ignore规则。你可以在这里配置某些目录如 node_modules 里不需要上传的包、图片源文件在上传时忽略能有效减小上传包体积。实际项目里把 source 目录下的源图片和构建后的压缩图片分开放上传时忽略源图片主包体积能小不少。5.2 sitemap.json与基础库版本管理sitemap.json是小程序对搜索引擎爬虫的索引规则配置文件结构很简单比如{ rules: [ { action: allow, page: * } ] }它决定微信收录哪些页面、拒绝哪些页面。一般来说不需要频繁改动但如果你的小程序页面里包含隐私页或临时页可以在 rules 里针对特定路径设置disallow避免被索引收录。基础库版本管理是工程化配置里非常实际的问题。微信小程序的 JS API 和组件能力随着基础库版本迭代而变化不同用户手机上的微信基础库版本可能不同。如果代码里调用了较新基础库才支持的 API而用户的基础库版本太旧功能就会直接失效。开发者工具里可以在“详情-本地设置”中切换调试基础库版本。线上环境的兼容建议在app.js的onLaunch里判断基础库版本并做能力降级或提示。比如const version wx.getSystemInfoSync().SDKVersion; if (compareVersion(version, 2.30.0) 0) { wx.showModal({ title: 提示, content: 当前微信版本过低部分功能无法使用 }); }这种显式提示比功能静默失败要好得多。5.3 开发者工具代码调试三板斧开发者工具是读懂和调试小程序代码的必要工具。我日常用得最多的三个功能Sources 面板断点调试、Console 日志输出、Wxml 面板元素审查。断点调试在开发者工具里打开调试器切到 Sources 面板找到对应的 js 文件直接在某一行点击设置断点然后触发页面操作代码就会运行到断点处暂停此时可以查看变量的值、调用栈、作用域。它对定位复杂交互逻辑的问题非常有效。Console 面板除了打印日志还支持直接在控制台里执行 JS 表达式。比如想快速修改某个页面的 data可以在 Console 里输入getCurrentPages()[0].setData({ xxx: 新值 })这个技巧在调试时很管用不用改代码重新编译就能临时验证效果。Wxml 面板可以直观地看到渲染出来的节点树点击某个节点右侧会显示它的数据和样式。它最大的价值是帮助你检查数据绑定是否生效如果绑定的数据结构不对面板里会出现 undefined 或空值如果样式不生效面板里会显示出被覆盖掉的样式来源。遇到“界面显示不对但不知道哪里写错”的问题时Wxml 面板是首选排查入口。6. 常见问题与调试实录6.1 页面白屏数据、路径、渲染三查白屏是我被问到最多的问题。一个页面进去什么都没有既可能是报错了也可能是数据渲染为空。首选打开 Console 面板看有没有报错。如果有wxs或 js 的红色报错信息直接点开报错文件定位。常见的是在 JS 里访问了未定义的对象属性比如接口数据结构里没有返回某个字段你在代码里直接data.list[0].name就会报错导致渲染中断。如果 Console 没有报错检查app.json的pages中该页面路径是否注册正确以及 WXML 里的数据绑定是否有对应的初始数据。一个非常隐蔽的问题是某些场景下页面渲染了但数据是异步的初始化data里是空数组接口还没返回时页面看起来就是空的。正确的做法是在data里给列表一个空数组的默认值并在 WXML 上用wx:if判断数据是否为空显示对应的空状态提示而不是放任白屏。如果只在真机上白屏、开发者工具正常优先检查网络请求是否因为域名未配置而失败。开发时可以在工具里关闭urlCheck临时绕过但上线前必须把正式域名配置到小程序后台的 request 合法域名列表里。6.2 样式不生效优先级与样式隔离样式不生效百分之六十是优先级问题百分之三十是样式隔离问题剩下的是缓存问题。在开发者工具里点击不生效的元素看 Computed 面板里显示的实际样式和来源文件很快能判断出是哪个选择器覆盖了它。如果自定义组件内的样式被页面样式覆盖那就是样式隔离的问题。你要么通过options: { styleIsolation: apply-shared }让组件接受页面样式影响要么用externalClasses明确暴露样式接口。直接选用apply-shared的体验短期内省事但后续维护时样式之间的依赖关系会很乱我建议不到万不得已不用。样式缓存问题在真机上比较明显。有时真机上的样式还是旧版可以在微信中清除全部缓存或者删除小程序重新进入。还有一个不常用但很有效的技巧在app.wxss里临时给 body 或 page 设置一个特殊的背景色如果真机上变了说明缓存只是覆盖了局部样式如果没变说明整个包的缓存都没刷新可以杀掉微信进程重进。6.3 setData性能优化与数据更新策略在一个页面里频繁调用setData或者传入大量数据都会导致渲染卡顿。优化思路很简单减少更新频率、缩小更新范围、拆分更新数据。减少更新频率的典型场景是滚动监听或实时位置更新。例如做一个地图上的移动轨迹每秒钟更新多次坐标会导致页面卡顿正确做法是定时器或节流函数控制频率比如每秒最多更新 2 次。缩小更新范围的关键是用路径表达式。this.setData({ items[2].status: done })只传一条数据效率远高于this.setData({ items: newArray })。拆分更新数据的意思是把大数据对象分批传输或者只传输渲染层真正需要展示的字段。比如商品列表接口返回了 30 个字段但页面只展示名称、价格、封面图存储在data里时就可以抽离出这几个字段降低存储和传输开销。对话框数据更新的一个常见错误是在onShow里无条件重新拉取全量数据并setData其实可以先用缓存数据渲染页面再在后台校验增量有更新时才setData局部变化。这个策略在列表页、详情页都有明显收益。6.4 真机与开发者工具的差异陷阱本地开发者工具表现正常、真机却出问题这是小程序开发里最磨人的场景。整理几个我遇到的典型案例。第一个是 API 兼容性差异。开发者工具默认模拟的是最新基础库而真机上用户微信可能是几个月前的版本某些 API比如wx.getWindowInfo、wx.setBackgroundFetchToken在老基础库上不存在或行为不同。排查方法是把开发者工具的基础库版本切换到最低兼容版本模拟测试通常能复现问题。第二个是渲染差异。部分 CSS 属性在真机的 WebView 上解析方式不同比如position: fixed在 iOS 上有时会因为页面滚动区域的问题失效。遇到这种情况尽量用 flex box 布局替代减少对 fixed 的依赖确实需要 fixed把它放在 page 根节点下不要嵌套在滚动容器里。第三个是网络差异。本地开发时接口可以走局域网真机上必须走外网地址或内网穿透。如果接口超时或返回异常先确认真机上能否访问该地址再检查请求报文是否带上了正确的 header。有些公司内网接口做了 IP 白名单真机出来的 IP 不在白名单里就会一直请求失败。第四个是缓存差异。真机上小程序包更新有缓存策略你上传了新代码但用户手机上还是老包。排查时到开发版、体验版页面上确认版本号和时间戳不要靠着“我传了就会更新”的直觉判断问题。我在实际维护项目中发现最稳的排查路径是先在开发者工具里把基础库切换到目标版本本地复现复现不了就换一台不同系统的真机还不行就给相关代码加日志在真机上跑通后看 console 输出。这三步下来绝大多数真机问题都能定位到具体的原因。小程序的代码构成表面上就是那四类文件的排列组合但真正上手后会发现文件之间的关系、运行时的双线程协作、全局与页面的配置层级这些隐藏的规则才是决定项目质量的关键。文件结构是骨架双线程机制是血脉配置策略是神经系统。把这些底层逻辑摸透了之后你再去写页面、拆组件、调性能思路会清晰很多遇到问题也不会一头扎进代码里而是能顺着构成去推断问题可能出在哪一层。
返回列表