数控系统被锁怎么办▉▉▉【一电一 17154833762-】▉▉▉前面两篇文章分别分享了 Vue 编译三部曲的前两曲:「 parse,template 转换为 AST」,「optimize,模型树优化」,今天小编就来说说关于数控系统不准怎么办?下面更多详细答案一起来看看吧!

数控系统不准怎么办(数控系统被锁怎么办)

数控系统不准怎么办

数控系统被锁怎么办▉▉▉【一电一 17154833762-】▉▉▉前面两篇文章分别分享了 Vue 编译三部曲的前两曲:「 parse,template 转换为 AST」,「optimize,模型树优化」。

我们先简单回顾一下前两曲。

parse: 将开发者写的 template 模板字符串转换成抽象语法树 AST ,AST 对于这里来说就是一个树状结构的 JavaScript 对象,描述了这个模板,这个对象包含了每一个元素的上下文关系。那么整个 parse 的过程是利用很多正则表达式顺序解析模板,当解析到开始标签、闭合标签、文本的时候都会分别执行对应的回调函数,来达到构造 AST 树的目的。

optimize: 深度遍历这个 AST 树,去检测它的每一棵树是不是静态节点,如果是静态节点表示生成的 DOM 永远不需要改变,这对运行时对模板的更新起到极大的优化作用,提升了运行效率。

而编译的最后一步就是把优化后的 AST 树转换成可执行的代码,即 generate生成 render code。 在执行 render code渲染函数生成 vnode。

接下来我们来看看 Vue generate 是如何将 AST 树转换为render code?。

前置知识,渲染函数

在这之前我们需要先了解一个前置的知识渲染函数,Vue 推荐在绝大多数情况下使用模板来创建你的 HTML。 然而在一些场景中,你真的需要 JavaScript 的完全编程的能力。这时你可以用渲染函数,它比模板更接近编译器。在之前的文章我们也讨论过为什么 Vue 推荐模板,但是有一些场景我们更愿意使用渲染函数?(详情戳这里)

为什么默认推荐的模板语法,引用一段时间 Vue 官网的原话如下: 任何合乎规范的 HTML 都是合法的 Vue 模板,这也带来了一些特有的优势:

对于很多习惯了 HTML 的开发者来说,模板比起 JSX 读写起来更自然。这里当然有主观偏好的成分,但如果这种区别会导致开发效率的提升,那么它就有客观的价值存在。 基于 HTML 的模板使得将已有的应用逐步迁移到 Vue 更为容易。 这也使得设计师和新人开发者更容易理解和参与到项目中。 你甚至可以使用其他模板预处理器,比如 Pug 来书写 Vue 的模板。

有些开发者认为模板意味着需要学习额外的 DSL (Domain-Specific Language 领域特定语言) 才能进行开发——我们认为这种区别是比较肤浅的。首先,JSX 并不是没有学习成本的——它是基于 JS 之上的一套额外语法。同时,正如同熟悉 JS 的人学习 JSX 会很容易一样,熟悉 HTML 的人学习 Vue 的模板语法也是很容易的。最后,DSL 的存在使得我们可以让开发者用更少的代码做更多的事,比如 v-on 的各种修饰符,在 JSX 中间实现对应的功能会需要多得多的代码。

更抽象一点来看,我们可以把组件区分为两类:一类是偏视图表现的 (presentational),一类则是偏逻辑的 (logical)。我们推荐在前者中使用模板,在后者中使用 JSX 或渲染函数。这两类组件的比例会根据应用类型的不同有所变化,但整体来说我们发现表现类的组件远远多于逻辑类组件。

在深入渲染函数之前,了解一些浏览器的工作原理是很重要的。以下面这段 HTML 为例:

<div> <h1>My title</h1> Some text content <!-- TODO: Add tagline --> </div> 复制代码

上述 HTML 对应的 DOM 节点树如下图所示:

每个元素都是一个节点。每段文字也是一个节点。甚至注释也都是节点。在之前的编译三部曲第一步中我们也介绍了,template生成 AST时,会把元素、文字、注释都创建成节点描述对象。

  • type = 1的基础元素节点

  • type = 2含有expression和tokens的文本节点

  • type = 3的纯文本节点或者是注释节点

    child = { type: 1, tag:"div", parent: null, children: [], attrsList: [] }; child = { type: 2, expression: res.expression, tokens: res.tokens, text: text }; child = { type: 3, text: text }; child = { type: 3, text: text, isComment: true }; 复制代码

    每一个节点都是页面的一个部分。就像家谱树一样,每个节点都可以有孩子节点、兄弟节点 (也就是说每个部分可以包含其它的一些部分)。

    高效地更新所有这些节点会是比较困难的,不过所幸你不必手动完成这个工作。你只需要告诉 Vue 你希望页面上的 HTML 是什么,这可以是在一个模板里:

    <h1>{{ blogTitle }}</h1> 复制代码

    或者一个渲染函数里:

    render: function (createElement) { return createElement('h1', this.blogTitle) } 复制代码

    在这两种情况下,Vue 都会自动保持页面的更新,即便是 blogTitle 发生了改变。

    Vue 通过建立一个虚拟 DOM 来追踪自己要如何改变真实 DOM。请仔细看这行代码:

    return createElement('h1', this.blogTitle) 复制代码

    createElement到底会返回什么呢? 其实不是一个实际的 DOM 元素。它更准确的名字可能是 createNodeDescription,因为它所包含的信息会告诉 Vue 页面上需要渲染什么样的节点,包括及其子节点的描述信息。我们把这样的节点描述为“虚拟节点 (virtual node)”,也常简写它为“VNode”。“虚拟 DOM”是我们对由 Vue 组件树建立起来的整个 VNode 树的称呼。那createElement是如何映射成虚拟DOM的了?

    createElement

    export function createElement ( context: Component, tag: any, data: any, children: any, normalizationType: any, alwaysNormalize: boolean ): VNode | Array<VNode> { ... return _createElement(context, tag, data, children, normalizationType) } 复制代码

    createElement 函数就是对 _createElement 函数的一个封装,它允许传入的参数更加灵活,在处理这些参数后,调用真正创建 VNode 的函数 _createElement:

    export function _createElement ( context: Component, tag?: string | Class<Component> | Function | Object, data?: VNodeData, children?: any, normalizationType?: number ): VNode | Array<VNode> { ... return vnode; } 复制代码

    _createElement 方法有 5 个参数:

  • context 表示 VNode 的上下文环境。

  • tag 表示标签,它可以是一个字符串,也可以是一个 Component。

  • data 表示 VNode 的数据。

  • children 表示当前 VNode 的子节点,它是任意类型的,它接下来需要被规范为标准的 VNode 数组。

  • normalizationType 表示子节点规范的类型,类型不同规范的方法也就不一样,它主要是参考 render 函数是编译生成的还是用户手写的。

    _createElement 实现内容略多,这里就不详细分析了,反正最后都会创建一个 VNode ,每个 VNode 有 children,children 每个元素也是一个 VNode,这样就形成了一个 VNode Tree,它很好的描述了我们的 DOM Tree。

    接下来熟悉一下如何在 createElement 函数中使用模板中的功能。这里是createElement 接收的参数:

    / @returns {VNode} createElement( // {String | Object | Function} // 一个 HTML 标签名、组件选项对象,或者 // resolve 了上述任何一种的一个 async 函数。必填项。 'div', // {Object} // 一个与模板中属性对应的数据对象。可选。 { // (详情见下一节) }, // {String | Array} // 子级虚拟节点 (VNodes),由 `createElement()` 构建而成, // 也可以使用字符串来生成“文本虚拟节点”。可选。 [ '先写一些文字', createElement('h1', '一则头条'), createElement(MyComponent, { props: { someProp: 'foobar' } }) ] ) 复制代码

    到这里我们回过头去看看上文说的这句话:「Vue 推荐在绝大多数情况下使用模板来创建你的 HTML。 然而在一些场景中,你真的需要 JavaScript 的完全编程的能力。这时你可以用渲染函数,它比模板更接近编译器。」

    而本文的重点generate编译器,它的作用和目的就是将 AST 转换为渲染函数 , 解析成如下格式:

    _c('div',{attrs:{"id":"app"}},[_v(_s(message))]) 复制代码

    接下来我们就一起走进 generate 源码世界。

    generate

    我们先用一个示例来看看 AST经过 generate 之后生成的render code 到底长什么样?

    例如有这样一段模块:

    data: { isShow: true, list: [ '小白', '小黄', '小黑', '小绿' ], } <div> <ul class="list" v-if="isShow"> <li v-for="(item, index) in list" @click="clickItem(item)" > {{item}}:{{index}} </li> </ul> </div> 复制代码

    它经过编译,执行 const code = generate(ast, options),生成的 render code就会是如下这样一个字符串,注意这是一个字符串,只是为了方便大家阅读,我进行了格式化。

    with (this) { return _c('div', [ isShow ? _c( 'ul', { staticClass: 'list' }, _l(list, function (item, index) { return _c( 'li', { on: { click: function ($event) { return clickItem(item); }, }, }, [_v('\n ' _s(item) ':' _s(index) '\n ')], ); }), 0, ) : _e(), ]); } 复制代码

    with 字符串?

    大家发现生成的渲染函数字符串居然是一个with包裹的字符串,这样做的原因是with的作用域和模板的作用域是契合的,可以极大的简化编译流程。

    但是肯定会有同学质疑with不是不推荐使用?并且有性能问题吗?为什么还要用?

    尤雨溪本人的回答是这样的:


    “ 因为没有什么太明显的坏处(经测试性能影响几乎可以忽略),但是 with 的作用域和模板的作用域正好契合,可以极大地简化模板编译过程。Vue 1.x 使用的正则替换 identifier path 是一个本质上 unsound 的方案,不能涵盖所有的 edge case;而走正经的 parse 到 AST 的路线会使得编译器代码量爆炸。虽然 Vue 2 的编译器是可以分离的,但凡是可能跑在浏览器里的部分,还是要考虑到尺寸问题。用 with 代码量可以很少,而且把作用域的处理交给 js 引擎来做也更可靠。

    用 with 的主要副作用是生成的代码不能在 strict mode / ES module 中运行,但直接在浏览器里编译的时候因为用了 new Function(),等同于 eval,不受这一点影响。

    当然,最理想的情况还是可以把 with 去掉,所以在使用预编译的时候(vue-loader 或 vueify),会自动把第一遍编译生成的代码进行一次额外处理,用完整的 AST 分析来处理作用域,把 with 拿掉,顺便支持模板中的 ES2015 语法。也就是说如果用 webpack vue 的时候,最终生成的代码是没有 with 的。”


    而对于性能的影响,使用with 的确会造成一定的性能降低。但是真实 DOM 的渲染时间比 Virtual DOM 要长,而是否使用 with 只是影响了 Virtual DOM 的渲染,对真实 DOM 的渲染没有影响。所以对于普通需求来说,这种性能的影响比较小。

    并且使用 with, 就不需要在模板里面写 this 了。而编译生成的 with(this) 可以在某种程度上实现对于作用域的动态注入。这样写方便又简单,极大的简化编译流程,虽然有小的性能影响,但是权衡之下肯定利大于弊。

    _c 函数

    并且在生成的渲染字符串中有这样一些醒目的标记,例如:_c

    vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false) 复制代码

    _c 其实在源码中对应的就是createElement函数,用于创建vnode。

    而还有一些常用的_(下划线)函数 如:_l 对应 renderList 渲染列表;_v 对应 createTextVNode 创建文本 VNode;_e 对于 createEmptyVNode创建空的 VNode。它们都被定义在 installRenderHelpers中。

    export function installRenderHelpers (target: any) { target._o = markOnce target._n = toNumber target._s = toString target._l = renderList target._t = renderSlot target._q = looseEqual target._i = looseIndexOf target._m = renderStatic target._f = resolveFilter target._k = checkKeyCodes target._b = bindObjectProps target._v = createTextVNode target._e = createEmptyVNode target._u = resolveScopedSlots target._g = bindObjectListeners } 复制代码

    好,接下来,我们回到 generate 源码中来。

    generate 函数

    const code = generate(ast, options) 复制代码

    function generate ( ast, options ) { var state = new CodegenState(options); var code = ast ? genElement(ast, state) : '_c("div")'; return { render: ("with(this){return " code "}"), staticRenderFns: state.staticRenderFns } } 复制代码

    进入generate流程调用 generate函数。generate函数代码不是很复杂,参数也比较简单

  • ast:转换优化后的语法树

  • options:编译器运行时的配置项

    函数首先调用 CodegenState 构造函数,创建实例对象 state 初始化编译的状态。CodegenState 的主要作用就是给实例初始化一些相关的属性。

  • options:基础的配置项

  • warn:警告函数

  • transforms:静态样式和属性、非静态样式和属性的处理函数引用

  • dataGenFns:模块数据函数的引用

  • directives:v-bind、v-model、v-text、v-html、v-on、内置指令对应 处理函数

  • isReservedTag:检查是否是保留标签

  • maybeComponent:检查元素是否为组件

  • staticRenderFns:存放静态节点的 render 函数

  • pre:记录标签是否使用了 v-pre

    var CodegenState = function CodegenState (options) { this.options = options; this.warn = options.warn || baseWarn; this.transforms = pluckModuleFunction(options.modules, 'transformCode'); this.dataGenFns = pluckModuleFunction(options.modules, 'genData'); this.directives = extend(extend({}, baseDirectives), options.directives); var isReservedTag = options.isReservedTag || no; this.maybeComponent = function (el) { return !!el.component || !isReservedTag(el.tag); }; this.onceId = 0; this.staticRenderFns = []; this.pre = false; }; 复制代码

    接下来就是最重要的一步是,生成render code string。

    var code = ast ? genElement(ast, state) : '_c("div")'; 复制代码

    有ast就调用 genElement函数,没有的话,默认就创建一个div。这里的重点是genElement函数, 接下来我们来重点看看 genElement干了些什么?

    genElement

    当调用 genElement函数时,传入已经优化处理好的ast。然后在函数中根据不同的节点属性执行不同的生成函数。

    ①,判断 el.parent是否有值,来进行 pre 属性的设置。

    ②,如果节点是一个静态根节点staticRoot = ture,并且节点还没有被解析过staticProcessed = undefined就会调用 genStatic 函数。此函数用于生成静态节点的渲染函数字符串。生成一个_m的函数字符串。详情请看genStatic函数解析↓。

    ③,如果节点存在v-once,并且节点还没有被解析过onceProcessed = undefined就会调用 genOnce 函数。此函数用于生成v-once节点的渲染函数字符串。生成一个_o的函数字符串。详情请看genonce函数解析↓。

    ④,如果存在v-for循环,并且节点还没有被解析过forProcessed = undefined就会调用 genFor函数。此函数用于节点存在循环的情况,生成一个_l的函数字符串。详情请看genFor函数解析↓。

    ⑤,如果存在v-if循环,并且节点还没有被解析过ifProcessed = undefined就会调用 genIf函数。此函数用于节点存在v-if、v-else-if、v-else的情况,生成一个包含三目表达式的字符串(或者是嵌套的三目表达式:a ? b ? ... : c : d),详情请看genIf函数解析↓。