好的,我们来详细解析一下这段 JavaScript 代码的执行顺序。 这段代码采用了经典的立即执行函数表达式(IIFE)和闭包设计模式,旨在创建一个独立的、不污染全局环境的自定义控件。 ### 整体概览 代码的执行可以分为两个主要阶段: 1. 定义阶段:浏览器解析并加载整个 <script> 块。 2. 执行阶段:代码开始从上到下运行。 --- ### 详细执行步骤 #### 阶段一:定义与初始化 (代码的开头部分) 1. 执行立即执行函数 (IIFE) * 代码从 (function (factory) { ... })(function () { ... }); 开始。这是一个立即执行函数,它会被立即调用。 * 外部函数执行:外层的 (function (factory) { ... }) 开始执行。 * 内部函数定义:此时,作为参数传入的 function () { ... } (即工厂函数) 还没有被执行,它只是被定义并作为参数 factory 传递给了外部函数。 2. 创建命名空间 * 外部函数内部,首先定义了一个变量 nameSpace = 'field_4338765648457043195'。 * 接着,它检查 window[nameSpace] 是否存在。在首次加载时,这个属性是不存在的,所以 if (!window[nameSpace]) 条件成立。 * 调用工厂函数:执行 var Builder = factory();。这是一个关键步骤,它会调用并执行作为参数传入的那个内部函数。 #### 阶段二:工厂函数的执行 (定义控件的“蓝图”) 当 factory() 被调用时,程序的执行流程进入了内部的 function () { ... } 函数。这个函数的作用是定义控件的构造函数和方法,并最终返回它。 1. 定义构造函数 App * 函数 function App(options) { ... } 被定义。这是自定义控件的“蓝图”。 * 当一个新的控件实例被创建时,这个函数内部的代码会执行: * const self = this;:保存 this 上下文,避免在嵌套函数中丢失。 * self.initParams(options);:调用 initParams 方法来初始化参数。 * self.initDom();:调用 initDom 方法来初始化 DOM 结构。 * self.events();:调用 events 方法来绑定事件监听器。 2. 扩展 App 的原型链 * App.prototype = { ... } 这部分代码为所有 App 实例添加了共享的方法。这些方法在此时被定义,但并未执行。 * 被定义的方法包括:initParams, initDom, events, appendChildDom, location。 3. 返回构造函数 * 工厂函数的最后一行是 return App;。 * 这意味着 factory() 函数的执行结果是 App 这个函数本身。 * 因此,在外部函数中,var Builder = factory(); 这行代码执行完毕后,Builder 变量就指向了我们刚刚定义的 App 构造函数。 #### 阶段三:暴露公共接口 (回到外部函数) 工厂函数执行完毕并返回 App 构造函数后,程序流程回到外部函数。 1. 挂载命名空间到 window * 代码执行 window[nameSpace] = { instance: {} };。 * 这在全局 window 对象上创建了一个名为 field_4338765648457043195 的属性,其值是一个包含 instance 属性的对象。这个 instance 对象将用来存储后续创建的控件实例。 2. 定义 test 方法 * window[nameSpace].test = function (options) { ... }; * 这个 test 方法是提供给外部环境(例如,页面加载完成后或某个用户交互触发时)来创建控件实例的入口。 * 当 test 方法被调用时,它会执行 new Builder(options),也就是 new App(options),从而创建一个 App 的新实例,并将其存储在 window[nameSpace].instance 对象中,以 options.privateId 作为键。 3. 定义 isNotNull 方法 * window[nameSpace].isNotNull = function (obj) { ... }; * 这是另一个暴露给外部的辅助方法。 至此,整个脚本的初始化过程全部完成。此时,一个自定义控件的框架已经准备就绪,但它的具体实例(如按钮的渲染、事件绑定)只有在外部代码调用 test 方法时才会真正发生。 --- ### 阶段四:控件实例的生命周期 (当外部调用 test 方法时) 假设在页面的其他地方,有代码调用了 window.field_4338765648457043195.test(someOptions);。 1. 创建实例 * test 方法被执行,new App(someOptions) 被调用。 * App 构造函数开始执行。 2. 执行 initParams * self.initParams(someOptions) 被调用。 * 实例的 adaptation, privateId, messageObj 等属性被赋值。 3. 执行 initDom * self.initDom() 被调用。 * 它会立即调用 self.appendChildDom()。 4. 执行 appendChildDom * 这个方法负责在页面上找到 ID 为 privateId 的元素,并向其内部写入 HTML 结构(一个按钮)。 * 它还为这个按钮绑定了一个 click 事件监听器。当按钮被点击时,会调用 self.location(...) 方法。 * 最后,它会检查 self.messageObj.auth 的值,如果是 'hide' 或 'browse',则会清空按钮并显示一个空的浏览状态的 div。 5. 执行 events * self.events() 被调用。 * 它通过 self.adaptation.ObserverEvent.listen 注册了一个事件监听器。当名为 'Event' + self.privateId 的事件被触发时,它会重新获取数据 (self.messageObj) 并再次调用 self.appendChildDom() 来更新视图。 6. 响应用户交互 * 点击按钮:如果用户点击了渲染出的按钮,location 方法会被执行。 * location 方法会显示一个遮罩层 (showMask())。 * 它会使用 $.ajax 向服务器发送一个同步的 POST 请求。 * 请求成功后,它会根据返回的数据 res,动态地向页面上的子表单(通过 thirdPartyFormAPI 和 csdk 操作)添加新行,并将返回的数据回填到表单中。 * 无论成功还是失败,最后都会隐藏遮罩层 (hideMask())。 * 数据刷新事件:如果外部框架触发了 'Event' + self.privateId 事件,events 方法中注册的回调函数会执行,从而刷新控件的显示内容。 ### 总结 | 顺序 | 执行内容 | 关键操作 | | :--- | :--- | :--- | | 1 | 外部IIFE执行 | 定义 nameSpace,检查 window 上是否存在该命名空间。 | | 2 | 调用工厂函数 | 执行 factory(),进入内部函数。 | | 3 | 工厂函数内部 | 定义 App 构造函数和其原型上的所有方法(initParams, initDom, location 等)。 | | 4 | 返回构造函数 | 工厂函数 return App;,外部 Builder 变量获得 App 函数的引用。 | | 5 | 挂载命名空间 | 在 window 上创建 field_... 对象,作为控件的唯一入口。 | | 6 | 定义公共方法 | 在命名空间对象上定义 test 和 isNotNull 方法,供外部调用。 | | 7 | (外部触发) | 页面其他脚本调用 window.field_...test(options)。 | | 8 | 创建实例 | new App(options) 被调用,App 构造函数开始执行。 | | 9 | 实例初始化 | 依次调用 initParams, initDom, events,完成控件的渲染和事件绑定。 | | 10| 响应用户操作 | 用户点击按钮时,执行 location 方法,进行数据请求和表单操作。