編程中,解決同一個問題通常有多種方法。這些解決方案在不同方面可能有所不同,例如長度、性能、使用的算法、可讀性等。
在本文中,我們將研究幾種快速簡潔的單行解決方案,以解決 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 基礎了。
當然我們也不完全是為了去準備面試而去學習這些常見模塊。死磕這些難啃的骨頭之后,你會從中學到很多優秀的思想,對你的職業生涯也是很有幫助的。而且閱讀代碼本身就是一個很好的習慣,讀懂并且理解寫代碼人的思維邏輯更加重要。
本文中涉及到的手寫模塊,大多數都是從網上以及自己的面試經驗借鑒而來的。希望能對你有個幫助。
要求以數組的形式返回字符串參數的所有排列組合。
注意:
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' ]
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
// 這里不能直接使用箭頭函數,否則無法訪問到 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=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=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
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
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
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 運算符創建一個用戶定義的對象類型的實例或具有構造函數的內置對象的實例。
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
}
}
實現思路和淺拷貝一致,只不過需要注意幾點
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)
}
}
函數返回結果會被緩存下來,只會計算一次。
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
}
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=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 返回 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是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)
}
};
優化一下
先看看下面輸出的內容
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)
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 這個函數接受三個參數
這里提醒一下,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
*請認真填寫需求信息,我們會在24小時內與您取得聯系。