整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          14 個實用又簡潔的單行 JS 代碼,好用到飛起!

          14 個實用又簡潔的單行 JS 代碼,好用到飛起!

          編程中,解決同一個問題通常有多種方法。這些解決方案在不同方面可能有所不同,例如長度、性能、使用的算法、可讀性等。

          在本文中,我們將研究幾種快速簡潔的單行解決方案,以解決 JavaScript 中經常出現的各種問題。

          什么是單行代碼?

          在我們開始之前,讓我們確保我們了解是什么單行代碼。

          單行代碼是問題的代碼解決方案,使用特定編程語言中的單個語句實現,無需任何第三方實用程序。

          該定義包含許多其他定義中沒有的重要區別特征:

          1). “……單句……”

          并非每一段只占用一行的代碼都是單行代碼。例如,看看這個將兩個平方和相加并返回結果的方法。

          const sum=(a, b)=> { const s1=a * a; const s2=b * b; return s1 + s2; }

          你會稱之為單行代碼嗎?在大多數情況下,這只會作為格式錯誤的代碼通過。Prettier 之類的工具可以輕松地將這三個語句自動拆分為多行。

          獲得兩個平方和的真正單行方法是這樣的:

          const sum=(a, b)=> a * a + b * b;

          一個簡短、簡潔的陳述可以同樣清晰地完成同樣的工作。

          另一方面,此方法跨越多行代碼以提高可讀性,但它仍然可以作為一行代碼通過:

          const capitalizeWithoutSpaces=(str)=>
            str
              .split('')
              .filter((char)=> char.trim())
              .map((char)=> char.toUpperCase())
              .join('');

          因此,盡管名稱如此,“單行”并不一定意味著是一行代碼。

          2). “……特定的編程語言……”

          這與每個高級語言程序在執行前都必須翻譯成低級語言這一事實有關。一個非常簡單的程序最終可能會占用數十或數百行匯編代碼和機器代碼。

          例如,這里是另一個也添加兩個平方和的單行代碼,這次是在 C++ 中:

          int sum(int a, int b) {
              return a * a + b * b;
          }

          讓我們看看編譯成匯編語言后的樣子:

          sum(int, int):
                  push    rbp
                  mov     rbp, rsp
                  mov     DWORD PTR [rbp-4], edi
                  mov     DWORD PTR [rbp-8], esi
                  mov     eax, DWORD PTR [rbp-4]
                  imul    eax, eax
                  mov     edx, eax
                  mov     eax, DWORD PTR [rbp-8]
                  imul    eax, eax
                  add     eax, edx
                  pop     rbp
                  ret

          這個匯編程序顯然不止一行或一行代碼。想象一下等效的機器語言程序會有多少。所以這個函數可以說是僅在 C++ 上下文中的單行函數。

          3). “……沒有任何第三方實用程序”

          對于單行代碼,它不應該引用編程語言本身不可用的任何方法或函數,記住我們之前看過的單行代碼:

          const capitalizeWithoutSpaces=(str)=>
            str
              .split('')
              .filter((char)=> char.trim())
              .map((char)=> char.toUpperCase())
              .join('');

          這里使用的所有方法都是內置的 JavaScript 方法。它不包括來自 NPM 或其他地方的第三方代碼。

          但是,如果我們決定實現自己的 filter() 方法來替換 Array filter(),則該方法將不再符合單行方法的條件。

          // Not a one-liner
          const capitalizeWithoutSpaces=(str)=>
            filter(str.split(''), (char)=> char.trim())
              .map((char)=> char.toUpperCase())
              .join('');
          function filter(arr, callback) {
            // Look at all these lines
            const result=[];
            for (const item of arr) {
              if (callback(item)) {
                result.push(item);
              }
            }
            return result;
          }

          拋開定義,現在讓我們看一些聰明的 JavaScript 單行代碼以及它們解決方案。

          1. 獲取數組的最小元素

          要獲得數組中的最小項,我們可以采用這種使用 for 循環和 if 語句的命令式方法。

          const getSmallest=(arr)=> {
            let smallest=Number.POSITIVE_INFINITY;
            for (const num of arr) {
              if (num < smallest) {
                smallest=num;
              }
            }
            return smallest;
          };
          const arr=[13, 7, 11, 3, 9, 15, 17];
          console.log(getSmallest(arr)); // 3

          這沒關系,但有一個簡潔且聲明性的單行替代方案同樣有效:

          const getSmallest=(arr)=>
            arr.reduce((smallest, num)=> Math.min(smallest, num));
          const arr=[13, 7, 11, 3, 9, 15, 17];
          console.log(getSmallest(arr)); // 3

          2. 獲取數組的最大元素

          這是獲取數組中最大元素的可接受方法。

          const getLargest=(arr)=> {
            let largest=Number.NEGATIVE_INFINITY;
            for (const num of arr) {
              if (num > largest) {
                largest=num;
              }
            }
            return largest;
          };
          const arr=[13, 7, 11, 3, 9, 15, 17];
          console.log(getLargest(arr)); // 17

          但就像我們看到的獲取最小數組元素一樣,有一種更短、更簡潔的方法。

          const getLargest=(arr)=>
            arr.reduce((largest, num)=> Math.max(largest, num));
          const arr=[13, 7, 11, 3, 9, 15, 17];
          console.log(getLargest(arr)); // 17

          您可以看到,此函數與單行 getSmallest() 函數之間的唯一區別是 Math.min() 已替換為 Math.max()。

          3. 打亂數組

          數組/列表洗牌的一個常見用途是在紙牌游戲中,其中牌組中的牌必須隨機排序。

          Fisher-Yates 洗牌是一種著名的洗牌算法。查看它在 JavaScript 中的可能實現:

          const shuffleArray=(arr)=> {
            for (let i=arr.length - 1; i > 0; i--) {
              const j=Math.floor(Math.random() * (i + 1));
              let temp=arr[i];
              arr[i]=arr[j];
              arr[j]=temp;
            }
            return arr;
          };
          const arr=[1, 2, 3, 4, 5];
          shuffleArray(arr);
          // [ 2, 3, 5, 1, 4 ] (varies)
          console.log(arr);

          用一些函數式編程魔法重構它,我們有:

          const shuffleArray=(arr)=>
            [...Array(arr.length)]
              .map((_, i)=> Math.floor(Math.random() * (i + 1)))
              .reduce(
                (shuffled, r, i)=>
                  shuffled.map((num, j)=>
                    j===i ? shuffled[r] : j===r ? shuffled[i] : num
                  ),
                arr
              );
          // [ 2, 4, 1, 3, 5 ] (varies)
          console.log(shuffleArray([1, 2, 3, 4, 5]));

          這以 O(n2) 時間復雜度(二次)運行,并且可能會導致大型數組出現性能問題,但它是一種優雅的解決方案。此外,與第一種方法不同,它不會改變原始數組。

          另一種函數式方法利用 Array sort() 方法的實現方式來隨機排列數組。

          const shuffleArray=(arr)=> arr.sort(()=> Math.random() - 0.5);
          const arr=[1, 2, 3, 4, 5];
          // [ 5, 2, 4, 1, 3 ] (varies)
          console.log(shuffleArray(arr));

          由于它使用了 sort(),因此,它的運行時間復雜度為 O(n log n),并且比前面的方法具有更好的性能。

          4. 按對象屬性對數組進行分組

          有時我們需要使用它們都具有的特定屬性對一組對象進行分組,例如,按國家/地區對用戶進行分組,按出版年份對書籍進行分組,按顏色對汽車進行分組等。

          在下面的示例中,我們根據姓名的長度將人物對象分組到一個數組中。

          const groupBy=(arr, groupFn)=> {
            const grouped={};
            for (const obj of arr) {
              const groupName=groupFn(obj);
              if (!grouped[groupName]) {
                grouped[groupName]=[];
              }
              grouped[groupName].push(obj);
            }
            return grouped;
          };
          const people=[
            { name: 'Matt' },
            { name: 'Sam' },
            { name: 'John' },
            { name: 'Mac' },
          ];
          const groupedByNameLength=groupBy(people, (person)=> person.name.length);
          /**
          {
            '3': [ { name: 'Sam' }, { name: 'Mac' } ],
            '4': [ { name: 'Matt' }, { name: 'John' } ]
          }
           */
          console.log(groupedByNameLength);

          這是單行代碼的解決方案:

          const groupBy=(arr, groupFn)=>
            arr.reduce(
              (grouped, obj)=> ({
                ...grouped,
                [groupFn(obj)]: [...(grouped[groupFn(obj)] || []), obj],
              }),
              {}
            );
          const people=[
            { name: 'Matt' },
            { name: 'Sam' },
            { name: 'John' },
            { name: 'Mac' },
          ];
          const groupedByNameLength=groupBy(people, (person)=> person.name.length);
          /**
          {
            '3': [ { name: 'Sam' }, { name: 'Mac' } ],
            '4': [ { name: 'Matt' }, { name: 'John' } ]
          }
           */
          console.log(groupedByNameLength);

          5.反轉字符串

          我們可以在 JavaScript 中使用反向 for 循環來反轉字符串,如下所示:

          const reverseString=(str)=> {
            let reversed='';
            for (let i=str.length - 1; i >=0; i--) {
              const ch=str[i];
              reversed +=ch;
            }
            return reversed;
          };
          const reverse=reverseString('javascript');
          console.log(reverse); // tpircsavaj

          但是再一次,我們可以利用強大的內置數組方法,如 reverse() 和 join() 來創建一個做同樣事情的單行代碼。

          const reverseString=(str)=> str.split('').reverse().join('');
          const reverse=reverseString('javascript');
          console.log(reverse); // tpircsavaj

          6. 生成隨機的十六進制顏色

          十六進制顏色代碼是指定 RGB 顏色的一種方式。它們具有#RRGGBB 格式,其中 RR 代表紅色,GG 代表綠色,BB 代表藍色。每種顏色的值范圍從 0 到 255,并以十六進制格式表示 - 0 到 FF。

          這個單行生成一個隨機的十六進制顏色并返回結果。

          const randomHexColor=()=>
            `#${Math.random().toString(16).slice(2, 8).padEnd(6, '0')}`;
          console.log(randomHexColor()); // #7a10ba (varies)
          console.log(randomHexColor()); // #65abdc (varies)

          7. 獲取數組的平均值

          這是眾多問題中的另一個問題,其中涉及循環的解決方案可以使用一種或多種 Array 方法來縮短。

          因此,雖然我們可以像這樣獲得數組中數字的平均值:

          const getAverage=(arr)=> {
            let sum=0;
            for (const num of arr) {
              sum +=num;
            }
            return sum / arr.length;
          };
          const arr=[5, 13, 9, 11, 10, 15, 7];
          const average=getAverage(arr);
          console.log(average); // 10

          Array reduce() 方法讓我們創建了這個緊湊的單行替代方案:

          const getAverage=(arr)=> arr.reduce((sum, num)=> sum + num, 0) / arr.length;
          const arr=[5, 13, 9, 11, 10, 15, 7];
          const average=getAverage(arr);
          console.log(average); // 10

          8. 檢查兩個數組是否包含相同的值

          這是一個確保兩個數組包含相同元素(以任何順序)并且這些元素在兩個數組中出現相同次數的問題。

          使用 for 循環,我們可以實現以下解決方案:

          const areEqual=(arr1, arr2)=> {
            if (arr1.length===arr2.length) {
              for (const num of arr1) {
                if (!arr2.includes(num)) {
                  return false;
                }
              }
              return true;
            }
            return false;
          };
          const arr1=[1, 2, 3, 4];
          const arr2=[3, 4, 1, 2];
          const arr3=[1, 2, 3];
          console.log(areEqual(arr1, arr2)); // true
          console.log(areEqual(arr1, arr3)); // false

          使用 Array sort() 和 join() 方法,我們可以創建這個單行替代方案:

          const areEqual=(arr1, arr2)=>
            arr1.sort().join(',')===arr2.sort().join(',');
          const arr1=[1, 2, 3, 4];
          const arr2=[3, 4, 1, 2];
          const arr3=[1, 2, 3];
          console.log(areEqual(arr1, arr2)); // true
          console.log(areEqual(arr1, arr3)); // false

          9. 從數組中刪除重復項

          我們可以像這樣從數組中刪除重復項:

          const removeDuplicates=(arr)=> {
            const result=[];
            for (const num of arr) {
              if (!result.includes(num)) {
                result.push(num);
              }
            }
            return result;
          };
          const arr=[1, 2, 3, 4, 5, 3, 1, 2, 5];
          const distinct=removeDuplicates(arr);
          console.log(distinct); // [1, 2, 3, 4, 5]

          但是我們可以利用 Set() 構造函數在短短一行中刪除重復項:

          const removeDuplicates=(arr)=> [...new Set(arr)];
          const arr=[1, 2, 3, 4, 5, 3, 1, 2, 5];
          const distinct=removeDuplicates(arr);
          console.log(distinct); // [1, 2, 3, 4, 5]

          10. 將Map轉換為 JSON

          這個簡短的函數讓我們可以快速將 Map 對象轉換為 JSON 字符串而不會丟失任何信息:

          const mapToJson=(map)=> JSON.stringify(Object.fromEntries(map));
          const map=new Map([
            ['user1', 'John'],
            ['user2', 'Kate'],
            ['user3', 'Peter'],
          ]);
          const json=mapToJson(map);
          // {"user1":"John","user2":"Kate","user3":"Peter"}
          console.log(json);

          11. 將 JSON 轉換為Map

          另一個一行可以反轉上面的轉換。以下函數會將 JSON 字符串轉換為 Map 對象。

          const jsonToMap=(json)=> new Map(Object.entries(JSON.parse(json)));
          const json='{"user1":"John","user2":"Kate","user3":"Peter"}';
          const map=jsonToMap(json);
          // Kate
          console.log(map.get('user2'));
          // Map(3) { 'user1'=> 'John', 'user2'=> 'Kate', 'user3'=> 'Peter' }
          console.log(map);

          12. 將蛇形字符串轉換為駝峰大小寫

          在蛇形字符串中,每個單詞由下劃線 (_) 分隔并以小寫字母開頭,例如:variable_name、bread_and_eggs 等。

          但是,對于駝峰式字符串,第一個單詞以小寫字母開頭,后面的單詞均以大寫字母開頭。單詞之間沒有空格或標點符號。駝峰式字符串的示例有:variableName、breadAndEggs 等。

          使用這個簡潔的函數,我們可以將任何蛇形大小寫的字符串轉換為駝峰大小寫。

          const snakeToCamelCase=(s)=>
            s.toLowerCase().replace(/(_\w)/g, (w)=> w.toUpperCase().substr(1));
          const str1='learn_javascript';
          const str2='coding_beauty';
          console.log(snakeToCamelCase(str1)); // learnJavaScript
          console.log(snakeToCamelCase(str2)); // codingBeauty

          13.生成隨機UUID

          “UUID”是大學唯一標識符的首字母縮寫詞。UUID 是一個 128 位的值,可唯一標識 Internet 上的對象或實體。

          這個單行生成一個隨機 UUID:

          const generateRandomUUID=(a)=>
            a
              ? (a ^ ((Math.random() * 16) >> (a / 4))).toString(16)
              : ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(
                  /[018]/g,
                  generateRandomUUID
                );
          console.log(generateRandomUUID()); // f138f635-acbd-4f78-9be5-ca3198c4cf34
          console.log(generateRandomUUID()); // 8935bb0d-6503-441f-bb25-7bc685b5b5bc

          14.條件流控制

          我們可以使用嵌套的三元運算符將 if...else 或 switch 語句轉換為單行語句。考慮一個返回特定范圍內數字的英文單詞形式的函數。

          使用 if...else 語句,這樣的函數可以這樣實現:

          const getNumWord=(num)=> {
            if (num===1) {
              return 'one';
            } else if (num===2) {
              return 'two';
            } else if (num===3) {
              return 'three';
            } else if (num===4) {
              return 'four';
            } else return 'unknown';
          };
          console.log(getNumWord(1)); // one
          console.log(getNumWord(3)); // three
          console.log(getNumWord(7)); // unknown

          使用 switch...case 語句:

          const getNumWord=(num)=> {
            switch (num) {
              case 1:
                return 'one';
              case 2:
                return 'two';
              case 3:
                return 'three';
              case 4:
                return 'four';
              default:
                return 'unknown';
            }
          };
          console.log(getNumWord(1)); // one
          console.log(getNumWord(3)); // three
          console.log(getNumWord(7)); // unknown

          現在使用嵌套的三元組來創建單行代碼:

          const getNumWord=(num)=>
            num===1
              ? 'one'
              : num===2
              ? 'two'
              : num===3
              ? 'three'
              : num===4
              ? 'four'
              : 'unknown';
          console.log(getNumWord(1)); // one
          console.log(getNumWord(3)); // three
          console.log(getNumWord(7)); // unknown

          我們已經了解了針對常見JavaScript編程問題的簡明解決方案。我們看到許多實例,其中包含多個語句的命令式解決方案被轉換為使用各種內置方法和語言結構的聲明式單行代碼。

          這些緊湊的解決方案有時性能和可讀性較低,但使用它們可以證明您的編程能力和對語言的掌握程度。使用任何一種方法,我們都是需要根據具體的情況來使用。

          不知道今年大家有沒有感受到來自互聯網的“寒氣”,至少我是感受到了,面試的時候手寫代碼時很常見很常見的事情了。有時候沒遇到過還真一時半會寫不出來,企業招聘的要求也是越來越高,尤其是一些大廠會對 JS 的功底有著更加苛刻的要求,所以學會手寫常見的 JS 模塊好像已經快變為一個基本技能了,也慢慢變為我們手寫 webpack 手寫 mini-vue 的一個 coding 基礎了。

          當然我們也不完全是為了去準備面試而去學習這些常見模塊。死磕這些難啃的骨頭之后,你會從中學到很多優秀的思想,對你的職業生涯也是很有幫助的。而且閱讀代碼本身就是一個很好的習慣,讀懂并且理解寫代碼人的思維邏輯更加重要。

          本文中涉及到的手寫模塊,大多數都是從網上以及自己的面試經驗借鑒而來的。希望能對你有個幫助。

          基礎手寫

          全排列(力扣原題)

          要求以數組的形式返回字符串參數的所有排列組合。

          注意:

          1. 字符串參數中的字符無重復且僅包含小寫字母
          2. 返回的排列組合數組不區分順序
          const _permute=string=> {
            const result=[]
            const map=new Map()
            const dfs=(path)=> {
              if (path.length===string.length) {
                result.push(path)
                return
              }
              for (let i=0; i < string.length; i++) {
                if (map.get(string[i])) continue
                map.set(string[i], true)
                path +=string[i]
                dfs(path)
                path=path.substring(0, path.length - 1)
                map.set(string[i], false)
              }
            }
            dfs('')
            return result
          }
          console.log(_permute('abc')) // [ 'abc', 'acb', 'bac', 'bca', 'cab', 'cba' ]
          

          instanceof

          • 如果 target 為基本數據類型直接返回 false
          • 判斷 Fn.prototype 是否在 target 的隱式原型鏈上
          const _instanceof=(target, Fn)=> {
            if ((typeof target !=='object' && typeof target !=='function') || target===null)
            return false
            
            let proto=target.__proto__
            while (true) {
              if (proto===null) return false
              if (proto===Fn.prototype) return true
              proto=proto.__proto__
            }
          }
          function A() {}
          const a=new A()
          console.log(_instanceof(a, A)) // true
          console.log(_instanceof(1, A)) // false
          

          Array.prototype.map

          • map 中的 exc 接受三個參數,分別是: 元素值、元素下標和原數組
          • map 返回的是一個新的數組,地址不一樣
          // 這里不能直接使用箭頭函數,否則無法訪問到 this
          Array.prototype._map=function (exc) {
            const result=[]
            this.forEach((item, index, arr)=> {
              result[index]=exc(item, index, arr)
            })
            return result
          }
          const a=new Array(2).fill(2)
          console.log(a.map((item, index, arr)=> item * index + 1)) // [1,3]
          console.log(a._map((item, index, arr)=> item * index + 1))// [1,3]
          

          Array.prototype.filter

          • filter 中的 exc 接受三個參數,與map一致,主要實現的是數組的過濾功能,會根據 exc 函數的返回值來判斷是否“留下”該值。
          • filter 返回的是一個新的數組,地址不一致。
          Array.prototype._filter=function (exc) {
            const result=[]
            this.forEach((item, index, arr)=> {
              if (exc(item, index, arr)) {
                result.push(item)
              }
            })
            return result
          }
          const b=[1, 3, 4, 5, 6, 2, 5, 1, 8, 20]
          
          console.log(b._filter(item=> item % 2===0)) // [ 4, 6, 2, 8, 20 ]
          

          Array.prototype.reduce

          • reduce 接受兩個參數,第一個為 exc 函數,第二個為初始值,如果不傳默認為 0
          • reduce 最終會返回一個值,當然不一定是 Number 類型的,取決于你是怎么計算的,每次的計算結果都會作為下次 exc 中的第一個參數
          Array.prototype._reduce=function (exc, initial=0) {
            let result=initial
            this.forEach((item)=> {
              result=exc(result, item)
            })
            return result
          }
          console.log(b.reduce((pre, cur)=> pre + cur, 0)) // 55
          console.log(b._reduce((pre, cur)=> pre + cur, 0)) // 55
          

          Object.create

          MDN[1] Object.create() 方法用于創建一個新對象,使用現有的對象來作為新創建對象的原型(prototype)。

          Object.prototype._create=function (proto) {
            const Fn=function () { }
            Fn.prototype=proto
            return new Fn()
          }
          function A() { }
          const obj=Object.create(A)
          const obj2=Object._create(A)
          console.log(obj.__proto__===A) // true
          console.log(obj.__proto__===A) // true
          

          Function.prototype.call

          call() 方法使用一個指定的 this 值和單獨給出的一個或多個參數來調用一個函數。

          Function.prototype._call=function (ctx, ...args) {
            // 如果不為空,則需要進行對象包裝
            const o=ctx==undefined ? window : Object(ctx)
            // 給 ctx 添加獨一無二的屬性
            const key=Symbol()
            o[key]=this
            // 執行函數,得到返回結果
            const result=o[key](...args "key")
            // 刪除該屬性
            delete o[key]
            return result
          }
          
          const obj={
            name: '11',
            fun() {
              console.log(this.name)
            }
          }
          
          const obj2={ name: '22' }
          obj.fun() // 11
          obj.fun.call(obj2) // 22
          obj.fun._call(obj2) // 22
          

          Function.prototype.bind

          bind() 方法創建一個新的函數,在 bind() 被調用時,這個新函數的 this 被指定為 bind() 的第一個參數,而其余參數將作為新函數的參數,供調用時使用。

          const obj={
            name: '11',
            fun() {
              console.log(this.name)
            }
          }
          Function.prototype._bind=function (ctx, ...args) {
            // 獲取函數體
            const _self=this
            // 用一個新函數包裹,避免立即執行
            const bindFn=(...reset)=> {
              return _self.call(ctx, ...args, ...reset)
            }
            return bindFn
          }
          const obj2={ name: '22' }
          obj.fun() // 11
          const fn=obj.fun.bind(obj2)
          const fn2=obj.fun._bind(obj2)
          fn() // 22
          fn2() // 22
          

          New 關鍵字

          new 運算符創建一個用戶定義的對象類型的實例或具有構造函數的內置對象的實例。

          const _new=function(constructor) {
            // 創建一個空對象
            const obj={}
            // 原型鏈掛載
            obj.__proto__=constructor.prototype;
            // 將obj 復制給構造體中的 this,并且返回結果
            const result=constructor.call(obj)
            // 如果返回對象不為一個對象則直接返回剛才創建的對象
            return typeof result==='object' && result !==null ? : result : obj
          }
          

          淺拷貝

          const _shallowClone=target=> {
            // 基本數據類型直接返回
            if (typeof target==='object' && target !==null) {
              // 獲取target 的構造體
              const constructor=target.constructor
              // 如果構造體為以下幾種類型直接返回
              if (/^(Function|RegExp|Date|Map|Set)$/i.test(constructor.name)) return target
              // 判斷是否是一個數組
              const cloneTarget=Array.isArray(target) ? [] : {}
              for (prop in target) {
                // 只拷貝其自身的屬性
                if (target.hasOwnProperty(prop)) {
                  cloneTarget[prop]=target[prop]
                }
              }
              return cloneTarget
            } else {
              return target
            }
          }
          

          深拷貝

          實現思路和淺拷貝一致,只不過需要注意幾點

          1. 函數 正則 日期 ES6新對象 等不是直接返回其地址,而是重新創建
          2. 需要避免出現循環引用的情況
          const _completeDeepClone=(target, map=new WeakMap())=> {
            // 基本數據類型,直接返回
            if (typeof target !=='object' || target===null) return target
            // 函數 正則 日期 ES6新對象,執行構造題,返回新的對象
            const constructor=target.constructor
            if (/^(Function|RegExp|Date|Map|Set)$/i.test(constructor.name)) return new constructor(target)
            // map標記每一個出現過的屬性,避免循環引用
            if (map.get(target)) return map.get(target)
            map.set(target, true)
            const cloneTarget=Array.isArray(target) ? [] : {}
            for (prop in target) {
              if (target.hasOwnProperty(prop)) {
                cloneTarget[prop]=_completeDeepClone(target[prop], map)
              }
            }
            return cloneTarget
          }
          

          寄生組合式繼承

          一圖勝千言

          function Parent(name) {
            this.name=name
          }
          Parent.prototype.getName=function () {
            return this.name
          }
          
          function Son(name, age) {
            // 這里其實就等于 this.name=name
            Parent.call(this, name)
            this.age=age
          }
          
          Son.prototype.getAge=function () {
            return this.age
          }
          Son.prototype.__proto__=Object.create(Parent.prototype)
          
          const son1=new Son('shao', 20)
          
          console.log(son1.getName()) // shao
          console.log(son1.getAge()) // 20
          

          發布訂閱者模式

          class EventEmitter {
            constructor() {
              // key: 事件名
              // value: callback [] 回調數組
              this.events={}
            }
            on(name, callback) {
              if (this.events[name]) {
                this.events[name].push(callback)
              } else {
                this.events[name]=[callback]
              }
            }
            off(name, callback) {
              if (!this.message[name]) return;
              if (!callback) {
                // 如果沒有callback,就刪掉整個事件
                this.message[name]=undefined;
              }
              this.message[name]=this.message[name].filter((item)=> item !==callback);
          
            }
            emit(name, ...args) {
              if (!this.events[name]) return
              this.events[name].forEach(cb=> cb(...args))
            }
          }
          

          觀察者模式

          class Observerd {
            constructor() {
              // 我要看看到底有多少人在觀察俺
              this.observerList=[]
            }
            addObserver(observer) {
              // 添加一個觀察俺的人
              this.observerList.push(observer)
            }
            notify() {
              // 我要鬧點動靜,所有觀察者都會知道這個信息,具體怎么做就是他們自己的事情了
              this.observerList.forEach(observer=> observer.update())
            }
          }
          
          
          class Observer {
            constructor(doSome) {
              // 觀察到小白鼠有動靜之后,觀察者做的事情
              this.doSome=doSome
            }
            update() {
              console.log(this.doSome)
            }
          }
          
          const ob1=new Observer('我是ob1,我觀察到小白鼠有反應了,太餓了,我得去吃個飯了')
          const ob2=new Observer('我是ob2,我觀察到小白鼠有反應了,我要繼續工作!')
          const xiaoBaiShu=new Observerd()
          xiaoBaiShu.addObserver(ob1)
          xiaoBaiShu.addObserver(ob2)
          xiaoBaiShu.notify() // .... .... 
          

          多說一句:怎么理解發布訂閱者和觀察者的區別呢 ?

          其實發布訂閱者模式只有一個中間者,好像啥事情都需要它親自來做。而且仔細觀察的話,發布訂閱者模式會存在一個事件名和事件的對應關系,今天可以發布天氣預報,只有訂閱了天氣預報的才會被通知,訂閱了 KFC瘋狂星期四鬧鐘事件 的不會被提醒。

          而觀察者模式,等被觀察者發出了一點動靜(執行notify),所有觀察者都會被通知。

          節流

          節流函數(throttle)就是讓事件處理函數(handler)在大于等于執行周期時才能執行,周期之內不執行,即事件一直被觸發,那么事件將會按每小段固定時間一次的頻率執行。

          function throttle(fn, delay=300) {
            // 這里始終記得字節二面的時候,建議我不寫 flag 好家伙
            let isThrottling=false
            // 核心思路,函數多次執行只有當 isThrottling 為 false 時才會進入函數體
            return function (...args) {
              if (!isThrottling) {
                isThrottling=true
                setTimeout(()=> {
                  isThrottling=false
                  fn.apply(this, args)
                }, delay)
              }
            }
          }
          

          防抖

          事件響應函數在一段時間后才執行,如果這段時間內再次調用,則重新計算執行時間

          function debounce(fn, delay=300) {
            let timer=null
            return function (...args) {
              // 每次進來都會清空定時器,所以在 delay 事件中重復執行之后執行最后一次
              clearInterval(timer)
              timer=setTimeout(()=> {
                fn.apply(this, args)
              }, delay)
            }
          }
          

          once 函數

          函數返回結果會被緩存下來,只會計算一次。

          const f=(x)=> x;
          const onceF=once(f);
          //=> 3
          onceF(3);
          //=> 3
          onceF(4);
          
          const once=(fn)=> {
            let res, isFirst=true
            return function (...args) {
              if (!isFirst) return res
              res=fn.call(this, ...args)
              isFirst=false
              return res
            }
          }
          

          累加函數應用

          實現一個累加函數,下面的幾種情況都能正確的調用。

          console.log(sum(1, 2)(3)()) // 6
          console.log(sum(1)(2)(3)()) // 6
          console.log(sum(1, 2, 4)(4)()) // 11
          
          function sum(...args) {
            let params=args
            const _sum=(...newArgs)=> {
              if (newArgs.length===0) {
                return params.reduce((pre, cur)=> pre + cur, 0)
              } else {
                params=[...params, ...newArgs]
                return _sum
              }
            }
            return _sum
          }
          

          進階

          實現 repeat 方法

          function repeat(fn, times, delay) {
            return async function (...args) {
              for (let i=0; i < times; i++) {
                await new Promise((resolve, reject)=> {
                  setTimeout(()=> {
                    fn.call(this, ...args)
                    resolve()
                  }, delay)
                })
              }
            }
          }
          const repeatFn=repeat(console.log, 4, 1000)
          // 函數調用四次,每次間隔 1s 打印 hello
          repeatFn('hello')
          

          實現 Promise.all/race/allSettled/any

          • Promise 身上的這些方法返回的都是一個 Promise
          • Promise.resolve 接受一個 Promise,若非 promise 則將其變成功狀態的 Promise
          // 有一個失敗則返回失敗的結果,全部成功返回全成功的數組
          Promise.all=function (promiseList=[]) {
            return new Promise((resolve, reject)=> {
              const result=[]
              let count=0
              if (promiseList.length===0) {
                resolve(result)
                return
              }
              for (let i=0; i < promiseList.length; i++) {
                Promise.resolve(promiseList[i]).then(res=> {
                  result[i]=res
                  count++
                  // 不能直接通過 result.length 進行比較,因為 會存在下標大的先賦值
                  // 例如 i=3 第一個返回結果,此時數組變為[empty,empty,empty,res]
                  if (count===promiseList.length) {
                    resolve(result)
                  }
                }).catch(e=> {
                  reject(e)
                })
              }
            })
          }
          // 返回第一個成功或失敗的結果
          Promise.race=function (promiseList=[]) {
            return new Promise((resolve, reject)=> {
              if (promiseList.length===0) {
                return resolve([])
              }
              for (let i=0; i < promiseList.length; i++) {
                Promise.resolve(promiseList[i]).then(res=> {
                  resolve(res)
                }).catch(e=> {
                  reject(e)
                })
              }
            })
          }
          // 無論成功約否都返回,但是會添加一個 status 字段用于標記成功/失敗
          Promise.allSettled=function (promiseList=[]) {
            return new Promise((resolve, reject)=> {
              const result=[]
              let count=0
          
              const addRes=(i, data)=> {
                result[i]=data
                count++
                if (count===promiseList.length) {
                  resolve(result)
                }
              }
              
              if (promiseList.length===0) return resolve(result)
              for (let i=0; i < promiseList.length; i++) {
                Promise.resolve(promiseList[i]).then(res=> {
                  addRes(i, { status: 'fulfilled', data: res })
                }).catch(e=> {
                  addRes(i, { status: 'rejected', data: e })
                })
              }
            })
          }
          // AggregateError,當多個錯誤需要包裝在一個錯誤中時,該對象表示一個錯誤。
          // 和 Promise.all 相反,全部失敗返回失敗的結果數組,有一個成功則返回成功結果
          Promise.any=function (promiseList=[]) {
            return new Promise((resolve, reject)=> {
              if (promiseList.length===0) return resolve([])
              let count=0
              const result=[]
              for (let i=0; i < promiseList.length; i++) {
                Promise.resolve(promiseList[i]).then(res=> {
                  resolve(res)
                }).catch(e=> {
                  count++
                  result[i]=e
                  if (count===promiseList.length) {
                    reject(new AggregateError(result))
                  }
                })
              }
            })
          }
          

          整數千分位加逗號

          1234567 -> 1,234,567
          
          function toThousands(num) {
            num=num.toString()
            let result=''
            while (num.length > 3) {
              result=',' + num.substring(num.length - 3) + result
              num=num.substring(0, num.length - 3)
            }
            result=num + result
            return result
          }
          console.log(toThousands(1234567)) // 1,234,567
          console.log(toThousands(123456)) // 123,456
          

          洗牌函數

          有幾張牌張牌,用 js 來進行亂序排列,要保持公平性

          const shuffle=(arr)=> {
            // 不影響原來的數組
            const result=[...arr]
            for (let i=result.length; i > 0; i--) {
              // 隨機從 [0,i - 1] 產生一個 index, 將 i - 1 于 index 對應數組的值進行交換
              const index=Math.floor(Math.random() * i);
              [result[index], result[i - 1]]=[result[i - 1], result[index]]
            }
            return result
          }
          const arr=[1, 2, 3, 4, 5]
          console.log(shuffle(arr)) // [ 3, 1, 2, 5, 4 ]
          console.log(shuffle(arr)) // [ 2, 3, 5, 1, 4 ]
          console.log(shuffle(arr)) // [ 4, 2, 3, 1, 5 ]
          console.log(shuffle(arr)) // [ 5, 4, 2, 3, 1 ]
          

          a==1 && a==2 && a==3

          如何讓 a==1 && a==2 && a==3 返回 true 呢

          方案一

          利用隱式轉換會調用 valueOf

          const a={
            value: 1,
            valueOf() {
              return this.value++
            }
          }
          console.log(a==1 && a==2 && a==3) // true
          

          方案二

          在對象 valueOf 函數不存在的情況下會調用 toString 方法

          const a={
            value: 1,
            toString() {
              return this.value++
            }
          }
          
          console.log(a==1 && a==2 && a==3) // true
          

          方案三

          利用Object.defineProperty 在全局 window 上掛載一個 a 屬性

          let _a=1
          Object.defineProperty(window, 'a', {
            get() {
              return _a++
            }
          })
          
          console.log(a==1 && a==2 && a==3)
          

          手寫LRU

          LRU是Least Recently Used的縮寫,即最近最少使用,是一種常用的頁面置換算法[2],選擇最近最久未使用的頁面予以淘汰。該算法賦予每個頁面[3]一個訪問字段,用來記錄一個頁面自上次被訪問以來所經歷的時間 t,當須淘汰一個頁面時,選擇現有頁面中其 t 值最大的,即最近最少使用的頁面予以淘汰。

          力扣地址[4]

          /**
           * @param {number} capacity
           */
          var LRUCache=function(capacity) {
              this.map=new Map()
              this.capacity=capacity
          };
          
          /** 
           * @param {number} key
           * @return {number}
           */
          LRUCache.prototype.get=function(key) {
              if(this.map.has(key)){
                  const value=this.map.get(key)
                  // 更新存儲位置
                  this.map.delete(key)
                  this.map.set(key,value)
                  return value
              }
              return - 1
          };
          
          /** 
           * @param {number} key 
           * @param {number} value
           * @return {void}
           */
          LRUCache.prototype.put=function(key, value) {
              if(this.map.has(key)){
                  this.map.delete(key)
              }
              this.map.set(key,value)
              // 如果此時超過了最長可存儲范圍
              if(this.map.size > this.capacity){
                  // 刪除 map 中最久未使用的元素
                  this.map.delete(this.map.keys().next().value)
              }
          };
          

          優化一下

          更上一層樓

          Generator

          先看看下面輸出的內容

          async function getResult() {
              await new Promise((resolve, reject)=> {
                  setTimeout(()=> {
                      resolve(1);
                      console.log(1);
                  }, 1000);
              })
              await new Promise((resolve, reject)=> {
                  setTimeout(()=> {
                      resolve(2);
                      console.log(2);
                  }, 500);
              })
              await new Promise((resolve, reject)=> {
                  setTimeout(()=> {
                      resolve(3);
                      console.log(3);
                  }, 100);
              })
          
          }
          getResult()
          // 1 2 3 
          

          那如何使用 Es6 中的 generator 實現類似的效果呢 ?

          function* getResult(params) {
              yield new Promise((resolve, reject)=> {
                  setTimeout(()=> {
                      resolve(1);
                      console.log(1);
                  }, 1000);
              })
              yield new Promise((resolve, reject)=> {
                  setTimeout(()=> {
                      resolve(2);
                      console.log(2);
                  }, 500);
              })
              yield new Promise((resolve, reject)=> {
                  setTimeout(()=> {
                      resolve(3);
                      console.log(3);
                  }, 100);
              })
          }
          const gen=getResult()
          // gen.next().value 就是每一次 yield 之后返回的 Promise
          // gen.next()={value: yeild 返回的數據,done: 迭代器是否走完}
          gen.next().value.then(()=> {
              gen.next().value.then(()=> {
                  gen.next();
              });
          });// 依次打印 1 2 3
          

          將 gen.next() 封裝一層,讓其自己能夠實現遞歸調用

          const gen=getResult()
          function co(g) {
            const nextObj=g.next();
            // 遞歸停止條件:當迭代器迭代到最后一個 yeild 
            if (nextObj.done) {
              return;
            }
            nextObj.value.then(()=>{
              co(g)
            })
          }
          co(gen)
          

          async-pool

          JS 控制并發請求, 參考文章 mp.weixin.qq.com/s/yWOPoef9ixuSBWApZQhjIg

          aysnc-pool 的基本使用

          const timeout=i=> new Promise(resolve=> setTimeout(()=> resolve(i), i));
          await asyncPool(2, [1000, 5000, 3000, 2000], timeout);
          

          asyncPool 這個函數接受三個參數

          • poolLimit(數字類型):表示限制的并發數;
          • array(數組類型):表示任務數組;
          • iteratorFn(函數類型):表示迭代函數,用于實現對每個任務項進行處理,該函數會返回一個 Promise 對象或異步函數。

          這里提醒一下,promise.then 中的函數執行是一異步的,而賦值是同步的

          const a=Promise.resolve().then(()=>console.log(a))
          // 等價于 此時 a 等于一個 pending 狀態的 promise
          const a=Promise.resolve().then()
          a.then(()=>{
            console.log(a)
          })
          

          手寫實現,這部分可能會多花點時間。可以拷貝代碼多調試幾次就知道了

          async function asyncPool(poolLimit, array, iteratorFn) {
            const ret=[]; // 存儲所有的異步任務
            const executing=[]; // 存儲正在執行的異步任務
            for (const item of array) {
              // 調用iteratorFn函數創建異步任務
              const p=Promise.resolve().then(()=> iteratorFn(item, array));
              ret.push(p); // 保存新的異步任務
          
              // 當poolLimit值小于或等于總任務個數時,進行并發控制
              if (poolLimit <=array.length) {
                // 當任務完成后,從正在執行的任務數組中移除已完成的任務
                const e=p.then(()=> executing.splice(executing.indexOf(e), 1));
                executing.push(e); // 保存正在執行的異步任務
                if (executing.length >=poolLimit) {
                  await Promise.race(executing); // 等待較快的任務執行完成
                }
              }
            }
            return Promise.all(ret);
          }
          
          const timeout=i=> new Promise(resolve=> setTimeout(()=> { console.log(i); resolve(i) }, i));
          // 當然,limit <=0 的時候 我們可以理解為只允許一個請求存在 
          asyncPool(2, [1000, 5000, 3000, 2000], timeout).then(res=> {
            console.log(res)
          })
          

          總共花費 6 s 時間,符合預期

          總結

          收集這些手寫的時候,自己也學到了很多東西,其實也是一個查漏補缺的過程。感謝你看到這里,點贊收藏 offer ++

          源:中國網2015-04-01

          存款保險制度的確立,以及利率市場化加速,這對于互聯網金融、民營銀行的良性發展意義重大。將來一部分管理能力差、經營不穩定、競爭力不強的銀行就可能會被退出市場,或被整合和收購。

          作者:王振峰

          醞釀20多年的存款保險制度終于出爐了。近日,國務院正式公布《存款保險條例》,并將于今年5月1日起正式實施。(3月31日 中國網)

          《條例》稱,建立和規范存款保險制度,是為了“依法保護存款人的合法權益,及時防范和化解金融風險,維護金融穩定”。《條例》共計二十三條,其中強制規定所有的銀行業金融機構,都必須依規投保存款保險。存款保險實行限額償付,目前最高償付50萬元。此外,《條例》還實行差別費率,有觀點認為,這將降低存款機構“道德風險”。

          今年全國兩會答記者問時,國務院總理李克強就明確表示,今年,我們就要出臺存款保險制度。他還指出,“我們允許個案性金融風險的發生,按市場化的原則進行清算,這是為了防止道德風險,也增強人們的風險意識”。而兩會期間人民銀行行長周小川的答問中,把存款保險制度推出的時間更具體一些;他表示,存款保險制度作為金融改革重要的一步棋,各方面條件已經基本成熟,“我個人估計,今年上半年就可以出臺”。

          對于推出存款保險制度的意義,普遍的觀點認為,這將有助于金融改革邁入深水區,我國金融改革整體推進已是箭在弦上。作為當前金融改革的重要環節,存款保險制度的建立,以及允許銀行破產制度,體現了新一輪金融改革重在制度構建、重在完善市場、重在機制形成、重在資源配置與功能優化。

          引入并推出存款保險制度,是存款利率自由浮動的先決條件;可以說存款保險一“吹號”,利率市場化“在路上”!建立金融機構破產退出機制,建立存款保險制度,可視為進行利率市場化建設的最重要配套制度建設。當然,存保制度之下,也將意味著今后那些經營不善的銀行可能會破產、被清算。存款保險制度的建立,也就意味打破了過去的“剛兌”機制,銀行一旦倒閑,國家再也不兜底了。普通老百姓應準備樹立銀行存儲及各種理財的風險意識:“雞蛋不要放在一個籃子里”。

          建立存款保險制度,主要也在于防范系統性風險,而堅持市場化導向,進一步釋放我國金融體系活力,建成起完善的金融機構體系、金融市場體系、金融運行體系和金融監管體系,使金融更好地為實體經濟服務,也是這個制度推行的重要出發點。存款保險制度的確立,以及利率市場化加速,這對于互聯網金融、民營銀行的良性發展意義重大。

          最高償付限額50萬元,銀行間存款分布可能比以前相對平衡,儲戶也可能會更多元化的投資,互聯網金融和民營銀行的發展也將更有空間和余地。尤其是出臺存款保險制度,這無疑為發展民營銀行掃清了障礙。在政策鼓勵金融行業向民營資本開放的背景下,或許民營資本進入銀行業的春天已經不遠了。

          今后,銀行體系內部之間、金融機構與非金融機構間、以及互聯網金融與傳統金融機構之間,誰能在競爭中脫穎而出,將更多地由市場來決定。這一方面有利于激活金融市場活力,有利于金融創新,進一步增強金融服務實體經濟的能力;另一方面,普通老百姓也因為這種市場競爭得到更優的金融服務,也可以有更多的選擇和更好的收益機會。當然,存款保險建立以后,銀行業金融機構因強制投保,運營成本也將提高不少,利率市場化也將促進銀行業洗牌,將來一部分管理能力差、經營不穩定、競爭力不強的銀行就可能會被退出市場,或被整合和收購。如此來看,隨著存款保險制度的推行,銀行洗牌時代也將來臨。

          http://opinion.china.com.cn/opinion_82_125982.html


          主站蜘蛛池模板: 亚洲日本va一区二区三区| 亚洲国产视频一区| 精品欧洲av无码一区二区14| 波多野结衣中文字幕一区| 亚洲精品伦理熟女国产一区二区 | 久久精品岛国av一区二区无码| 国产成人免费一区二区三区| 国模视频一区二区| 一区二区三区视频观看| 亚洲国产成人久久综合一区77 | 国产品无码一区二区三区在线| 亚洲综合无码一区二区痴汉 | 国产美女精品一区二区三区| 久久se精品一区精品二区| 天堂资源中文最新版在线一区 | 消息称老熟妇乱视频一区二区| 国99精品无码一区二区三区| 精品国产乱子伦一区二区三区 | 日本精品一区二区三本中文| 无码人妻一区二区三区精品视频 | 亚洲爆乳精品无码一区二区 | 日韩精品一区二区三区老鸦窝| 日本人真淫视频一区二区三区| 伊人激情AV一区二区三区| 亚洲一区二区三区四区视频 | 国产手机精品一区二区| 亚洲一区日韩高清中文字幕亚洲| 亚洲色精品vr一区二区三区| 国产精品亚洲一区二区无码| 国产91精品一区二区麻豆网站 | 免费萌白酱国产一区二区三区 | 亚洲av午夜福利精品一区人妖| 精品国产一区二区三区在线 | 日韩A无码AV一区二区三区| 国产视频一区在线观看| 一区二区网站在线观看| 午夜视频一区二区| 国产精品久久久久一区二区| 久久精品无码一区二区WWW| 国产丝袜美女一区二区三区| 国产午夜毛片一区二区三区|