自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

盤點 Solid.js 源碼中的那些迷惑行為

開發(fā) 前端
看過源碼之后感覺有的地方設計的很巧妙,但有些地方又不是很嚴謹。也怪 jsx? 太靈活了,不可能做判斷把所有情況都做到面面俱到,當你要寫一些在 ?React? 里能運行的騷操作可能在 ?Solid? 里就啞火了。?

前言

我研究 Solid.js 源碼已經(jīng)有一段時間了,在鉆研的過程中我發(fā)現(xiàn)了其中的一些迷惑行為,在搞懂之后終于恍然大悟,忍不住想要分享給大家。不過這么說其實也不太準確,因為在嚴格意義上來講 Solid.js 其實是被劃分為了兩個部分的。我只認真鉆研了其中一個部分,所以也不能說鉆研 Solid.js 源碼,因為另外一個部分壓根就不叫 Solid。

兩部分

有些同學看到這可能就會感到疑惑了,哪兩個部分?Solid、.js?其實是這樣:大家應該都聽說過 Solid.js 是一個重編譯、輕運行的框架吧,所以它可以被分為編譯器和運行時兩個部分。

那有人可能會問:你要是這么說的話那豈不是 Vue 也可以被分為兩部分,畢竟 Vue 也有編譯器和運行時,為什么從來沒有人說過 Vue 是兩部分組成的呢?是這樣,Vue 的編譯器和運行時全都放在了同一倉庫內(nèi)的 Monorepo 中:

圖片

圖片

你可以說 Vue2 和 Vue3 是兩個部分,因為它倆被放在了兩個不同的倉庫中:

圖片

雖然它倆已經(jīng)是兩個不同的倉庫了,但好歹也都是 vuejs 名下的吧:

圖片

而 Solid.js 的兩部分不僅不在同一個倉庫內(nèi),甚至連組織名都不一樣:

圖片

圖片

一個是 solidjs/solid:

圖片

而另一個則是 ryansolid/dom-expressions:

圖片

ryan 是 Solid.js 作者的名字,所以 ryan + solid = ryansolid(有點迷,為啥不放在 solidjs 旗下非要單獨開一個 ryansolid)

這個 dom-expressions 就是 Solid.js 的編譯器,那為啥不像 Vue 編譯器似的都放在同一個倉庫內(nèi)呢?因為 Vue 的編譯器就是專門為 Vue 設計的,你啥時候看非 Vue項目中使用 xxx.vue 這樣的寫法過?

.vue 這種單文件組件就只有 Vue 使用,雖說其他框架也有單文件組件的概念并且有著類似的寫法(如:xxx.svelte)但人家 Svelte也不會去用 Vue 的編譯器去編譯人家的 Svelte 組件。不過 Solid 不一樣,Solid沒自創(chuàng)一個 xxx.solid,而是明智的選擇了 xxx.jsx。

SFC VS JSX

單文件組件和 jsx 各有利弊,不能說哪一方就一定比另一方更好。但對于一個聲明式框架作者而言,選擇單文件組件的好處是可以自定義各種語法,并且還可以犧牲一定的靈活性來換取更優(yōu)的編譯策略。缺點就是成本太高了,單單語法高亮和 TS 支持度這一方面就得寫一個非常復雜的插件才能填平。

好在 Vue 的單文件組件插件 Volar 已經(jīng)可以支持自定義自己的單文件組件插件了,這有效的降低了框架作者的開發(fā)成本。但 Solid 剛開始的時候還沒有 Volar 呢(可以去看看 Volar 的源碼有多復雜 這還僅僅只是一個插件就需要花費那么多時間和精力),甚至直到現(xiàn)在 Volar 也沒個文檔,就只有 Vue 那幫人在用 Volar(畢竟是他們自己研究的):

圖片

并且人家選擇 jsx 也有可能并非是為了降低開發(fā)成本,而是單純的鐘意于 jsx 語法而已。那么為什么選擇 jsx 會降低開發(fā)成本呢?首先就是不用自己寫 parser、generator 等一堆編譯相關(guān)的東西了,一個 babel 插件就能識別 jsx 語法。語法高亮、TS 支持度這方面更是不用操心,甚至用戶都不需要為編輯器安裝任何插件(何時聽過 jsx 插件)。

并且由于 React 是全球占有率最高的框架,jsx 已被廣泛接受(甚至連 Vue 都支持 jsx)但如果選擇單文件組件的話又會產(chǎn)生有人喜歡這種寫法有人喜歡那種寫法的問題,比方說同樣使用 sfc 的 Vue 和 Svelte,if-else 寫法分別是這樣:

<template>
  <h1 v-if="xxx" />
  <div v-else />
</template>
{#if xxx}
  <h1 />
{:else}
  <div />
{/if}

有人喜歡上面那種寫法就有人喜歡下面那種寫法,眾口難調(diào),無論選擇哪種寫法可能都會導致另一部分的用戶失望。而 jsx 就靈活的多了,if-else 想寫成什么樣都可以根據(jù)自己的喜好來:

if (xxx) {
  return <h1 />
} else {
  return <div />
}
// 或者
return xxx ? <h1 /> : <div />
// 亦或
let Title = 'h1'
if (xxx) Title = 'div'
return <Title />

jsx 最大程度的融合了 js,正是因為它對 js 良好的兼容性才導致它的適用范圍更廣,而不是像 Vue、Svelte 那樣只適用于自己的框架。

畢竟每種模板語言的 if-else、循環(huán)等功能寫法都不太一樣,當然 jsx 里的 if-else 也可以有各種千奇百怪的寫法,但畢竟還是 js 寫法,而不是自創(chuàng)的 ng-if、v-else、{:else if} {% for i in xxx %}等各種不互通的寫法。

正是由于 jsx 的這個優(yōu)勢導致了很多非 React 框架(如:Preact、Stancil、Solid 等)用 jsx 也照樣用的飛起,那么既然 jsx 可以不跟 React 綁定,那 Ryan 自創(chuàng)的 jsx編譯策略也同樣可以不跟 Solid 綁定啊對不對?

這是一款可以和 Solid.js 搭配使用的 babel 插件,也同樣是一款可以和 MobX、和 Knockout、和 S.js、甚至和 Rx.js 搭配使用的插件,只要你有一款響應式系統(tǒng),那么 dom-expressions 就可以為你提供 jsx 服務。

Solid.js

所以這才是 Ryan 沒把 dom-expressions 放在 solidjs/solid 里的重要原因之一,但 Solid.js 又是一個注重編譯的框架,沒了 dom-expressions 還不行,所以只能說 Solid.js 是由兩部分組成的。

DOM Expressions

DOM Expressions 翻譯過來就是 DOM 表達式的意思,有人可能會問那你標題為啥不寫成《盤點 DOM Expressions 源碼中的那些迷惑行為》?拜托!誰知道 DOM Expressions 到底是個什么鬼!

如果不是我苦口婆心的說了這么多,有幾個能知道這玩意就是 Solid.js 的編譯器,甭說國內(nèi)了,就連國外都沒幾個知道 DOM Expressions的。你要說 Solid.js 那別人可能會豎起大拇指說聲 Excellent,但你要說 DOM Expressions 那別人說的很可能就是 What the fuck is that? 了。不信你看它倆的??對比:

圖片

圖片

再來看看 Ryan 在油管上親自直播 DOM Expressions時的慘淡數(shù)據(jù):

圖片

這都沒我隨便寫篇文章的點贊量高,信不信如果我把標題中的 Solid.js 換成了 DOM Expression 的話點贊量都不會有 Ryan 直播的數(shù)據(jù)好?好歹人家還是 Solid的作者,都只能獲得如此慘淡的數(shù)據(jù),那更別提我了。

言歸正傳,為了防止大家不知道 Solid.js 編譯后的產(chǎn)物與 React 編譯后的產(chǎn)物有何不同,我們先來寫一段簡單的 jsx:

import c from 'c'
import xxx from 'xxx'

export function Component () {
  return (
    <div a="1" b={2} c={c} onClick={() => {}}>
      { 1 + 2 }
      { xxx }
    </div>
  )
}

React 編譯產(chǎn)物:

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]
  });
}

Solid 編譯產(chǎn)物:

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"]);

Solid 編譯后的產(chǎn)物乍一看有點不太易讀,我來給大家寫一段偽代碼,用來幫助大家快速理解 Solid 到底把那段 jsx 編譯成了啥:

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;
  })();
}

這樣看上去就清晰多了吧?直接編譯成了真實的 DOM 操作,這也是它性能為何能夠如此強悍的原因之一,沒有中間商(虛擬DOM)賺差價。但大家有沒有感覺有個地方看起來好像有點多此一舉,就是那個自執(zhí)行函數(shù):

export function Component() {
  return (() => {
    el.onclick = () => {};
    el.setAttribute("c", c);
    el.insertBefore(xxx);
    return el;
  })();
}

為何不直接編譯成這樣:

export function Component() {
  el.onclick = () => {};
  el.setAttribute("c", c);
  el.insertBefore(xxx);
  return el;
}

效果其實都是一樣的,不信你試著運行下面這段代碼:

let num = 1
console.log(num) // 1

num = (() => {
  return 1
})()
console.log(num) // 還是 1 但感覺多了一個脫褲子放屁的步驟

看了源碼才知道,原來看似多此一舉的舉動實則是有苦衷的。因為我們這是典型的站在上帝視角來審視編譯后的代碼,源碼的做法是只對 jsx 進行遍歷,在剛剛那種情況下所編譯出來的代碼確實不是最優(yōu)解,但它能保證在各種的場景下都能正常運行。

我們來寫一段比較罕見的代碼大家就能明白過來怎么回事了:

if (<div a={value} onClick={() => {}} />) {
  // do something…
}

當然這么寫沒有任何的意義,這是為了幫助大家理解為何 Solid 要把它的 jsx 編譯成一段自執(zhí)行函數(shù)才會寫成這樣的。我們來寫一段偽代碼,實際上 Solid 編譯出來的并不是這樣的代碼,但相信大家能夠明白其中的含義:

<div a={value} notallow={() => {}} />
// 將會被編譯成
const el = document.createElement('div')
el.setAttribute('a', value)
el.onclick = () => {}

發(fā)現(xiàn)問題所在了么?原本 jsx 只有一行代碼,但編譯過后卻變成三行了。所以如果不加一個自執(zhí)行函數(shù)的話將會變成:

if (const el = document.createElement('div'); el.setAttribute('a', value); el.onclick = () => {}) {
  // do something…
}

這很明顯是錯誤的語法,if 括號里根本不能寫成這樣,會報錯的!但如果把 if 括號里的代碼放在自執(zhí)行函數(shù)中那就沒問題了:

if ((() => {
  const el = document.createElement('div')
  el.setAttribute('a', value)
  el.onclick = () => {}
  return el
})()) {
  // do something…
}

我知道肯定有人會說把那三行代碼提出去不就得了么:

const el = document.createElement('div')
el.setAttribute('a', value)
el.onclick = () => {}
if (el) {
  // do something…
}

還記得我之前說過的那句:我們是站在上帝視角來審判 Solid 編譯后代碼的么?理論上來說這么做確實可以,但編譯成本無疑會高上許多,因為還要判斷 jsx 到底寫在了哪里,根據(jù)上下文的不同來生成不同的代碼,但這樣肯定沒有只編譯 jsx 而不管 jsx 到底是被寫在了哪里來的方便。而且我們上述的那種方式也不是百分百沒問題的,照樣還是會有一些意想不到的場景:

for (let i = 0, j; j = <div a={i} />, i < 3; i++) {
  console.log(j)
}

但假如按照我們那種策略來編譯代碼的話:

const el = document.createElement('div')
el.setAttribute('a', i)
for (let i = 0, j; j = el, i < 3; i++) {
  console.log(j)
}

此時就會出現(xiàn)問題,因為 el 用到了變量 i,而 el 又被提到外面去了所以訪問不到 i變量,所以 el 這幾行代碼必須要在 jsx 的原位置上才行,只有自執(zhí)行函數(shù)能夠做到這一點。由于 js 是一門極其靈活的語言,各種騷操作數(shù)不勝數(shù),所以把編譯后的代碼全都加上一段自執(zhí)行函數(shù)才是性價比最高并且最省事的選擇之一。

迷之嘆號??

有次在用 playground.solidjs.com 編譯 jsx 時驚奇的發(fā)現(xiàn):

圖片

不知大家看到這段 <h1>Hello, <!>!</h1> 時是什么感受,反正我的第一感覺就是出 bug 了,把我的嘆號 ! 給編譯成 <!> 了。

但令人摸不著頭腦的是,這段代碼完全可以正常運行,沒有出現(xiàn)任何的 bug。隨著測試的深入,發(fā)現(xiàn)其實并不是把我的嘆號 ! 給編譯成 <!> 了,只是恰巧在那個位置上我寫了個嘆號,就算不寫嘆號也照樣會有這個 <!>的:

圖片

發(fā)現(xiàn)沒?<!> 出現(xiàn)的位置恰巧就是 {xxx} 的位置,我們在調(diào)試的時候發(fā)現(xiàn)最終生成的代碼其實是這樣:

<h1>1<!---->2</h1>

也就是說當我們 .innerHTML = '<!>' 的時候其實就相當于 .innerHTML = '' 了,很多人看到這個空注釋節(jié)點以后肯定會聯(lián)想到 Vue,當我們在 Vue 中使用 v-if="false" 時,按理說這個節(jié)點就已經(jīng)不復存在了。但每當我們打開控制臺時就會看到原本 v-if 的那個位置變成了這樣:

圖片

尤雨溪為何要留下一個看似沒有任何意義的空注釋節(jié)點呢?廣大強迫癥小伙伴們?nèi)滩涣肆?,趕忙去 GitHub 里開個 issue 問尤雨溪:

圖片

尤雨溪給出的答案是這樣:

圖片

那 Solid 加一個這玩意也是和 Vue 一樣的原由么?隨著對源碼的深入,我發(fā)現(xiàn)它跟 Vue 的  原由并不一樣,我們再來用一段偽代碼來幫助大家理解 Solid 為什么需要一段空注釋節(jié)點:

<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

// 在空節(jié)點之前插入 xxx 而空節(jié)點恰好就在 1 2 之間 所以就相當于在 1 2 之間插入了 xxx
el.insertBefore(xxx, el2)

看懂了么,Solid 需要在 1 和 2 之間插入 xxx,如果不加這個空節(jié)點的話那就找不到該往哪插了:

<h1>1{xxx}2</h1>
// 假如編譯成沒有空節(jié)點的樣子:
const el = template('<h1>12</h1>')
const el1 = el1.firstChild  // 12
const el2 = el2.nextSibling  // 沒有兄弟節(jié)點了 只有一個子節(jié)點:12

el.insertBefore(xxx, 特么的往哪插?)

所以當大家在 playground.solidjs.com 中發(fā)現(xiàn)有 <!> 這種奇怪符號時,請不要覺得這是個 bug,這是為了留個占位符,方便 Solid 找到插入點。只不過大多數(shù)人都想不到,把這個 <!> 賦值給 innerHTML 后會在頁面上生成一個 <!---->。

迷之 ref

無論是 Vue 還是 React 都是用 ref 來獲取 DOM 的,Solid 的整體 API 設計的與 React 較為相似,ref 自然也不例外:

圖片

但它也有自己的小創(chuàng)新,就是 ref 既可以傳函數(shù)也可以傳普通變量。如果是函數(shù)的話就把 DOM 傳進去,如果是普通變量的話就直接賦值:

// 偽代碼
<h1 ref={title} />
// 將會編譯成:
const el = document.createElement('h1')
typeof title === 'function'
  ? title(el)
  : title = el

但在查看源碼時發(fā)現(xiàn)了一個未被覆蓋到的情況:

// 簡化后的源碼
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)) {
      ...
    }
  }
}

稍微給大家解釋一下,這個 transformAttributes 是用來編譯 jsx 上的屬性的:

圖片

當 key 等于 ref 時需要進行一些特殊處理,非常迷的一個命名就是這個 isFunction,看名字大家肯定會認為這個變量代表的是屬性值是否為函數(shù)。我來用人話給大家翻譯一下這個變量賦的值代表什么含義:t.isIdentifier(value.expression)的意思是這個 value 是否為變量名:

圖片

比方說 ref={a} 中的 a 就是個變量名,但如果是 ref={1}、ref={() => {}}那就不是變量名,剩下那倆條件是判斷這個變量名是否是 const 聲明的。也就是說:

const isFunction = value 是個變量名 && 是用 const 聲明的

這特么就能代表 value 是個 function 了?

在我眼里看來這個變量叫 isConst 還差不多,我們再來梳理一下這段邏輯:

// 簡化后的源碼
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)) {
      ...
    }
  }
}

接下來就是 if-else 條件判斷里的條件了,再來翻譯下,t.isLVal 代表的是:value 是否可以放在等號左側(cè),這是什么意思呢?一個例子就能讓大家明白:

// 此時 key = 'ref'、value = () => {}
<h1 ref={() => {}} />

// 現(xiàn)在我們需要寫一個等號 看看 value 能不能放在等號的左側(cè):
() => {} = xxx // 很明顯這是錯誤的語法 所以 t.isLVal(value.expression) 是 false

// 但假如寫成這樣:
<h1 ref={a.b.c} />

a.b.c = xxx // 這是正確的語法 所以 t.isLVal(value.expression) 現(xiàn)在為 true

明白了 t.isLVal 接下來就是 t.isFunction 了,這個從命名上就能看出來是判斷是否為函數(shù)的。然后就是 t.isCallExpression,這是用來判斷是否為函數(shù)調(diào)用的:

// 這就是 callExpression
xxx()

翻譯完了,接下來咱們就來分析一遍:

當 value 不是常量并且不能放在等號左側(cè)時(這種情況有處理)

當 value 是常量或者是一個函數(shù)字面量時(這種情況有處理)

當 value 是一個正在調(diào)用的函數(shù)時(這種情況有處理)

不知大家看完這仨判斷后有什么感悟,反正當我捋完這段邏輯的時候感覺有點迷,因為好像壓根兒就沒覆蓋掉全部情況??!咱們先這么分一下:value 肯定是變量名、字面量以及常量中的其中一種對吧?是常量的情況下有覆蓋,不是常量時就有漏洞了,因為它用了個并且符號 &&,也就是說當 value 不是常量時必須還要同時滿足不能放在等號左側(cè)這種情況才會進入到這個判斷中去,那假如我們寫一個三元表達式或者二元表達式那豈不就哪個判斷也沒進么?不信我們來試一下:

圖片

可以看到編譯后的 abc 三個變量直接變暗了,哪都沒有用到這仨變量,也就是說相當于吞掉了這段邏輯(畢竟哪個分支都沒進就相當于沒處理)不過有人可能會感到疑惑,三元表達式明明能放到等號左側(cè)啊:

圖片

實際上并不是你想的那樣,等號和三元表達式放在一起時有優(yōu)先級關(guān)系,調(diào)整一下格式你就明白是怎樣運行的了:

const _tmpl$ = /*#__PURE__*/_$template(`<h1>Hello`)
a ? b : c = 1
// 實際上相當于
a
  ? b
  : (c = 1)
// 相當于
if (a) {
  b
} else {
  c = 1
}

如果我們用括號來把優(yōu)先級放在三元這邊就會直接報錯了:

圖片

二元表達式也是同理:

圖片

我想在 ref 里寫成這樣沒毛病吧:

<h1 ref={a || b} />

雖然這種寫法比較少見,但這也不是你漏掉判斷的理由呀!畢竟好多用 Solid.js 的人都是用過 React 的,他們會把在 React 那養(yǎng)成的習慣不自覺的帶到 Solid.js 里來,而且這不也是 Solid.js 把 API 設計的盡可能與 React 有一定相似性的重要原因之一嗎?

但人家在 React 沒問題的寫法到了你這就出問題了的話,是會非常影響你這框架的口碑的!而且在文檔里還沒有提到任何關(guān)于 ref 不能寫表達式的說明:

圖片

后來我仔細想了一下,發(fā)現(xiàn)還真不是他們不小心漏掉的,而是有意為之。至于為什么會有意為之那就要看它編譯后的產(chǎn)物了:

// 偽代碼
<div ref={a} />
// 將會被編譯為:
const el = template(`<div>`)
typeof a === 'function' ? a(el) : a = el

其中咱們重點看 a = el 這段代碼,a 就是我們寫在 ref 里的,但假如我們給它換成一個二元表達式就會變成:

// 偽代碼
<div ref={a || b} />
// 將會被編譯為:
const el = template(`<div>`)
a || b = el

a || b 不能放在等號左側(cè),所以源碼中的 isLVal 就是為了過濾這種情況的。那為什么不能編譯成:

(a = el) || (b = el)

這么編譯是錯的,因為假如 a 為 false,a 就不應該被賦值,但實際上 a 會被賦值為 el:

圖片

所以要把二元編譯成三元:

圖片

如果是并且符號就要編譯成取反:

// 偽代碼
<div ref={a && b} />
// 將會被編譯為:
const el = template(`<div>`)
!a ? a = el : b = el

然后三元表達式以及嵌套三元表達式:

<div
  ref={
    Math.random() > 0.5
      ? refFactory() && refArr[0] && (refTarget1 = refTarget2) && (refTarget1 > refTarget2)
      : refTarget1
        ? refTarget2
        : refTarget3
  }
/>

當然可能并不會有人這么寫,Solid 那幫人也是這么想的,所以就算了,太麻煩了,如果真要是有復雜的條件的話可以用函數(shù):

<div
  ref={
    el => Math.random() > 0.5
      ? refTarget1 = el
      : refTarget2 = el
  }
/>

就先不管 isLVal 為 false 的情況了,不過我還是覺得至少要在官網(wǎng)上提一嘴,不然真有人寫成這樣的時候又搜不到答案的話那多影響口碑啊!

總結(jié)

看過源碼之后感覺有的地方設計的很巧妙,但有些地方又不是很嚴謹。也怪 jsx 太靈活了,不可能做判斷把所有情況都做到面面俱到,當你要寫一些在 React 里能運行的騷操作可能在 Solid 里就啞火了。

責任編輯:武曉燕 來源: 前端學不動
相關(guān)推薦

2022-08-12 08:02:11

Solid.jsReact

2020-06-17 09:01:37

C語言代碼開發(fā)

2012-12-20 12:24:33

2022-06-30 09:00:23

Vue.js版本名稱

2021-09-04 07:56:44

Pythonos模塊

2020-05-20 13:24:28

MySQL優(yōu)化數(shù)據(jù)庫

2022-03-27 20:52:41

Chrome插件開發(fā)

2010-09-14 11:36:24

上網(wǎng)行為管理網(wǎng)絡安全網(wǎng)康科技

2021-08-30 10:25:48

JavaScript進階操作前端

2021-08-26 10:25:04

JavaScript進階操作 前端

2021-10-19 07:41:45

React組件前端

2014-12-17 14:41:21

云計算互聯(lián)網(wǎng)混合云

2017-04-20 14:58:16

2022-01-11 06:53:23

IPO開發(fā)容器

2020-05-14 09:15:52

設計模式SOLID 原則JS

2013-08-28 10:18:48

2023-01-31 16:35:34

JavaScript測試框架

2021-12-06 09:36:38

網(wǎng)絡攻擊黑客網(wǎng)絡安全

2023-02-15 09:00:49

2021-04-07 10:02:51

Python字典Python基礎
點贊
收藏

51CTO技術(shù)棧公眾號