整合營銷服務商

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

          免費咨詢熱線:

          JavaScript 工具函數大全「新」



          一線大廠筆試題靈感來源

          目錄:

          • 第一部分:數組
          • 第二部分:函數
          • 第三部分:字符串
          • 第四部分:對象
          • 第五部分:數字
          • 第六部分:瀏覽器操作及其它

          篩選自以下兩篇文章:

          《127 Helpful JavaScript Snippets You Can Learn in 30 Seconds or Less》

          《30 seconds of code》

          https://github.com/30-seconds/30-seconds-of-code

          原本只想篩選下上面的那篇文章,在精簡掉了部分多余且無用的工具函數后,感覺不夠。于是順藤摸瓜,找到了原地址: 30 seconds of code

          然后將所有代碼段都看了遍,篩選了以下一百多段代碼片段,并加入了部分自己的理解。

          另外,本文工具函數的命名非常值得借鑒。


          1. 第一部分:數組

          1. all:布爾全等判斷

          const all = (arr, fn = Boolean) => arr.every(fn);
          
          all([4, 2, 3], x => x > 1); // true
          all([1, 2, 3]); // true
          復制代碼

          2. allEqual:檢查數組各項相等

          const allEqual = arr => arr.every(val => val === arr[0]);
          
          allEqual([1, 2, 3, 4, 5, 6]); // false
          allEqual([1, 1, 1, 1]); // true
          復制代碼

          3. approximatelyEqual:約等于

          const approximatelyEqual = (v1, v2, epsilon = 0.001) => Math.abs(v1 - v2) < epsilon;
          
          approximatelyEqual(Math.PI / 2.0, 1.5708); // true
          復制代碼

          4. arrayToCSV:數組轉CSV格式(帶空格的字符串)

          
          const arrayToCSV = (arr, delimiter = ',') =>
            arr.map(v => v.map(x => `"${x}"`).join(delimiter)).join('\n');
            
          arrayToCSV([['a', 'b'], ['c', 'd']]); // '"a","b"\n"c","d"'
          arrayToCSV([['a', 'b'], ['c', 'd']], ';'); // '"a";"b"\n"c";"d"'
          復制代碼

          5. arrayToHtmlList:數組轉li列表

          此代碼段將數組的元素轉換為<li>標簽,并將其附加到給定ID的列表中。

          const arrayToHtmlList = (arr, listID) =>
            (el => (
              (el = document.querySelector('#' + listID)),
              (el.innerHTML += arr.map(item => `<li>${item}</li>`).join(''))
            ))();
            
          arrayToHtmlList(['item 1', 'item 2'], 'myListID');
          復制代碼

          6. average:平均數

          const average = (...nums) => nums.reduce((acc, val) => acc + val, 0) / nums.length;
          average(...[1, 2, 3]); // 2
          average(1, 2, 3); // 2
          復制代碼

          7. averageBy:數組對象屬性平均數

          此代碼段將獲取數組對象屬性的平均值

          const averageBy = (arr, fn) =>
            arr.map(typeof fn === 'function' ? fn : val => val[fn]).reduce((acc, val) => acc + val, 0) /
            arr.length;
            
          averageBy([{ n: 4 }, { n: 2 }, { n: 8 }, { n: 6 }], o => o.n); // 5
          averageBy([{ n: 4 }, { n: 2 }, { n: 8 }, { n: 6 }], 'n'); // 5
          復制代碼

          8. bifurcate:拆分斷言后的數組

          可以根據每個元素返回的值,使用reduce()和push() 將元素添加到第二次參數fn中 。

          const bifurcate = (arr, filter) =>
            arr.reduce((acc, val, i) => (acc[filter[i] ? 0 : 1].push(val), acc), [[], []]);
          bifurcate(['beep', 'boop', 'foo', 'bar'], [true, true, false, true]); 
          // [ ['beep', 'boop', 'bar'], ['foo'] ]
          復制代碼

          9. castArray:其它類型轉數組

          const castArray = val => (Array.isArray(val) ? val : [val]);
          
          castArray('foo'); // ['foo']
          castArray([1]); // [1]
          castArray(1); // [1]
          復制代碼

          10. compact:去除數組中的無效/無用值

          const compact = arr => arr.filter(Boolean);
          
          compact([0, 1, false, 2, '', 3, 'a', 'e' * 23, NaN, 's', 34]); 
          // [ 1, 2, 3, 'a', 's', 34 ]
          復制代碼

          11. countOccurrences:檢測數值出現次數

          const countOccurrences = (arr, val) => arr.reduce((a, v) => (v === val ? a + 1 : a), 0);
          countOccurrences([1, 1, 2, 1, 2, 3], 1); // 3
          復制代碼

          12. deepFlatten:遞歸扁平化數組

          const deepFlatten = arr => [].concat(...arr.map(v => (Array.isArray(v) ? deepFlatten(v) : v)));
          
          deepFlatten([1, [2], [[3], 4], 5]); // [1,2,3,4,5]
          復制代碼

          13. difference:尋找差異(并返回第一個數組獨有的)

          此代碼段查找兩個數組之間的差異,并返回第一個數組獨有的。

          
          const difference = (a, b) => {
            const s = new Set(b);
            return a.filter(x => !s.has(x));
          };
          
          difference([1, 2, 3], [1, 2, 4]); // [3]
          復制代碼

          14. differenceBy:先執行再尋找差異

          在將給定函數應用于兩個列表的每個元素之后,此方法返回兩個數組之間的差異。

          const differenceBy = (a, b, fn) => {
            const s = new Set(b.map(fn));
            return a.filter(x => !s.has(fn(x)));
          };
          
          differenceBy([2.1, 1.2], [2.3, 3.4], Math.floor); // [1.2]
          differenceBy([{ x: 2 }, { x: 1 }], [{ x: 1 }], v => v.x); // [ { x: 2 } ]
          復制代碼

          15. dropWhile:刪除不符合條件的值

          此代碼段從數組頂部開始刪除元素,直到傳遞的函數返回為true。

          const dropWhile = (arr, func) => {
            while (arr.length > 0 && !func(arr[0])) arr = arr.slice(1);
            return arr;
          };
          
          dropWhile([1, 2, 3, 4], n => n >= 3); // [3,4]
          復制代碼

          16. flatten:指定深度扁平化數組

          此代碼段第二參數可指定深度。

          const flatten = (arr, depth = 1) =>
            arr.reduce((a, v) => a.concat(depth > 1 && Array.isArray(v) ? flatten(v, depth - 1) : v), []);
          
          flatten([1, [2], 3, 4]); // [1, 2, 3, 4]
          flatten([1, [2, [3, [4, 5], 6], 7], 8], 2); // [1, 2, 3, [4, 5], 6, 7, 8]
          復制代碼

          17. indexOfAll:返回數組中某值的所有索引

          此代碼段可用于獲取數組中某個值的所有索引,如果此值中未包含該值,則返回一個空數組。

          const indexOfAll = (arr, val) => arr.reduce((acc, el, i) => (el === val ? [...acc, i] : acc), []);
          
          indexOfAll([1, 2, 3, 1, 2, 3], 1); // [0,3]
          indexOfAll([1, 2, 3], 4); // []
          復制代碼

          18. intersection:兩數組的交集

          
          const intersection = (a, b) => {
            const s = new Set(b);
            return a.filter(x => s.has(x));
          };
          
          intersection([1, 2, 3], [4, 3, 2]); // [2, 3]
          復制代碼

          19. intersectionWith:兩數組都符合條件的交集

          此片段可用于在對兩個數組的每個元素執行了函數之后,返回兩個數組中存在的元素列表。

          
          const intersectionBy = (a, b, fn) => {
            const s = new Set(b.map(fn));
            return a.filter(x => s.has(fn(x)));
          };
          
          intersectionBy([2.1, 1.2], [2.3, 3.4], Math.floor); // [2.1]
          復制代碼

          20. intersectionWith:先比較后返回交集

          const intersectionWith = (a, b, comp) => a.filter(x => b.findIndex(y => comp(x, y)) !== -1);
          
          intersectionWith([1, 1.2, 1.5, 3, 0], [1.9, 3, 0, 3.9], (a, b) => Math.round(a) === Math.round(b)); // [1.5, 3, 0]
          復制代碼

          21. minN:返回指定長度的升序數組

          const minN = (arr, n = 1) => [...arr].sort((a, b) => a - b).slice(0, n);
          
          minN([1, 2, 3]); // [1]
          minN([1, 2, 3], 2); // [1,2]
          復制代碼

          22. negate:根據條件反向篩選

          
          const negate = func => (...args) => !func(...args);
          
          [1, 2, 3, 4, 5, 6].filter(negate(n => n % 2 === 0)); // [ 1, 3, 5 ]
          復制代碼

          23. randomIntArrayInRange:生成兩數之間指定長度的隨機數組

          const randomIntArrayInRange = (min, max, n = 1) =>
            Array.from({ length: n }, () => Math.floor(Math.random() * (max - min + 1)) + min);
            
          randomIntArrayInRange(12, 35, 10); // [ 34, 14, 27, 17, 30, 27, 20, 26, 21, 14 ]
          復制代碼

          24. sample:在指定數組中獲取隨機數

          const sample = arr => arr[Math.floor(Math.random() * arr.length)];
          
          sample([3, 7, 9, 11]); // 9
          復制代碼

          25. sampleSize:在指定數組中獲取指定長度的隨機數

          此代碼段可用于從數組中獲取指定長度的隨機數,直至窮盡數組。 使用Fisher-Yates算法對數組中的元素進行隨機選擇。

          const sampleSize = ([...arr], n = 1) => {
            let m = arr.length;
            while (m) {
              const i = Math.floor(Math.random() * m--);
              [arr[m], arr[i]] = [arr[i], arr[m]];
            }
            return arr.slice(0, n);
          };
          
          sampleSize([1, 2, 3], 2); // [3,1]
          sampleSize([1, 2, 3], 4); // [2,3,1]
          復制代碼

          26. shuffle:“洗牌” 數組

          此代碼段使用Fisher-Yates算法隨機排序數組的元素。

          
          const shuffle = ([...arr]) => {
            let m = arr.length;
            while (m) {
              const i = Math.floor(Math.random() * m--);
              [arr[m], arr[i]] = [arr[i], arr[m]];
            }
            return arr;
          };
          
          const foo = [1, 2, 3];
          shuffle(foo); // [2, 3, 1], foo = [1, 2, 3]
          復制代碼

          27. nest:根據parent_id生成樹結構(阿里一面真題)

          根據每項的parent_id,生成具體樹形結構的對象。

          const nest = (items, id = null, link = 'parent_id') =>
            items
              .filter(item => item[link] === id)
              .map(item => ({ ...item, children: nest(items, item.id) }));
          復制代碼

          用法:

          const comments = [
            { id: 1, parent_id: null },
            { id: 2, parent_id: 1 },
            { id: 3, parent_id: 1 },
            { id: 4, parent_id: 2 },
            { id: 5, parent_id: 4 }
          ];
          const nestedComments = nest(comments); // [{ id: 1, parent_id: null, children: [...] }]
          復制代碼


          強烈建議去理解這個的實現,因為這是我親身遇到的阿里一面真題:


          2. 第二部分:函數

          1. attempt:捕獲函數運行異常

          該代碼段執行一個函數,返回結果或捕獲的錯誤對象。

          onst attempt = (fn, ...args) => {
            try {
              return fn(...args);
            } catch (e) {
              return e instanceof Error ? e : new Error(e);
            }
          };
          var elements = attempt(function(selector) {
            return document.querySelectorAll(selector);
          }, '>_>');
          if (elements instanceof Error) elements = []; // elements = []
          復制代碼

          2. defer:推遲執行

          此代碼段延遲了函數的執行,直到清除了當前調用堆棧。

          const defer = (fn, ...args) => setTimeout(fn, 1, ...args);
          
          defer(console.log, 'a'), console.log('b'); // logs 'b' then 'a'
          復制代碼

          3. runPromisesInSeries:運行多個Promises

          const runPromisesInSeries = ps => ps.reduce((p, next) => p.then(next), Promise.resolve());
          const delay = d => new Promise(r => setTimeout(r, d));
          
          runPromisesInSeries([() => delay(1000), () => delay(2000)]);
          //依次執行每個Promises ,總共需要3秒鐘才能完成
          復制代碼

          4. timeTaken:計算函數執行時間

          
          const timeTaken = callback => {
            console.time('timeTaken');
            const r = callback();
            console.timeEnd('timeTaken');
            return r;
          };
          
          timeTaken(() => Math.pow(2, 10)); // 1024, (logged): timeTaken: 0.02099609375ms
          復制代碼

          5. createEventHub:簡單的發布/訂閱模式

          創建一個發布/訂閱(發布-訂閱)事件集線,有emit,on和off方法。

          1. 使用Object.create(null)創建一個空的hub對象。
          2. emit,根據event參數解析處理程序數組,然后.forEach()通過傳入數據作為參數來運行每個處理程序。
          3. on,為事件創建一個數組(若不存在則為空數組),然后.push()將處理程序添加到該數組。
          4. off,用.findIndex()在事件數組中查找處理程序的索引,并使用.splice()刪除。
          const createEventHub = () => ({
            hub: Object.create(null),
            emit(event, data) {
              (this.hub[event] || []).forEach(handler => handler(data));
            },
            on(event, handler) {
              if (!this.hub[event]) this.hub[event] = [];
              this.hub[event].push(handler);
            },
            off(event, handler) {
              const i = (this.hub[event] || []).findIndex(h => h === handler);
              if (i > -1) this.hub[event].splice(i, 1);
              if (this.hub[event].length === 0) delete this.hub[event];
            }
          });
          復制代碼

          用法:

          const handler = data => console.log(data);
          const hub = createEventHub();
          let increment = 0;
          
          // 訂閱,監聽不同事件
          hub.on('message', handler);
          hub.on('message', () => console.log('Message event fired'));
          hub.on('increment', () => increment++);
          
          // 發布:發出事件以調用所有訂閱給它們的處理程序,并將數據作為參數傳遞給它們
          hub.emit('message', 'hello world'); // 打印 'hello world' 和 'Message event fired'
          hub.emit('message', { hello: 'world' }); // 打印 對象 和 'Message event fired'
          hub.emit('increment'); // increment = 1
          
          // 停止訂閱
          hub.off('message', handler);
          復制代碼

          6. memoize:緩存函數

          通過實例化一個Map對象來創建一個空的緩存。

          通過檢查輸入值的函數輸出是否已緩存,返回存儲一個參數的函數,該參數將被提供給已記憶的函數;如果沒有,則存儲并返回它。

          const memoize = fn => {
            const cache = new Map();
            const cached = function(val) {
              return cache.has(val) ? cache.get(val) : cache.set(val, fn.call(this, val)) && cache.get(val);
            };
            cached.cache = cache;
            return cached;
          };
          復制代碼

          Ps: 這個版本可能不是很清晰,還有Vue源碼版的:

          /**
           * Create a cached version of a pure function.
           */
          export function cached<F: Function> (fn: F): F {
            const cache = Object.create(null)
            return (function cachedFn (str: string) {
              const hit = cache[str]
              return hit || (cache[str] = fn(str))
            }: any)
          }
          復制代碼

          7. once:只調用一次的函數

          const once = fn => {
            let called = false
            return function () {
              if (!called) {
                called = true
                fn.apply(this, arguments)
              }
            }
          };
          復制代碼

          用法:

          const startApp = function(event) {
            console.log(this, event); // document.body, MouseEvent
          };
          document.body.addEventListener('click', once(startApp)); // 只執行一次startApp
          復制代碼

          8. flattenObject:以鍵的路徑扁平化對象

          使用遞歸。

          1. 利用Object.keys(obj)聯合Array.prototype.reduce(),以每片葉子節點轉換為扁平的路徑節點。
          2. 如果鍵的值是一個對象,則函數使用調用適當的自身prefix以創建路徑Object.assign()。
          3. 否則,它將適當的前綴鍵值對添加到累加器對象。
          4. prefix除非您希望每個鍵都有一個前綴,否則應始終省略第二個參數。
          const flattenObject = (obj, prefix = '') =>
            Object.keys(obj).reduce((acc, k) => {
              const pre = prefix.length ? prefix + '.' : '';
              if (typeof obj[k] === 'object') Object.assign(acc, flattenObject(obj[k], pre + k));
              else acc[pre + k] = obj[k];
              return acc;
            }, {});
            
          flattenObject({ a: { b: { c: 1 } }, d: 1 }); // { 'a.b.c': 1, d: 1 }
          復制代碼

          9. unflattenObject:以鍵的路徑展開對象

          與上面的相反,展開對象。

          const unflattenObject = obj =>
            Object.keys(obj).reduce((acc, k) => {
              if (k.indexOf('.') !== -1) {
                const keys = k.split('.');
                Object.assign(
                  acc,
                  JSON.parse(
                    '{' +
                      keys.map((v, i) => (i !== keys.length - 1 ? `"${v}":{` : `"${v}":`)).join('') +
                      obj[k] +
                      '}'.repeat(keys.length)
                  )
                );
              } else acc[k] = obj[k];
              return acc;
            }, {});
            
          unflattenObject({ 'a.b.c': 1, d: 1 }); // { a: { b: { c: 1 } }, d: 1 }
          復制代碼

          這個的用途,在做Tree組件或復雜表單時取值非常舒服。

          3. 第三部分:字符串

          1.byteSize:返回字符串的字節長度

          const byteSize = str => new Blob([str]).size;
          
          byteSize(''); // 4
          byteSize('Hello World'); // 11
          復制代碼

          2. capitalize:首字母大寫

          const capitalize = ([first, ...rest]) =>
            first.toUpperCase() + rest.join('');
            
          capitalize('fooBar'); // 'FooBar'
          capitalize('fooBar', true); // 'Foobar'
          復制代碼

          3. capitalizeEveryWord:每個單詞首字母大寫

          const capitalizeEveryWord = str => str.replace(/\b[a-z]/g, char => char.toUpperCase());
          
          capitalizeEveryWord('hello world!'); // 'Hello World!'
          復制代碼

          4. decapitalize:首字母小寫

          const decapitalize = ([first, ...rest]) =>
            first.toLowerCase() + rest.join('')
          
          decapitalize('FooBar'); // 'fooBar'
          decapitalize('FooBar'); // 'fooBar'
          復制代碼

          5. luhnCheck:銀行卡號碼校驗(luhn算法)

          Luhn算法的實現,用于驗證各種標識號,例如信用卡號,IMEI號,國家提供商標識號等。

          與String.prototype.split('')結合使用,以獲取數字數組。獲得最后一個數字。實施luhn算法。如果被整除,則返回,否則返回。

          const luhnCheck = num => {
            let arr = (num + '')
              .split('')
              .reverse()
              .map(x => parseInt(x));
            let lastDigit = arr.splice(0, 1)[0];
            let sum = arr.reduce((acc, val, i) => (i % 2 !== 0 ? acc + val : acc + ((val * 2) % 9) || 9), 0);
            sum += lastDigit;
            return sum % 10 === 0;
          };
          復制代碼

          用例:

          luhnCheck('4485275742308327'); // true
          luhnCheck(6011329933655299); //  false
          luhnCheck(123456789); // false
          復制代碼

          補充:銀行卡號碼的校驗規則

          關于luhn算法,可以參考以下文章:

          銀行卡號碼校驗算法(Luhn算法,又叫模10算法)

          銀行卡號碼的校驗采用Luhn算法,校驗過程大致如下:

          1. 從右到左給卡號字符串編號,最右邊第一位是1,最右邊第二位是2,最右邊第三位是3….
          2. 從右向左遍歷,對每一位字符t執行第三個步驟,并將每一位的計算結果相加得到一個數s。
          3. 對每一位的計算規則:如果這一位是奇數位,則返回t本身,如果是偶數位,則先將t乘以2得到一個數n,如果n是一位數(小于10),直接返回n,否則將n的個位數和十位數相加返回。
          4. 如果s能夠整除10,則此號碼有效,否則號碼無效。

          因為最終的結果會對10取余來判斷是否能夠整除10,所以又叫做模10算法。

          當然,還是庫比較香: bankcardinfo



          6. splitLines:將多行字符串拆分為行數組。

          使用String.prototype.split()和正則表達式匹配換行符并創建一個數組。

          const splitLines = str => str.split(/\r?\n/);
          
          splitLines('This\nis a\nmultiline\nstring.\n'); // ['This', 'is a', 'multiline', 'string.' , '']
          復制代碼

          7. stripHTMLTags:刪除字符串中的HTMl標簽

          從字符串中刪除HTML / XML標簽。

          使用正則表達式從字符串中刪除HTML / XML 標記。

          const stripHTMLTags = str => str.replace(/<[^>]*>/g, '');
          
          stripHTMLTags('<p><em>lorem</em> <strong>ipsum</strong></p>'); // 'lorem ipsum'
          復制代碼

          4. 第四部分:對象

          1. dayOfYear:當前日期天數

          const dayOfYear = date =>
            Math.floor((date - new Date(date.getFullYear(), 0, 0)) / 1000 / 60 / 60 / 24);
          
          dayOfYear(new Date()); // 285
          復制代碼

          2. forOwn:迭代屬性并執行回調

          const forOwn = (obj, fn) => Object.keys(obj).forEach(key => fn(obj[key], key, obj));
          forOwn({ foo: 'bar', a: 1 }, v => console.log(v)); // 'bar', 1
          復制代碼

          3. Get Time From Date:返回當前24小時制時間的字符串

          const getColonTimeFromDate = date => date.toTimeString().slice(0, 8);
          
          getColonTimeFromDate(new Date()); // "08:38:00"
          復制代碼

          4. Get Days Between Dates:返回日期間的天數

          const getDaysDiffBetweenDates = (dateInitial, dateFinal) =>
            (dateFinal - dateInitial) / (1000 * 3600 * 24);
            
          getDaysDiffBetweenDates(new Date('2019-01-01'), new Date('2019-10-14')); // 286
          復制代碼

          5. is:檢查值是否為特定類型。

          const is = (type, val) => ![, null].includes(val) && val.constructor === type;
          
          is(Array, [1]); // true
          is(ArrayBuffer, new ArrayBuffer()); // true
          is(Map, new Map()); // true
          is(RegExp, /./g); // true
          is(Set, new Set()); // true
          is(WeakMap, new WeakMap()); // true
          is(WeakSet, new WeakSet()); // true
          is(String, ''); // true
          is(String, new String('')); // true
          is(Number, 1); // true
          is(Number, new Number(1)); // true
          is(Boolean, true); // true
          is(Boolean, new Boolean(true)); // true
          復制代碼

          6. isAfterDate:檢查是否在某日期后

          const isAfterDate = (dateA, dateB) => dateA > dateB;
          
          isAfterDate(new Date(2010, 10, 21), new Date(2010, 10, 20)); // true
          復制代碼

          7. isBeforeDate:檢查是否在某日期前

          const isBeforeDate = (dateA, dateB) => dateA < dateB;
          
          isBeforeDate(new Date(2010, 10, 20), new Date(2010, 10, 21)); // true
          復制代碼

          8 tomorrow:獲取明天的字符串格式時間

          
          const tomorrow = () => {
            let t = new Date();
            t.setDate(t.getDate() + 1);
            return t.toISOString().split('T')[0];
          };
          
          tomorrow(); // 2019-10-15 (如果明天是2019-10-15)
          復制代碼

          9. equals:全等判斷

          在兩個變量之間進行深度比較以確定它們是否全等。

          此代碼段精簡的核心在于Array.prototype.every()的使用。

          const equals = (a, b) => {
            if (a === b) return true;
            if (a instanceof Date && b instanceof Date) return a.getTime() === b.getTime();
            if (!a || !b || (typeof a !== 'object' && typeof b !== 'object')) return a === b;
            if (a.prototype !== b.prototype) return false;
            let keys = Object.keys(a);
            if (keys.length !== Object.keys(b).length) return false;
            return keys.every(k => equals(a[k], b[k]));
          };
          復制代碼

          用法:

          equals({ a: [2, { e: 3 }], b: [4], c: 'foo' }, { a: [2, { e: 3 }], b: [4], c: 'foo' }); // true
          復制代碼

          5. 第五部分:數字

          1. randomIntegerInRange:生成指定范圍的隨機整數

          const randomIntegerInRange = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min;
          
          randomIntegerInRange(0, 5); // 3
          復制代碼

          2. randomNumberInRange:生成指定范圍的隨機小數

          const randomNumberInRange = (min, max) => Math.random() * (max - min) + min;
          
          randomNumberInRange(2, 10); // 6.0211363285087005
          復制代碼

          3. round:四舍五入到指定位數

          const round = (n, decimals = 0) => Number(`${Math.round(`${n}e${decimals}`)}e-${decimals}`);
          
          round(1.005, 2); // 1.01
          復制代碼

          4. sum:計算數組或多個數字的總和

          
          const sum = (...arr) => [...arr].reduce((acc, val) => acc + val, 0);
          
          sum(1, 2, 3, 4); // 10
          sum(...[1, 2, 3, 4]); // 10
          復制代碼

          5. toCurrency:簡單的貨幣單位轉換

          const toCurrency = (n, curr, LanguageFormat = undefined) =>
            Intl.NumberFormat(LanguageFormat, { style: 'currency', currency: curr }).format(n);
            
          toCurrency(123456.789, 'EUR'); // €123,456.79
          toCurrency(123456.789, 'USD', 'en-us'); // $123,456.79  
          toCurrency(123456.789, 'USD', 'fa'); // ??????????
          toCurrency(322342436423.2435, 'JPY'); // ¥322,342,436,423 
          復制代碼

          6. 第六部分:瀏覽器操作及其它

          1. bottomVisible:檢查頁面底部是否可見

          const bottomVisible = () =>
            document.documentElement.clientHeight + window.scrollY >=
            (document.documentElement.scrollHeight || document.documentElement.clientHeight);
          
          bottomVisible(); // true
          復制代碼

          2. Create Directory:檢查創建目錄

          此代碼段調用fs模塊的existsSync()檢查目錄是否存在,如果不存在,則mkdirSync()創建該目錄。

          const fs = require('fs');
          const createDirIfNotExists = dir => (!fs.existsSync(dir) ? fs.mkdirSync(dir) : undefined);
          createDirIfNotExists('test'); 
          復制代碼

          3. currentURL:返回當前鏈接url

          const currentURL = () => window.location.href;
          
          currentURL(); // 'https://juejin.im'
          復制代碼

          4. distance:返回兩點間的距離

          該代碼段通過計算歐幾里得距離來返回兩點之間的距離。

          const distance = (x0, y0, x1, y1) => Math.hypot(x1 - x0, y1 - y0);
          
          distance(1, 1, 2, 3); // 2.23606797749979
          復制代碼

          5. elementContains:檢查是否包含子元素

          此代碼段檢查父元素是否包含子元素。

          const elementContains = (parent, child) => parent !== child && parent.contains(child);
          
          elementContains(document.querySelector('head'), document.querySelector('title')); // true
          elementContains(document.querySelector('body'), document.querySelector('body')); // false
          復制代碼

          6. getStyle:返回指定元素的生效樣式

          const getStyle = (el, ruleName) => getComputedStyle(el)[ruleName];
          
          getStyle(document.querySelector('p'), 'font-size'); // '16px'
          復制代碼

          7. getType:返回值或變量的類型名

          const getType = v =>
            v === undefined ? 'undefined' : v === null ? 'null' : v.constructor.name.toLowerCase();
            
          getType(new Set([1, 2, 3])); // 'set'
          getType([1, 2, 3]); // 'array'
          復制代碼

          8. hasClass:校驗指定元素的類名

          const hasClass = (el, className) => el.classList.contains(className);
          hasClass(document.querySelector('p.special'), 'special'); // true
          復制代碼

          9. hide:隱藏所有的指定標簽

          const hide = (...el) => [...el].forEach(e => (e.style.display = 'none'));
          
          hide(document.querySelectorAll('img')); // 隱藏所有<img>標簽
          復制代碼

          10. httpsRedirect:HTTP 跳轉 HTTPS

          const httpsRedirect = () => {
            if (location.protocol !== 'https:') location.replace('https://' + location.href.split('//')[1]);
          };
          
          httpsRedirect(); // 若在`http://www.baidu.com`, 則跳轉到`https://www.baidu.com`
          復制代碼

          11. insertAfter:在指定元素之后插入新元素

          const insertAfter = (el, htmlString) => el.insertAdjacentHTML('afterend', htmlString);
          
          // <div id="myId">...</div> <p>after</p>
          insertAfter(document.getElementById('myId'), '<p>after</p>'); 
          復制代碼

          12. insertBefore:在指定元素之前插入新元素

          const insertBefore = (el, htmlString) => el.insertAdjacentHTML('beforebegin', htmlString);
          
          insertBefore(document.getElementById('myId'), '<p>before</p>'); // <p>before</p> <div id="myId">...</div>
          復制代碼

          13. isBrowser:檢查是否為瀏覽器環境

          此代碼段可用于確定當前運行時環境是否為瀏覽器。這有助于避免在服務器(節點)上運行前端模塊時出錯。

          const isBrowser = () => ![typeof window, typeof document].includes('undefined');
          
          isBrowser(); // true (browser)
          isBrowser(); // false (Node)
          復制代碼

          14. isBrowserTab:檢查當前標簽頁是否活動

          const isBrowserTabFocused = () => !document.hidden;
          
          isBrowserTabFocused(); // true
          復制代碼

          15. nodeListToArray:轉換nodeList為數組

          const nodeListToArray = nodeList => [...nodeList];
          
          nodeListToArray(document.childNodes); // [ <!DOCTYPE html>, html ]
          復制代碼

          16. Random Hexadecimal Color Code:隨機十六進制顏色

          
          const randomHexColorCode = () => {
            let n = (Math.random() * 0xfffff * 1000000).toString(16);
            return '#' + n.slice(0, 6);
          };
          
          randomHexColorCode(); // "#e34155"
          復制代碼

          17. scrollToTop:平滑滾動至頂部

          該代碼段可用于平滑滾動到當前頁面的頂部。

          const scrollToTop = () => {
            const c = document.documentElement.scrollTop || document.body.scrollTop;
            if (c > 0) {
              window.requestAnimationFrame(scrollToTop);
              window.scrollTo(0, c - c / 8);
            }
          };
          
          scrollToTop();
          復制代碼

          18. smoothScroll:滾動到指定元素區域

          該代碼段可將指定元素平滑滾動到瀏覽器窗口的可見區域。

          const smoothScroll = element =>
            document.querySelector(element).scrollIntoView({
              behavior: 'smooth'
            });
            
          smoothScroll('#fooBar'); 
          smoothScroll('.fooBar'); 
          復制代碼

          19. detectDeviceType:檢測移動/PC設備

          const detectDeviceType = () =>
            /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)
              ? 'Mobile'
              : 'Desktop';
          復制代碼

          20. getScrollPosition:返回當前的滾動位置

          默認參數為window ,pageXOffset(pageYOffset)為第一選擇,沒有則用scrollLeft(scrollTop)

          const getScrollPosition = (el = window) => ({
            x: el.pageXOffset !== undefined ? el.pageXOffset : el.scrollLeft,
            y: el.pageYOffset !== undefined ? el.pageYOffset : el.scrollTop
          });
          
          getScrollPosition(); // {x: 0, y: 200}
          復制代碼

          21. size:獲取不同類型變量的字節長度

          這個的實現非常巧妙,利用Blob類文件對象的特性,獲取對象的長度。

          另外,多重三元運算符,是真香。

          const size = val =>
            Array.isArray(val)
              ? val.length
              : val && typeof val === 'object'
              ? val.size || val.length || Object.keys(val).length
              : typeof val === 'string'
              ? new Blob([val]).size
              : 0;
          
          size([1, 2, 3, 4, 5]); // 5
          size('size'); // 4
          size({ one: 1, two: 2, three: 3 }); // 3
          
          復制代碼

          22. escapeHTML:轉義HTML

          當然是用來防XSS攻擊啦。

          const escapeHTML = str =>
            str.replace(
              /[&<>'"]/g,
              tag =>
                ({
                  '&': '&',
                  '<': '<',
                  '>': '>',
                  "'": ''',
                  '"': '"'
                }[tag] || tag)
            );
          
          escapeHTML('<a href="#">Me & you</a>'); // '<a href="#">Me & you</a>'
          復制代碼



          ?? 看完三件事

          如果你覺得這篇內容對你挺有啟發,我想邀請你幫我三個小忙:

          1. 點贊,讓更多的人也能看到這篇內容(收藏不點贊,都是耍流氓 -_-)
          2. 關注公眾號「前端勸退師」,不定期分享原創知識。
          3. 也看看其它文章


          原鏈接:https://juejin.im/post/5da1a04ae51d45783d6122bf

          階函數

          高階函數可以接收函數作為參數,同時也可以返回一個新的函數。

          高階函數之所以高階,是因為高階函數的參數和返回值對象可以是函數,這超越了普通函數處理的數據類型,例如字符串(strings)、數字(numbers)、布爾值(booleans)等。

          JavaScript 中,函數的應用場景很豐富:

          • 作為變量存儲
          • 在數組中使用
          • 作為對象屬性(即方法)
          • 作為參數傳遞
          • 作為其他函數的返回值

          理解高階函數的關鍵在于,函數即數據

          數據是函數運作的基本

          數據:字符串(Strings)

          sayHi = (name) => `Hi, ${name}!`;
          result = sayHi('User');
          console.log(result); // 'Hi, User!'
          

          數據:數字(Numbers)

          double = (x) => x * 2;
          result = double(4);
          console.log(result); // 8
          

          數據:布爾值(Booleans)

          getClearance = (allowed) => allowed ? 'Access granted' : 'Access denied';
          result1 = getClearance(true);
          result2 = getClearance(false);
          console.log(result1); // 'Access granted'
          console.log(result2); // 'Access denied'
          

          數據:對象(Objects)

          getFirstName = (obj) => obj.firstName;
          result = getFirstName({ 
           firstName: 'Yazeed'
          });
          console.log(result); // 'Yazeed'
          

          數據:數組(Arrays)

          len = (array) => array.length;
          result = len([1, 2, 3]);
          console.log(result); // 3
          

          在所有的主流語言中,以上這五種數據類型被稱為 “頭等對象”(原文:first-class citizen, https://www.wikiwand.com/en/First-class_citizen)。

          為什么是“頭等”呢?因為這五種數據類型既可以作為參數傳遞,又可以存儲在變量或者數組中,還可以作為變量用于計算,是數據的基本形式。

          函數也是數據

          函數作為參數

          isEven = (num) => num % 2 === 0;
          result = [1, 2, 3, 4].filter(isEven);
          console.log(result); // [2, 4]
          

          請觀察 filter 函數是如何使用 isEven 函數來判斷要保留哪些內容的。這里的 isEven 是一個函數,作為參數傳入了 filter 函數中。

          filter 函數每次在做判斷的時候都會調用 isEven 函數,用 isEven 函數返回的布爾值來決定當前數值的去留。

          函數作為返回值

          add = (x) => (y) => x + y;
          

          add 函數需要兩個參數,但不需要它們倆同時傳入,第一次傳參傳入 x 就會返還一個新函數,這個函數需要傳入 y 參數。

          能夠這樣操作的基礎在于 JavaScript 語言允許函數本身作為返回值存在,就像函數可以返回字符串(strings)、數字(numbers)、布爾值(booleans)等,JS 函數還可以返回另一個函數。

          當然,我們也可以使用“雙重調用”的方式,一次性提供 x 和 y 兩個參數:

          result = add(10)(20);
          console.log(result); // 30
          

          或者分兩次調用,先傳參數 x,再傳參數 y:

          add10 = add(10);
          result = add10(20);
          console.log(result); // 30
          

          在上面這個例子中, add10 函數是第一次調用 add 函數的返回值,可以嘗試用 console.log把結果打出來觀察一下。

          add10 函數會接收 y 參數,然后返回 x + y 值。一旦 y 值到位,函數會立馬進行運算并返回結果。

          可重復利用性

          高階函數的魅力在于它的可重復利用性,如果不是高階函數,map、filter、reduce 等強大的數組函數就不可能存在。

          假設我們有一組用戶,如下所示,然后我們要對該數組進行操作。

          users = [
           {
           name: 'Yazeed',
           age: 25
           },
           {
           name: 'Sam',
           age: 30
           },
           {
           name: 'Bill',
           age: 20
           }
          ];
          

          Map

          沒有高階函數的話,我們必須回到 for 循環的懷抱才能實現 map 函數的操作。

          getName = (user) => user.name;
          usernames = [];
          for (let i = 0; i < users.length; i++) {
           const name = getName(users[i]);
           usernames.push(name);
          }
          console.log(usernames);
          // ["Yazeed", "Sam", "Bill"]
          

          用 map 函數就簡單多啦!

          usernames = users.map(getName);
          console.log(usernames);
          // ["Yazeed", "Sam", "Bill"]
          

          Filter

          在沒有高階函數的情況下,必須要用 for 循環來實現 filter 函數的功能。

          startsWithB = (string) => string.toLowerCase().startsWith('b');
          namesStartingWithB = [];
          for (let i = 0; i < users.length; i++) {
           if (startsWithB(users[i].name)) {
           namesStartingWithB.push(users[i]);
           }
          }
          console.log(namesStartingWithB);
          // [{ "name": "Bill", "age": 20 }]
          


          用 filter 函數就簡單多啦!

          namesStartingWithB = users.filter((user) => startsWithB(user.name));
          console.log(namesStartingWithB);
          // [{ "name": "Bill", "age": 20 }]
          

          Reduce

          reduce 函數也是的……沒有高階函數的話,很多高端操作都是無法實現的!

          total = 0;
          for (let i = 0; i < users.length; i++) {
           total += users[i].age;
          }
          console.log(total);
          // 75
          


          那這樣是不是簡單多啦?

          totalAge = users.reduce((total, user) => user.age + total, 0);
          console.log(totalAge);
          // 75
          


          總結

          • 字符串(strings)、數字(numbers)、布爾值(booleans)、數組(arrays)、對象(objects)可以作為變量(variables)、數組(arrays)、屬性( properties)或者方法(methods)存儲起來。
          • JavaScript 語言中,函數也是像數據一樣同等對待的。
          • 因此函數可以作為另外一個函數的參數或者返回值使用,這樣的做法叫高階函數
          • map、filter、 reduce 等函數就是高階函數的最佳代表,它們讓數組的處理(改變,搜索,相加等)變得簡單不少!

          版權

          原文鏈接:https://mp.weixin.qq.com/s/JAsy4TVv3tzL0q8RxXBf7Q

          作者:jingruzhang

          . this 適合你嗎?

          我看到許多文章在介紹 JavaScript 的 this 時都會假設你學過某種面向對象的編程語言,比如 Java、C++ 或 Python 等。但這篇文章面向的讀者是那些不知道 this 是什么的人。我盡量不用任何術語來解釋 this 是什么,以及 this 的用法。

          也許你一直不敢解開 this 的秘密,因為它看起來挺奇怪也挺嚇人的。或許你只在 StackOverflow 說你需要用它的時候(比如在 React 里實現某個功能)才會使用。

          在深入介紹 this 之前,我們首先需要理解函數式編程和面向對象編程之間的區別。

          2. 函數式編程 vs 面向對象編程

          你可能不知道,JavaScript 同時擁有面向對象和函數式的結構,所以你可以自己選擇用哪種風格,或者兩者都用。

          我在很早以前使用 JavaScript 時就喜歡函數式編程,而且會像躲避瘟疫一樣避開面向對象編程,因為我不理解面向對象中的關鍵字,比如 this。我不知道為什么要用 this。似乎沒有它我也可以做好所有的工作。

          而且我是對的。

          在某種意義上 。也許你可以只專注于一種結構并且完全忽略另一種,但這樣你只能是一個 JavaScript 開發者。為了解釋函數式和面向對象之間的區別,下面我們通過一個數組來舉例說明,數組的內容是 Facebook 的好友列表。

          假設你要做一個 Web 應用,當用戶使用 Facebook 登錄你的 Web 應用時,需要顯示他們的 Facebook 的好友信息。你需要訪問 Facebook 并獲得用戶的好友數據。這些數據可能是 firstName、lastName、username、numFriends、friendData、birthday 和 lastTenPosts 等信息。

          const data = [
           {
           firstName: 'Bob',
           lastName: 'Ross',
           username: 'bob.ross', 
           numFriends: 125,
           birthday: '2/23/1985',
           lastTenPosts: ['What a nice day', 'I love Kanye West', ...],
           },
           ...
          ]
          

          假設上述數據是你通過 Facebook API 獲得的。現在需要將其轉換成方便你的項目使用的格式。我們假設你想顯示的好友信息如下:

          • 姓名,格式為`${firstName} ${lastName}`
          • 三篇隨機文章
          • 距離生日的天數


          3. 函數式方式

          函數式的方式就是將整個數組或者數組中的某個元素傳遞給某個函數,然后返回你需要的信息:

          const fullNames = getFullNames(data)
          // ['Ross, Bob', 'Smith, Joanna', ...]
          

          首先我們有 Facebook API 返回的原始數據。為了將其轉換成需要的格式,首先要將數據傳遞給一個函數,函數的輸出是(或者包含)經過修改的數據,這些數據可以在應用中向用戶展示。

          我們可以用類似的方法獲得隨機三篇文章,并且計算距離好友生日的天數。

          函數式的方式是:將原始數據傳遞給一個函數或者多個函數,獲得對你的項目有用的數據格式。

          4. 面向對象的方式

          對于編程初學者和 JavaScript 初學者,面向對象的概念可能有點難以理解。其思想是,我們要將每個好友變成一個對象,這個對象能夠生成你一切開發者需要的東西。

          你可以創建一個對象,這個對象對應于某個好友,它有 fullName 屬性,還有兩個函數 getThreeRandomPosts 和 getDaysUntilBirthday。

          function initializeFriend(data) {
           return {
           fullName: `${data.firstName} ${data.lastName}`,
           getThreeRandomPosts: function() {
           // get three random posts from data.lastTenPosts
           },
           getDaysUntilBirthday: function() {
           // use data.birthday to get the num days until birthday
           }
           };
          }
          const objectFriends = data.map(initializeFriend)
          objectFriends[0].getThreeRandomPosts() 
          // Gets three of Bob Ross's posts
          

          面向對象的方式就是為數據創建對象,每個對象都有自己的狀態,并且包含必要的信息,能夠生成需要的數據。

          5. 這跟 this 有什么關系?

          你也許從來沒想過要寫上面的 initializeFriend 代碼,而且你也許認為,這種代碼可能會很有用。但你也注意到,這并不是真正的面向對象。

          其原因就是,上面例子中的 getThreeRandomPosts 或 getdaysUntilBirtyday 能夠正常工作的原因其實是閉包。因為使用了閉包,它們在 initializeFriend 返回之后依然能訪問 data。關于閉包的更多信息可以看看這篇文章:作用域和閉包(https://github.com/getify/You-Dont-Know-JS/blob/master/scope%20%26%20closures/ch5.md)。

          還有一個方法該怎么處理?我們假設這個方法叫做 greeting。注意方法(與 JavaScript 的對象有關的方法)其實只是一個屬性,只不過屬性值是函數而已。我們想在 greeting 中實現以下功能:

          function initializeFriend(data) {
           return {
           fullName: `${data.firstName} ${data.lastName}`,
           getThreeRandomPosts: function() {
           // get three random posts from data.lastTenPosts
           },
           getDaysUntilBirthday: function() {
           // use data.birthday to get the num days until birthday
           },
           greeting: function() {
           return `Hello, this is ${fullName}'s data!`
           }
           };
          }
          

          這樣能正常工作嗎?

          不能!

          我們新建的對象能夠訪問 initializeFriend 中的一切變量,但不能訪問這個對象本身的屬性或方法。當然你會問,

          難道不能在 greeting 中直接用 data.firstName 和 data.lastName 嗎?

          當然可以。但要是想在 greeting 中加入距離好友生日的天數怎么辦?我們最好還是有辦法在 greeting 中調用 getDaysUntilBirthday。

          這時輪到 this 出場了!



          6. 終于——this 是什么

          this 在不同的環境中可以指代不同的東西。默認的全局環境中 this 指代的是全局對象(在瀏覽器中 this 是 window 對象),這沒什么太大的用途。而在 this 的規則中具有實用性的是這一條:

          如果在對象的方法中使用 this,而該方法在該對象的上下文中調用,那么 this 指代該對象本身。

          你會說“在該對象的上下文中調用”……是啥意思?

          別著急,我們一會兒就說。

          所以,如果我們想從 greeting 中調用 getDaysUntilBirtyday 我們只需要寫 this.getDaysUntilBirthday,因為此時的 this 就是對象本身。

          附注:不要在全局作用域的普通函數或另一個函數的作用域中使用 this!this 是個面向對象的東西,它只在對象的上下文(或類的上下文)中有意義。

          我們利用 this 來重寫 initializeFriend:

          function initializeFriend(data) {
           return {
           lastTenPosts: data.lastTenPosts,
           birthday: data.birthday, 
           fullName: `${data.firstName} ${data.lastName}`,
           getThreeRandomPosts: function() {
           // get three random posts from this.lastTenPosts
           },
           getDaysUntilBirthday: function() {
           // use this.birthday to get the num days until birthday
           },
           greeting: function() {
           const numDays = this.getDaysUntilBirthday() 
           return `Hello, this is ${this.fullName}'s data! It is ${numDays} until ${this.fullName}'s birthday!`
           }
           };
          }
          

          現在,在 initializeFriend 執行結束后,該對象需要的一切都位于對象本身的作用域之內了。我們的方法不需要再依賴于閉包,它們只會用到對象本身包含的信息。

          好吧,這是 this 的用法之一,但你說過 this 在不同的上下文中有不同的含義。那是什么意思?為什么不一定會指向對象自己?

          有時候,你需要將 this 指向某個特定的東西。一種情況就是事件處理函數。比如我們希望在用戶點擊好友時打開好友的 Facebook 首頁。我們會給對象添加下面的 onClick 方法:

          function initializeFriend(data) {
           return {
           lastTenPosts: data.lastTenPosts,
           birthday: data.birthday,
           username: data.username, 
           fullName: `${data.firstName} ${data.lastName}`,
           getThreeRandomPosts: function() {
           // get three random posts from this.lastTenPosts
           },
           getDaysUntilBirthday: function() {
           // use this.birthday to get the num days until birthday
           },
           greeting: function() {
           const numDays = this.getDaysUntilBirthday() 
           return `Hello, this is ${this.fullName}'s data! It is ${numDays} until ${this.fullName}'s birthday!`
           },
           onFriendClick: function() {
           window.open(`https://facebook.com/${this.username}`)
           }
           };
          }
          

          注意我們在對象中添加了 username 屬性,這樣 onFriendClick 就能訪問它,從而在新窗口中打開該好友的 Facebook 首頁。現在只需要編寫 HTML:

          <button id="Bob_Ross">
           <!-- A bunch of info associated with Bob Ross -->
          </button> 
          

          還有 JavaScript:

          const bobRossObj = initializeFriend(data[0])
          const bobRossDOMEl = document.getElementById('Bob_Ross')
          bobRossDOMEl.addEventListener("onclick", bobRossObj.onFriendClick)
          

          在上述代碼中,我們給 Bob Ross 創建了一個對象。然后我們拿到了 Bob Ross 對應的 DOM 元素。然后執行 onFriendClick 方法來打開 Bob 的 Facebook 主頁。似乎沒問題,對吧?

          有問題!

          哪里出錯了?

          注意我們調用 onclick 處理程序的代碼是 bobRossObj.onFriendClick。看到問題了嗎?要是寫成這樣的話能看出來嗎?

          bobRossDOMEl.addEventListener("onclick", function() {
           window.open(`https://facebook.com/${this.username}`)
          })
          

          現在看到問題了嗎?如果把事件處理程序寫成 bobRossObj.onFriendClick,實際上是把 bobRossObj.onFriendClick 上保存的函數拿出來,然后作為參數傳遞。它不再“依附”在 bobRossObj 上,也就是說,this 不再指向 bobRossObj。它實際指向全局對象,也就是說 this.username 不存在。似乎我們沒什么辦法了。

          輪到綁定上場了!



          7. 明確綁定 this

          我們需要明確地將 this 綁定到 bobRossObj 上。我們可以通過 bind 實現:

          const bobRossObj = initializeFriend(data[0])
          const bobRossDOMEl = document.getElementById('Bob_Ross')
          bobRossObj.onFriendClick = bobRossObj.onFriendClick.bind(bobRossObj)
          bobRossDOMEl.addEventListener("onclick", bobRossObj.onFriendClick)
          

          之前,this 是按照默認的規則設置的。但使用 bind 之后,我們明確地將 bobRossObj.onFriendClick 中的 this 的值設置為 bobRossObj 對象本身。

          到此為止,我們看到了為什么要使用 this,以及為什么要明確地綁定 this。最后我們來介紹一下,this 實際上是箭頭函數。

          8. 箭頭函數

          你也許注意到了箭頭函數最近很流行。人們喜歡箭頭函數,因為很簡潔、很優雅。而且你還知道箭頭函數和普通函數有點區別,盡管不太清楚具體區別是什么。

          簡而言之,兩者的區別在于:

          在定義箭頭函數時,不管 this 指向誰,箭頭函數內部的 this 永遠指向同一個東西。

          嗯……這貌似沒什么用……似乎跟普通函數的行為一樣啊?

          我們通過 initializeFriend 舉例說明。假設我們想添加一個名為 greeting 的函數:

          function initializeFriend(data) {
           return {
           lastTenPosts: data.lastTenPosts,
           birthday: data.birthday,
           username: data.username, 
           fullName: `${data.firstName} ${data.lastName}`,
           getThreeRandomPosts: function() {
           // get three random posts from this.lastTenPosts
           },
           getDaysUntilBirthday: function() {
           // use this.birthday to get the num days until birthday
           },
           greeting: function() {
           function getLastPost() {
           return this.lastTenPosts[0]
           }
           const lastPost = getLastPost() 
           return `Hello, this is ${this.fullName}'s data!
           ${this.fullName}'s last post was ${lastPost}.`
           },
           onFriendClick: function() {
           window.open(`https://facebook.com/${this.username}`)
           }
           };
          }
          

          這樣能運行嗎?如果不能,怎樣修改才能運行?

          答案是不能。因為 getLastPost 沒有在對象的上下文中調用,因此getLastPost 中的 this 按照默認規則指向了全局對象。

          你說沒有“在對象的上下文中調用”……難道它不是從 initializeFriend 返回的內部調用的嗎?如果這還不叫“在對象的上下文中調用”,那我就不知道什么才算了。

          我知道“在對象的上下文中調用”這個術語很模糊。也許,判斷函數是否“在對象的上下文中調用”的好方法就是檢查一遍函數的調用過程,看看是否有個對象“依附”到了函數上。

          我們來檢查下執行 bobRossObj.onFriendClick() 時的情況。“給我對象 bobRossObj,找到其中的 onFriendClick 然后調用該屬性對應的函數”。

          我們同樣檢查下執行 getLastPost() 時的情況。“給我名為 getLastPost 的函數然后執行。”看到了嗎?我們根本沒有提到對象。

          好了,這里有個難題來測試你的理解程度。假設有個函數名為 functionCaller,它的功能就是調用一個函數:

          functionCaller(fn) {
           fn()
          }
          

          如果調用 functionCaller(bobRossObj.onFriendClick) 會怎樣?你會認為 onFriendClick 是“在對象的上下文中調用”的嗎?this.username有定義嗎?

          我們來檢查一遍:“給我 bobRosObj 對象然后查找其屬性 onFriendClick。取出其中的值(這個值碰巧是個函數),然后將它傳遞給 functionCaller,取名為 fn。然后,執行名為 fn 的函數。”注意該函數在調用之前已經從 bobRossObj 對象上“脫離”了,因此并不是“在對象的上下文中調用”的,所以 this.username 沒有定義。

          這時可以用箭頭函數解決這個問題:

          function initializeFriend(data) {
           return {
           lastTenPosts: data.lastTenPosts,
           birthday: data.birthday,
           username: data.username, 
           fullName: `${data.firstName} ${data.lastName}`,
           getThreeRandomPosts: function() {
           // get three random posts from this.lastTenPosts
           },
           getDaysUntilBirthday: function() {
           // use this.birthday to get the num days until birthday
           },
           greeting: function() {
           const getLastPost = () => {
           return this.lastTenPosts[0]
           }
           const lastPost = getLastPost() 
           return `Hello, this is ${this.fullName}'s data!
           ${this.fullName}'s last post was ${lastPost}.`
           },
           onFriendClick: function() {
           window.open(`https://facebook.com/${this.username}`)
           }
           };
          }
          

          上述代碼的規則是:

          在定義箭頭函數時,不管 this 指向誰,箭頭函數內部的 this 永遠指向同一個東西。

          箭頭函數是在 greeting 中定義的。我們知道,在 greeting 內部的 this 指向對象本身。因此,箭頭函數內部的 this 也指向對象本身,這正是我們需要的結果。

          9. 結論

          this 有時很不好理解,但它對于開發 JavaScript 應用非常有用。本文當然沒能介紹 this 的所有方面。一些沒有涉及到的話題包括:

          • call 和 apply;
          • 使用 new 時 this 會怎樣;
          • 在 ES6 的 class 中 this 會怎樣。

          我建議你首先問問自己在這些情況下的 this,然后在瀏覽器中執行代碼來檢驗你的結果。

          原文地址:https://mp.weixin.qq.com/s/L9eac6rzkSE_JqxXyg3FQw


          主站蜘蛛池模板: 久久中文字幕一区二区| 日本不卡一区二区视频a| 波多野结衣AV无码久久一区| 无码国产精品一区二区免费虚拟VR | 色综合久久一区二区三区| 亚洲AV综合色一区二区三区| 加勒比精品久久一区二区三区| 亚洲第一区二区快射影院| 亚洲国产精品乱码一区二区 | 亚洲一区二区三区高清在线观看| 国产一区三区二区中文在线 | 国产精品一区二区四区| 亚洲欧洲日韩国产一区二区三区| 国产精品无码AV一区二区三区| 一区二区精品在线观看| 视频精品一区二区三区| 国产精品日韩一区二区三区| 国产乱码精品一区二区三区香蕉 | 免费视频一区二区| 红桃AV一区二区三区在线无码AV| 亚洲免费一区二区| 国产在线观看一区精品| 一区二区三区影院| 国产免费一区二区三区不卡 | 国产精品污WWW一区二区三区| 一区二区高清在线观看| 国产三级一区二区三区| 精品一区二区三区视频在线观看| 日韩a无吗一区二区三区| 国产日韩视频一区| 日本一区二区在线免费观看| 国产一国产一区秋霞在线观看| 免费看无码自慰一区二区| 精品国产日韩一区三区| 鲁大师成人一区二区三区| 一区二区3区免费视频| 在线观看国产区亚洲一区成人| 久久中文字幕一区二区| 亚洲综合一区二区| 精品国产一区二区麻豆| 精品乱子伦一区二区三区高清免费播放|