Vmo前端數(shù)據(jù)模型設計

Vmo 是一個用于前端的數(shù)據(jù)模型兢交。解決前端接口訪問混亂,服務端數(shù)據(jù)請求方式不統(tǒng)一锭沟,數(shù)據(jù)返回結(jié)果不一致的微型框架抽兆。

Vmo 主要用于處理數(shù)據(jù)請求,數(shù)據(jù)模型管理族淮”韬欤可配合當前主流前端框架進行數(shù)據(jù)模型管理 Vue,React,Angular凭涂。

能夠有效處理以下問題:

  • 接口請求混亂,axios.get...隨處可見贴妻。
  • 數(shù)據(jù)管理混亂切油,請求到的數(shù)據(jù)結(jié)果用完即丟、拿到的數(shù)據(jù)直接放進Store名惩。
  • 數(shù)據(jù)可靠性弱澎胡,不能保證請求數(shù)據(jù)是否穩(wěn)定,字段是否多娩鹉、是否少攻谁。
  • Action方法混亂,Action中及存在同步對Store的修改弯予,又存在異步請求修改Store戚宦。
  • 代碼提示弱,請求到的數(shù)據(jù)無法使用TypeScript進行代碼提示锈嫩,只能定義 any 類型受楼。
  • 無效字段增多,人員變動呼寸,字段含義信息逐步丟失艳汽,新業(yè)務定義新字段。
  • 項目遷移繁重对雪,項目重構(gòu)時骚灸,對字段不理解,重構(gòu)過程功能點慌植、數(shù)據(jù)丟失甚牲。

背景介紹

隨著現(xiàn)有大前端的蓬勃發(fā)展,Vue蝶柿、React 等框架不斷流行丈钙,RN、Weex交汤、Electron 等使用 JS 開發(fā)客戶端應用的不斷發(fā)展雏赦,Taro、mpVue芙扎、CML 等新型小程序框架的不斷創(chuàng)新星岗。JavaScript 將變得更加流行與多樣,使用 JS 同構(gòu)各端項目將不再是夢戒洼。

JS 的靈活在賦予大家方便的同時也同樣存在著一些問題俏橘,同樣實現(xiàn)一個數(shù)據(jù)獲取到頁面渲染的簡單操作,可能就會有非常多的寫法圈浇。正常的寥掐,在 Vue 中靴寂,可能會直接這樣寫:

const methods = {
  /**
   * 獲得分類信息
   */
  async getBarData() {
    try {
      const { data } = await axios.get(url, params);

      return data;
    } catch (e) {
      console.error("something error", e);
    }
  }
};

這樣的做法在功能上講沒什么問題,但在新增一些其他動作后召耘,這樣的做法就變得非常難以管理百炬。

比如,需要在請求中加入一些關(guān)聯(lián)請求污它,需要獲取一個商品頁的列表剖踊,查詢參數(shù)包含,分頁參數(shù)(當前頁衫贬,查詢數(shù))德澈,分類 Id,搜索內(nèi)容祥山,排序方式,篩選項掉伏。

在執(zhí)行該請求時缝呕,發(fā)現(xiàn)分類 Id 也需要另外一個接口去獲取。于是代碼成了:

const params = {
  sort: -1,
  search: "",
  filter: "",
  page: {
    start: 1,
    number: 10
  }
};
const methods = {
  /**
   * 獲得商品列表
   */
  async getGoodsData() {
    try {
      const { data } = await axios.get(url.goodsType); // 獲取所有分類Id
      const { id: typeId } = data;
      const res = await axios.get(url.goods, { ...params, typeId }); // 獲取商品

      return res.data;
    } catch (e) {
      console.error("something error", e);
    }
  }
};

這樣看上去貌似是完成了這個業(yè)務斧散,但其實在業(yè)務不斷變化的環(huán)境下供常,這樣直接在組件中書寫接口請求是非常脆弱的。

比如以下問題:

  • 返回結(jié)果中鸡捐,有字段需要單獨處理后才能使用栈暇。比如:后端可能返回的一個數(shù)組是,隔開
  • 返回結(jié)果中,有字段在某種情況下缺失
  • 接口地址發(fā)生變動
  • 隨著業(yè)務變動箍镜,接口字段需要改動
  • 其他組件需要使用同樣這份數(shù)據(jù)源祈,但不能保證組件調(diào)用順序
  • 部分接口數(shù)據(jù)需要前端緩存
  • 接口存儲方式發(fā)生變化。比如:有網(wǎng)絡走接口色迂,沒網(wǎng)絡走 LocalStorage
  • 前端項目框架遷移香缺,接口不變。Vue 轉(zhuǎn) React歇僧?Vue 轉(zhuǎn)小程序图张?

為了讓讀者更容易理解我所說的痛點,我列舉了幾個反例場景來說明:

反例場景 1

const methods = {
  /**
   * 獲取過濾項信息
   */
  async getFilterInfo() {
    try {
      const { data: filterInfo } = await axios.get(url.goodsType); // 獲取所有分類Id
      // filterInfo.ids => "2,3,5234,342,412"
      filterInfo.ids = filterInfo.ids.map(id => id.split(","));

      return filterInfo;
    } catch (e) {
      console.error("something error", e);
    }
  }
};

在這個例子中诈悍,獲取過濾項信息中返回的結(jié)果信息假設為:

{
  "ids": "2,3,5234,342,412",
  ...
}

在數(shù)據(jù)解析中祸轮,就需要處理為前端接受的數(shù)組,類似的解析還有非常多侥钳。

也許現(xiàn)在看這段代碼無關(guān)痛癢适袜,但若每次調(diào)用這個接口都需要這樣處理,長期處理類似字段舷夺。甚至有很多開發(fā)者在一開始拿到這個字段都會暫時不去處理痪蝇,到用到的地方再處理鄙陡,每用一次處理一次。

那想想該是多么非常惡心的一件事情躏啰。

如果使用Vmo會在數(shù)據(jù)模型開始時趁矾,就使用load()來對數(shù)據(jù)做適配,拿到的數(shù)據(jù)能夠穩(wěn)定保證是我們所定義的那種類型给僵。

反例場景 2

// component1
// 需要使用 Goods 數(shù)據(jù)

const mounted = async () => {
  const goods = await this.getGoodsData();
  this.$store.commit("saveGoods", goods); // 在store中存儲

  this.goods = goods;
};

const methods = {
  /**
   * 獲得商品列表
   */
  async getGoodsData() {
    try {
      const { data } = await axios.get(url.goodsType); // 獲取所有分類Id
      const { id: typeId } = data;
      const res = await axios.get(url.goods, { ...params, typeId }); // 獲取商品

      return res.data;
    } catch (e) {
      console.error("something error", e);
    }
  }
};
// component2
// 也需要使用 Goods 數(shù)據(jù)

const mounted = async () => {
  const goods = this.$store.state.goods;

  this.goods = goods;
};

在這個例子中毫捣,簡單描述了兩個組件代碼(也許看上去很 low,但這種代碼確實存在)帝际,他們都會需要使用到商品數(shù)據(jù)蔓同。按照正常流程組件組件的加載流程可能是

component1->component2

這樣的順序加載,那么上面這段是可以正常運行的蹲诀。但假若業(yè)務要求斑粱,突然有一個component3要在兩個組件之前加載,并且也需要使用商品數(shù)據(jù)脯爪,那么對于組件的改動是非常頭疼的(因為實際業(yè)務中则北,可能你的數(shù)據(jù)加載要比這里復雜的多)。

反例場景 3

小明是一位前端開發(fā)人員痕慢,他與后端人員愉快的配合 3 個月完成了一款完整的 H5 SPA 應用尚揣。

業(yè)務發(fā)展的很快,又經(jīng)過數(shù)十次迭代掖举,他們的日活量很快達到了 5000快骗,但存在 H5 的普遍痛點,用戶留存率不高塔次。

于是產(chǎn)品決定使用小程序重構(gòu)當前項目方篮,UI、后端接口不用改變励负。

小明排期卻說要同樣 3 個月恭取,對此產(chǎn)品非常不理解,認為當初從無到有才用了 3 個月熄守,現(xiàn)在簡單遷移為什么也需要這么久蜈垮。

小明認為,雖然接口裕照、UI 不變攒发。但小程序與 H5 之間存在語法差異,為了考慮后續(xù) H5晋南、小程序多端迭代保持統(tǒng)一惠猿,需要花時間在技術(shù)建設上,抽離出公共部分负间,以減輕后續(xù)維護成本偶妖。

產(chǎn)品非常不理解問開發(fā)姜凄,如果不抽離會怎么樣,能快點嗎趾访?就簡單的復制過來呢态秧?于是小明為難之下,非常不滿的說那可能 2 周扼鞋。

Deal申鱼!就這么辦。

2 周開發(fā)云头,1 周測試捐友,成功上線!

第 4 周溃槐,隨著需求迭代匣砖,后端修改了一個接口的返回內(nèi)容,前后端聯(lián)動上線后發(fā)現(xiàn)之前的 H5 頁面出現(xiàn)大面積白屏昏滴。

事后定位發(fā)現(xiàn)猴鲫,由于后端修改導致 H5 數(shù)據(jù)解析出現(xiàn) JS 異常。項目組一致認為是由于前段人員考慮不夠全面造成的本次事故影涉,應該由小明承擔責任变隔。

5 個月后规伐,小明離職...

反例場景 4

在業(yè)務場景中假設有一段接口返回的 Json 如下:

{
  "c": "0",
  "m": "",
  "d": {
    "bannerList": [
      {
        "bannerId": "...",
        "bannerImg": "...",
        "bannerUrl": "...",
        "backendColor": null
      }
    ],
    "itemList": [
      {
        "obsSkuId": "...",
        "obsItemId": "...",
        "categoryId": null,
        "itemName": "...",
        "mainPic": "...",
        "imgUrlList": null,
        "suggestedPriceInCent": null,
        "priceInCent": null,
        "obsBrandId": "...",
        "width": null,
        "height": null,
        "length": null,
        "bcsPattern": null,
        "commissionPercent": null,
        "buyLink": "...",
        "phoneBuyLink": false,
        "storeIdList": null,
        "storeNameList": null,
        "storeNumber": null,
        "cityIdList": null,
        "provinceIdList": null,
        "obsModelId": null,
        "desc": null,
        "shelfImmediately": null,
        "status": 1,
        "brandName": "...",
        "modelPreviewImg": null,
        "similarModelIdList": null,
        "similarModelImgList": null,
        "relatedModelId": null,
        "relatedModelImg": null,
        "brandAddress": null,
        "promotionActivityVO": null,
        "tagIds": null,
        "tagGroups": [],
        "favored": false
      }
    ],
    "newsList": [
      {
        "id": "...",
        "img": "...",
        "title": "...",
        "desc": "...",
        "date": null,
        "order": null
      }
    ],
    "activityList": [],
    "itemListOrder": 1,
    "activityOrder": 4,
    "lessonOrder": 3,
    "newsOrder": 1,
    "designerOrder": 2,
    "comboListOrder": 2
  }
}

可以看到里面有非常多的字段蟹倾,雖然一些公司會嘗試使用類似 Yapi 等一些接口管理系統(tǒng)定義字段。

但隨著業(yè)務發(fā)展猖闪,版本快速迭代鲜棠,人員變動等因素影響,很有可能有一天

問前端人員培慌,前端人員說這個是后端傳過來就這樣豁陆,我不清楚。

問后端人員吵护,后端人員說這個是前端這么要的盒音,我不清楚。

這上面的字段公司上下沒有一個人能夠完全描述清楚其作用馅而。

這個時候如果該接口有業(yè)務變動祥诽,需要做字段調(diào)整,為了不產(chǎn)生未知的接口事故瓮恭,很可能就說提出不改變之前的接口內(nèi)容雄坪,新增一個接口字段實現(xiàn)功能的方案。

長此以往屯蹦,接口返回越來越多维哈,直到項目組花大力氣绳姨,重寫接口,前端重寫接口對接阔挠。

閃亮登場

基礎原型

先來看一段 Vmo 的代碼:

import { Vmo, Field } from "@vmojs/base";

interface IFilterValue {
  name: string;
  value: string;
}
export default class FilterModel extends Vmo {
  @Field
  public key: string;
  @Field
  public name: string;
  @Field
  public filters: IFilterValue[];

  public get firstFilter(): IFilterValue {
    return this.filters[0];
  }

  /**
   * 將數(shù)據(jù)適配\轉(zhuǎn)換為模型字段
   * @param data
   */
  protected load(data: any): this {
    data.filters = data.values;
    return super.load(data);
  }
}

const data = {
  key: "styles",
  name: "風格",
  values: [
    { name: "現(xiàn)代簡約", value: "1" },
    { name: "中式現(xiàn)代", value: "3" },
    { name: "歐式豪華", value: "4" }
  ]
};

const filterModel = new FilterModel(data); // Vmo通過load方法對數(shù)據(jù)做適配

通過以上方式就成功的將一組 json 數(shù)據(jù)實例化為一個FilterModel的數(shù)據(jù)模型飘庄。這將會為你帶來什么好處呢?

  • 適配來源數(shù)據(jù)谒亦,處理需要改變的字段類型竭宰,如string => array
  • 可靠的字段定義,即使接口字段變動份招,數(shù)據(jù)模型字段也不會變
  • TypeScript書寫提示切揭,一路回車不用說了,爽
  • 計算屬性锁摔,如firstFilter
  • 一次定義廓旬,終生受益。不認識\未使用的字段 say GoodBye
  • 如果項目需要遷移谐腰、后端同構(gòu)孕豹,拿來即用。

派生能力

在 Vmo 的設計中十气,數(shù)據(jù)模型只是基類励背,你同樣可以為數(shù)據(jù)模型賦予一些 "特殊能力" ,比如數(shù)據(jù)獲取砸西。

AxiosVmo 是基于 Vmo 派生的一個使用 axios 作為 Driver(驅(qū)動器) 實現(xiàn)數(shù)據(jù)獲取叶眉、存儲能力的簡單子類。

你同樣可以封裝自己的 Driver 芹枷,通過相同接口衅疙,實現(xiàn)多態(tài)方法,來做到在不同介質(zhì)上存儲和獲取數(shù)據(jù)鸳慈。比如 IndexDB,LocalStorage饱溢。

import { AxiosVmo } from "@vmojs/axios";
import { Field, mapValue } from "@vmojs/base";
import { USER_URL } from "../constants/Urls";
import FilterModel from "./FilterModel";

// 商品查詢參數(shù)
interface IGoodsQuery {
  id: number;
  search?: string;
  filter?: any;
}

interface IGoodsCollection {
  goods: GoodsModel[];
  goodsRows: number;
  filters: FilterModel[];
}

export default class GoodsModel extends AxiosVmo {
  protected static requestUrl: string = USER_URL;

  @Field
  public id: number;
  @Field
  public catId: number;
  @Field
  public aliasName: string;
  @Field
  public uid: number;
  @Field
  public userId: number;
  @Field
  public size: { x: number; y: number };

  /**
   * 返回GoodsModel 集合
   * @param query
   */
  public static async list(query: IGoodsQuery): Promise<GoodsModel[]> {
    const { items } = await this.fetch(query);
    return items.map(item => new GoodsModel(item));
  }

  /**
   * 返回GoodsModel 集合 及附屬信息
   * @param query
   */
  public static async listWithDetail(
    query: IGoodsQuery
  ): Promise<IGoodsCollection> {
    const { items, allRows, aggr } = await this.fetch(query);
    const goods = items.map(item => new GoodsModel(item));
    const filters = aggr.map(item => new FilterModel(item));
    return { goods, goodsRows: allRows, filters };
  }

  public static async fetch(query: IGoodsQuery): Promise<any> {
    const result = await this.driver.get(this.requestUrl, query);
    return result;
  }

  /**
   * 將請求的數(shù)據(jù)適配轉(zhuǎn)換為Model
   * @param data
   */
  protected load(data: any): this {
    data.catId = data.cat_id;
    data.aliasName = data.aliasname;
    data.userId = data.user_id;

    return super.load(data);
  }
}

(async () => {
  // 通過靜態(tài)方法創(chuàng)建 GoodsModel 集合
  const goods = await GoodsModel.listWithDetail({ id: 1 });
})();

像上面這樣的一個GoodsModel中,即定義了數(shù)據(jù)模型走芋,又定義了接口地址绩郎、請求方式與適配方法。 在返回結(jié)果中會創(chuàng)建出GoodsModel的數(shù)據(jù)模型集合翁逞。

最終打印的結(jié)果:

Action 與 Store

與以往前端思維不同肋杖,我大費周章的折騰這么一套出來。到底與原來一些常用框架思維中的 action 完成一切到底有什么不同呢熄攘?

請大家思考一個問題兽愤,action 的定義到底是什么呢?

最初 Flux 設計中, action 的設計就是為了改變 Store 中的 state浅萧,來達到狀態(tài)可控逐沙、流向明確的目的。

Redux 中的 action 甚至都是不支持異步操作的洼畅,后來有一些變相的方式實現(xiàn)異步 action吩案,后來又有了Redux-thunkRedux-saga這類異步中間件實現(xiàn)帝簇。

所以徘郭,最開始 action 的設計初衷是為了管理 Store 中狀態(tài),后來因為需要丧肴,開發(fā)者們賦予了 action 異步調(diào)用接口并改變 Store 狀態(tài)的能力残揉。

所以很多項目中,看到 action 經(jīng)常會類似這樣的方法芋浮,getUsers()調(diào)用接口獲取用戶數(shù)據(jù)抱环,addUser()添加用戶,removeUser()刪除用戶纸巷。

那么哪個方法會有異步請求呢镇草?哪個方法是直接操作 Store 而不會發(fā)生接口請求呢?

Vmo 希望能夠提供一種設計思路瘤旨,將數(shù)據(jù)模型梯啤、異步獲取與頁面狀態(tài) 分開管理維護。

將數(shù)據(jù)獲取存哲、適配處理因宇、關(guān)聯(lián)處理等復雜的數(shù)據(jù)操作,交給Vmo宏胯。

Vmo處理后的數(shù)據(jù)模型羽嫡,交給 Store本姥。作為最終的頁面狀態(tài)肩袍。

Mobx

Vmo還可以配合Mobx使用,完成數(shù)據(jù)模型與數(shù)據(jù)響應結(jié)合使用婚惫。

import { Vmo, Field } from "@vmojs/base";
import { observable } from "mobx";

interface IFilterValue {
  name: string;
  value: string;
}
export default class FilterModel extends Vmo {
  @Field
  @observable
  public key: string;
  @Field
  @observable
  public name: string;
  @Field
  @observable
  public filters: IFilterValue[];

  /**
   * 將數(shù)據(jù)適配\轉(zhuǎn)換為模型字段
   * @param data
   */
  protected load(data: any): this {
    data.filters = data.values;
    return super.load(data);
  }
}

總結(jié)

Vmo 強調(diào)的是一種設計

通過Vmo希望能夠幫助前端人員建立起對數(shù)據(jù)的重視氛赐,對數(shù)據(jù)模型的認知。對數(shù)據(jù)的操作處理交給Model先舷,恢復Store對前端狀態(tài)的設計初衷艰管。

Vmo 是我的第一個個人開源項目,凝聚了我對目前大前端數(shù)據(jù)處理的思考沉淀蒋川,源碼實現(xiàn)并不復雜牲芋,主要是想提供一種設計思路。

GitHub 中有完整的 Example,感興趣的讀者可以移步至項目地址查看缸浦。

項目地址

讓各位觀眾老爺見笑了夕冲,歡迎指點討論~

個人郵箱:wyy.xb@qq.com

個人微信:wangyinye (請注明來意及掘金)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市裂逐,隨后出現(xiàn)的幾起案子歹鱼,更是在濱河造成了極大的恐慌,老刑警劉巖卜高,帶你破解...
    沈念sama閱讀 222,104評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件弥姻,死亡現(xiàn)場離奇詭異,居然都是意外死亡掺涛,警方通過查閱死者的電腦和手機庭敦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來薪缆,“玉大人螺捐,你說我怎么就攤上這事“牵” “怎么了定血?”我有些...
    開封第一講書人閱讀 168,697評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長诞外。 經(jīng)常有香客問我澜沟,道長,這世上最難降的妖魔是什么峡谊? 我笑而不...
    開封第一講書人閱讀 59,836評論 1 298
  • 正文 為了忘掉前任茫虽,我火速辦了婚禮,結(jié)果婚禮上既们,老公的妹妹穿的比我還像新娘濒析。我一直安慰自己,他們只是感情好啥纸,可當我...
    茶點故事閱讀 68,851評論 6 397
  • 文/花漫 我一把揭開白布号杏。 她就那樣靜靜地躺著,像睡著了一般斯棒。 火紅的嫁衣襯著肌膚如雪盾致。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,441評論 1 310
  • 那天荣暮,我揣著相機與錄音庭惜,去河邊找鬼。 笑死穗酥,一個胖子當著我的面吹牛护赊,可吹牛的內(nèi)容都是我干的惠遏。 我是一名探鬼主播,決...
    沈念sama閱讀 40,992評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼骏啰,長吁一口氣:“原來是場噩夢啊……” “哼爽哎!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起器一,我...
    開封第一講書人閱讀 39,899評論 0 276
  • 序言:老撾萬榮一對情侶失蹤课锌,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后祈秕,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體渺贤,經(jīng)...
    沈念sama閱讀 46,457評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,529評論 3 341
  • 正文 我和宋清朗相戀三年请毛,在試婚紗的時候發(fā)現(xiàn)自己被綠了志鞍。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,664評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡方仿,死狀恐怖固棚,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情仙蚜,我是刑警寧澤此洲,帶...
    沈念sama閱讀 36,346評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站委粉,受9級特大地震影響呜师,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜贾节,卻給世界環(huán)境...
    茶點故事閱讀 42,025評論 3 334
  • 文/蒙蒙 一汁汗、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧栗涂,春花似錦知牌、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至暖释,卻和暖如春袭厂,著一層夾襖步出監(jiān)牢的瞬間墨吓,已是汗流浹背球匕。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留帖烘,地道東北人亮曹。 一個月前我還...
    沈念sama閱讀 49,081評論 3 377
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親照卦。 傳聞我的和親對象是個殘疾皇子式矫,可洞房花燭夜當晚...
    茶點故事閱讀 45,675評論 2 359

推薦閱讀更多精彩內(nèi)容

  • 國家電網(wǎng)公司企業(yè)標準(Q/GDW)- 面向?qū)ο蟮挠秒娦畔?shù)據(jù)交換協(xié)議 - 報批稿:20170802 前言: 排版 ...
    庭說閱讀 11,007評論 6 13
  • ## 框架和庫的區(qū)別?> 框架(framework):一套完整的軟件設計架構(gòu)和**解決方案**。> > 庫(lib...
    Rui_bdad閱讀 2,917評論 1 4
  • 本文首發(fā)于:CSDN「前端開發(fā)者說」公眾號故慈。CSDN「前端開發(fā)者說」公眾號(ID:bigfrontend),專注前...
    RachelQG閱讀 4,826評論 2 20
  • 一:什么是閉包框全?閉包的用處察绷? (1)閉包就是能夠讀取其他函數(shù)內(nèi)部變量的函數(shù)。在本質(zhì)上津辩,閉包就 是將函數(shù)內(nèi)部和函數(shù)外...
    xuguibin閱讀 9,636評論 1 52
  • 下班的時候喘沿,電梯間里闸度,人不多,不像平時人多擁擠蚜印,我低著頭看著手機筋岛。 耳邊傳來兩個姑娘的對話。 其中一個說晒哄,我今天去...
    顧紫閱讀 3,416評論 0 4