<address id="ttjl9"></address>

      <noframes id="ttjl9"><address id="ttjl9"><nobr id="ttjl9"></nobr></address>
      <form id="ttjl9"></form>
        <em id="ttjl9"><span id="ttjl9"></span></em>
        <address id="ttjl9"></address>

          <noframes id="ttjl9"><form id="ttjl9"></form>

          首頁

          JavaScript中的map()和forEach()有什么區別?

          seo達人

          閱讀之前

          基本上,在JavaScript中遍歷對象取決于對象是否可迭代。默認情況下,數組是可迭代的。map 和 forEach 包含在Array.prototype 中,因此我們無需考慮可迭代性。如果你想進一步學習,我推薦你看看什么是JavaScript中的可迭代對象!


          什么是map()和forEach()?

          map 和 forEach 是數組中的幫助器方法,可以輕松地在數組上循環。我們曾經像下面這樣循環遍歷一個數組,沒有任何輔助函數。


          var array = ['1', '2', '3'];

          for (var i = 0; i < array.length; i += 1) {

           console.log(Number(array[i]));

          }

          // 1

          // 2

          // 3

          自JavaScript時代開始以來,就一直存在 for 循環。它包含3個表達式:初始值,條件和最終表達式。


          這是循環數組的經典方法。從ECMAScript 5開始,新功能似乎使我們更加快樂。


          map

          map 的作用與 for 循環完全相同,只是 map 會創建一個新數組,其結果是在調用數組中的每個元素上調用提供的函數。


          它需要兩個參數:一個是稍后在調用 map 或 forEach 時調用的回調函數,另一個是回調函數被調用時使用的名為 thisArg 的上下文變量。


          const arr = ['1', '2', '3'];

          // 回調函數接受3個參數

          // 數組的當前值作為第一個參數

          // 當前值在數組中的位置作為第二個參數

          // 原始源數組作為第三個參數

          const cb = (str, i, origin) => {

           console.log(`${i}: ${Number(str)} / ${origin}`);

          };

          arr.map(cb);

          // 0: 1 / 1,2,3

          // 1: 2 / 1,2,3

          // 2: 3 / 1,2,3

          回調函數可以如下使用。


          arr.map((str) => { console.log(Number(str)); })

          map 的結果不等于原始數組。


          const arr = [1];

          const new_arr = arr.map(d => d);


          arr === new_arr; // false

          你還可以將對象作為 thisArg 傳遞到map。


          const obj = { name: 'Jane' };


          [1].map(function() {

           // { name: 'Jane' }

           console.dir(this);

          }, obj);


          [1].map(() => {

           // window

           console.dir(this);

          }, obj);

          對象 obj 成為 map 的 thisArg。但是箭頭回調函數無法將 obj 作為其 thisArg。


          這是因為箭頭函數與正常函數不同。


          forEach

          forEach 是數組的另一個循環函數,但 map 和 forEach 在使用中有所不同。map 和 forEach 可以使用兩個參數——回調函數和 thisArg,它們用作其 this。


          const arr = ['1', '2', '3'];

          // 回調函數接受3個參數

          // 數組的當前值作為第一個參數

          // 當前值在數組中的位置作為第二個參數

          // 原始源數組作為第三個參數

          const cb = (str, i, origin) => {

           console.log(`${i}: ${Number(str)} / ${origin}`);

          };

          arr.forEach(cb);

          // 0: 1 / 1,2,3

          // 1: 2 / 1,2,3

          // 2: 3 / 1,2,3

          那有什么不同?


          map 返回其原始數組的新數組,但是 forEach 卻沒有。但是它們都確保了原始對象的不變性。


          [1,2,3].map(d => d + 1); // [2, 3, 4];

          [1,2,3].forEach(d => d + 1); // undefined;

          如果更改數組內的值,forEach 不能確保數組的不變性。這個方法只有在你不接觸里面的任何值時,才能保證不變性。


          [{a: 1, b: 2}, {a: 10, b: 20}].forEach((obj) => obj.a += 1);

          // [{a: 2, b: 2}, {a: 11, b: 21}]

          // 數組已更改!

          何時使用map()和forEach()?

          由于它們之間的主要區別在于是否有返回值,所以你會希望使用 map 來制作一個新的數組,而使用 forEach 只是為了映射到數組上。


          這是一個簡單的例子。


          const people = [

           { name: 'Josh', whatCanDo: 'painting' },

           { name: 'Lay', whatCanDo: 'security' },

           { name: 'Ralph', whatCanDo: 'cleaning' }

          ];


          function makeWorkers(people) {

           return people.map((person) => {

             const { name, whatCanDo } = person;

             return <li key={name}>My name is {name}, I can do {whatCanDo}</li>

           });

          }


          <ul>makeWorkers(people)</ul>

          比如在React中,map 是非常常用的制作元素的方法,因為 map 在對原數組的數據進行操作后,會創建并返回一個新的數組。


          const mySubjectId = ['154', '773', '245'];


          function countSubjects(subjects) {

           let cnt = 0;

           

           subjects.forEach(subject => {

             if (mySubjectId.includes(subject.id)) {

               cnt += 1;

             }

           });

           

           return cnt;

          }


          countSubjects([

           { id: '223', teacher: 'Mark' },

           { id: '154', teacher: 'Linda' }

          ]);

          // 1

          另一方面,當你想對數據進行某些操作而不創建新數組時,forEach 很有用。順便說一句,可以使用 filter 重構示例。


          subjects.filter(subject => mySubjectId.includes(subject.id)).length;

          綜上所述,我建議你在創建一個新的數組時使用map,當你不需要制作一個新的數組,而是要對數據做一些事情時,就使用forEach。


          速度比較

          有些帖子提到 map 比 forEach 快。所以,我很好奇這是不是真的。我找到了這個對比結果。






          該代碼看起來非常相似,但結果卻相反。有些測試說 forEach 更快,有些說 map 更快。也許你在告訴自己 map/forEach 比其他的快,你可能是對的。老實說,我不確定。我認為在現代Web開發中,可讀性比 map 和 forEach 之間的速度重要得多。


          但可以肯定的是——兩者都比JavaScript內置的 for 循環慢。

          藍藍設計www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 、平面設計服務




          細聊Concent & Recoil , 探索react數據流的新開發模式

          seo達人

          序言

          之前發表了一篇文章 redux、mobx、concent特性大比拼, 看后生如何對局前輩,吸引了不少感興趣的小伙伴入群開始了解和使用 concent,并獲得了很多正向的反饋,實實在在的幫助他們提高了開發體驗,群里人數雖然還很少,但大家熱情高漲,技術討論氛圍濃厚,對很多新鮮技術都有保持一定的敏感度,如上個月開始逐漸被提及得越來越多的出自facebook的狀態管理方案 recoil,雖然還處于實驗狀態,但是相必大家已經私底下開始欲欲躍試了,畢竟出生名門,有fb背書,一定會大放異彩。


          不過當我體驗完recoil后,我對其中標榜的更新保持了懷疑態度,有一些誤導的嫌疑,這一點下文會單獨分析,是否屬于誤導讀者在讀完本文后自然可以得出結論,總之本文主要是分析Concent與Recoil的代碼風格差異性,并探討它們對我們將來的開發模式有何新的影響,以及思維上需要做什么樣的轉變。


          數據流方案之3大流派

          目前主流的數據流方案按形態都可以劃分以下這三類


          redux流派

          redux、和基于redux衍生的其他作品,以及類似redux思路的作品,代表作有dva、rematch等等。


          mobx流派

          借助definePerperty和Proxy完成數據劫持,從而達到響應式編程目的的代表,類mobx的作品也有不少,如dob等。


          Context流派

          這里的Context指的是react自帶的Context api,基于Context api打造的數據流方案通常主打輕量、易用、概覽少,代表作品有unstated、constate等,大多數作品的核心代碼可能不超過500行。


          到此我們看看Recoil應該屬于哪一類?很顯然按其特征屬于Context流派,那么我們上面說的主打輕量對

          Recoil并不適用了,打開其源碼庫發現代碼并不是幾百行完事的,所以基于Context api做得好用且強大就未必輕量,由此看出facebook對Recoil是有野心并給予厚望的。


          我們同時也看看Concent屬于哪一類呢?Concent在v2版本之后,重構數據追蹤機制,啟用了defineProperty和Proxy特性,得以讓react應用既保留了不可變的追求,又享受到了運行時依賴收集和ui更新的性能提升福利,既然啟用了defineProperty和Proxy,那么看起來Concent應該屬于mobx流派?


          事實上Concent屬于一種全新的流派,不依賴react的Context api,不破壞react組件本身的形態,保持追求不可變的哲學,僅在react自身的渲染調度機制之上建立一層邏輯層狀態分發調度機制,defineProperty和Proxy只是用于輔助收集實例和衍生數據對模塊數據的依賴,而修改數據入口還是setState(或基于setState封裝的dispatch, invoke, sync),讓Concent可以0入侵的接入react應用,真正的即插即用和無感知接入。


          即插即用的核心原理是,Concent自建了一個平行于react運行時的全局上下文,精心維護這模塊與實例之間的歸屬關系,同時接管了組件實例的更新入口setState,保留原始的setState為reactSetState,所有當用戶調用setState時,concent除了調用reactSetState更新當前實例ui,同時智能判斷提交的狀態是否也還有別的實例關心其變化,然后一并拿出來依次執行這些實例的reactSetState,進而達到了狀態全部同步的目的。




          Recoil初體驗

          我們以常用的counter來舉例,熟悉一下Recoil暴露的四個高頻使用的api


          atom,定義狀態

          selector, 定義派生數據

          useRecoilState,消費狀態

          useRecoilValue,消費派生數據

          定義狀態

          外部使用atom接口,定義一個key為num,初始值為0的狀態


          const numState = atom({

           key: "num",

           default: 0

          });

          定義派生數據

          外部使用selector接口,定義一個key為numx10,初始值是依賴numState再次計算而得到


          const numx10Val = selector({

           key: "numx10",

           get: ({ get }) => {

             const num = get(numState);

             return num * 10;

           }

          });

          定義異步的派生數據

          selector的get支持定義異步函數


          需要注意的點是,如果有依賴,必需先書寫好依賴在開始執行異步邏輯

          const delay = () => new Promise(r => setTimeout(r, 1000));


          const asyncNumx10Val = selector({

           key: "asyncNumx10",

           get: async ({ get }) => {

             // !!!這句話不能放在delay之下, selector需要同步的確定依賴

             const num = get(numState);

             await delay();

             return num * 10;

           }

          });

          消費狀態

          組件里使用useRecoilState接口,傳入想要獲去的狀態(由atom創建而得)


          const NumView = () => {

           const [num, setNum] = useRecoilState(numState);


           const add = ()=>setNum(num+1);


           return (

             <div>

               {num}<br/>

               <button onClick={add}>add</button>

             </div>

           );

          }

          消費派生數據

          組件里使用useRecoilValue接口,傳入想要獲去的派生數據(由selector創建而得),同步派生數據和異步派生數據,皆可通過此接口獲得


          const NumValView = () => {

           const numx10 = useRecoilValue(numx10Val);

           const asyncNumx10 = useRecoilValue(asyncNumx10Val);


           return (

             <div>

               numx10 :{numx10}<br/>

             </div>

           );

          };

          渲染它們查看結果

          暴露定義好的這兩個組件, 查看在線示例


          export default ()=>{

           return (

             <>

               <NumView />

               <NumValView />

             </>

           );

          };

          頂層節點包裹React.Suspense和RecoilRoot,前者用于配合異步計算函數需要,后者用于注入Recoil上下文


          const rootElement = document.getElementById("root");

          ReactDOM.render(

           <React.StrictMode>

             <React.Suspense fallback={<div>Loading...</div>}>

               <RecoilRoot>

                 <Demo />

               </RecoilRoot>

             </React.Suspense>

           </React.StrictMode>,

           rootElement

          );



          Concent初體驗

          如果讀過concent文檔(還在持續建設中...),可能部分人會認為api太多,難于記住,其實大部分都是可選的語法糖,我們以counter為例,只需要使用到以下兩個api即可


          run,定義模塊狀態(必需)、模塊計算(可選)、模塊觀察(可選)

          運行run接口后,會生成一份concent全局上下文

          setState,修改狀態

          定義狀態&修改狀態

          以下示例我們先脫離ui,直接完成定義狀態&修改狀態的目的


          import { run, setState, getState } from "concent";


          run({

           counter: {// 聲明一個counter模塊

             state: { num: 1 }, // 定義狀態

           }

          });


          console.log(getState('counter').num);// log: 1

          setState('counter', {num:10});// 修改counter模塊的num值為10

          console.log(getState('counter').num);// log: 10

          我們可以看到,此處和redux很類似,需要定義一個單一的狀態樹,同時第一層key就引導用戶將數據模塊化管理起來.


          引入reducer

          上述示例中我們直接掉一個呢setState修改數據,但是真實的情況是數據落地前有很多同步的或者異步的業務邏輯操作,所以我們對模塊填在reducer定義,用來聲明修改數據的方法集合。


          import { run, dispatch, getState } from "concent";


          const delay = () => new Promise(r => setTimeout(r, 1000));


          const state = () => ({ num: 1 });// 狀態聲明

          const reducer = {// reducer聲明

           inc(payload, moduleState) {

             return { num: moduleState.num + 1 };

           },

           async asyncInc(payload, moduleState) {

             await delay();

             return { num: moduleState.num + 1 };

           }

          };


          run({

           counter: { state, reducer }

          });

          然后我們用dispatch來觸發修改狀態的方法


          因dispatch會返回一個Promise,所以我們需要用一個async 包裹起來執行代碼

          import { dispatch } from "concent";


          (async ()=>{

           console.log(getState("counter").num);// log 1

           await dispatch("counter/inc");// 同步修改

           console.log(getState("counter").num);// log 2

           await dispatch("counter/asyncInc");// 異步修改

           console.log(getState("counter").num);// log 3

          })()

          注意dispatch調用時基于字符串匹配方式,之所以保留這樣的調用方式是為了照顧需要動態調用的場景,其實更推薦的寫法是


          import { dispatch } from "concent";


          (async ()=>{

           console.log(getState("counter").num);// log 1

           await dispatch(reducer.inc);// 同步修改

           console.log(getState("counter").num);// log 2

           await dispatch(reducer.asyncInc);// 異步修改

           console.log(getState("counter").num);// log 3

          })()

          接入react

          上述示例主要演示了如何定義狀態和修改狀態,那么接下來我們需要用到以下兩個api來幫助react組件生成實例上下文(等同于與vue 3 setup里提到的渲染上下文),以及獲得消費concent模塊數據的能力


          register, 注冊類組件為concent組件

          useConcent, 注冊函數組件為concent組件

          import { register, useConcent } from "concent";


          @register("counter")

          class ClsComp extends React.Component {

           changeNum = () => this.setState({ num: 10 })

           render() {

             return (

               <div>

                 <h1>class comp: {this.state.num}</h1>

                 <button onClick={this.changeNum}>changeNum</button>

               </div>

             );

           }

          }


          function FnComp() {

           const { state, setState } = useConcent("counter");

           const changeNum = () => setState({ num: 20 });

           

           return (

             <div>

               <h1>fn comp: {state.num}</h1>

               <button onClick={changeNum}>changeNum</button>

             </div>

           );

          }

          注意到兩種寫法區別很小,除了組件的定義方式不一樣,其實渲染邏輯和數據來源都一模一樣。


          渲染它們查看結果

          在線示例


          const rootElement = document.getElementById("root");

          ReactDOM.render(

           <React.StrictMode>

             <div>

               <ClsComp />

               <FnComp />

             </div>

           </React.StrictMode>,

           rootElement

          );



          對比Recoil,我們發現沒有頂層并沒有Provider或者Root類似的組件包裹,react組件就已接入concent,做到真正的即插即用和無感知接入,同時api保留為與react一致的寫法。


          組件調用reducer

          concent為每一個組件實例都生成了實例上下文,方便用戶直接通過ctx.mr調用reducer方法


          mr 為 moduleReducer的簡寫,直接書寫為ctx.moduleReducer也是合法的

          //  --------- 對于類組件 -----------

          changeNum = () => this.setState({ num: 10 })

          // ===> 修改為

          changeNum = () => this.ctx.mr.inc(10);// or this.ctx.mr.asynCtx()


          //  --------- 對于函數組件 -----------

          const { state, mr } = useConcent("counter");// useConcent 返回的就是ctx

          const changeNum = () => mr.inc(20);// or ctx.mr.asynCtx()

          異步計算函數

          run接口里支持擴展computed屬性,即讓用戶定義一堆衍生數據的計算函數集合,它們可以是同步的也可以是異步的,同時支持一個函數用另一個函數的輸出作為輸入來做二次計算,計算的輸入依賴是自動收集到的。


          const computed = {// 定義計算函數集合

           numx10({ num }) {

             return num * 10;

           },

           // n:newState, o:oldState, f:fnCtx

           // 結構出num,表示當前計算依賴是num,僅當num發生變化時觸發此函數重計算

           async numx10_2({ num }, o, f) {

             // 必需調用setInitialVal給numx10_2一個初始值,

             // 該函數僅在初次computed觸發時執行一次

             f.setInitialVal(num * 55);

             await delay();

             return num * 100;

           },

           async numx10_3({ num }, o, f) {

             f.setInitialVal(num * 1);

             await delay();

             // 使用numx10_2再次計算

             const ret = num * f.cuVal.numx10_2;

             if (ret % 40000 === 0) throw new Error("-->mock error");

             return ret;

           }

          }


          // 配置到counter模塊

          run({

           counter: { state, reducer, computed }

          });

          上述計算函數里,我們刻意讓numx10_3在某個時候報錯,對于此錯誤,我們可以在run接口的第二位options配置里定義errorHandler來捕捉。


          run({/**storeConfig*/}, {

             errorHandler: (err)=>{

                 alert(err.message);

             }

          })

          當然更好的做法,利用concent-plugin-async-computed-status插件來完成對所有模塊計算函數執行狀態的統一管理。


          import cuStatusPlugin from "concent-plugin-async-computed-status";


          run(

           {/**storeConfig*/},

           {

             errorHandler: err => {

               console.error('errorHandler ', err);

               // alert(err.message);

             },

             plugins: [cuStatusPlugin], // 配置異步計算函數執行狀態管理插件

           }

          );

          該插件會自動向concent配置一個cuStatus模塊,方便組件連接到它,消費相關計算函數的執行狀態數據


          function Test() {

           const { moduleComputed, connectedState, setState, state, ccUniqueKey } = useConcent({

             module: "counter",// 屬于counter模塊,狀態直接從state獲得

             connect: ["cuStatus"],// 連接到cuStatus模塊,狀態從connectedState.{$moduleName}獲得

           });

           const changeNum = () => setState({ num: state.num + 1 });

           

           // 獲得counter模塊的計算函數執行狀態

           const counterCuStatus = connectedState.cuStatus.counter;

           // 當然,可以更細粒度的獲得指定結算函數的執行狀態

           // const {['counter/numx10_2']:num1Status, ['counter/numx10_3']: num2Status} = connectedState.cuStatus;


           return (

             <div>

               {state.num}

               <br />

               {counterCuStatus.done ? moduleComputed.numx10 : 'computing'}

               {/** 此處拿到錯誤可以用于渲染,當然也拋出去 */}

               {/** 讓ErrorBoundary之類的組件捕捉并渲染降級頁面 */}

               {counterCuStatus.err ? counterCuStatus.err.message : ''}

               <br />

               {moduleComputed.numx10_2}

               <br />

               {moduleComputed.numx10_3}

               <br />

               <button onClick={changeNum}>changeNum</button>

             </div>

           );

          }

          ![]https://raw.githubusercontent...


          查看在線示例


          更新

          開篇我說對Recoli提到的更新保持了懷疑態度,有一些誤導的嫌疑,此處我們將揭開疑團


          大家知道hook使用規則是不能寫在條件控制語句里的,這意味著下面語句是不允許的


          const NumView = () => {

           const [show, setShow] = useState(true);

           if(show){// error

             const [num, setNum] = useRecoilState(numState);

           }

          }

          所以用戶如果ui渲染里如果某個狀態用不到此數據時,某處改變了num值依然會觸發NumView重渲染,但是concent的實例上下文里取出來的state和moduleComputed是一個Proxy對象,是在實時的收集每一輪渲染所需要的依賴,這才是真正意義上的按需渲染和更新。


          const NumView = () => {

           const [show, setShow] = useState(true);

           const {state} = useConcent('counter');

           // show為true時,當前實例的渲染對state.num的渲染有依賴

           return {show ? <h1>{state.num}</h1> : 'nothing'}

          }



          點我查看代碼示例


          當然如果用戶對num值有ui渲染完畢后,有發生改變時需要做其他事的需求,類似useEffect的效果,concent也支持用戶將其抽到setup里,定義effect來完成此場景訴求,相比useEffect,setup里的ctx.effect只需定義一次,同時只需傳遞key名稱,concent會自動對比前一刻和當前刻的值來決定是否要觸發副作用函數。


          conset setup = (ctx)=>{

           ctx.effect(()=>{

             console.log('do something when num changed');

             return ()=>console.log('clear up');

           }, ['num'])

          }


          function Test1(){

           useConcent({module:'cunter', setup});

           return <h1>for setup<h1/>

          }

          更多關于effect與useEffect請查看此文


          current mode

          關于concent是否支持current mode這個疑問呢,這里先說答案,concent是100%完全支持的,或者進一步說,所有狀態管理工具,最終觸發的都是setState或forceUpdate,我們只要在渲染過程中不要寫具有任何副作用的代碼,讓相同的狀態輸入得到的渲染結果冪,即是在current mode下運行安全的代碼。


          current mode只是對我們的代碼提出了更苛刻的要求。


          // bad

          function Test(){

            track.upload('renderTrigger');// 上報渲染觸發事件

            return <h1>bad case</h1>

          }


          // good

          function Test(){

            useEffect(()=>{

               // 就算僅執行了一次setState, current mode下該組件可能會重復渲染,

               // 但react內部會保證該副作用只觸發一次

               track.upload('renderTrigger');

            })

            return <h1>bad case</h1>

          }

          我們首先要理解current mode原理是因為fiber架構模擬出了和整個渲染堆棧(即fiber node上存儲的信息),得以有機會讓react自己以組件為單位調度組件的渲染過程,可以懸停并再次進入渲染,安排優先級高的先渲染,重度渲染的組件會切片為多個時間段反復渲染,而concent的上下文本身是獨立于react存在的(接入concent不需要再頂層包裹任何Provider), 只負責處理業務生成新的數據,然后按需派發給對應的實例(實例的狀態本身是一個個孤島,concent只負責同步建立起了依賴的store的數據),之后就是react自己的調度流程,修改狀態的函數并不會因為組件反復重入而多次執行(這點需要我們遵循不該在渲染過程中書寫包含有副作用的代碼原則),react僅僅是調度組件的渲染時機,而組件的中斷和重入針對也是這個渲染過程。


          所以同樣的,對于concent


          const setup = (ctx)=>{

           ctx.effect(()=>{

              // effect是對useEffect的封裝,

              // 同樣在current mode下該副作用也只觸發一次(由react保證)

               track.upload('renderTrigger');

           });

          }


          // good

          function Test2(){

            useConcent({setup})

            return <h1>good case</h1>

          }

          同樣的,依賴收集在current mode模式下,重復渲染僅僅是導致觸發了多次收集,只要狀態輸入一樣,渲染結果冪等,收集到的依賴結果也是冪等的。


          // 假設這是一個渲染很耗時的組件,在current mode模式下可能會被中斷渲染

          function HeavyComp(){

           const { state } = useConcent({module:'counter'});// 屬于counter模塊


          // 這里讀取了num 和 numBig兩個值,收集到了依賴

          // 即當僅當counter模塊的num、numBig的發生變化時,才觸發其重渲染(最終還是調用setState)

          // 而counter模塊的其他值發生變化時,不會觸發該實例的setState

           return (

             <div>num: {state.num} numBig: {state.numBig}</div>

           );

          }

          最后我們可以梳理一下,hook本身是支持把邏輯剝離到用的自定義hook(無ui返回的函數),而其他狀態管理也只是多做了一層工作,引導用戶把邏輯剝離到它們的規則之下,最終還是把業務處理數據交回給react組件調用其setState或forceUpdate觸發重渲染,current mode的引入并不會對現有的狀態管理或者新生的狀態管理方案有任何影響,僅僅是對用戶的ui代碼提出了更高的要求,以免因為current mode引發難以排除的bug


          為此react還特別提供了React.Strict組件來故意觸發雙調用機制, https://reactjs.org/docs/stri... 以引導用戶書寫更符合規范的react代碼,以便適配將來提供的current mode。

          react所有新特性其實都是被fiber激活了,有了fiber架構,衍生出了hook、time slicing、suspense以及將來的Concurrent Mode,class組件和function組件都可以在Concurrent Mode下安全工作,只要遵循規范即可。


          摘取自: https://reactjs.org/docs/stri...


          Strict mode can’t automatically detect side effects for you, but it can help you spot them by making them a little more deterministic. This is done by intentionally double-invoking the following functions:


          Class component constructor, render, and shouldComponentUpdate methods

          Class component static getDerivedStateFromProps method

          Function component bodies

          State updater functions (the first argument to setState)

          Functions passed to useState, useMemo, or useReducer

          所以呢,React.Strict其實為了引導用戶寫能夠在Concurrent Mode里運行的代碼而提供的輔助api,先讓用戶慢慢習慣這些限制,循序漸進一步一步來,最后再推出Concurrent Mode。


          結語

          Recoil推崇狀態和派生數據更細粒度控制,寫法上demo看起來簡單,實際上代碼規模大之后依然很繁瑣。


          // 定義狀態

          const numState = atom({key:'num', default:0});

          const numBigState = atom({key:'numBig', default:100});

          // 定義衍生數據

          const numx2Val = selector({

           key: "numx2",

           get: ({ get }) => get(numState) * 2,

          });

          const numBigx2Val = selector({

           key: "numBigx2",

           get: ({ get }) => get(numBigState) * 2,

          });

          const numSumBigVal = selector({

           key: "numSumBig",

           get: ({ get }) => get(numState) + get(numBigState),

          });


          // ---> ui處消費狀態或衍生數據

          const [num] = useRecoilState(numState);

          const [numBig] = useRecoilState(numBigState);

          const numx2 = useRecoilValue(numx2Val);

          const numBigx2 = useRecoilValue(numBigx2Val);

          const numSumBig = useRecoilValue(numSumBigVal);

          Concent遵循redux單一狀態樹的本質,推崇模塊化管理數據以及派生數據,同時依靠Proxy能力完成了運行時依賴收集和追求不可變的完美整合。


          run({

           counter: {// 聲明一個counter模塊

             state: { num: 1, numBig: 100 }, // 定義狀態

             computed:{// 定義計算,參數列表里解構具體的狀態時確定了依賴

                numx2: ({num})=> num * 2,

                numBigx2: ({numBig})=> numBig * 2,

                numSumBig: ({num, numBig})=> num + numBig,

              }

           },

          });


          // ---> ui處消費狀態或衍生數據,在ui處結構了才產生依賴

          const { state, moduleComputed, setState } = useConcent('counter')

          const { numx2, numBigx2, numSumBig} = moduleComputed;

          const { num, numBig } = state;

          所以你將獲得:


          運行時的依賴收集 ,同時也遵循react不可變的原則

          一切皆函數(state, reducer, computed, watch, event...),能獲得更友好的ts支持

          支持中間件和插件機制,很容易兼容redux生態

          同時支持集中與分形模塊配置,同步與異步模塊加載,對大型工程的彈性重構過程更加友好


          藍藍設計www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 、平面設計服務



          如何使JavaScript休眠或等待

          seo達人

          JavaScript不具有 sleep() 函數,該函數會導致代碼在恢復執行之前等待指定的時間段。如果需要JavaScript等待,該怎么做呢?


          假設您想將三則消息記錄到Javascript控制臺,每條消息之間要延遲一秒鐘。JavaScript中沒有 sleep() 方法,所以你可以嘗試使用下一個最好的方法 setTimeout()。


          不幸的是,setTimeout() 不能像你期望的那樣正常工作,這取決于你如何使用它。你可能已經在JavaScript循環中的某個點上試過了,看到 setTimeout() 似乎根本不起作用。


          問題的產生是由于將 setTimeout() 誤解為 sleep() 函數,而實際上它是按照自己的一套規則工作的。


          在本文中,我將解釋如何使用 setTimeout(),包括如何使用它來制作一個睡眠函數,使JavaScript暫停執行并在連續的代碼行之間等待。


          瀏覽一下 setTimeout() 的文檔,它似乎需要一個 "延遲 "參數,以毫秒為單位。


          回到原始問題,您嘗試調用 setTimeout(1000) 在兩次調用 console.log() 函數之間等待1秒。


          不幸的是 setTimeout() 不能這樣工作:


          setTimeout(1000)

          console.log(1)

          setTimeout(1000)

          console.log(2)

          setTimeout(1000)

          console.log(3)


          for (let i = 0; i <= 3; i++) {

           setTimeout(1000)

           console.log(`#${i}`)

          }

          這段代碼的結果完全沒有延遲,就像 setTimeout() 不存在一樣。


          回顧文檔,你會發現問題在于實際上第一個參數應該是函數調用,而不是延遲。畢竟,setTimeout() 實際上不是 sleep() 方法。


          你重寫代碼以將回調函數作為第一個參數并將必需的延遲作為第二個參數:


          setTimeout(() => console.log(1), 1000)

          setTimeout(() => console.log(2), 1000)

          setTimeout(() => console.log(3), 1000)


          for (let i = 0; i <= 3; i++) {

           setTimeout(() => console.log(`#${i}`), 1000)

          }

          這樣一來,三個console.log的日志信息在經過1000ms(1秒)的單次延時后,會一起顯示,而不是每次重復調用之間延時1秒的理想效果。


          在討論如何解決此問題之前,讓我們更詳細地研究一下 setTimeout() 函數。


          檢查setTimeout ()

          你可能已經注意到上面第二個代碼片段中使用了箭頭函數。這些是必需的,因為你需要將匿名回調函數傳遞給 setTimeout(),該函數將在超時后運行要執行的代碼。


          在匿名函數中,你可以指定在超時時間后執行的任意代碼:


          // 使用箭頭語法的匿名回調函數。

          setTimeout(() => console.log("你好!"), 1000)

          // 這等同于使用function關鍵字

          setTimeout(function() { console.log("你好!") }, 1000)

          理論上,你可以只傳遞函數作為第一個參數,回調函數的參數作為剩余的參數,但對我來說,這似乎從來沒有正確的工作:


          // 應該能用,但不能用

          setTimeout(console.log, 1000, "你好")

          人們使用字符串解決此問題,但是不建議這樣做。從字符串執行JavaScript具有安全隱患,因為任何不當行為者都可以運行作為字符串注入的任意代碼。


          // 應該沒用,但確實有用

          setTimeout(`console.log("你好")`, 1000)

          那么,為什么在我們的第一組代碼示例中 setTimeout() 失敗?好像我們在正確使用它,每次都重復了1000ms的延遲。


          原因是 setTimeout() 作為同步代碼執行,并且對 setTimeout() 的多次調用均同時運行。每次調用 setTimeout() 都會創建異步代碼,該代碼將在給定延遲后稍后執行。由于代碼段中的每個延遲都是相同的(1000毫秒),因此所有排隊的代碼將在1秒鐘的單個延遲后同時運行。


          如前所述,setTimeout() 實際上不是 sleep() 函數,取而代之的是,它只是將異步代碼排入隊列以供以后執行。幸運的是,可以使用 setTimeout() 在JavaScript中創建自己的 sleep() 函數。


          如何編寫sleep函數

          通過Promises,async 和 await 的功能,您可以編寫一個 sleep() 函數,該函數將按預期運行。


          但是,你只能從 async 函數中調用此自定義 sleep() 函數,并且需要將其與 await 關鍵字一起使用。


          這段代碼演示了如何編寫一個 sleep() 函數:


          const sleep = (delay) => new Promise((resolve) => setTimeout(resolve, delay))


          const repeatedGreetings = async () => {

           await sleep(1000)

           console.log(1)

           await sleep(1000)

           console.log(2)

           await sleep(1000)

           console.log(3)

          }

          repeatedGreetings()

          此JavaScript sleep() 函數的功能與您預期的完全一樣,因為 await 導致代碼的同步執行暫停,直到Promise被解決為止。


          一個簡單的選擇

          另外,你可以在第一次調用 setTimeout() 時指定增加的超時時間。


          以下代碼等效于上一個示例:


          setTimeout(() => console.log(1), 1000)

          setTimeout(() => console.log(2), 2000)

          setTimeout(() => console.log(3), 3000)

          使用增加超時是可行的,因為代碼是同時執行的,所以指定的回調函數將在同步代碼執行的1、2和3秒后執行。


          它會循環運行嗎?

          如你所料,以上兩種暫停JavaScript執行的選項都可以在循環中正常工作。讓我們看兩個簡單的例子。


          這是使用自定義 sleep() 函數的代碼段:


          const sleep = (delay) => new Promise((resolve) => setTimeout(resolve, delay))


          async function repeatGreetingsLoop() {

           for (let i = 0; i <= 5; i++) {

               await sleep(1000)

             console.log(`Hello #${i}`)

             }

          }

          repeatGreetingsLoop()

          這是一個簡單的使用增加超時的代碼片段:


          for (let i = 0; i <= 5; i++) {

           setTimeout(() => console.log(`Hello #${i}`), 1000 * i)

          }

          我更喜歡后一種語法,特別是在循環中使用。


          總結

          JavaScript可能沒有 sleep() 或 wait() 函數,但是使用內置的 setTimeout() 函數很容易創建一個JavaScript,只要你謹慎使用它即可。


          就其本身而言,setTimeout() 不能用作 sleep() 函數,但是你可以使用 async 和 await 創建自定義JavaScript sleep() 函數。


          采用不同的方法,可以將交錯的(增加的)超時傳遞給 setTimeout() 來模擬 sleep() 函數。之所以可行,是因為所有對setTimeout() 的調用都是同步執行的,就像JavaScript通常一樣。


          希望這可以幫助你在代碼中引入一些延遲——僅使用原始JavaScript,而無需外部庫或框架。


          藍藍設計www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 、平面設計服務

          5 個 JS 數組技巧可提高你的開發技能

          seo達人

          1. 隨機排列

          在開發者,有時候我們需要對數組的順序進行重新的洗牌。 在 JS 中并沒有提供數組隨機排序的方法,這里提供一個隨機排序的方法:


          function shuffle(arr) {

           var i, j, temp;

           for (i = arr.length - 1; i > 0; i--) {

             j = Math.floor(Math.random() * (i + 1));

             temp = arr[i];

             arr[i] = arr[j];

             arr[j] = temp;

           }

           return arr;

          }

          2. 唯一值

          在開發者,我們經常需要過濾重復的值,這里提供幾種方式來過濾數組的重復值。


          使用 Set 對象

          使用 Set() 函數,此函數可與單個值數組一起使用。對于數組中嵌套的對象值而言,不是一個好的選擇。


          const numArray = [1,2,3,4,2,3,4,5,1,1,2,3,3,4,5,6,7,8,2,4,6];


          // 使用 Array.from 方法

          Array.from(new Set(numArray));


          // 使用展開方式

          [...new Set(numArray)]

          使用 Array.filter

          使用 filter 方法,我們可以對元素是對象的進行過濾。


          const data = [

           {id: 1, name: 'Lemon'},

           {id: 2, name: 'Mint'},

           {id: 3, name: 'Mango'},

           {id: 4, name: 'Apple'},

           {id: 5, name: 'Lemon'},

           {id: 6, name: 'Mint'},

           {id: 7, name: 'Mango'},

           {id: 8, name: 'Apple'},

          ]


          function findUnique(data) {

           return data.filter((value, index, array) => {

             if (array.findIndex(item => item.name === value.name) === index) {

               return value;

             }

           })

          }

          3. 使用 loadsh 的 lodash 方法

          import {uniqBy} from 'lodash'


          const data = [

           {id: 1, name: 'Lemon'},

           {id: 2, name: 'Mint'},

           {id: 3, name: 'Mango'},

           {id: 4, name: 'Apple'},

           {id: 5, name: 'Lemon'},

           {id: 6, name: 'Mint'},

           {id: 7, name: 'Mango'},

           {id: 8, name: 'Apple'},

          ]


          function findUnique(data) {

           return uniqBy(data, e => {

                 return e.name

             })

          }

          3. 按屬性對 對象數組 進行排序

          我們知道 JS 數組中的 sort 方法是按字典順序進行排序的,所以對于字符串類, 該方法是可以很好的正常工作,但對于數據元素是對象類型,就不太好使了,這里我們需要自定義一個排序方法。


          在比較函數中,我們將根據以下條件返回值:


          小于0:A 在 B 之前

          大于0 :B 在 A 之前

          等于0 :A 和 B 彼此保持不變

          const data = [

           {id: 1, name: 'Lemon', type: 'fruit'},

           {id: 2, name: 'Mint', type: 'vegetable'},

           {id: 3, name: 'Mango', type: 'grain'},

           {id: 4, name: 'Apple', type: 'fruit'},

           {id: 5, name: 'Lemon', type: 'vegetable'},

           {id: 6, name: 'Mint', type: 'fruit'},

           {id: 7, name: 'Mango', type: 'fruit'},

           {id: 8, name: 'Apple', type: 'grain'},

          ]


          function compare(a, b) {

           // Use toLowerCase() to ignore character casing

           const typeA = a.type.toLowerCase();

           const typeB = b.type.toLowerCase();


           let comparison = 0;

           if (typeA > typeB) {

             comparison = 1;

           } else if (typeA < typeB) {

             comparison = -1;

           }

           return comparison;

          }


          data.sort(compare)

          4. 把數組轉成以指定符號分隔的字符串

          JS 中有個方法可以做到這一點,就是使用數組中的 .join() 方法,我們可以傳入指定的符號來做數組進行分隔。


          const data = ['Mango', 'Apple', 'Banana', 'Peach']


          data.join(',');

          // return "Mango,Apple,Banana,Peach"

          5. 從數組中選擇一個元素

          對于此任務,我們有多種方式,一種是使用 forEach 組合 if-else 的方式 ,另一種可以使用filter 方法,但是使用forEach 和filter的缺點是:


          在forEach中,我們要額外的遍歷其它不需要元素,并且還要使用 if 語句來提取所需的值。

          在filter 方法中,我們有一個簡單的比較操作,但是它將返回的是一個數組,而是我們想要是根據給定條件從數組中獲得單個對象。

          為了解決這個問題,我們可以使用 find函數從數組中找到確切的元素并返回該對象,這里我們不需要使用if-else語句來檢查元素是否滿足條件。


          const data = [

           {id: 1, name: 'Lemon'},

           {id: 2, name: 'Mint'},

           {id: 3, name: 'Mango'},

           {id: 4, name: 'Apple'}

          ]


          const value = data.find(item => item.name === 'Apple')

          // value = {id: 4, name: 'Apple'}

          藍藍設計www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 、平面設計服務


          Vue 數據更新了但頁面沒有更新的 7 種情況匯總及延伸總結

          seo達人

          1. Vue 無法檢測實例被創建時不存在于 data 中的 property

          原因:由于 Vue 會在初始化實例時對 property 執行 getter/setter 轉化,所以 property 必須在 data 對象上存在才能讓 Vue 將它轉換為響應式的。

          場景:


          var vm = new Vue({

           data:{},

           // 頁面不會變化

           template: '<div>{{message}}</div>'

          })

          vm.message = 'Hello!' // `vm.message` 不是響應式的

          解決辦法:


          var vm = new Vue({

           data: {

             // 聲明 a、b 為一個空值字符串

             message: '',

           },

           template: '<div>{{ message }}</div>'

          })

          vm.message = 'Hello!'

          2. Vue 無法檢測對象 property 的添加或移除

          原因:官方 - 由于 JavaScript(ES5) 的限制,Vue.js 不能檢測到對象屬性的添加或刪除。因為 Vue.js 在初始化實例時將屬性轉為 getter/setter,所以屬性必須在 data 對象上才能讓 Vue.js 轉換它,才能讓它是響應的。

          場景:


          var vm = new Vue({

           data:{

             obj: {

               id: 001

             }

           },

           // 頁面不會變化

           template: '<div>{{ obj.message }}</div>'

          })


          vm.obj.message = 'hello' // 不是響應式的

          delete vm.obj.id       // 不是響應式的

          解決辦法:


          // 動態添加 - Vue.set

          Vue.set(vm.obj, propertyName, newValue)


          // 動態添加 - vm.$set

          vm.$set(vm.obj, propertyName, newValue)


          // 動態添加多個

          // 代替 Object.assign(this.obj, { a: 1, b: 2 })

          this.obj = Object.assign({}, this.obj, { a: 1, b: 2 })


          // 動態移除 - Vue.delete

          Vue.delete(vm.obj, propertyName)


          // 動態移除 - vm.$delete

          vm.$delete(vm.obj, propertyName)

          3. Vue 不能檢測通過數組索引直接修改一個數組項

          原因:官方 - 由于 JavaScript 的限制,Vue 不能檢測數組和對象的變化;尤雨溪 - 性能代價和獲得用戶體驗不成正比。

          場景:


          var vm = new Vue({

           data: {

             items: ['a', 'b', 'c']

           }

          })

          vm.items[1] = 'x' // 不是響應性的

          解決辦法:


          // Vue.set

          Vue.set(vm.items, indexOfItem, newValue)


          // vm.$set

          vm.$set(vm.items, indexOfItem, newValue)


          // Array.prototype.splice

          vm.items.splice(indexOfItem, 1, newValue)

          拓展:Object.defineProperty() 可以監測數組的變化

          Object.defineProperty() 可以監測數組的變化。但對數組新增一個屬性(index)不會監測到數據變化,因為無法監測到新增數組的下標(index),刪除一個屬性(index)也是。

          場景:


          var arr = [1, 2, 3, 4]

          arr.forEach(function(item, index) {

             Object.defineProperty(arr, index, {

             set: function(value) {

               console.log('觸發 setter')

               item = value

             },

             get: function() {

               console.log('觸發 getter')

               return item

             }

           })

          })

          arr[1] = '123'  // 觸發 setter

          arr[1]          // 觸發 getter 返回值為 "123"

          arr[5] = 5      // 不會觸發 setter 和 getter

          4. Vue 不能監測直接修改數組長度的變化

          原因:官方 - 由于 JavaScript 的限制,Vue 不能檢測數組和對象的變化;尤雨溪 - 性能代價和獲得用戶體驗不成正比。

          場景:


          var vm = new Vue({

           data: {

             items: ['a', 'b', 'c']

           }

          })

          vm.items.length = 2 // 不是響應性的

          解決辦法:


          vm.items.splice(newLength)

          5. 在異步更新執行之前操作 DOM 數據不會變化

          原因:Vue 在更新 DOM 時是異步執行的。只要偵聽到數據變化,Vue 將開啟一個隊列,并緩沖在同一事件循環中發生的所有數據變更。如果同一個 watcher 被多次觸發,只會被推入到隊列中一次。這種在緩沖時去除重復數據對于避免不必要的計算和 DOM 操作是非常重要的。然后,在下一個的事件循環“tick”中,Vue 刷新隊列并執行實際 (已去重的) 工作。Vue 在內部對異步隊列嘗試使用原生的 Promise.then、MutationObserver 和 setImmediate,如果執行環境不支持,則會采用 setTimeout(fn, 0) 代替。

          場景:


          <div id="example">{{message}}</div>

          var vm = new Vue({

           el: '#example',

           data: {

             message: '123'

           }

          })

          vm.message = 'new message' // 更改數據

          vm.$el.textContent === 'new message' // false

          vm.$el.style.color = 'red' // 頁面沒有變化

          解決辦法:


          var vm = new Vue({

           el: '#example',

           data: {

             message: '123'

           }

          })

          vm.message = 'new message' // 更改數據

          //使用 Vue.nextTick(callback) callback 將在 DOM 更新完成后被調用

          Vue.nextTick(function () {

           vm.$el.textContent === 'new message' // true

           vm.$el.style.color = 'red' // 文字顏色變成紅色

          })

          拓展:異步更新帶來的數據響應的誤解

          <!-- 頁面顯示:我更新啦! -->

          <div id="example">{{message.text}}</div>

          var vm = new Vue({

           el: '#example',

           data: {

             message: {},

           }

          })

          vm.$nextTick(function () {

           this.message = {}

           this.message.text = '我更新啦!'

          })

          上段代碼中,我們在 data 對象中聲明了一個 message 空對象,然后在下次 DOM 更新循環結束之后觸發的異步回調中,執行了如下兩段代碼:


          this.message = {};

          this.message.text = '我更新啦!'

          到這里,模版更新了,頁面最后會顯示 我更新啦!。


          模板更新了,應該具有響應式特性,如果這么想那么你就已經走入了誤區。


          一開始我們在 data 對象中只是聲明了一個 message 空對象,并不具有 text 屬性,所以該 text 屬性是不具有響應式特性的。


          但模板切切實實已經更新了,這又是怎么回事呢?


          那是因為 Vue.js 的 DOM 更新是異步的,即當 setter 操作發生后,指令并不會立馬更新,指令的更新操作會有一個延遲,當指令更新真正執行的時候,此時 text 屬性已經賦值,所以指令更新模板時得到的是新值。


          模板中每個指令/數據綁定都有一個對應的 watcher 對象,在計算過程中它把屬性記錄為依賴。之后當依賴的 setter 被調用時,會觸發 watcher 重新計算 ,也就會導致它的關聯指令更新 DOM。



          具體流程如下所示:


          執行 this.message = {}; 時, setter 被調用。

          Vue.js 追蹤到 message 依賴的 setter 被調用后,會觸發 watcher 重新計算。

          this.message.text = '我更新啦!'; 對 text 屬性進行賦值。

          異步回調邏輯執行結束之后,就會導致它的關聯指令更新 DOM,指令更新開始執行。

          所以真正的觸發模版更新的操作是 this.message = {};這一句引起的,因為觸發了 setter,所以單看上述例子,具有響應式特性的數據只有 message 這一層,它的動態添加的屬性是不具備的。


          對應上述第二點 - Vue 無法檢測對象 property 的添加或移除

          6. 循環嵌套層級太深,視圖不更新?

          看到網上有些人說數據更新的層級太深,導致數據不更新或者更新緩慢從而導致試圖不更新?


          由于我沒有遇到過這種情況,在我試圖重現這種場景的情況下,發現并沒有上述情況的發生,所以對于這一點不進行過多描述(如果有人在真實場景下遇到這種情況留個言吧)。


          針對上述情況有人給出的解決方案是使用強制更新:


          如果你發現你自己需要在 Vue 中做一次強制更新,99.9% 的情況,是你在某個地方做錯了事。

          vm.$forceUpdate()

          7. 拓展:路由參數變化時,頁面不更新(數據不更新)

          拓展一個因為路由參數變化,而導致頁面不更新的問題,頁面不更新本質上就是數據沒有更新。


          原因:路由視圖組件引用了相同組件時,當路由參會變化時,會導致該組件無法更新,也就是我們常說中的頁面無法更新的問題。

          場景:


          <div id="app">

           <ul>

             <li><router-link to="/home/foo">To Foo</router-link></li>    

             <li><router-link to="/home/baz">To Baz</router-link></li>    

             <li><router-link to="/home/bar">To Bar</router-link></li>    

           </ul>    

           <router-view></router-view>

          </div>

          const Home = {

           template: `<div>{{message}}</div>`,

           data() {

             return {

               message: this.$route.params.name

             }

           }

          }


          const router = new VueRouter({

           mode:'history',

             routes: [

             {path: '/home', component: Home },

             {path: '/home/:name', component: Home }

           ]

          })


          new Vue({

           el: '#app',

           router

          })

          上段代碼中,我們在路由構建選項 routes 中配置了一個動態路由 '/home/:name',它們共用一個路由組件 Home,這代表他們復用 RouterView 。


          當進行路由切換時,頁面只會渲染第一次路由匹配到的參數,之后再進行路由切換時,message 是沒有變化的。


          解決辦法:


          解決的辦法有很多種,這里只列舉我常用到幾種方法。

          通過 watch 監聽 $route 的變化。


          const Home = {

           template: `<div>{{message}}</div>`,

           data() {

             return {

               message: this.$route.params.name

             }

           },

           watch: {

                '$route': function() {

                this.message = this.$route.params.name

             }

             }

          }

          ...

          new Vue({

           el: '#app',

           router

          })

          給 <router-view> 綁定 key 屬性,這樣 Vue 就會認為這是不同的 <router-view>。


          弊端:如果從 /home 跳轉到 /user 等其他路由下,我們是不用擔心組件更新問題的,所以這個時候 key 屬性是多余的。

          <div id="app">

           ...

           <router-view :key="key"></router-view>

          </div>

          理解 redux-thunk 源碼

          seo達人

          前言

          前面幾篇我們就 Redux 展開了幾篇文章,這次我們來實現 react-thunk,就不是叫實現 redux-thunk 了,直接上源碼,因為源碼就11行。如果對 Redux 中間件還不理解的,可以看我寫的 Redux 文章。


          實現一個迷你Redux(基礎版)

          實現一個Redux(完善版)

          淺談React的Context API

          帶你實現 react-redux

          為什么要用 redux-thunk

          在使用 Redux 過程,通過 dispatch 方法派發一個 action 對象。當我們使用 redux-thunk 后,可以 dispatch 一個 function。redux-thunk會自動調用這個 function,并且傳遞 dispatch, getState 方法作為參數。這樣一來,我們就能在這個 function 里面處理異步邏輯,處理復雜邏輯,這是原來 Redux 做不到的,因為原來就只能 dispatch 一個簡單對象。


          用法

          redux-thunk 作為 redux 的中間件,主要用來處理異步請求,比如:


          export function fetchData() {

           return (dispatch, getState) => {

             // to do ...

             axios.get('https://jsonplaceholder.typicode.com/todos/1').then(res => {

               console.log(res)

             })

           }

          }

          redux-thunk 源碼

          redux-thunk 的源碼比較簡潔,實際就11行。前幾篇我們說到 redux 的中間件形式,

          本質上是對 store.dispatch 方法進行了增強改造,基本是類似這種形式:


          const middleware = (store) => next => action => {}

          在這里就不詳細解釋了,可以看 實現一個Redux(完善版)


          先給個縮水版的實現:


          const thunk = ({ getState, dispatch }) => next => action => {

             if (typeof action === 'function') {

                 return action(dispatch, getState)

             }

             return next(action)

          }

          export default thunk

          原理:即當 action 為 function 的時候,就調用這個 function (傳入 dispatch, getState)并返回;如果不是,就直接傳給下一個中間件。

          完整源碼如下:


          function createThunkMiddleware(extraArgument) {

           return ({ dispatch, getState }) => next => action => {

             // 如果action是一個function,就返回action(dispatch, getState, extraArgument),否則返回next(action)。

             if (typeof action === 'function') {

               return action(dispatch, getState, extraArgument)

             }

             // next為之前傳入的store.dispatch,即改寫前的dispatch

             return next(action)

           }

          }


          const thunk = createThunkMiddleware()

          // 給thunk設置一個變量withExtraArgument,并且將createThunkMiddleware整個函數賦給它

          thunk.withExtraArgument = createThunkMiddleware


          export default thunk

          我們發現其實還多了 extraArgument 傳入,這個是自定義參數,如下用法:


          const api = "https://jsonplaceholder.typicode.com/todos/1";

          const whatever = 10;


          const store = createStore(

           reducer,

           applyMiddleware(thunk.withExtraArgument({ api, whatever })),

          );


          // later

          function fetchData() {

           return (dispatch, getState, { api, whatever }) => {

             // you can use api and something else here

           };

          }

          總結

          同 redux-thunk 非常流行的庫 redux-saga 一樣,都是在 redux 中做異步請求等副作用。Redux 相關的系列文章就暫時寫到這部分為止,下次會寫其他系列。

          Typescript 內置的模塊導入兼容方式

          seo達人

          一、前言

          前端的模塊化規范包括 commonJS、AMD、CMD 和 ES6。其中 AMD 和 CMD 可以說是過渡期的產物,目前較為常見的是commonJS 和 ES6。在 TS 中這兩種模塊化方案的混用,往往會出現一些意想不到的問題。


          二、import * as

          考慮到兼容性,我們一般會將代碼編譯為 es5 標準,于是 tsconfig.json 會有以下配置:


          {

           "compilerOptions": {

             "module": "commonjs",

             "target": "es5",

           }

          }

          代碼編譯后最終會以 commonJS 的形式輸出。

          使用 React 的時候,這種寫法 import React from "react" 會收到一個莫名其妙的報錯:


          Module "react" has no default export

          這時候你只能把代碼改成這樣:import * as React from "react"。

          究其原因,React 是以 commonJS 的規范導出的,而 import React from "react" 這種寫法會去找 React 模塊中的 exports.default,而 React 并沒有導出這個屬性,于是就報了如上錯誤。而 import * as React 的寫法會取 module.exports 中的值,這樣使用起來就不會有任何問題。我們來看看 React 模塊導出的代碼到底是怎樣的(精簡過):


          ...

          var React = {

           Children: {

             map: mapChildren,

             forEach: forEachChildren,

             count: countChildren,

             toArray: toArray,

             only: onlyChild

           },


           createRef: createRef,

           Component: Component,

           PureComponent: PureComponent,

           ...

          }


          module.exports = React;

          可以看到,React 導出的是一個對象,自然也不會有 default 屬性。


          二、esModuleInterop

          為了兼容這種這種情況,TS 提供了配置項 esModuleInterop 和 allowSyntheticDefaultImports,加上后就不會有報錯了:


          {

           "compilerOptions": {

             "module": "commonjs",

             "target": "es5",

             "allowSyntheticDefaultImports": true,

             "esModuleInterop": true

           }

          }

          其中 allowSyntheticDefaultImports 這個字段的作用只是在靜態類型檢查時,把 import 沒有 exports.default 的報錯忽略掉。

          而 esModuleInterop 會真正的在編譯的過程中生成兼容代碼,使模塊能正確的導入。還是開始的代碼:


          import React from "react";

          現在 TS 編譯后是這樣的:


          var __importDefault = (this && this.__importDefault) || function (mod) {

             return (mod && mod.__esModule) ? mod : { "default": mod };

          };


          Object.defineProperty(exports, "__esModule", { value: true });


          var react_1 = __importDefault(require("react"));

          編譯器幫我們生成了一個新的對象,將模塊賦值給它的 default 屬性,運行時就不會報錯了。


          三、Tree Shaking

          如果把 TS 按照 ES6 規范編譯,就不需要加上 esModuleInterop,只需要 allowSyntheticDefaultImports,防止靜態類型檢查時報錯。


          {

           "compilerOptions": {

             "module": "es6",

             "target": "es6",

             "allowSyntheticDefaultImports": true

           }

          }

          什么情況下我們會考慮導出成 ES6 規范呢?多數情況是為了使用 webpack 的 tree shaking 特性,因為它只對 ES6 的代碼生效。


          順便再發散一下,講講 babel-plugin-component。


          import { Button, Select } from 'element-ui'

          上面的代碼經過編譯后,是下面這樣的:


          var a = require('element-ui');

          var Button = a.Button;

          var Select = a.Select;

          var a = require('element-ui') 會引入整個組件庫,即使只用了其中的 2 個組件。

          babel-plugin-component 的作用是將代碼做如下轉換:


          // 轉換前

          import { Button, Select } from 'element-ui'

          // 轉換后

          import Button from 'element-ui/lib/button'

          import Select from 'element-ui/lib/select'

          最終編譯出來是這個樣子,只會加載用到的組件:


          var Button = require('element-ui/lib/button');

          var Select = require('element-ui/lib/select');

          四、總結

          本文講解了 TypeScript 是如何導入不同模塊標準打包的代碼的。無論你導入的是 commonJS 還是 ES6 的代碼,萬無一失的方式是把 esModuleInterop 和 allowSyntheticDefaultImports 都配置上。

          使用 VSCode 開發 Gatsby 項目配置

          seo達人

          初始化

          使用 https://github.com/XYShaoKang... 作為基礎模板

          gatsby new gatsby-project-config https://github.com/XYShaoKang/gatsby-hello-world

          Prettier 配置

          安裝 VSCode 擴展

          按 Ctrl + P (MAC 下: Cmd + P) 輸入以下命令,按回車安裝


          ext install esbenp.prettier-vscode

          安裝依賴

          yarn add -D prettier

          Prettier 配置文件.prettierrc.js

          // .prettierrc.js

          module.exports = {

           trailingComma: 'es5',

           tabWidth: 2,

           semi: false,

           singleQuote: true,

           endOfLine: 'lf',

           printWidth: 50,

           arrowParens: 'avoid',

          }

          ESLint 配置

          安裝 VSCode 擴展

          按 Ctrl + P (MAC 下: Cmd + P) 輸入以下命令,按回車安裝


          ext install dbaeumer.vscode-eslint

          安裝 ESLint 依賴

          yarn add -D eslint babel-eslint eslint-config-google eslint-plugin-react eslint-plugin-filenames

          ESLint 配置文件.eslintrc.js

          使用官方倉庫的配置,之后在根據需要修改


          // https://github.com/gatsbyjs/gatsby/blob/master/.eslintrc.js

          // .eslintrc.js

          module.exports = {

           parser: 'babel-eslint',

           extends: [

             'google',

             'eslint:recommended',

             'plugin:react/recommended',

           ],

           plugins: ['react', 'filenames'],

           parserOptions: {

             ecmaVersion: 2016,

             sourceType: 'module',

             ecmaFeatures: {

               jsx: true,

             },

           },

           env: {

             browser: true,

             es6: true,

             node: true,

             jest: true,

           },

           globals: {

             before: true,

             after: true,

             spyOn: true,

             __PATH_PREFIX__: true,

             __BASE_PATH__: true,

             __ASSET_PREFIX__: true,

           },

           rules: {

             'arrow-body-style': [

               'error',

               'as-needed',

               { requireReturnForObjectLiteral: true },

             ],

             'no-unused-expressions': [

               'error',

               {

                 allowTaggedTemplates: true,

               },

             ],

             'consistent-return': ['error'],

             'filenames/match-regex': [

               'error',

               '^[a-z-\\d\\.]+$',

               true,

             ],

             'no-console': 'off',

             'no-inner-declarations': 'off',

             quotes: ['error', 'backtick'],

             'react/display-name': 'off',

             'react/jsx-key': 'warn',

             'react/no-unescaped-entities': 'off',

             'react/prop-types': 'off',

             'require-jsdoc': 'off',

             'valid-jsdoc': 'off',

           },

           settings: {

             react: {

               version: '16.4.2',

             },

           },

          }

          解決 Prettier ESLint 規則沖突

          推薦配置


          安裝依賴


          yarn add -D eslint-config-prettier eslint-plugin-prettier

          在.eslintrc.js中的extends添加'plugin:prettier/recommended'


          module.exports = {

           extends: ['plugin:prettier/recommended'],

          }

          VSCode 中 Prettier 和 ESLint 協作

          方式一:使用 ESLint 擴展來格式化代碼

          配置.vscode/settings.json


          // .vscode/settings.json

          {

           "eslint.format.enable": true,

           "[javascript]": {

             "editor.defaultFormatter": "dbaeumer.vscode-eslint"

           },

           "[javascriptreact]": {

             "editor.defaultFormatter": "dbaeumer.vscode-eslint"

           }

          }

          ESLint 擴展會默認忽略.開頭的文件,比如.eslintrc.js

          如果需要格式化.開頭的文件,可以在.eslintignore中添加一個否定忽略來啟用對應文件的格式化功能.


          !.eslintrc.js

          或者直接使用!.*,這樣可以開啟所有點文件的格式化功能


          方式二:使用 Prettier 擴展來格式化代碼

          在版prettier-vscode@v5.0.0中已經刪除了直接對linter的集成,所以版沒法像之前那樣,通過prettier-eslint來集成ESLint的修復了(一定要這樣用的話,可以通過降級到prettier-vscode@4來使用了).如果要使用Prettier來格式化的話,就只能按照官方指南中的說的集成方法,讓Prettier來處理格式,通過配置在保存時使用ESlint自動修復代碼.只是這樣必須要保存文件時,才能觸發ESLint的修復了.


          配置 VSCode 使用 Prettier 來格式化 js 和 jsx 文件

          在項目中新建文件.vscode/settings.json


          // .vscode/settings.json

          {

           "[javascript]": {

             "editor.defaultFormatter": "esbenp.prettier-vscode"

           },

           "[javascriptreact]": {

             "editor.defaultFormatter": "esbenp.prettier-vscode"

           },

           "editor.codeActionsOnSave": {

             "source.fixAll.eslint": true

           }

          }

          說實話這個體驗很糟糕,之前直接一鍵格式化代碼并且修復 ESLint 錯誤,可以對比格式化之前和格式化之后的代碼,如果感覺不對可以直接撤銷更改就好了.現在必須要通過保存,才能觸發修復 ESlint 錯誤.而在開發過程中,通過監聽文件改變來觸發熱加載或者重新編譯是很常見的操作.這樣之后每次想要去修復 ESLint 錯誤,還是只是想看看修復錯誤之后的樣子,都必須要去觸發熱加載或重新編譯,每次操作的成本就太高了.

          我更推薦第一種方式使用 ESLint 擴展來對代碼進行格式化.


          調試 Gatsby 配置

          調試構建過程

          添加配置文件.vscode/launch.json


          // .vscode/launch.json

          {

           // 使用 IntelliSense 了解相關屬性。

           // 懸停以查看現有屬性的描述。

           // 欲了解更多信息,請訪問: https://go.microsoft.com/fwlink/?linkid=830387

           "version": "0.2.0",

           "configurations": [

             {

               "name": "Gatsby develop",

               "type": "node",

               "request": "launch",

               "protocol": "inspector",

               "program": "${workspaceRoot}/node_modules/gatsby/dist/bin/gatsby",

               "args": ["develop"],

               "stopOnEntry": false,

               "runtimeArgs": ["--nolazy"],

               "sourceMaps": false,

               "outputCapture": "std"

             }

           ]

          }

          的gatsby@2.22.*版本中調試不能進到斷點,解決辦法是降級到2.21.*,yarn add gatsby@2.21.40,等待官方修復再使用版本的

          調試客戶端

          需要安裝 Debugger for Chrome 擴展


          ext install msjsdiag.debugger-for-chrome

          添加配置文件.vscode/launch.json


          // .vscode/launch.json

          {

           // 使用 IntelliSense 了解相關屬性。

           // 懸停以查看現有屬性的描述。

           // 欲了解更多信息,請訪問: https://go.microsoft.com/fwlink/?linkid=830387

           "version": "0.2.0",

           "configurations": [

             {

               "type": "chrome",

               "request": "launch",

               "name": "Gatsby Client Debug",

               "url": "http://localhost:8000",

               "webRoot": "${workspaceFolder}"

             }

           ]

          }

          先啟動 Gatsby,yarn develop,然后按 F5 開始調試.

          JavaScript必須掌握的基礎 --- 閉包

          seo達人

          閉包(Closure)的定義

          閉包是一個讓初級JavaScript使用者既熟悉又陌生的一個概念。因為閉包在我們書寫JavaScript代碼時,隨處可見,但是我們又不知道哪里用了閉包。

          關于閉包的定義,網上(書上)的解釋總是千奇百怪,我們也只能“取其精華去其糟粕”去總結一下。

          1. 即使函數在當前作用域外調用,但是還能訪問當前作用域中的變量和函數
          2. 有權訪問另一個函數作用域中的變量(函數)的函數。
          3. 閉包是指那些能夠訪問自由變量的函數

          ECMAScript中,閉包指的是:

          1. 從理論角度:所有的函數都是閉包。因為它們都在創建的時候就將上層上下文的數據保存起來了。哪怕是簡單的全局變量也是如此,因為函數中訪問全局變量也就相當于是在訪問自由變量,這個時候使用最外層的作用域。
          2. 從實踐角度:一下才算是閉包:

            • 即使創建它的上下文已經銷毀,它仍然存在。
            • 在代碼中引用了自由變量。

          閉包跟詞法作用域,作用域鏈,執行上下文這幾個JavaScript中重要的概念都有關系,因此要想真的理解閉包,至少要對那幾個概念不陌生。

          閉包的優點:

          1. 可以是用函數內部的變量(函數),也可以說是可以訪問函數作用域。
          2. 擁有私有變量,避免污染全局變量

          閉包的缺點:

          1. 私有變量一直存在,占用內存。

          我們來一步一步引出閉包。

          自執行函數 ( IIFE )

          自執行函數也叫立即調用函數(IIFE),是一個在定義時就執行的函數。

          var a=1;
          (function() { console.log(a)
          })()

          上述代碼是一個最簡單的自執行函數。

          在ES6之前,是沒有塊級作用域的,只有全局作用域和函數作用域,因此自執行函數還能在ES6之前實現塊級作用域。

          // ES6 塊級作用域 var a = 1; if(true) { let a=111; console.log(a); // 111 } console.log(a); // 1 

          這里 if{} 中用let聲明了一個 a。這個 a 就具有塊級作用域,在這個 {} 中訪問 a ,永遠訪問的都是 let 聲明的a,跟全局作用域中的a沒有關系。如果我們把 let 換成 var ,就會污染全局變量 a 。

          如果用自執行函數來實現:

          var a = 1;
          (function() { if(true) { var a=111; console.log(a); // 111 }
          })() console.log(a); // 1

          為什么要在這里要引入自執行函數的概念呢?因為通常我們會用自執行函數來創建閉包,實現一定的效果。

          來看一個基本上面試提問題:

          for(var i=0;i<5;i++) {
              setTimeout(function() { console.log(i);
              },1000)
          }

          在理想狀態下我們期望輸出的是 0 ,1 ,2 ,3 ,4。但是實際上輸出的是5 ,5 ,5 ,5 ,5。為什么是這樣呢?其實這里不僅僅涉及到作用域,作用域鏈還涉及到Event Loop、微任務、宏任務。但是在這里不講這些。

          下面我們先解釋它為什么會輸出 5個5,然后再用自執行函數來修改它,以達到我們預期的結果。

          提示:for 循環中,每一次的都聲明一個同名變量,下一個變量的值為上一次循環執行完同名變量的值。

          首先用var聲明變量 for 是不會產生塊級作用域的,所以在 () 中聲明的 i 為全局變量。相當于:

          // 偽代碼 var i; for(i=0;i<5;i++) {
              setTimeout(function() { console.log(i);
              },1000)
          }

          setTimeout中的第一個參數為一個全局的匿名函數。相當于:

          // 偽代碼 var i; var f = function() { console.log(i);
          } for(i=0;i<5;i++) {
              setTimeout(f,1000)
          }

          由于setTimeout是在1秒之后執行的,這個時候for循環已經執行完畢,此時的全局變量 i 已經變成了 5 。1秒后5個setTimeout中的匿名函數會同時執行,也就是5個 f 函數執行。這個時候 f 函數使用的變量 i 根據作用域鏈的查找規則找到了全局作用域中的 i 。因此會輸出 5 個5。

          那我們怎樣來修改它呢?

          • 思路1:讓setTimeout匿名函數中訪問的變量 i 不再訪問全局作用域中的 i 。因此把它包裹在一個函數作用域中。這時 匿名函數訪問變量 i 時,會先去包裹它的函數作用域中查找。
          for(var i=0;i<5;i++) {
              (function (){ setTimeout(function() { console.log(i);
                  },1000)
              })();
          }

          上述例子會輸出我們期望的值嗎?答案是否。為什么呢?我們雖然把 setTimeout 包裹在一個匿名函數中了,但是當setTimeout中匿名函數執行時,首先去匿名函數中查找 i 的值,找不到還是會找到全局作用域中,最終 i 的值仍然是全局變量中的 i ,仍然為 5個5.

          那我們把外層的匿名函數中聲明一個變量 j 讓setTimeout中的匿名函數訪問這個 j 不就找不到全局變量中的變量了嗎。

          for(var i=0;i<5;i++) {
              (function (){ var j = i;
                  setTimeout(function() { console.log(j);
                  },1000)
              })();
          }

          這個時候才達到了我們預期的結果:0 1 2 3 4。

          我們來優化一下:

          for(var i=0;i<5;i++) {
              (function (i){ setTimeout(function() { console.log(i);
                  },1000)
              })(i);
          }

          *思路2:用 let 聲明變量,產生塊級作用域。

          for(let i=0;i<5;i++) {
              setTimeout(function() { console.log(i);
              },1000)
          }

          這時for循環5次,產生 5 個塊級作用域,也會聲明 5 個具有塊級作用域的變量 i ,因此setTimeout中的匿名函數每次執行時,訪問的 i 都是當前塊級作用域中的變量 i 。

          理論中的閉包

          什么是理論中的閉包?就是看似像閉包,其實并不是閉包。它只是類似于閉包。

           function foo() { var a=2; function bar() { console.log(a); // 2 }
              bar();
          }
          foo();

          上述代碼根據最上面我們對閉包的定義,它并不完全是閉包,雖然是一個函數可以訪問另一個函數中的變量,但是被嵌套的函數是在當前詞法作用域中被調用的。

          實踐中的閉包

          我們怎樣把上述代碼foo 函數中的bar函數,在它所在的詞法作用域外執行呢?

          下面的代碼就清晰的展示了閉包:

          function foo() { var a=2; function bar() { console.log(a);
              } return bar;
          } var baz=foo();
          baz(); // 2 —— 朋友,這就是閉包的效果。

          上述代碼中 bar 被當做 foo函數返回值。foo函數執行后把返回值也就是 bar函數 賦值給了全局變量 baz。當 baz 執行時,實際上也就是 bar 函數的執行。我們知道 foo 函數在執行后,foo 的內部作用域會被銷毀,因為引擎有垃圾回收期來釋放不再使用的內存空間。所以在bar函數執行時,實際上foo函數內部的作用域已經不存在了,理應來說 bar函數 內部再訪問 a 變量時是找不到的。但是閉包的神奇之處就在這里。由于 bar 是在 foo 作用域中被聲明的,所以 bar函數 會一直保存著對 foo 作用域的引用。這時就形成了閉包。

          我們先看個例子:

          var scope = "global scope"; function checkscope(){ var scope = "local scope"; function f(){ return scope;
              } return f;
          } var foo = checkscope();
          foo();

          我們用偽代碼來解釋JavaScript引擎在執行上述代碼時的步驟:

          1. JavaScript引擎遇到可執行代碼時,就會進入一個執行上下文(環境)
          2. 首先遇到的是全局代碼,因此進入全局執行上下文,把全局執行上下文壓入執行上下文棧。
          3. 全局上下文創建時會先在內部創建VO/AO,作用域鏈,this。然后執行代碼。
          4. 當遇到 checkscope 函數執行時,進入checkscope的執行上下文,然后壓入執行上下文棧。
          5. checkscope 執行上下文創建時會先在內部創建VO/AO,作用域鏈,this。然后執行代碼。
          6. 當checkscope 函數執行完畢時,會從執行上下文棧中彈出,此時它的AO也會被瀏覽器回收。(這是理想狀態下)
          7. 執行foo函數,向上查找foo的值,發現foo的值為checkscope函數內部函數f。因此這一步為執行 checkscope 內部函數f。
          8. 執行f函數同執行 checkscope 的步驟一致。
          9. f 函數執行完畢,從執行上下文棧中彈出。

          但是我們想一個問題,checkscope函數執行完畢,它的執行上下文從棧中彈出,也就是銷毀了不存在了,f 函數還能訪問包裹函數的作用域中的變量(scope)嗎?答案是可以。

          理由是在第6步,我們說過當checkscope 執行函數執行完畢時,它的執行上下文會從棧中彈出,此時活動對象也會被回收,按理說當 f 在訪問checkscope的活動對象時是訪問不到的。

          其實這里還有個概念,叫做作用域鏈:當 checkscope 函數被創建時,會創建對應的作用域鏈,里面值存放著包裹它的作用域對應執行上下文的變量對象,在這里只是全局執行上下文的變量對象,當checkscope執行時,此時的作用域鏈變化了 ,里面存放的是變量對象(活動對象)的集合,最頂端是當前函數的執行上下文的活動對象。端是全局執行上下文的變量對象。類似于:

          checkscope.scopeChain = [
              checkscope.AO
              global.VO
          ] 

          當checkscope執行碰到了 f 函數的創建,因此 f 函數也會創建對應的作用域鏈,默認以包裹它的函數執行時對應的作用域鏈為基礎。因此此時 f 函數創建時的作用域鏈如下:

          checkscope.scopeChain = [
              checkscope.AO
              global.VO
          ]

          當 f 函數執行時,此時的作用域鏈變化如下:

          checkscope.scopeChain = [
              f.AO
              checkscope.AO
              global.VO
          ]

          當checkscope函數執行完畢,內部作用域會被回收,但是 f函數 的作用域鏈還是存在的,里面存放著 checkscope函數的活動對象,因此在f函數執行時會從作用域鏈中查找內部使用的 scope 標識符,從而在作用域鏈的第二位找到了,也就是在 checkscope.AO 找到了變量scope的值。

          正是因為JavaScript做到了這一點,因此才會有閉包的概念。還有人說閉包并不是為了擁有它采取設計它的,而是設計作用域鏈時的副作用產物。

          閉包是JavaScript中最難的點,也是平常面試中常問的問題,我們必須要真正的去理解它,如果只靠死記硬背是經不起考驗的。

          日歷

          鏈接

          個人資料

          藍藍設計的小編 http://www.syprn.cn

          存檔

          亚洲va欧美va天堂v国产综合