盘点Solid.js源码中的那些迷惑行为

前言

我研究 Solid.js 源码已经有一段时间了,在钻研的过程中我发现了其中的一些迷惑行为,在搞懂之后终于恍然大悟,忍不住想要分享给大家。不过这么说其实也不太准确,因为在严格意义上来讲 Solid.js 其实是被划分为了两个部分的。我只认真钻研了其中一个部分,所以也不能说钻研 Solid.js 源码,因为另外一个部分压根就不叫 Solid。

为改则等地区用户提供了全套网页设计制作服务,及改则网站建设行业解决方案。主营业务为网站设计制作、网站制作、改则网站设计,以传统方式定制建设网站,并提供域名空间备案等一条龙服务,秉承以专业、用心的态度为用户提供真诚的服务。我们深信只要达到每一位用户的要求,就会得到认可,从而选择与我们长期合作。这样,我们也可以走得更远!

两部分

有些同学看到这可能就会感到疑惑了,哪两个部分?Solid、.js?其实是这样:大家应该都听说过 Solid.js 是一个重编译、轻运行的框架吧,所以它可以被分为编译器和运行时两个部分。

那有人可能会问:你要是这么说的话那岂不是 Vue 也可以被分为两部分,毕竟 Vue 也有编译器和运行时,为什么从来没有人说过 Vue 是两部分组成的呢?是这样,Vue 的编译器和运行时全都放在了同一仓库内的 Monorepo 中:

你可以说 Vue2 和 Vue3 是两个部分,因为它俩被放在了两个不同的仓库中:

虽然它俩已经是两个不同的仓库了,但好歹也都是 vuejs 名下的吧:

而 Solid.js 的两部分不仅不在同一个仓库内,甚至连组织名都不一样:

一个是 solidjs/solid:

而另一个则是 ryansolid/dom-expressions:

ryan 是 Solid.js 作者的名字,所以 ryan + solid = ryansolid(有点迷,为啥不放在 solidjs 旗下非要单独开一个 ryansolid)

这个 dom-expressions 就是 Solid.js 的编译器,那为啥不像 Vue 编译器似的都放在同一个仓库内呢?因为 Vue 的编译器就是专门为 Vue 设计的,你啥时候看非 Vue项目中使用 xxx.vue 这样的写法过?

.vue 这种单文件组件就只有 Vue 使用,虽说其他框架也有单文件组件的概念并且有着类似的写法(如:xxx.svelte)但人家 Svelte也不会去用 Vue 的编译器去编译人家的 Svelte 组件。不过 Solid 不一样,Solid没自创一个 xxx.solid,而是明智的选择了 xxx.jsx。

SFC VS JSX

单文件组件和 jsx 各有利弊,不能说哪一方就一定比另一方更好。但对于一个声明式框架作者而言,选择单文件组件的好处是可以自定义各种语法,并且还可以牺牲一定的灵活性来换取更优的编译策略。缺点就是成本太高了,单单语法高亮和 TS 支持度这一方面就得写一个非常复杂的插件才能填平。

好在 Vue 的单文件组件插件 Volar 已经可以支持自定义自己的单文件组件插件了,这有效的降低了框架作者的开发成本。但 Solid 刚开始的时候还没有 Volar 呢(可以去看看 Volar 的源码有多复杂 这还仅仅只是一个插件就需要花费那么多时间和精力),甚至直到现在 Volar 也没个文档,就只有 Vue 那帮人在用 Volar(毕竟是他们自己研究的):

并且人家选择 jsx 也有可能并非是为了降低开发成本,而是单纯的钟意于 jsx 语法而已。那么为什么选择 jsx 会降低开发成本呢?首先就是不用自己写 parser、generator 等一堆编译相关的东西了,一个 babel 插件就能识别 jsx 语法。语法高亮、TS 支持度这方面更是不用操心,甚至用户都不需要为编辑器安装任何插件(何时听过 jsx 插件)。

并且由于 React 是全球占有率最高的框架,jsx 已被广泛接受(甚至连 Vue 都支持 jsx)但如果选择单文件组件的话又会产生有人喜欢这种写法有人喜欢那种写法的问题,比方说同样使用 sfc 的 Vue 和 Svelte,if-else 写法分别是这样:

{#if xxx}
  

{:else}
{/if}

有人喜欢上面那种写法就有人喜欢下面那种写法,众口难调,无论选择哪种写法可能都会导致另一部分的用户失望。而 jsx 就灵活的多了,if-else 想写成什么样都可以根据自己的喜好来:

if (xxx) {
  return 

} else { return
} // 或者 return xxx ?

:
// 亦或 let Title = 'h1' if (xxx) Title = 'div' return </code></pre><p>jsx 最大程度的融合了 js,正是因为它对 js 良好的兼容性才导致它的适用范围更广,而不是像 Vue、Svelte 那样只适用于自己的框架。</p></p><p>毕竟每种模板语言的 if-else、循环等功能写法都不太一样,当然 jsx 里的 if-else 也可以有各种千奇百怪的写法,但毕竟还是 js 写法,而不是自创的 ng-if、v-else、{:else if} {% for i in xxx %}等各种不互通的写法。</p></p><p>正是由于 jsx 的这个优势导致了很多非 React 框架(如:Preact、Stancil、Solid 等)用 jsx 也照样用的飞起,那么既然 jsx 可以不跟 React 绑定,那 Ryan 自创的 jsx编译策略也同样可以不跟 Solid 绑定啊对不对?</p></p><p>这是一款可以和 Solid.js 搭配使用的 babel 插件,也同样是一款可以和 MobX、和 Knockout、和 S.js、甚至和 Rx.js 搭配使用的插件,只要你有一款响应式系统,那么 dom-expressions 就可以为你提供 jsx 服务。</p> <h4>Solid.js</h4> <p>所以这才是 Ryan 没把 dom-expressions 放在 solidjs/solid 里的重要原因之一,但 Solid.js 又是一个注重编译的框架,没了 dom-expressions 还不行,所以只能说 Solid.js 是由两部分组成的。</p></p> </p> <h3>DOM Expressions</h3> </p><p>DOM Expressions 翻译过来就是 DOM 表达式的意思,有人可能会问那你标题为啥不写成《盘点 DOM Expressions 源码中的那些迷惑行为》?拜托!谁知道 DOM Expressions 到底是个什么鬼!</p></p><p>如果不是我苦口婆心的说了这么多,有几个能知道这玩意就是 Solid.js 的编译器,甭说国内了,就连国外都没几个知道 DOM Expressions的。你要说 Solid.js 那别人可能会竖起大拇指说声 Excellent,但你要说 DOM Expressions 那别人说的很可能就是 What the fuck is that? 了。不信你看它俩的对比:</p></p><p> </p><p> </p><p>再来看看 Ryan 在油管上亲自直播 DOM Expressions时的惨淡数据:</p></p><p> </p><p>这都没我随便写篇文章的点赞量高,信不信如果我把标题中的 Solid.js 换成了 DOM Expression 的话点赞量都不会有 Ryan 直播的数据好?好歹人家还是 Solid的作者,都只能获得如此惨淡的数据,那更别提我了。</p></p><p>言归正传,为了防止大家不知道 Solid.js 编译后的产物与 React 编译后的产物有何不同,我们先来写一段简单的 jsx:</p></p> <pre><code>import c from 'c' import xxx from 'xxx' export function Component () { return ( <div a="1" b={2} c={c} onClick={() => {}}> { 1 + 2 } { xxx } </div> ) }</code></pre> </p><p>React 编译产物:</p> <pre><code>import c from 'c'; import xxx from 'xxx'; import { jsxs as _jsxs } from "react/jsx-runtime"; export function Component() { return /*#__PURE__*/_jsxs("div", { a: "1", b: 2, c: c, onClick: () => {}, children: [1 + 2, xxx] }); }</code></pre><p>Solid 编译产物:</p></p> <pre><code>import { template as _$template } from "solid-js/web"; import { delegateEvents as _$delegateEvents } from "solid-js/web"; import { insert as _$insert } from "solid-js/web"; import { setAttribute as _$setAttribute } from "solid-js/web"; const _tmpl$ = /*#__PURE__*/_$template(`<div a="1" b="2">3`); import c from 'c'; import xxx from 'xxx'; export function Component() { return (() => { const _el$ = _tmpl$(), _el$2 = _el$.firstChild; _el$.$$click = () => {}; _$setAttribute(_el$, "c", c); _$insert(_el$, xxx, null); return _el$; })(); } _$delegateEvents(["click"]);</code></pre> </p><p>Solid 编译后的产物乍一看有点不太易读,我来给大家写一段伪代码,用来帮助大家快速理解 Solid 到底把那段 jsx 编译成了啥:</p></p> <pre><code>import c from 'c'; import xxx from 'xxx'; const template = doucment.createElement('template') template.innerHTML = '<div a="1" b="2">3</div>' const el = template.content.firstChild.cloneNode(true) // 大家可以简单的理解为 el 就是 <div a="1" b="2">3</div> export function Component() { return (() => { el.onclick = () => {}; el.setAttribute("c", c); el.insertBefore(xxx); return el; })(); }</code></pre> </p><p>这样看上去就清晰多了吧?直接编译成了真实的 DOM 操作,这也是它性能为何能够如此强悍的原因之一,没有中间商(虚拟DOM)赚差价。但大家有没有感觉有个地方看起来好像有点多此一举,就是那个自执行函数:</p> <pre><code>export function Component() { return (() => { el.onclick = () => {}; el.setAttribute("c", c); el.insertBefore(xxx); return el; })(); }</code></pre><p>为何不直接编译成这样:</p> <pre><code>export function Component() { el.onclick = () => {}; el.setAttribute("c", c); el.insertBefore(xxx); return el; }</code></pre><p>效果其实都是一样的,不信你试着运行下面这段代码:</p></p> <pre><code>let num = 1 console.log(num) // 1 num = (() => { return 1 })() console.log(num) // 还是 1 但感觉多了一个脱裤子放屁的步骤</code></pre> </p><p>看了源码才知道,原来看似多此一举的举动实则是有苦衷的。因为我们这是典型的站在上帝视角来审视编译后的代码,源码的做法是只对 jsx 进行遍历,在刚刚那种情况下所编译出来的代码确实不是最优解,但它能保证在各种的场景下都能正常运行。</p></p><p>我们来写一段比较罕见的代码大家就能明白过来怎么回事了:</p></p> <pre><code>if (<div a={value} onClick={() => {}} />) { // do something… }</code></pre> </p><p>当然这么写没有任何的意义,这是为了帮助大家理解为何 Solid 要把它的 jsx 编译成一段自执行函数才会写成这样的。我们来写一段伪代码,实际上 Solid 编译出来的并不是这样的代码,但相信大家能够明白其中的含义:</p> <pre><code><div a={value} notallow={() => {}} /> // 将会被编译成 const el = document.createElement('div') el.setAttribute('a', value) el.onclick = () => {}</code></pre><p>发现问题所在了么?原本 jsx 只有一行代码,但编译过后却变成三行了。所以如果不加一个自执行函数的话将会变成:</p></p> <pre><code>if (const el = document.createElement('div'); el.setAttribute('a', value); el.onclick = () => {}) { // do something… }</code></pre> </p><p>这很明显是错误的语法,if 括号里根本不能写成这样,会报错的!但如果把 if 括号里的代码放在自执行函数中那就没问题了:</p> <pre><code>if ((() => { const el = document.createElement('div') el.setAttribute('a', value) el.onclick = () => {} return el })()) { // do something… }</code></pre><p>我知道肯定有人会说把那三行代码提出去不就得了么:</p></p> <pre><code>const el = document.createElement('div') el.setAttribute('a', value) el.onclick = () => {} if (el) { // do something… }</code></pre> </p><p>还记得我之前说过的那句:我们是站在上帝视角来审判 Solid 编译后代码的么?理论上来说这么做确实可以,但编译成本无疑会高上许多,因为还要判断 jsx 到底写在了哪里,根据上下文的不同来生成不同的代码,但这样肯定没有只编译 jsx 而不管 jsx 到底是被写在了哪里来的方便。而且我们上述的那种方式也不是百分百没问题的,照样还是会有一些意想不到的场景:</p></p> <pre><code>for (let i = 0, j; j = <div a={i} />, i < 3; i++) { console.log(j) }</code></pre> </p><p>但假如按照我们那种策略来编译代码的话:</p></p> <pre><code>const el = document.createElement('div') el.setAttribute('a', i) for (let i = 0, j; j = el, i < 3; i++) { console.log(j) }</code></pre> </p><p>此时就会出现问题,因为 el 用到了变量 i,而 el 又被提到外面去了所以访问不到 i变量,所以 el 这几行代码必须要在 jsx 的原位置上才行,只有自执行函数能够做到这一点。由于 js 是一门极其灵活的语言,各种骚操作数不胜数,所以把编译后的代码全都加上一段自执行函数才是性价比最高并且最省事的选择之一。</p></p> </p> <h3>迷之叹号️</h3> </p><p>有次在用 playground.solidjs.com 编译 jsx 时惊奇的发现:</p></p><p> </p><p>不知大家看到这段 <h1>Hello, <!>!</h1> 时是什么感受,反正我的第一感觉就是出 bug 了,把我的叹号 ! 给编译成 <!> 了。</p></p><p>但令人摸不着头脑的是,这段代码完全可以正常运行,没有出现任何的 bug。随着测试的深入,发现其实并不是把我的叹号 ! 给编译成 <!> 了,只是恰巧在那个位置上我写了个叹号,就算不写叹号也照样会有这个 <!>的:</p></p><p> </p><p>发现没?<!> 出现的位置恰巧就是 {xxx} 的位置,我们在调试的时候发现最终生成的代码其实是这样:</p></p> <pre><code><h1>1<!---->2</h1></code></pre> </p><p>也就是说当我们 .innerHTML = '<!>' 的时候其实就相当于 .innerHTML = '' 了,很多人看到这个空注释节点以后肯定会联想到 Vue,当我们在 Vue 中使用 v-if="false" 时,按理说这个节点就已经不复存在了。但每当我们打开控制台时就会看到原本 v-if 的那个位置变成了这样:</p></p><p> </p><p>尤雨溪为何要留下一个看似没有任何意义的空注释节点呢?广大强迫症小伙伴们忍不了了,赶忙去 GitHub 里开个 issue 问尤雨溪:</p></p><p> </p><p>尤雨溪给出的答案是这样:</p></p><p> </p><p>那 Solid 加一个这玩意也是和 Vue 一样的原由么?随着对源码的深入,我发现它跟 Vue 的  原由并不一样,我们再来用一段伪代码来帮助大家理解 Solid 为什么需要一段空注释节点:</p> <pre><code><h1>1{xxx}2</h1> // 将会被编译成: const el = template('<h1>12</h1>') const el1 = el.firstChild // 1 const el2 = el1.nextSibling // const el3 = el2.nextSibling // 2 // 在空节点之前插入 xxx 而空节点恰好就在 1 2 之间 所以就相当于在 1 2 之间插入了 xxx el.insertBefore(xxx, el2)</code></pre><p>看懂了么,Solid 需要在 1 和 2 之间插入 xxx,如果不加这个空节点的话那就找不到该往哪插了:</p></p> <pre><code><h1>1{xxx}2</h1> // 假如编译成没有空节点的样子: const el = template('<h1>12</h1>') const el1 = el1.firstChild // 12 const el2 = el2.nextSibling // 没有兄弟节点了 只有一个子节点:12 el.insertBefore(xxx, 特么的往哪插?)</code></pre> </p><p>所以当大家在 playground.solidjs.com 中发现有 <!> 这种奇怪符号时,请不要觉得这是个 bug,这是为了留个占位符,方便 Solid 找到插入点。只不过大多数人都想不到,把这个 <!> 赋值给 innerHTML 后会在页面上生成一个 <!---->。</p></p> </p> <h3>迷之 ref</h3> </p><p>无论是 Vue 还是 React 都是用 ref 来获取 DOM 的,Solid 的整体 API 设计的与 React 较为相似,ref 自然也不例外:</p></p><p> </p><p>但它也有自己的小创新,就是 ref 既可以传函数也可以传普通变量。如果是函数的话就把 DOM 传进去,如果是普通变量的话就直接赋值:</p></p> <pre><code>// 伪代码 <h1 ref={title} /> // 将会编译成: const el = document.createElement('h1') typeof title === 'function' ? title(el) : title = el</code></pre> </p><p>但在查看源码时发现了一个未被覆盖到的情况:</p> <pre><code>// 简化后的源码 transformAttributes () { if (key === "ref") { let binding, isFunction = t.isIdentifier(value.expression) && (binding = path.scope.getBinding(value.expression.name)) && binding.kind === "const"; if (!isFunction && t.isLVal(value.expression)) { ... } else if (isFunction || t.isFunction(value.expression)) { ... } else if (t.isCallExpression(value.expression)) { ... } } }</code></pre><p>稍微给大家解释一下,这个 transformAttributes 是用来编译 jsx 上的属性的:</p></p><p> </p><p>当 key 等于 ref 时需要进行一些特殊处理,非常迷的一个命名就是这个 isFunction,看名字大家肯定会认为这个变量代表的是属性值是否为函数。我来用人话给大家翻译一下这个变量赋的值代表什么含义:t.isIdentifier(value.expression)的意思是这个 value 是否为变量名:</p></p><p> </p><p>比方说 ref={a} 中的 a 就是个变量名,但如果是 ref={1}、ref={() => {}}那就不是变量名,剩下那俩条件是判断这个变量名是否是 const 声明的。也就是说:</p></p> <pre><code>const isFunction = value 是个变量名 && 是用 const 声明的</code></pre> </p><p>这特么就能代表 value 是个 function 了?</p></p><p>在我眼里看来这个变量叫 isConst 还差不多,我们再来梳理一下这段逻辑:</p></p> <pre><code>// 简化后的源码 transformAttributes () { if (key === "ref") { const isConst = value is 常量 if (!isConst && t.isLVal(value.expression)) { ... } else if (isConst || t.isFunction(value.expression)) { ... } else if (t.isCallExpression(value.expression)) { ... } } }</code></pre> </p><p>接下来就是 if-else 条件判断里的条件了,再来翻译下,t.isLVal 代表的是:value 是否可以放在等号左侧,这是什么意思呢?一个例子就能让大家明白:</p></p> <pre><code>// 此时 key = 'ref'、value = () => {} <h1 ref={() => {}} /> // 现在我们需要写一个等号 看看 value 能不能放在等号的左侧: () => {} = xxx // 很明显这是错误的语法 所以 t.isLVal(value.expression) 是 false // 但假如写成这样: <h1 ref={a.b.c} /> a.b.c = xxx // 这是正确的语法 所以 t.isLVal(value.expression) 现在为 true</code></pre><p>明白了 t.isLVal 接下来就是 t.isFunction 了,这个从命名上就能看出来是判断是否为函数的。然后就是 t.isCallExpression,这是用来判断是否为函数调用的:</p></p> <pre><code>// 这就是 callExpression xxx()</code></pre> </p><p>翻译完了,接下来咱们就来分析一遍:</p><p>当 value 不是常量并且不能放在等号左侧时(这种情况有处理)</p><p>当 value 是常量或者是一个函数字面量时(这种情况有处理)</p><p>当 value 是一个正在调用的函数时(这种情况有处理)</p></p><p>不知大家看完这仨判断后有什么感悟,反正当我捋完这段逻辑的时候感觉有点迷,因为好像压根儿就没覆盖掉全部情况啊!咱们先这么分一下:value 肯定是变量名、字面量以及常量中的其中一种对吧?是常量的情况下有覆盖,不是常量时就有漏洞了,因为它用了个并且符号 &&,也就是说当 value 不是常量时必须还要同时满足不能放在等号左侧这种情况才会进入到这个判断中去,那假如我们写一个三元表达式或者二元表达式那岂不就哪个判断也没进么?不信我们来试一下:</p></p><p> </p><p>可以看到编译后的 abc 三个变量直接变暗了,哪都没有用到这仨变量,也就是说相当于吞掉了这段逻辑(毕竟哪个分支都没进就相当于没处理)不过有人可能会感到疑惑,三元表达式明明能放到等号左侧啊:</p></p><p> <p> </p><p>实际上并不是你想的那样,等号和三元表达式放在一起时有优先级关系,调整一下格式你就明白是怎样运行的了:</p> <pre><code>const _tmpl$ = /*#__PURE__*/_$template(`<h1>Hello`) a ? b : c = 1 // 实际上相当于 a ? b : (c = 1) // 相当于 if (a) { b } else { c = 1 }</code></pre><p>如果我们用括号来把优先级放在三元这边就会直接报错了:</p></p><p> <p> </p><p>二元表达式也是同理:</p><p> <p> </p><p>我想在 ref 里写成这样没毛病吧:</p></p> <pre><code><h1 ref={a || b} /></code></pre> </p><p>虽然这种写法比较少见,但这也不是你漏掉判断的理由呀!毕竟好多用 Solid.js 的人都是用过 React 的,他们会把在 React 那养成的习惯不自觉的带到 Solid.js 里来,而且这不也是 Solid.js 把 API 设计的尽可能与 React 有一定相似性的重要原因之一吗?</p></p><p>但人家在 React 没问题的写法到了你这就出问题了的话,是会非常影响你这框架的口碑的!而且在文档里还没有提到任何关于 ref 不能写表达式的说明:</p></p><p> </p><p>后来我仔细想了一下,发现还真不是他们不小心漏掉的,而是有意为之。至于为什么会有意为之那就要看它编译后的产物了:</p></p> <pre><code>// 伪代码 <div ref={a} /> // 将会被编译为: const el = template(`<div>`) typeof a === 'function' ? a(el) : a = el</code></pre> </p><p>其中咱们重点看 a = el 这段代码,a 就是我们写在 ref 里的,但假如我们给它换成一个二元表达式就会变成:</p> <pre><code>// 伪代码 <div ref={a || b} /> // 将会被编译为: const el = template(`<div>`) a || b = el</code></pre><p>a || b 不能放在等号左侧,所以源码中的 isLVal 就是为了过滤这种情况的。那为什么不能编译成:</p> <pre><code>(a = el) || (b = el)</code></pre><p>这么编译是错的,因为假如 a 为 false,a 就不应该被赋值,但实际上 a 会被赋值为 el:</p></p><p> <p> </p><p>所以要把二元编译成三元:</p><p> <p> </p><p>如果是并且符号就要编译成取反:</p></p> <pre><code>// 伪代码 <div ref={a && b} /> // 将会被编译为: const el = template(`<div>`) !a ? a = el : b = el</code></pre> </p><p>然后三元表达式以及嵌套三元表达式:</p></p> <pre><code><div ref={ Math.random() > 0.5 ? refFactory() && refArr[0] && (refTarget1 = refTarget2) && (refTarget1 > refTarget2) : refTarget1 ? refTarget2 : refTarget3 } /></code></pre> </p><p>当然可能并不会有人这么写,Solid 那帮人也是这么想的,所以就算了,太麻烦了,如果真要是有复杂的条件的话可以用函数:</p></p> <pre><code><div ref={ el => Math.random() > 0.5 ? refTarget1 = el : refTarget2 = el } /></code></pre> </p><p>就先不管 isLVal 为 false 的情况了,不过我还是觉得至少要在官网上提一嘴,不然真有人写成这样的时候又搜不到答案的话那多影响口碑啊!</p></p> <h3>总结</h3> </p><p>看过源码之后感觉有的地方设计的很巧妙,但有些地方又不是很严谨。也怪 jsx 太灵活了,不可能做判断把所有情况都做到面面俱到,当你要写一些在 React 里能运行的骚操作可能在 Solid 里就哑火了。</p> <br> 当前文章:盘点Solid.js源码中的那些迷惑行为 <br> 网页链接:<a href="http://www.xwwzsj.com/article/coggesc.html">http://www.xwwzsj.com/article/coggesc.html</a> </div> </div> <div class="other"> <h3>其他资讯</h3> <ul> <li> <a href="/article/cosdscd.html">易语言数据库登录验证,让信息安全得到保障。(易语言数据库登录验证)</a> </li><li> <a href="/article/cosdcpd.html">Linux下安全文件传输的方式(linux文件发送)</a> </li><li> <a href="/article/cosdshj.html">c语言中windows代表什么?cwindows视频教程</a> </li><li> <a href="/article/cosdsid.html">美国ip服务器租用卡顿怎么解决的</a> </li><li> <a href="/article/cosdspp.html">打开软件为什么必须wifi</a> </li> </ul> </div> </div> <div class="f_service_con"> <div class="h_fumin"> <div class="h_fumin_lei"> <div class="h_fumin_lei_tu"><img src="/Public/Home/images/f_service01.png"></div> <p>售后响应及时</p><span>7×24小时客服热线</span> </div> <div class="h_fumin_lei"> <div class="h_fumin_lei_tu"><img src="/Public/Home/images/f_service02.png"></div> <p>数据备份</p><span>更安全、更高效、更稳定</span> </div> <div class="h_fumin_lei"> <div class="h_fumin_lei_tu"><img src="/Public/Home/images/f_service03.png"></div> <p>价格公道精准</p><span>项目经理精准报价不弄虚作假</span> </div> <div class="h_fumin_lei"> <div class="h_fumin_lei_tu"><img src="/Public/Home/images/f_service04.png"></div> <p>合作无风险</p><span>重合同讲信誉,无效全额退款</span> </div> </div> </div> <div class="footerbar"> <div class="footer-t"> <div class="f-box"> <div class="f-1"> <div class="f-t"> <h2>联系我们</h2> <span>TEL</span> </div> <div class="f-b"> <h1><a href="tel:13518219792" rel="nofollow">135-1821-9792</a></h1> <h1><a href="tel:028-86922220" rel="nofollow">028-86922220</a></h1> <p>地址:成都市太升南路288号锦天国际</p> </div> </div> <div class="f-2"> <div class="f-t"> <h2>快捷导航</h2> <span>Shortcut</span> </div> <div class="f-b"> <ul > </ul> <ul > <li><a href="/jianshe" title="宣威网站建设">宣威网站建设</a></li> <li><a href="/jianshe#ym_websiteBox2" title="品牌网站建设">品牌网站建设</a></li> <li><a href="/jianshe#ym_websiteBox1" title="企业网站建设">企业网站建设</a></li> <li><a href="/jianshe#ym_websiteBox4" title="集团网站建设">集团网站建设</a></li> <li><a href="/jianshe#ym_websiteBox4_2" title="外贸网站建设">外贸网站建设</a></li> <li><a href="/jianshe#ym_websiteBox4_5" title="企业宣传视频">企业宣传视频</a></li> </ul> <ul > <li><a href="/weixin" title="微信开发">微信开发</a></li> <li><a href="/weixin#item1" title="公众号开发">公众号开发</a></li> <li><a href="/weixin#item2" title="微商城建设">微商城建设</a></li> <li><a href="/weixin#item3" title="微官网建设">微官网建设</a></li> <li><a href="/weixin#item4" title="小程序开发">小程序开发</a></li> </ul> <ul> <li><a href="/case/" title="网站作品案例">网站作品案例</a></li> <li><a href="/case/" title="品牌网站案例">品牌网站案例</a></li> <li><a href="/case/" title="集团网站案例">集团网站案例</a></li> <li><a href="/case/" title="企业网站案例">企业网站案例</a></li> <li><a href="/case/" title="外贸网站案例">外贸网站案例</a></li> <li><a href="/case/" title="营销网站案例">营销网站案例</a></li> </ul> <ul style="margin:0;"> <li><a href="/about/">瑞达杰昌建站</a></li> <li><a href="/about/">公司简介</a></li> <li><a href="/about#ab_item3">企业文化</a></li> <li><a href="/contact">联系我们</a></li> <li><a href="/Pay.html">付款方式</a></li> <li><a href="/jianshe#ym_websiteBox8">售后服务</a></li> </ul> <div style="clear:both;"></div> </div> </div> <div class="f-3"> <div class="f-t"> <h2>二维码</h2> <span>QR CODE</span> </div> <div class="f-b"> <ul> <li><img src="/Public/Home/images/fewm.png"> <p>微信公众号</p> </li> <li style="margin: 0"><img src="/Public/Home/images/fewm2.png"> <p>手机端网站</p> </li> <div style="clear:both;"></div> </ul> </div> </div> <div style="clear:both;"></div> </div> </div> <div class="footer-about"> <div class="w1200">瑞达杰昌建站工作室是一家专注从事于高品质视觉体验及互联网设计开发,<a href="/" target="_blank">宣威网站建设</a>,<a href="/jianshe" target="_blank">宣威网站设计</a>,<a href="/jianshe" target="_blank">宣威网页设计</a>,<a href="/jianshe" target="_blank">宣威网站制作</a>,<a href="/jianshe#ym_websiteBox2" target="_blank">品牌网站建设</a>,<a href="/jianshe#ym_websiteBox3" target="_blank">营销网站建设</a>,<a href="/jianshe#ym_websiteBox4" target="_blank">集团网站建设</a>,<a href="/jianshe#ym_websiteBox1" target="_blank">企业网站建设</a>,<a href="/jianshe#ym_websiteBox4_2" target="_blank">外贸网站建设</a>,<a href="/jianshe#ym_websiteBox4_3" target="_blank">响应式网站建设</a>,<a href="/weixin#item4" target="_blank">小程序开发</a>,<a href="/weixin" target="_blank">微信开发</a>,<a href="/jianshe#ym_websiteBox4_4" target="_blank">企业形象设计</a>,<a href="/jianshe#ym_websiteBox4_5" target="_blank">企业宣传视频</a>等服务,瑞达杰昌建站位于宣威市龙岗区大运软件小镇,瑞达杰昌建站拥有经验丰富的高级网站建设工程师和一流的网页高端设计人员,具备各种规模与类型网站建设的雄厚实力,在网站建设领域树立了自己独特的设计风格。 </div> <div class="friend-links"> <h6 class="clearfix"> <span class="tilte">友情链接</span> <a class="exchagne" href="http://wpa.qq.com/msgrd?v=3&uin=631063699&site=qq&menu=yes">交换友情链接</a> </h6> <div class="link-list clearfix"> <div class="link-slider"> <a href="http://www.kswsj.com/" title="成都网站制作" target="_blank">成都网站制作</a>   <a href="http://www.sclingao.com.cn/" title="sclingao.com.cn" target="_blank">sclingao.com.cn</a>   <a href="http://www.nxgcgz.com/" title="成都发电机维保公司" target="_blank">成都发电机维保公司</a>   <a href="https://www.cdcxhl.com/" title="成都建站" target="_blank">成都建站</a>   <a href="http://www.cdxwcx.cn/tuoguan/guanghua.html" title="成都电信光华数据中心" target="_blank">成都电信光华数据中心</a>   <a href="http://www.neijiangfdj.com/" title="内江柴油发电机" target="_blank">内江柴油发电机</a>   <a href="http://www.bjjike.cn/" title="红光高低压开关厂" target="_blank">红光高低压开关厂</a>   <a href="http://www.cdhuace.com/zhuangxiu.html" title="成都铺面装修" target="_blank">成都铺面装修</a>   <a href="https://www.cdcxhl.com/tuoguan/xixin/" title="成都西信机房" target="_blank">成都西信机房</a>   <a href="http://www.scwlttbz.com/" title="成都展厅设计" target="_blank">成都展厅设计</a>    </div> </div> </div> </div> <div class="footer-b"> <div class="f-box"> <ul> <li><a href="/jianshe#ym_websiteBox6" target="_blank">服务流程</a></li> <li><a href="/jianshe#ym_websiteBox8" target="_blank">售后服务</a></li> <li><a href="/about/" target="_blank">联系我们</a></li> <li><a href="https://www.cdxwcx.com/pay/" target="_blank">付款方式</a></li> <li><a href="https://www.cdcxhl.com/menu.html" target="_blank">网站地图</a></li> <li><a href="#" target="_blank">sitemap</a></li> <li> <p> <script data-cfasync="false" src="/Public/Home/js/email-decode.min.js"></script> </p> </li> <div style="clear:both;"></div> </ul> <p class="copy">Copyright © 2025 青羊区瑞达杰昌互联网信息服务工作室(个体工商户) 宣威建站 All Rights Reserved   <a href="https://beian.miit.gov.cn/" target="_blank" rel="nofollow">蜀ICP备2025133229号-6</a> <a style="display:none" target="_blank" href="###"><img style="vertical-align:middle" border="0" src="" width="65" height="25" /></a> </p> <div style="clear:both;"></div> </div> </div> <div class="sj_footer"> <div class="f-box"> <ul> <li><a href="/jianshe" target="_blank">网站建设</a></li> <li><a href="/jianshe#ym_websiteBox6" target="_blank">服务流程</a></li> <li><a href="/jianshe#ym_websiteBox8" target="_blank">售后服务</a></li> <li><a href="https://www.cdxwcx.com/pay/" target="_blank">付款方式</a></li> <li><a href="/about/" target="_blank">关于我们</a></li> <li><a href="https://www.cdcxhl.com/menu.html" target="_blank">网站地图</a></li> <div style="clear:both;"></div> </ul> <p class="copy">Copyright © 2025 青羊区瑞达杰昌互联网信息服务工作室(个体工商户) 宣威建站</p> <p class="copy"> <a href="https://beian.miit.gov.cn/" target="_blank" rel="nofollow">蜀ICP备2025133229号-6</a>  <a href="###" target="_blank"><img src="/Public/Home/images/govicon.gif" width="20" height="28" border="0" style="border-width:0px;border:hidden; border:none;"></a></p> <div style="clear:both;"></div> </div> </div> </div> <script type='text/javascript' src='/Public/Home/js/qqkefu.js'></script> <div class="qqkefu"> <ul> <li class="qq_czaa" id="130"><b class="a"></b>135-1821-9792</li> <li class="qq_czaa" id="130"><a href="tencent://message/?uin=1683211881"><b class="b"></b>业务咨询QQ</a></li> <li class="qq_czaa" id="130"><a href="javascript:showDiv()"><b class="f"></b>提交合作意向表</a></li> <li class="qq_czb"> <b class="c"></b> <div class="erweima"> <p><img src="/Public/Home/images/right_erweima.png"></p> </div> </li> <li class="top"><span></span></li> </ul> </div> <div id="popDiv" class="mydiv" style="display:none;"> <a class="mydiv_clk" href="javascript:closeDiv()">X</a> <div class="mydiv_list"> <div class="c_f_title"><span class="c_f_t">合作意向表</span></div> <div class="c_f_con"> <form id="form1" name="form1" class="mess_form" method="post" action="/post_order"> <input name='enews' type='hidden' value='AddFeedback'> <input name="bid" value="1" type="hidden"> <input type="hidden" name="ecmsfrom" value="9"> <input type="hidden" name='title' value="客户提交需求"> <li class="c_n"><span>公司名称</span> <dl><input name='gsname' id='gsname' type="text"></dl> </li> <li class="c_n"><span>邮箱</span> <dl><input name='gemail' id='gemail' type="text"></dl> </li> <li class="c_n xmm"> <div class="xmm_01"><span>姓名</span> <dl class="c_n_i"><input name='name' id='name' type="text"></dl> </div> <div class="xmm_01"><span style="text-align:center">电话</span> <dl class="c_n_i"><input name="tel" type="text"></dl> </div> </li> <li class="c_tser">您需要的服务</li> <li class="clearfix"> <dd><label><input type="radio" name='hobby' id='hobby' value="高端网站建设"><span>高端网站建设</span></label></dd> <dd><label><input type="radio" name='hobby' id='hobby' value="我需要做微信营销"><span>我需要做微信营销</span></label></dd> <dd><label><input type="radio" name='hobby' id='hobby' value="要找长期合作,需要年度服务"><span>要找长期合作,需要年度服务</span></label></dd> <dd><label><input type="radio" name='hobby' id='hobby' value="我需要做购物商城"><span>我需要做购物商城</span></label></dd> <dd><label><input type="radio" name='hobby' id='hobby' value="我需要网站改版"><span>我需要网站改版</span></label></dd> <dd><label><input type="radio" name='hobby' id='hobby' value="其他"><span>其他</span></label></dd> </li> <li class="c_tser">您关注的地方</li> <li class="clearfix"> <dd><label><input type="radio" name='hobby2' id='hobby2' value="对功能要求比较高"><span>对功能要求比较高</span></label></dd> <dd><label><input type="radio" name='hobby2' id='hobby2' value="对设计创意要求比较高"><span>对设计创意要求比较高</span></label></dd> <dd><label><input type="radio" name='hobby2' id='hobby2' value="需要可以购物支付"><span>需要可以购物支付</span></label></dd> <dd><label><input type="radio" name='hobby2' id='hobby2' value="搜索引擎排名"><span>搜索引擎排名</span></label></dd> </li> <li class="c_tser">预算</li> <li class="clearfix clearfix2"> <dd><label><input type="radio" name='hobby3' id='hobby3' value="一万以内"><span>一万以内</span></label> </dd> <dd><label><input type="radio" name='hobby3' id='hobby3' value="1-3万"><span>1-3万</span></label> </dd> <dd><label><input type="radio" name='hobby3' id='hobby3' value="3-5万"><span>3-5万</span></label> </dd> <dd><label><input type="radio" name='hobby3' id='hobby3' value="5万以上"><span>5万以上</span></label> </dd> <dd><label><input type="radio" name='hobby3' id='hobby3' value="需招投标"><span>需招投标</span></label> </dd> </li> <li class="c_n" style="border-top:1px solid #eee; padding-top:10px"><span>验证码</span> <dl class="c_n_i yzmm"><input type="text" name='code' id='code' value=""></dl><span style="text-align:center"><img src="/Public/Home/images/1661eb19783442c38063791555cd0d80.gif" onclick="this.src=this.src + '?'" width="100" height="40"></span> </li> <li class="clearfix"> <dd class="submit"><input name='submit' type="submit" value="提交需求"></dd> </li> </form> </div> </div> </div> <div id="bg" class="bg" style="display:none;"></div> <div id='popIframe' class='popIframe' frameborder='0'></div> <script> //提交需求选项 $(document).ready(function (e) { $(".mess_form").submit(function () { if ($("#gsname").val() == "") { alert("请填写您的公司名称!"); $("#gsname").focus(); return false; } if ($("#gemail").val() == "") { alert("请填写您的邮箱"); $("#gemail").focus(); return false; } if ($("#name").val() == "") { alert("请填写您的姓名!"); $("#name").focus(); return false; } if ($("#tel").val() == "") { alert("请填写您的电话!"); $("#tel").focus(); return false; } if ($("#hobby").val() == "") { alert("请选择您需要的服务!"); $("#hobby").focus(); return false; } if ($("#hobby2").val() == "") { alert("请选择您关注的地方!"); $("#hobby2").focus(); return false; } if ($("#hobby3").val() == "") { alert("请选择您的预算!"); $("#hobby3").focus(); return false; } if ($("#code").val() == "") { alert("请填写正确的验证码!"); $("#code").focus(); return false; } }); }); </script> <script language="javascript" type="text/javascript"> //提交需求窗口 function showDiv() { document.getElementById('popDiv').style.display = 'block'; document.getElementById('popIframe').style.display = 'block'; document.getElementById('bg').style.display = 'block'; } function closeDiv() { document.getElementById('popDiv').style.display = 'none'; document.getElementById('bg').style.display = 'none'; document.getElementById('popIframe').style.display = 'none'; } </script> <script type="text/javascript" src="/Public/Home/js/scrolltopcontrol.js"></script> <script type="text/javascript" src="/Public/Home/js/su_new.js"></script> </body> </html> <script> $(".con img").each(function(){ var src = $(this).attr("src"); //获取图片地址 var str=new RegExp("http"); var result=str.test(src); if(result==false){ var url = "https://www.cdcxhl.com"+src; //绝对路径 $(this).attr("src",url); } }); window.onload=function(){ document.oncontextmenu=function(){ return false; } } </script>