1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        實(shí)例詳解你不知道的十個(gè) JS 小技巧

        共 18435字,需瀏覽 37分鐘

         ·

        2022-06-28 04:21

        大廠技術(shù)  高級(jí)前端  Node進(jìn)階

        點(diǎn)擊上方 程序員成長(zhǎng)指北,關(guān)注公眾號(hào)

        回復(fù)1,加入高級(jí)Node交流群

        原文鏈接: https://juejin.cn/post/7021047385337888775

        我是加菲貓?? ,如果你喜歡我的文章,歡迎點(diǎn)贊分享哦

        總結(jié)了一些開發(fā)常用的 JS 小技巧,讓你的代碼更優(yōu)雅!

        1. 使用 const 定義

        在開發(fā)中不要過(guò)度聲明變量,盡量使用表達(dá)式和鏈?zhǔn)秸{(diào)用形式。然后一般能用 const 就不要用 let 。這種模式寫多了之后,你會(huì)發(fā)現(xiàn)在項(xiàng)目中幾乎找不到幾個(gè)用 let 的地方:

        // bad
        let result = false;
        if (userInfo.age > 30) {
          result = true;
        }
        // good
        const result = userInfo.age > 30;

        在項(xiàng)目中很多同事都會(huì)這樣寫,

        handleFormChange(e) {
          let isUpload;
          if (e.target.value === 'upload') {
            isUpload = true;
          } else {
            isUpload = false;
          }
        }

        但實(shí)際上 ===== 的表達(dá)式可以直接給變量賦值:

        handleFormChange(e) {
          const isUpload = (e.target.value === 'upload');
        }

        如果要取反也很簡(jiǎn)單:

        handleFormChange(e) {
          const isManual = (e.target.value !== 'upload');
        }

        2. 有條件地向?qū)ο?、?shù)組添加屬性

        1) 向?qū)ο筇砑訉傩?/span>

        可以使用展開運(yùn)算符來(lái)有條件地向?qū)ο笾刑砑訉傩裕?/p>

        const condition = true;
        const person = {
          id1,
          name"dby",
          ...(condition && { age12 }),
        };

        如果 conditiontrue ,則 { age: 16 } 會(huì)被添加到對(duì)象中;如果 conditionfalse ,相當(dāng)于展開 false ,不會(huì)對(duì)對(duì)象產(chǎn)生任何影響

        2) 向數(shù)組添加屬性

        這是 CRA 中 Webpack 配置的源碼:

        module.exports = {
          plugins: [
            new HtmlWebpackPlugin(),
            isEnvProduction &&
              new MiniCssExtractPlugin(),
            useTypeScript &&
              new ForkTsCheckerWebpackPlugin(),
          ].filter(Boolean),
        }

        上面代碼中,只有 isEnvProductiontrue 才會(huì)添加 MiniCssExtractPlugin 插件;當(dāng) isEnvProductionfalse 則會(huì)添加 false 到數(shù)組中,所以最后使用了 filter 過(guò)濾

        上面這樣寫,邏輯上是沒(méi)有問(wèn)題,但是在 TS 項(xiàng)目中使用的時(shí)候,類型推斷會(huì)有點(diǎn)問(wèn)題。因?yàn)榇嬖谝环N布爾值添加到數(shù)組的中間狀態(tài),而 TS 只能靜態(tài)分析,不能執(zhí)行代碼。

        試問(wèn)下面 arr 數(shù)組的類型:

        const arr = ['111'true && '222'].filter(Boolean); // string[]
        const arr = ['111'false && '222'].filter(Boolean); // (string | boolean)[]

        下面是一種類型安全的做法:

        module.exports = {
          plugins: [
            new HtmlWebpackPlugin(),
            ...(isEnvProduction ? [new MiniCssExtractPlugin()] : []),
            ...(useTypeScript ? [new ForkTsCheckerWebpackPlugin()] : []),
          ],
        }

        3. 數(shù)組小技巧

        1) 訪問(wèn)數(shù)組最后一個(gè)元素

        在開發(fā)中我們常常需要訪問(wèn)數(shù)組最后一個(gè)元素,常規(guī)的做法是這樣:

        const rollbackUserList = [1234];
        const lastUser = rollbackUserList[rollbackUserList.length - 1]; // 4

        上面這樣寫邏輯上沒(méi)問(wèn)題,但是我們發(fā)現(xiàn)當(dāng)數(shù)組的變量名很長(zhǎng)的時(shí)候,整個(gè)表達(dá)式會(huì)變得非常長(zhǎng),影響代碼可讀性。一種方法是把上述邏輯封裝一下:

        // 將獲取數(shù)組最后一個(gè)元素的邏輯封裝為一個(gè)函數(shù)
        const getLastEle = (arr) => arr[arr.length - 1];
        const lastUser = getLastEle(rollbackUserList); // 4

        可以看到這樣語(yǔ)義性就比較好了。還有一種是利用 Array.prototype.slice 方法,通過(guò)傳遞負(fù)索引,從右往左定位到最后一個(gè)元素:

        const lastUser = rollbackUserList.slice(-1)[0]; // 4
        // 或者使用解構(gòu)賦值
        const [lastUser] = rollbackUserList.slice(-1); // 4

        注意 slice 方法返回的是一個(gè) 新數(shù)組 而不是元素本身,需要手動(dòng)從數(shù)組中取出元素,看起來(lái)不是很優(yōu)雅。實(shí)際上 ES2022 專門提供了一個(gè) Array.prototype.at 方法,用于根據(jù)給定索引獲取數(shù)組元素:

        const lastUser = rollbackUserList.at(-1); // 4

        at 方法傳遞索引為正時(shí)從左往右定位(這與直接通過(guò)下標(biāo)訪問(wèn)的作用一致),索引為負(fù)時(shí)從右往左定位。在訪問(wèn)數(shù)組最后一個(gè)元素的場(chǎng)景中非常好用。從 Chrome 92 開始已經(jīng)支持 at 方法,core-js 也提供了 polyfill。

        developer.mozilla.org/en-US/docs/…[1]

        2) 異步函數(shù)組合(異步串行調(diào)用)

        我們知道,在函數(shù)式編程中有一個(gè)函數(shù)組合 compose 的技巧,可以把多個(gè)函數(shù)組合為一個(gè)函數(shù),創(chuàng)建一個(gè)從右到左的數(shù)據(jù)流,右邊函數(shù)執(zhí)行的結(jié)果作為參數(shù)傳入左邊。例如:

        /**
         * 給定參數(shù):[A, B, C, D]
         * 調(diào)用順序:A(B(C(D())))
         */

        const compose = (middlewares) => (initialValue) => middlewares.reduceRight(
            (accu, cur) => cur(accu),
            initialValue
        );

        從上面的代碼可以看出 compose 是不能用于 Promise,因?yàn)?compose 需要將上一次調(diào)用的返回值,作為參數(shù)傳入下一次調(diào)用,但 Promise 本身是一個(gè)包裝過(guò)的數(shù)據(jù)結(jié)構(gòu),只有通過(guò) then 方法才能拿到返回值。所以如果是異步的情況,我們通常會(huì)把 reduce 改成普通 FOR 循環(huán)。

        如果遇到異步的情況,例如想做一系列串行的請(qǐng)求,是否可以更優(yōu)雅呢?本人在 Vite 源碼中找到了答案,在 Vite 源碼中有這么一段:

        await postBuildHooks.reduce(
          (queue, hook) => queue.then(() => hook(build as any)),
          Promise.resolve();
        )

        參考: github.com/vitejs/vite…[2]

        可以看出,這里對(duì) reducer 函數(shù)進(jìn)行了包裝,將 hook 的執(zhí)行放到 then 方法回調(diào)中,這樣就可以保證調(diào)用順序。按照這個(gè)思路,我們對(duì)上面 compose 方法稍微改動(dòng)一下,就可以得到一個(gè) asyncCompose

        /**
         * 給定參數(shù):[A, B, C, D]
         * 調(diào)用順序:Promise.resolve().then(res => D(res)).then(res => C(res)).then(res => B(res)).then(res => A(res))
         * 以上只是為了讓大家看清楚,簡(jiǎn)化之后如下:Promise.resolve().then(D).then(C).then(B).then(A)
         */

        const asyncCompose = (middlewares) => (initialValue) => middlewares.reduceRight(
            (queue, hook) => queue.then(res => hook(res)),
            Promise.resolve(initialValue)
        );

        4. 解構(gòu)賦值

        解構(gòu)賦值很方便,項(xiàng)目中經(jīng)常會(huì)用到,可以分為以下兩個(gè)場(chǎng)景:

        • 對(duì)象/數(shù)組的解構(gòu);
        • 函數(shù)參數(shù)解構(gòu);

        這里介紹四種常用技巧。

        1) 深度解構(gòu)

        大部分時(shí)候我們只會(huì)解構(gòu)一層,但實(shí)際上解構(gòu)賦值是可以深度解構(gòu)的:

        let obj = {
          name"dby",
          a: {
            b1
          }
        }
        const { a: { b } } = obj;
        console.log(b); // 1

        2) 解構(gòu)時(shí)使用別名

        假如后端返回的對(duì)象鍵名不是我們想要的,可以使用別名:

        const obj = {
          // 這個(gè)鍵名太長(zhǎng)了,我們希望把它換掉
          aaa_bbb_ccc: {
            name"dby",
            age12,
            sextrue
          }
        }
        const { aaa_bbb_ccc: user } = obj;
        console.log(user); // { name: "dby", age: 12, sex: true }

        3) 解構(gòu)時(shí)使用默認(rèn)值

        對(duì)象的解構(gòu)可以使用默認(rèn)值,默認(rèn)值生效的條件是,對(duì)象的屬性值嚴(yán)格等于 undefined

        fetchUserInfo()
          .then(({ aaa_bbb_ccc: user = {} }) => {
            // ...
          })

        以上三個(gè)特性可以結(jié)合使用

        4) 使用短路避免報(bào)錯(cuò)

        解構(gòu)賦值雖然好用,但是要注意解構(gòu)的對(duì)象不能為 undefined 、null ,否則會(huì)報(bào)錯(cuò),故要給被解構(gòu)的對(duì)象一個(gè)默認(rèn)值:

        const {a,b,c,d,e} = obj || {};

        5. 展開運(yùn)算符

        使用展開運(yùn)算符合并兩個(gè)數(shù)組或者兩個(gè)對(duì)象:

        const a = [1,2,3];
        const b = [1,5,6];
        // bad
        const c = a.concat(b);
        // good
        const c = [...new Set([...a,...b])];

        const obj1 = { a:1 };
        const obj2 = { b:1 };
        // bad
        const obj = Object.assign({}, obj1, obj2);
        // good
        const obj = { ...obj1, ...obj2 };

        這里要注意一個(gè)問(wèn)題,對(duì)象和數(shù)組合并雖然看上去都是 ... ,但是實(shí)際上是有區(qū)別的。

        ES2015 擴(kuò)展運(yùn)算符只規(guī)定了在數(shù)組函數(shù)參數(shù)中使用,但并沒(méi)有規(guī)定可以在對(duì)象中使用,并且是基于 for...of 的,因此被展開的只能是數(shù)組、字符串、Set、Map 等可迭代對(duì)象,假如將普通對(duì)象展開到數(shù)組就會(huì)報(bào)錯(cuò)。

        對(duì)象中的 ... 實(shí)際上是 ES2018 中的對(duì)象展開語(yǔ)法,相當(dāng)于 Object.assign

        babeljs.io/docs/en/bab…[3]

        6. 檢查屬性是否存在對(duì)象中

        可以使用 in 關(guān)鍵字檢查對(duì)象中是否存在某個(gè)屬性:

        const person = { name"dby"salary1000 };
        console.log('salary' in person); // true
        console.log('age' in person); // false

        但是 in 關(guān)鍵字其實(shí)并不安全,會(huì)把原型上的屬性也包括進(jìn)去,例如:

        "hasOwnProperty" in {}; // true
        "toString" in {}; // true

        所以推薦使用下面的方法進(jìn)行判斷:

        Object.prototype.hasOwnProperty.call(person, "salary"); // true

        不過(guò)上面這樣的問(wèn)題就是太長(zhǎng)了,每次使用都要這樣寫很麻煩。ECMA 有一個(gè)新的提案 Object.hasOwn() ,相當(dāng)于 Object.prototype.hasOwnProperty.call() 的別名:

        Object.hasOwn(person, "salary"); // true

        developer.mozilla.org/en-US/docs/…[4]

        需要注意這個(gè)語(yǔ)法存在兼容性問(wèn)題(Chrome > 92),不過(guò)只要正確配置 polyfill 就可以放心使用。

        7. 對(duì)象的遍歷

        項(xiàng)目中很多同事都會(huì)這樣寫:

        for (let key in obj) {
          if (Object.prototype.hasOwnProperty.call(obj, key)) {
            // ...
          }
        }

        吐槽:用 Object.keys 或者 Object.entries 轉(zhuǎn)成數(shù)組就可以用數(shù)組方法遍歷了,而且遍歷的是自身屬性,不會(huì)遍歷到原型上。

        Object.keys(obj).forEach(key => {
          // ...
        })

        Object.entries(obj).forEach(([key, val]) => {
          // ...
        })

        舉個(gè)例子,將對(duì)象的 key、value 拼接為查詢字符串:

        const _stringify = (obj) => Object.entries(obj).map(([key, val]) => `${key}=${val}`).join("&");

        _stringify({
            a1,
            b2,
            c"2333"
        });
        // 'a=1&b=2&c=2333'

        反駁:有時(shí)候不想遍歷整個(gè)對(duì)象,數(shù)組方法不能用 break 終止循環(huán)呀。

        吐槽:看來(lái)你對(duì)數(shù)組方法掌握還是不夠徹底,使用 find 方法找到符合條件的項(xiàng)就不會(huì)繼續(xù)遍歷。

        Object.keys(obj).find(key => key === "name");

        總結(jié):在開發(fā)中盡量不要寫 for 循環(huán),無(wú)論數(shù)組和對(duì)象。對(duì)象就通過(guò) Object.keys 、Object.values 、Object.entries 轉(zhuǎn)為數(shù)組進(jìn)行遍歷。這樣可以寫成 JS 表達(dá)式,充分利用函數(shù)式編程。

        8. 使用 includes 簡(jiǎn)化 if 判斷

        在項(xiàng)目中經(jīng)??吹竭@樣的代碼:

        if (a === 1 || a === 2 || a === 3 || a === 4) {
            // ...
        }

        可以使用 includes 簡(jiǎn)化代碼:

        if ([1234].includes(a)) {
            // ...
        }

        9. Promise 三點(diǎn)總結(jié)

        1) async/await 優(yōu)雅異常處理

        async 函數(shù)中,只要其中某個(gè) Promise 報(bào)錯(cuò),整個(gè) async 函數(shù)的執(zhí)行就中斷了,因此異常處理非常重要。但實(shí)際上 async 函數(shù)的異常處理非常麻煩,很多同事都不愿意寫。有沒(méi)有一種簡(jiǎn)單的方法呢?看到一個(gè) await-to-js 的 npm 包,可以優(yōu)雅處理 async 函數(shù)的異常,不需要手動(dòng)添加 try...catch 捕獲異常:

        import to from 'await-to-js';

        async function asyncFunctionWithThrow({
          const [err, user] = await to(UserModel.findById(1));
          if (!user) throw new Error('User not found');
        }

        www.npmjs.com/package/awa…[5]

        實(shí)際上就是在 await 前面返回的 Promise 封裝了一層,提前處理異常。源碼非常簡(jiǎn)單,本人自己也實(shí)現(xiàn)了下:

        function to(awaited{
          // 不管是不是 Promise 一律轉(zhuǎn)為 Promise
          const p = Promise.resolve(awaited);
          // await-to-js 采用 then...catch 的用法
          // 但實(shí)際上 then 方法第一個(gè)回調(diào)函數(shù)里面并不包含會(huì)拋出異常的代碼
          // 因此使用 then 方法第二個(gè)回調(diào)函數(shù)捕獲異常,不需要額外的 catch
          return p.then(
            res => {
              return [null, res];
            },
            err => {
              return [err, undefined];
            }
          )
        }

        2) Promise 作為狀態(tài)機(jī)

        看到有同事寫過(guò)這樣的代碼:

        function validateUserInfo(user{
          if (!userList.find(({ id }) => id === user.id)) {
            return {
              code-1,
              message"用戶未注冊(cè)"
            }
          }
          if (
            !userList.find(
              ({ username, password }) =>
                username === user.username &&
                password === user.password
            )
          ) {
            return {
              code-1,
              message"用戶名或密碼錯(cuò)誤"
            }
          }
          return {
            code0,
            message"登錄成功"
          }
        }

        觀察發(fā)現(xiàn)這邊其實(shí)就是兩個(gè)狀態(tài),然后還需要一個(gè)字段提示操作結(jié)果。這種情況下我們可以使用 Promise 。有人說(shuō)為啥咧,明明沒(méi)有異步邏輯啊。我們知道,Promise 其實(shí)就是一個(gè)狀態(tài)機(jī),即使不需要處理異步邏輯,我們也可以使用狀態(tài)機(jī)的特性:

        function validateUserInfo(user{
          if (!userList.find(({ id }) => id === user.id)) {
            return Promise.reject("用戶未注冊(cè)");
          }
          if (
            !userList.find(
              ({ username, password }) =>
                username === user.username &&
                password === user.password
            )
          ) {
            return Promise.reject("用戶名或密碼錯(cuò)誤");
          }
          return Promise.resolve("登錄成功");
        }

        // 使用如下
        validateUserInfo(userInfo)
          .then(res => {
            message.success(res);
          })
          .catch(err => {
            message.error(err);
          })

        明顯這樣代碼就變得非常優(yōu)雅了,但其實(shí)還可以更優(yōu)雅。我們知道 async 函數(shù)返回值是一個(gè) Promise 實(shí)例,因此下面兩個(gè)函數(shù)是等價(jià)的:

        // 普通函數(shù)返回一個(gè) Promsie.resolve 包裹的值
        const request = (x) => Promise.resolve(x);

        // async 函數(shù)返回一個(gè)值
        const request = async (x) => x;

        既然最后返回一個(gè) Promise ,為何不直接在函數(shù)前面加 async 修飾符呢。這樣成功的結(jié)果只要直接返回就行,不用 Promise.resolve 包裹:

        async function validateUserInfo(user{
          if (!userList.find(({ id }) => id === user.id)) {
            return Promise.reject("用戶未注冊(cè)");
          }
          if (
            !userList.find(
              ({ username, password }) =>
                username === user.username &&
                password === user.password
            )
          ) {
            return Promise.reject("用戶名或密碼錯(cuò)誤");
          }
          return "登錄成功";
        }

        對(duì) async 函數(shù)不熟悉的同學(xué),可以參考 阮一峰 ES6 教程[6]

        更進(jìn)一步,由于在 Promise 內(nèi)部拋出異常等同于被 reject ,因此我們可以使用 throw 語(yǔ)句替代 Promise.reject()

        async function validateUserInfo(user{
          if (!userList.find(({ id }) => id === user.id)) {
            throw "用戶未注冊(cè)";
          }
          if (
            !userList.find(
              ({ username, password }) =>
                username === user.username &&
                password === user.password
            )
          ) {
            throw "用戶名或密碼錯(cuò)誤";
          }
          return "登錄成功";
        }

        throw 語(yǔ)句的用法可以參考 MDN 文檔[7]

        3) Promise 兩點(diǎn)使用誤區(qū)

        不建議在 Promise 里面使用 try...catch,這樣即使 Promise 內(nèi)部報(bào)錯(cuò),狀態(tài)仍然是 fullfilled,會(huì)進(jìn)入 then 方法回調(diào),不會(huì)進(jìn)入 catch 方法回調(diào)。

        function request({
          return new Promise((resolve, reject) => {
            try {
              // ...
              resolve("ok");
            } catch(e) {
              console.log(e);
            }
          })
        }

        request()
          .then(res => {
            console.log("請(qǐng)求結(jié)果:", res);
          })
          .catch(err => {
            // 由于在 Promise 中使用了 try...catch
            // 因此即使 Promise 內(nèi)部報(bào)錯(cuò),也不會(huì)被 catch 捕捉到
            console.log(err);
          })

        Promise 內(nèi)部的異常,老老實(shí)實(shí)往外拋就行,讓 catch 方法來(lái)處理,符合單一職責(zé)原則

        不建議在 async 函數(shù)中,既不使用 await,也不使用 return,這樣就算內(nèi)部的 Promise reject 也無(wú)法捕捉到:

        async function handleFetchUser(userList{
          // 這里既沒(méi)有使用 await,也沒(méi)有使用 return
          Promise.all(userList.map(u => request(u)));
        }

        handleFetchUser(userList)
          .then(res => {
            // 由于沒(méi)有返回值,這里拿到的是 undefined
            console.log(res);
          })
          .catch(err => {
            // 即使 handleFetchUser 內(nèi)部的 Promise reject
            // async 函數(shù)返回的 Promise 仍然是 fullfilled
            // 此時(shí)仍然會(huì)進(jìn)入 then 方法回調(diào),無(wú)法被 catch 捕捉到
            console.log(err);
          })

        如果確實(shí)有這種需求,建議不要使用 async 函數(shù),直接改用普通函數(shù)即可

        10. 字符串小技巧

        1) 字符串不滿兩位補(bǔ)零

        這個(gè)需求在開發(fā)中挺常見(jiàn)。例如,調(diào)用 Date api 獲取到日期可能只有一位:

        let date = new Date().getDate(); // 3

        常規(guī)做法:

        if (data.toString().length == 1) {
            date = `0${date}`;
        }

        使用 String.prototype.slice

        // 不管幾位,都在前面拼接一個(gè) 0 ,然后截取最后兩位
        date = `0${date}`.slice(-2);

        使用 String.prototype.padStart

        // 當(dāng)字符串長(zhǎng)度小于第一個(gè)參數(shù)值,就在前面補(bǔ)第二個(gè)參數(shù)
        date = `${date}`.padStart(20);

        2) 千分位分隔符

        實(shí)現(xiàn)如下的需求:

        • 從后往前每三個(gè)數(shù)字前加一個(gè)逗號(hào)
        • 開頭不能加逗號(hào)

        這樣看起來(lái)非常符合 (?=p) 的規(guī)律,p 可以表示每三個(gè)數(shù)字,要添加逗號(hào)所處的位置正好是 (?=p) 匹配出來(lái)的位置。

        第一步,先嘗試把最后一個(gè)逗號(hào)弄出來(lái):

        "300000000".replace(/(?=\d{3}$)/",")
        // '300000,000'

        第二步,把所有逗號(hào)都弄出來(lái):

        "300000000".replace(/(?=(\d{3})+$)/g",")
        // ',300,000,000'

        使用括號(hào)把一個(gè) p 模式變成一個(gè)整體

        第三步,去掉首位的逗號(hào):

        "300000000".replace(/(?!^)(?=(\d{3})+$)/g",")
        // '300,000,000'

        3) 借用數(shù)組拼接字符串

        很多同學(xué)都知道 模板字符串 可以很方便地進(jìn)行字符串拼接,但是需要拼接較多參數(shù)的時(shí)候,這樣就顯得比較麻煩:

        // HH -> 23
        // mm -> 58
        // ss -> 32
        const timeString = `${HH}:${mm}:${ss}`;
        // scheme -> https://
        // host -> 10.3.71.108
        // port -> :8080
        const URLString = `${scheme}${host}${port}`

        實(shí)際上,拼接字符串,除了使用模板字符串的方式,還可以使用數(shù)組:

        const timeString = [HH, mm, ss].join(":");
        const URLString = [scheme, host, port].join("");

        順便一提,本人之前維護(hù)過(guò)一個(gè) jQuery 項(xiàng)目,就使用這種方式拼接 html 模板:

        const dataSource = ["dby""dm""233"];
        const template = dataSource.map(name => `<div>${name}</div>`).join("");

        4) 判斷字符串前綴、后綴

        判斷字符串前綴、后綴不要一言不合就使用正則表達(dá)式:

        const url = "https://bili98.cn";
        const isHTTPS = /^https:\/\//.test(url); // true

        const fileName = "main.py";
        const isPythonCode = /\.py$/.test(fileName); // true

        推薦使用 String.prototype.startsWithString.prototype.endsWith,語(yǔ)義性更好:

        const url = "https://bili98.cn";
        const isHTTPS = url.startsWith("https://"// true

        const fileName = "main.py";
        const isPythonCode = fileName.endsWith(".py"); // true

        參考

        我的代碼簡(jiǎn)潔之道[8]

        你會(huì)用ES6,那倒是用??![9]

        有個(gè)開發(fā)者總結(jié)這 15 優(yōu)雅的 JavaScript 個(gè)技巧[10]

        Node 社群



        我組建了一個(gè)氛圍特別好的 Node.js 社群,里面有很多 Node.js小伙伴,如果你對(duì)Node.js學(xué)習(xí)感興趣的話(后續(xù)有計(jì)劃也可以),我們可以一起進(jìn)行Node.js相關(guān)的交流、學(xué)習(xí)、共建。下方加 考拉 好友回復(fù)「Node」即可。



        如果你覺(jué)得這篇內(nèi)容對(duì)你有幫助,我想請(qǐng)你幫我2個(gè)小忙:

        1. 點(diǎn)個(gè)「在看」,讓更多人也能看到這篇文章
        2. 訂閱官方博客 www.inode.club 讓我們一起成長(zhǎng)

        點(diǎn)贊和在看就是最大的支持

        瀏覽 24
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            无码成人av | 三级片在线视频观看 | 偷拍美女洗澡网站 | 毛片导航 | 国产成人成人A片在线乱码软件 | xxxx中国一级片 | 婷婷五月狠狠 | 欧美亚洲国产日韩 | 五月婷婷中文版 | 大香蕉插插 |