Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537
一線大廠筆試題靈感來源
目錄:
篩選自以下兩篇文章:
《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
原本只想篩選下上面的那篇文章,在精簡掉了部分多余且無用的工具函數(shù)后,感覺不夠。于是順藤摸瓜,找到了原地址: 30 seconds of code
然后將所有代碼段都看了遍,篩選了以下一百多段代碼片段,并加入了部分自己的理解。
另外,本文工具函數(shù)的命名非常值得借鑒。
const all = (arr, fn = Boolean) => arr.every(fn);
all([4, 2, 3], x => x > 1); // true
all([1, 2, 3]); // true
復(fù)制代碼
const allEqual = arr => arr.every(val => val === arr[0]);
allEqual([1, 2, 3, 4, 5, 6]); // false
allEqual([1, 1, 1, 1]); // true
復(fù)制代碼
const approximatelyEqual = (v1, v2, epsilon = 0.001) => Math.abs(v1 - v2) < epsilon;
approximatelyEqual(Math.PI / 2.0, 1.5708); // true
復(fù)制代碼
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"'
復(fù)制代碼
此代碼段將數(shù)組的元素轉(zhuǎn)換為<li>標(biāo)簽,并將其附加到給定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');
復(fù)制代碼
const average = (...nums) => nums.reduce((acc, val) => acc + val, 0) / nums.length;
average(...[1, 2, 3]); // 2
average(1, 2, 3); // 2
復(fù)制代碼
此代碼段將獲取數(shù)組對象屬性的平均值
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
復(fù)制代碼
可以根據(jù)每個元素返回的值,使用reduce()和push() 將元素添加到第二次參數(shù)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'] ]
復(fù)制代碼
const castArray = val => (Array.isArray(val) ? val : [val]);
castArray('foo'); // ['foo']
castArray([1]); // [1]
castArray(1); // [1]
復(fù)制代碼
const compact = arr => arr.filter(Boolean);
compact([0, 1, false, 2, '', 3, 'a', 'e' * 23, NaN, 's', 34]);
// [ 1, 2, 3, 'a', 's', 34 ]
復(fù)制代碼
const countOccurrences = (arr, val) => arr.reduce((a, v) => (v === val ? a + 1 : a), 0);
countOccurrences([1, 1, 2, 1, 2, 3], 1); // 3
復(fù)制代碼
const deepFlatten = arr => [].concat(...arr.map(v => (Array.isArray(v) ? deepFlatten(v) : v)));
deepFlatten([1, [2], [[3], 4], 5]); // [1,2,3,4,5]
復(fù)制代碼
此代碼段查找兩個數(shù)組之間的差異,并返回第一個數(shù)組獨有的。
const difference = (a, b) => {
const s = new Set(b);
return a.filter(x => !s.has(x));
};
difference([1, 2, 3], [1, 2, 4]); // [3]
復(fù)制代碼
在將給定函數(shù)應(yīng)用于兩個列表的每個元素之后,此方法返回兩個數(shù)組之間的差異。
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 } ]
復(fù)制代碼
此代碼段從數(shù)組頂部開始刪除元素,直到傳遞的函數(shù)返回為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]
復(fù)制代碼
此代碼段第二參數(shù)可指定深度。
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]
復(fù)制代碼
此代碼段可用于獲取數(shù)組中某個值的所有索引,如果此值中未包含該值,則返回一個空數(shù)組。
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); // []
復(fù)制代碼
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]
復(fù)制代碼
此片段可用于在對兩個數(shù)組的每個元素執(zhí)行了函數(shù)之后,返回兩個數(shù)組中存在的元素列表。
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]
復(fù)制代碼
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]
復(fù)制代碼
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]
復(fù)制代碼
const negate = func => (...args) => !func(...args);
[1, 2, 3, 4, 5, 6].filter(negate(n => n % 2 === 0)); // [ 1, 3, 5 ]
復(fù)制代碼
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 ]
復(fù)制代碼
const sample = arr => arr[Math.floor(Math.random() * arr.length)];
sample([3, 7, 9, 11]); // 9
復(fù)制代碼
此代碼段可用于從數(shù)組中獲取指定長度的隨機數(shù),直至窮盡數(shù)組。 使用Fisher-Yates算法對數(shù)組中的元素進(jìn)行隨機選擇。
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]
復(fù)制代碼
此代碼段使用Fisher-Yates算法隨機排序數(shù)組的元素。
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]
復(fù)制代碼
根據(jù)每項的parent_id,生成具體樹形結(jié)構(gòu)的對象。
const nest = (items, id = null, link = 'parent_id') =>
items
.filter(item => item[link] === id)
.map(item => ({ ...item, children: nest(items, item.id) }));
復(fù)制代碼
用法:
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: [...] }]
復(fù)制代碼
強烈建議去理解這個的實現(xiàn),因為這是我親身遇到的阿里一面真題:
該代碼段執(zhí)行一個函數(shù),返回結(jié)果或捕獲的錯誤對象。
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 = []
復(fù)制代碼
此代碼段延遲了函數(shù)的執(zhí)行,直到清除了當(dāng)前調(diào)用堆棧。
const defer = (fn, ...args) => setTimeout(fn, 1, ...args);
defer(console.log, 'a'), console.log('b'); // logs 'b' then 'a'
復(fù)制代碼
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)]);
//依次執(zhí)行每個Promises ,總共需要3秒鐘才能完成
復(fù)制代碼
const timeTaken = callback => {
console.time('timeTaken');
const r = callback();
console.timeEnd('timeTaken');
return r;
};
timeTaken(() => Math.pow(2, 10)); // 1024, (logged): timeTaken: 0.02099609375ms
復(fù)制代碼
創(chuàng)建一個發(fā)布/訂閱(發(fā)布-訂閱)事件集線,有emit,on和off方法。
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];
}
});
復(fù)制代碼
用法:
const handler = data => console.log(data);
const hub = createEventHub();
let increment = 0;
// 訂閱,監(jiān)聽不同事件
hub.on('message', handler);
hub.on('message', () => console.log('Message event fired'));
hub.on('increment', () => increment++);
// 發(fā)布:發(fā)出事件以調(diào)用所有訂閱給它們的處理程序,并將數(shù)據(jù)作為參數(shù)傳遞給它們
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);
復(fù)制代碼
通過實例化一個Map對象來創(chuàng)建一個空的緩存。
通過檢查輸入值的函數(shù)輸出是否已緩存,返回存儲一個參數(shù)的函數(shù),該參數(shù)將被提供給已記憶的函數(shù);如果沒有,則存儲并返回它。
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;
};
復(fù)制代碼
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)
}
復(fù)制代碼
const once = fn => {
let called = false
return function () {
if (!called) {
called = true
fn.apply(this, arguments)
}
}
};
復(fù)制代碼
用法:
const startApp = function(event) {
console.log(this, event); // document.body, MouseEvent
};
document.body.addEventListener('click', once(startApp)); // 只執(zhí)行一次startApp
復(fù)制代碼
使用遞歸。
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 }
復(fù)制代碼
與上面的相反,展開對象。
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 }
復(fù)制代碼
這個的用途,在做Tree組件或復(fù)雜表單時取值非常舒服。
const byteSize = str => new Blob([str]).size;
byteSize(''); // 4
byteSize('Hello World'); // 11
復(fù)制代碼
const capitalize = ([first, ...rest]) =>
first.toUpperCase() + rest.join('');
capitalize('fooBar'); // 'FooBar'
capitalize('fooBar', true); // 'Foobar'
復(fù)制代碼
const capitalizeEveryWord = str => str.replace(/\b[a-z]/g, char => char.toUpperCase());
capitalizeEveryWord('hello world!'); // 'Hello World!'
復(fù)制代碼
const decapitalize = ([first, ...rest]) =>
first.toLowerCase() + rest.join('')
decapitalize('FooBar'); // 'fooBar'
decapitalize('FooBar'); // 'fooBar'
復(fù)制代碼
Luhn算法的實現(xiàn),用于驗證各種標(biāo)識號,例如信用卡號,IMEI號,國家提供商標(biāo)識號等。
與String.prototype.split('')結(jié)合使用,以獲取數(shù)字?jǐn)?shù)組。獲得最后一個數(shù)字。實施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;
};
復(fù)制代碼
用例:
luhnCheck('4485275742308327'); // true
luhnCheck(6011329933655299); // false
luhnCheck(123456789); // false
復(fù)制代碼
關(guān)于luhn算法,可以參考以下文章:
銀行卡號碼校驗算法(Luhn算法,又叫模10算法)
銀行卡號碼的校驗采用Luhn算法,校驗過程大致如下:
因為最終的結(jié)果會對10取余來判斷是否能夠整除10,所以又叫做模10算法。
當(dāng)然,還是庫比較香: bankcardinfo
使用String.prototype.split()和正則表達(dá)式匹配換行符并創(chuàng)建一個數(shù)組。
const splitLines = str => str.split(/\r?\n/);
splitLines('This\nis a\nmultiline\nstring.\n'); // ['This', 'is a', 'multiline', 'string.' , '']
復(fù)制代碼
從字符串中刪除HTML / XML標(biāo)簽。
使用正則表達(dá)式從字符串中刪除HTML / XML 標(biāo)記。
const stripHTMLTags = str => str.replace(/<[^>]*>/g, '');
stripHTMLTags('<p><em>lorem</em> <strong>ipsum</strong></p>'); // 'lorem ipsum'
復(fù)制代碼
const dayOfYear = date =>
Math.floor((date - new Date(date.getFullYear(), 0, 0)) / 1000 / 60 / 60 / 24);
dayOfYear(new Date()); // 285
復(fù)制代碼
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
復(fù)制代碼
const getColonTimeFromDate = date => date.toTimeString().slice(0, 8);
getColonTimeFromDate(new Date()); // "08:38:00"
復(fù)制代碼
const getDaysDiffBetweenDates = (dateInitial, dateFinal) =>
(dateFinal - dateInitial) / (1000 * 3600 * 24);
getDaysDiffBetweenDates(new Date('2019-01-01'), new Date('2019-10-14')); // 286
復(fù)制代碼
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
復(fù)制代碼
const isAfterDate = (dateA, dateB) => dateA > dateB;
isAfterDate(new Date(2010, 10, 21), new Date(2010, 10, 20)); // true
復(fù)制代碼
const isBeforeDate = (dateA, dateB) => dateA < dateB;
isBeforeDate(new Date(2010, 10, 20), new Date(2010, 10, 21)); // true
復(fù)制代碼
const tomorrow = () => {
let t = new Date();
t.setDate(t.getDate() + 1);
return t.toISOString().split('T')[0];
};
tomorrow(); // 2019-10-15 (如果明天是2019-10-15)
復(fù)制代碼
在兩個變量之間進(jìn)行深度比較以確定它們是否全等。
此代碼段精簡的核心在于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]));
};
復(fù)制代碼
用法:
equals({ a: [2, { e: 3 }], b: [4], c: 'foo' }, { a: [2, { e: 3 }], b: [4], c: 'foo' }); // true
復(fù)制代碼
const randomIntegerInRange = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min;
randomIntegerInRange(0, 5); // 3
復(fù)制代碼
const randomNumberInRange = (min, max) => Math.random() * (max - min) + min;
randomNumberInRange(2, 10); // 6.0211363285087005
復(fù)制代碼
const round = (n, decimals = 0) => Number(`${Math.round(`${n}e${decimals}`)}e-${decimals}`);
round(1.005, 2); // 1.01
復(fù)制代碼
const sum = (...arr) => [...arr].reduce((acc, val) => acc + val, 0);
sum(1, 2, 3, 4); // 10
sum(...[1, 2, 3, 4]); // 10
復(fù)制代碼
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
復(fù)制代碼
const bottomVisible = () =>
document.documentElement.clientHeight + window.scrollY >=
(document.documentElement.scrollHeight || document.documentElement.clientHeight);
bottomVisible(); // true
復(fù)制代碼
此代碼段調(diào)用fs模塊的existsSync()檢查目錄是否存在,如果不存在,則mkdirSync()創(chuàng)建該目錄。
const fs = require('fs');
const createDirIfNotExists = dir => (!fs.existsSync(dir) ? fs.mkdirSync(dir) : undefined);
createDirIfNotExists('test');
復(fù)制代碼
const currentURL = () => window.location.href;
currentURL(); // 'https://juejin.im'
復(fù)制代碼
該代碼段通過計算歐幾里得距離來返回兩點之間的距離。
const distance = (x0, y0, x1, y1) => Math.hypot(x1 - x0, y1 - y0);
distance(1, 1, 2, 3); // 2.23606797749979
復(fù)制代碼
此代碼段檢查父元素是否包含子元素。
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
復(fù)制代碼
const getStyle = (el, ruleName) => getComputedStyle(el)[ruleName];
getStyle(document.querySelector('p'), 'font-size'); // '16px'
復(fù)制代碼
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'
復(fù)制代碼
const hasClass = (el, className) => el.classList.contains(className);
hasClass(document.querySelector('p.special'), 'special'); // true
復(fù)制代碼
const hide = (...el) => [...el].forEach(e => (e.style.display = 'none'));
hide(document.querySelectorAll('img')); // 隱藏所有<img>標(biāo)簽
復(fù)制代碼
const httpsRedirect = () => {
if (location.protocol !== 'https:') location.replace('https://' + location.href.split('//')[1]);
};
httpsRedirect(); // 若在`http://www.baidu.com`, 則跳轉(zhuǎn)到`https://www.baidu.com`
復(fù)制代碼
const insertAfter = (el, htmlString) => el.insertAdjacentHTML('afterend', htmlString);
// <div id="myId">...</div> <p>after</p>
insertAfter(document.getElementById('myId'), '<p>after</p>');
復(fù)制代碼
const insertBefore = (el, htmlString) => el.insertAdjacentHTML('beforebegin', htmlString);
insertBefore(document.getElementById('myId'), '<p>before</p>'); // <p>before</p> <div id="myId">...</div>
復(fù)制代碼
此代碼段可用于確定當(dāng)前運行時環(huán)境是否為瀏覽器。這有助于避免在服務(wù)器(節(jié)點)上運行前端模塊時出錯。
const isBrowser = () => ![typeof window, typeof document].includes('undefined');
isBrowser(); // true (browser)
isBrowser(); // false (Node)
復(fù)制代碼
const isBrowserTabFocused = () => !document.hidden;
isBrowserTabFocused(); // true
復(fù)制代碼
const nodeListToArray = nodeList => [...nodeList];
nodeListToArray(document.childNodes); // [ <!DOCTYPE html>, html ]
復(fù)制代碼
const randomHexColorCode = () => {
let n = (Math.random() * 0xfffff * 1000000).toString(16);
return '#' + n.slice(0, 6);
};
randomHexColorCode(); // "#e34155"
復(fù)制代碼
該代碼段可用于平滑滾動到當(dāng)前頁面的頂部。
const scrollToTop = () => {
const c = document.documentElement.scrollTop || document.body.scrollTop;
if (c > 0) {
window.requestAnimationFrame(scrollToTop);
window.scrollTo(0, c - c / 8);
}
};
scrollToTop();
復(fù)制代碼
該代碼段可將指定元素平滑滾動到瀏覽器窗口的可見區(qū)域。
const smoothScroll = element =>
document.querySelector(element).scrollIntoView({
behavior: 'smooth'
});
smoothScroll('#fooBar');
smoothScroll('.fooBar');
復(fù)制代碼
const detectDeviceType = () =>
/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)
? 'Mobile'
: 'Desktop';
復(fù)制代碼
默認(rèn)參數(shù)為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}
復(fù)制代碼
這個的實現(xiàn)非常巧妙,利用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
復(fù)制代碼
當(dāng)然是用來防XSS攻擊啦。
const escapeHTML = str =>
str.replace(
/[&<>'"]/g,
tag =>
({
'&': '&',
'<': '<',
'>': '>',
"'": ''',
'"': '"'
}[tag] || tag)
);
escapeHTML('<a href="#">Me & you</a>'); // '<a href="#">Me & you</a>'
復(fù)制代碼
如果你覺得這篇內(nèi)容對你挺有啟發(fā),我想邀請你幫我三個小忙:
原鏈接:https://juejin.im/post/5da1a04ae51d45783d6122bf
高階函數(shù)可以接收函數(shù)作為參數(shù),同時也可以返回一個新的函數(shù)。
高階函數(shù)之所以高階,是因為高階函數(shù)的參數(shù)和返回值對象可以是函數(shù),這超越了普通函數(shù)處理的數(shù)據(jù)類型,例如字符串(strings)、數(shù)字(numbers)、布爾值(booleans)等。
JavaScript 中,函數(shù)的應(yīng)用場景很豐富:
理解高階函數(shù)的關(guān)鍵在于,函數(shù)即數(shù)據(jù)。
sayHi = (name) => `Hi, ${name}!`; result = sayHi('User'); console.log(result); // 'Hi, User!'
double = (x) => x * 2; result = double(4); console.log(result); // 8
getClearance = (allowed) => allowed ? 'Access granted' : 'Access denied'; result1 = getClearance(true); result2 = getClearance(false); console.log(result1); // 'Access granted' console.log(result2); // 'Access denied'
getFirstName = (obj) => obj.firstName; result = getFirstName({ firstName: 'Yazeed' }); console.log(result); // 'Yazeed'
len = (array) => array.length; result = len([1, 2, 3]); console.log(result); // 3
在所有的主流語言中,以上這五種數(shù)據(jù)類型被稱為 “頭等對象”(原文:first-class citizen, https://www.wikiwand.com/en/First-class_citizen)。
為什么是“頭等”呢?因為這五種數(shù)據(jù)類型既可以作為參數(shù)傳遞,又可以存儲在變量或者數(shù)組中,還可以作為變量用于計算,是數(shù)據(jù)的基本形式。
isEven = (num) => num % 2 === 0; result = [1, 2, 3, 4].filter(isEven); console.log(result); // [2, 4]
請觀察 filter 函數(shù)是如何使用 isEven 函數(shù)來判斷要保留哪些內(nèi)容的。這里的 isEven 是一個函數(shù),作為參數(shù)傳入了 filter 函數(shù)中。
filter 函數(shù)每次在做判斷的時候都會調(diào)用 isEven 函數(shù),用 isEven 函數(shù)返回的布爾值來決定當(dāng)前數(shù)值的去留。
add = (x) => (y) => x + y;
add 函數(shù)需要兩個參數(shù),但不需要它們倆同時傳入,第一次傳參傳入 x 就會返還一個新函數(shù),這個函數(shù)需要傳入 y 參數(shù)。
能夠這樣操作的基礎(chǔ)在于 JavaScript 語言允許函數(shù)本身作為返回值存在,就像函數(shù)可以返回字符串(strings)、數(shù)字(numbers)、布爾值(booleans)等,JS 函數(shù)還可以返回另一個函數(shù)。
當(dāng)然,我們也可以使用“雙重調(diào)用”的方式,一次性提供 x 和 y 兩個參數(shù):
result = add(10)(20); console.log(result); // 30
或者分兩次調(diào)用,先傳參數(shù) x,再傳參數(shù) y:
add10 = add(10); result = add10(20); console.log(result); // 30
在上面這個例子中, add10 函數(shù)是第一次調(diào)用 add 函數(shù)的返回值,可以嘗試用 console.log把結(jié)果打出來觀察一下。
add10 函數(shù)會接收 y 參數(shù),然后返回 x + y 值。一旦 y 值到位,函數(shù)會立馬進(jìn)行運算并返回結(jié)果。
高階函數(shù)的魅力在于它的可重復(fù)利用性,如果不是高階函數(shù),map、filter、reduce 等強大的數(shù)組函數(shù)就不可能存在。
假設(shè)我們有一組用戶,如下所示,然后我們要對該數(shù)組進(jìn)行操作。
users = [ { name: 'Yazeed', age: 25 }, { name: 'Sam', age: 30 }, { name: 'Bill', age: 20 } ];
沒有高階函數(shù)的話,我們必須回到 for 循環(huán)的懷抱才能實現(xiàn) map 函數(shù)的操作。
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 函數(shù)就簡單多啦!
usernames = users.map(getName); console.log(usernames); // ["Yazeed", "Sam", "Bill"]
在沒有高階函數(shù)的情況下,必須要用 for 循環(huán)來實現(xiàn) filter 函數(shù)的功能。
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 函數(shù)就簡單多啦!
namesStartingWithB = users.filter((user) => startsWithB(user.name)); console.log(namesStartingWithB); // [{ "name": "Bill", "age": 20 }]
reduce 函數(shù)也是的……沒有高階函數(shù)的話,很多高端操作都是無法實現(xiàn)的!
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
原文鏈接:https://mp.weixin.qq.com/s/JAsy4TVv3tzL0q8RxXBf7Q
作者:jingruzhang
. this 適合你嗎?
我看到許多文章在介紹 JavaScript 的 this 時都會假設(shè)你學(xué)過某種面向?qū)ο蟮木幊陶Z言,比如 Java、C++ 或 Python 等。但這篇文章面向的讀者是那些不知道 this 是什么的人。我盡量不用任何術(shù)語來解釋 this 是什么,以及 this 的用法。
也許你一直不敢解開 this 的秘密,因為它看起來挺奇怪也挺嚇人的?;蛟S你只在 StackOverflow 說你需要用它的時候(比如在 React 里實現(xiàn)某個功能)才會使用。
在深入介紹 this 之前,我們首先需要理解函數(shù)式編程和面向?qū)ο缶幊讨g的區(qū)別。
2. 函數(shù)式編程 vs 面向?qū)ο缶幊?/strong>
你可能不知道,JavaScript 同時擁有面向?qū)ο蠛秃瘮?shù)式的結(jié)構(gòu),所以你可以自己選擇用哪種風(fēng)格,或者兩者都用。
我在很早以前使用 JavaScript 時就喜歡函數(shù)式編程,而且會像躲避瘟疫一樣避開面向?qū)ο缶幊蹋驗槲也焕斫饷嫦驅(qū)ο笾械年P(guān)鍵字,比如 this。我不知道為什么要用 this。似乎沒有它我也可以做好所有的工作。
而且我是對的。
在某種意義上 。也許你可以只專注于一種結(jié)構(gòu)并且完全忽略另一種,但這樣你只能是一個 JavaScript 開發(fā)者。為了解釋函數(shù)式和面向?qū)ο笾g的區(qū)別,下面我們通過一個數(shù)組來舉例說明,數(shù)組的內(nèi)容是 Facebook 的好友列表。
假設(shè)你要做一個 Web 應(yīng)用,當(dāng)用戶使用 Facebook 登錄你的 Web 應(yīng)用時,需要顯示他們的 Facebook 的好友信息。你需要訪問 Facebook 并獲得用戶的好友數(shù)據(jù)。這些數(shù)據(jù)可能是 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', ...], }, ... ]
假設(shè)上述數(shù)據(jù)是你通過 Facebook API 獲得的?,F(xiàn)在需要將其轉(zhuǎn)換成方便你的項目使用的格式。我們假設(shè)你想顯示的好友信息如下:
3. 函數(shù)式方式
函數(shù)式的方式就是將整個數(shù)組或者數(shù)組中的某個元素傳遞給某個函數(shù),然后返回你需要的信息:
const fullNames = getFullNames(data) // ['Ross, Bob', 'Smith, Joanna', ...]
首先我們有 Facebook API 返回的原始數(shù)據(jù)。為了將其轉(zhuǎn)換成需要的格式,首先要將數(shù)據(jù)傳遞給一個函數(shù),函數(shù)的輸出是(或者包含)經(jīng)過修改的數(shù)據(jù),這些數(shù)據(jù)可以在應(yīng)用中向用戶展示。
我們可以用類似的方法獲得隨機三篇文章,并且計算距離好友生日的天數(shù)。
函數(shù)式的方式是:將原始數(shù)據(jù)傳遞給一個函數(shù)或者多個函數(shù),獲得對你的項目有用的數(shù)據(jù)格式。
4. 面向?qū)ο蟮姆绞?/strong>
對于編程初學(xué)者和 JavaScript 初學(xué)者,面向?qū)ο蟮母拍羁赡苡悬c難以理解。其思想是,我們要將每個好友變成一個對象,這個對象能夠生成你一切開發(fā)者需要的東西。
你可以創(chuàng)建一個對象,這個對象對應(yīng)于某個好友,它有 fullName 屬性,還有兩個函數(shù) 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
面向?qū)ο蟮姆绞骄褪菫閿?shù)據(jù)創(chuàng)建對象,每個對象都有自己的狀態(tài),并且包含必要的信息,能夠生成需要的數(shù)據(jù)。
5. 這跟 this 有什么關(guān)系?
你也許從來沒想過要寫上面的 initializeFriend 代碼,而且你也許認(rèn)為,這種代碼可能會很有用。但你也注意到,這并不是真正的面向?qū)ο蟆?/p>
其原因就是,上面例子中的 getThreeRandomPosts 或 getdaysUntilBirtyday 能夠正常工作的原因其實是閉包。因為使用了閉包,它們在 initializeFriend 返回之后依然能訪問 data。關(guān)于閉包的更多信息可以看看這篇文章:作用域和閉包(https://github.com/getify/You-Dont-Know-JS/blob/master/scope%20%26%20closures/ch5.md)。
還有一個方法該怎么處理?我們假設(shè)這個方法叫做 greeting。注意方法(與 JavaScript 的對象有關(guān)的方法)其實只是一個屬性,只不過屬性值是函數(shù)而已。我們想在 greeting 中實現(xiàn)以下功能:
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 中的一切變量,但不能訪問這個對象本身的屬性或方法。當(dāng)然你會問,
難道不能在 greeting 中直接用 data.firstName 和 data.lastName 嗎?
當(dāng)然可以。但要是想在 greeting 中加入距離好友生日的天數(shù)怎么辦?我們最好還是有辦法在 greeting 中調(diào)用 getDaysUntilBirthday。
這時輪到 this 出場了!
6. 終于——this 是什么
this 在不同的環(huán)境中可以指代不同的東西。默認(rèn)的全局環(huán)境中 this 指代的是全局對象(在瀏覽器中 this 是 window 對象),這沒什么太大的用途。而在 this 的規(guī)則中具有實用性的是這一條:
如果在對象的方法中使用 this,而該方法在該對象的上下文中調(diào)用,那么 this 指代該對象本身。
你會說“在該對象的上下文中調(diào)用”……是啥意思?
別著急,我們一會兒就說。
所以,如果我們想從 greeting 中調(diào)用 getDaysUntilBirtyday 我們只需要寫 this.getDaysUntilBirthday,因為此時的 this 就是對象本身。
附注:不要在全局作用域的普通函數(shù)或另一個函數(shù)的作用域中使用 this!this 是個面向?qū)ο蟮臇|西,它只在對象的上下文(或類的上下文)中有意義。
我們利用 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!` } }; }
現(xiàn)在,在 initializeFriend 執(zhí)行結(jié)束后,該對象需要的一切都位于對象本身的作用域之內(nèi)了。我們的方法不需要再依賴于閉包,它們只會用到對象本身包含的信息。
好吧,這是 this 的用法之一,但你說過 this 在不同的上下文中有不同的含義。那是什么意思?為什么不一定會指向?qū)ο笞约海?/p>
有時候,你需要將 this 指向某個特定的東西。一種情況就是事件處理函數(shù)。比如我們希望在用戶點擊好友時打開好友的 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 首頁?,F(xiàn)在只需要編寫 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 創(chuàng)建了一個對象。然后我們拿到了 Bob Ross 對應(yīng)的 DOM 元素。然后執(zhí)行 onFriendClick 方法來打開 Bob 的 Facebook 主頁。似乎沒問題,對吧?
有問題!
哪里出錯了?
注意我們調(diào)用 onclick 處理程序的代碼是 bobRossObj.onFriendClick??吹絾栴}了嗎?要是寫成這樣的話能看出來嗎?
bobRossDOMEl.addEventListener("onclick", function() { window.open(`https://facebook.com/${this.username}`) })
現(xiàn)在看到問題了嗎?如果把事件處理程序?qū)懗?bobRossObj.onFriendClick,實際上是把 bobRossObj.onFriendClick 上保存的函數(shù)拿出來,然后作為參數(shù)傳遞。它不再“依附”在 bobRossObj 上,也就是說,this 不再指向 bobRossObj。它實際指向全局對象,也就是說 this.username 不存在。似乎我們沒什么辦法了。
輪到綁定上場了!
7. 明確綁定 this
我們需要明確地將 this 綁定到 bobRossObj 上。我們可以通過 bind 實現(xiàn):
const bobRossObj = initializeFriend(data[0]) const bobRossDOMEl = document.getElementById('Bob_Ross') bobRossObj.onFriendClick = bobRossObj.onFriendClick.bind(bobRossObj) bobRossDOMEl.addEventListener("onclick", bobRossObj.onFriendClick)
之前,this 是按照默認(rèn)的規(guī)則設(shè)置的。但使用 bind 之后,我們明確地將 bobRossObj.onFriendClick 中的 this 的值設(shè)置為 bobRossObj 對象本身。
到此為止,我們看到了為什么要使用 this,以及為什么要明確地綁定 this。最后我們來介紹一下,this 實際上是箭頭函數(shù)。
8. 箭頭函數(shù)
你也許注意到了箭頭函數(shù)最近很流行。人們喜歡箭頭函數(shù),因為很簡潔、很優(yōu)雅。而且你還知道箭頭函數(shù)和普通函數(shù)有點區(qū)別,盡管不太清楚具體區(qū)別是什么。
簡而言之,兩者的區(qū)別在于:
在定義箭頭函數(shù)時,不管 this 指向誰,箭頭函數(shù)內(nèi)部的 this 永遠(yuǎn)指向同一個東西。
嗯……這貌似沒什么用……似乎跟普通函數(shù)的行為一樣???
我們通過 initializeFriend 舉例說明。假設(shè)我們想添加一個名為 greeting 的函數(shù):
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 沒有在對象的上下文中調(diào)用,因此getLastPost 中的 this 按照默認(rèn)規(guī)則指向了全局對象。
你說沒有“在對象的上下文中調(diào)用”……難道它不是從 initializeFriend 返回的內(nèi)部調(diào)用的嗎?如果這還不叫“在對象的上下文中調(diào)用”,那我就不知道什么才算了。
我知道“在對象的上下文中調(diào)用”這個術(shù)語很模糊。也許,判斷函數(shù)是否“在對象的上下文中調(diào)用”的好方法就是檢查一遍函數(shù)的調(diào)用過程,看看是否有個對象“依附”到了函數(shù)上。
我們來檢查下執(zhí)行 bobRossObj.onFriendClick() 時的情況。“給我對象 bobRossObj,找到其中的 onFriendClick 然后調(diào)用該屬性對應(yīng)的函數(shù)”。
我們同樣檢查下執(zhí)行 getLastPost() 時的情況?!敖o我名為 getLastPost 的函數(shù)然后執(zhí)行?!笨吹搅藛幔课覀兏緵]有提到對象。
好了,這里有個難題來測試你的理解程度。假設(shè)有個函數(shù)名為 functionCaller,它的功能就是調(diào)用一個函數(shù):
functionCaller(fn) { fn() }
如果調(diào)用 functionCaller(bobRossObj.onFriendClick) 會怎樣?你會認(rèn)為 onFriendClick 是“在對象的上下文中調(diào)用”的嗎?this.username有定義嗎?
我們來檢查一遍:“給我 bobRosObj 對象然后查找其屬性 onFriendClick。取出其中的值(這個值碰巧是個函數(shù)),然后將它傳遞給 functionCaller,取名為 fn。然后,執(zhí)行名為 fn 的函數(shù)?!弊⒁庠摵瘮?shù)在調(diào)用之前已經(jīng)從 bobRossObj 對象上“脫離”了,因此并不是“在對象的上下文中調(diào)用”的,所以 this.username 沒有定義。
這時可以用箭頭函數(shù)解決這個問題:
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}`) } }; }
上述代碼的規(guī)則是:
在定義箭頭函數(shù)時,不管 this 指向誰,箭頭函數(shù)內(nèi)部的 this 永遠(yuǎn)指向同一個東西。
箭頭函數(shù)是在 greeting 中定義的。我們知道,在 greeting 內(nèi)部的 this 指向?qū)ο蟊旧?。因此,箭頭函數(shù)內(nèi)部的 this 也指向?qū)ο蟊旧?,這正是我們需要的結(jié)果。
9. 結(jié)論
this 有時很不好理解,但它對于開發(fā) JavaScript 應(yīng)用非常有用。本文當(dāng)然沒能介紹 this 的所有方面。一些沒有涉及到的話題包括:
我建議你首先問問自己在這些情況下的 this,然后在瀏覽器中執(zhí)行代碼來檢驗?zāi)愕慕Y(jié)果。
原文地址:https://mp.weixin.qq.com/s/L9eac6rzkSE_JqxXyg3FQw
*請認(rèn)真填寫需求信息,我們會在24小時內(nèi)與您取得聯(lián)系。