题目
柯里化实现add函数
1 | function add(){ |
高阶记忆函数
1 | function memorize(fn){ |
多数组的笛卡尔乘积
1 | function descartes(data){ |
斐波那契数列
只有不再用到外层函数的内部变量,内层函数的调用帧才会取代外层函数的调用帧,否则就无法进行“尾调用优化”1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36// 性能最差(太深调用栈)
function Fibonacci (n) {
if ( n <= 1 ) {return 1};
return Fibonacci(n - 1) + Fibonacci(n - 2);
}
// 递归优化(尾递归优化)
function Fibonacci2 (n , ac1 = 1 , ac2 = 1) {
if( n <= 1 ) {return ac2};
return Fibonacci2 (n - 1, ac2, ac1 + ac2);
}
// 循环版
function Fibonacci3(n){
if (n===1 || n===2) {
return 1;
}
let ac1 = 1, ac2 = 1;
for (let i = 2; i < n; i++){
[ac1, ac2] = [ac2, ac1 + ac2];
}
return ac2;
}
// 加入缓存版本
function getFio () {
var cache = []
function Fibonacci(n){
if(cache[n]){return cache[n]}
if ( n <= 1 ) {cache[n]=1; return 1};
var temp = Fibonacci(n - 1) + Fibonacci(n - 2);
cache[n] = temp
return temp
}
return Fibonacci
}
阶乘(尾递归)
尾递归的实现,往往需要改写递归函数,确保最后一步只调用自身。做到这一点的方法,就是把所有用到的内部变量改写成函数的参数。1
2
3
4function factorial(n, total = 1) {
if (n === 1) return total;
return factorial(n - 1, n * total);
}
对象上实现for of
1 | // method 1 |
计算数组中每个元素出现的次数
方法:reduce1
2
3
4
5
6
7
8
9
10
11
12
13
14
15//计算数组中每个元素出现的次数
let names = ['Alice', 'Bob', 'Tiff', 'Bruce', 'Alice'];
var getNameCounts = function(namesArray){
return namesArray.reduce(function(result,item, index, arr){
if(!result[item]){
result[item] =1
}else{
result[item]+=1
}
return result
},{})
}
getNameCounts(names)
// {Alice: 2, Bob: 1, Tiff: 1, Bruce: 1}
数组去重
2种方法: reduce/ Set1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16var arr = [1,2,3,4,4,1]
// method 1
var removeDuplicate = function(arr){
return arr.reduce(function(prev,cur,index, arr){
if(!prev.includes(cur)){
prev.push(cur)
}
return prev
},[])
}
removeDuplicate(arr)
// [1, 2, 3, 4]
// method 2
[...new Set(arr)]
Array.from(new Set(arr))
将二维数组转化为一维
2 种方法: reduce / flat1
2
3
4
5
6
7
8
9
10
11
12// method 1
var arr = [[0, 1], [2, 3], [4, 5]]
var flattenDim2Arr = function(arr){
return arr.reduce(function(prev,cur){
return prev.concat(cur)
},[])
}
flattenDim2Arr(arr)
// [0,1,2,3,4,5]
// method 2
arr.flat()
将多维数组转化为一维
3种方法: reduce(递归)/迭代/ flat1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21var arr = [[0, 1], [2, 3], [4,[5,6,7]]]
// method 1
var flatternMultiDimArr = function(arr){
return arr.reduce(function(prev,cur){
return prev.concat(Array.isArray(cur)?flattenDim2Arr(cur):cur)
},[])
}
flatternMultiDimArr(arr)
// [0, 1, 2, 3, 4, 5, 6, 7]
// method 2
var flattern = function(arr){
while(arr.some(x=> Array.isArray(x))){
arr = [].concat(...arr)
}
return arr
}
// method 3
arr.flat(Infinity)
对象里的属性求和
1 | var result = [ |
闭包
请写一个函数 fn,其参数是一个函数 fn2,其返回值是一个函数 fn3,fn3 会调用 fn2。
请写一个函数满足 add(1, 2, 3, …) 得到所有参数的和,参数个数不确定。
请写一个函数满足 add2(1)(2)(3)(………)() 在遇到没有参数的调用时,返回所有参数的和。
请再写一次 add2,命名为 add3,要求其内部使用了 add 函数,且只在最后求和时使用了 add。
issue#1 闭包,fn3在上级作用域获得fn21
2
3
4
5
6
7
8
9
10var fn= function(fn2){
let func = arguments[0]
return function(){
func.call(this)
}
}
var fn2 = function(){console.log('i am fn2')}
var result = fn(fn2)
result()
issue#21
2
3
4
5 var add = function(){
return Array.from(arguments).reduce((sum,item)=>sum+ item,0)
}
add(1,2,3,4)
issue#31
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20// 没有参数的时候返回累加值 有参数的时候返回函数本身
(function(){
let result = 0
window.add2= function(n){
if(n=== undefined){
let argsArray = [...arguments]
result += argsArray.reduce((sum,item)=>sum+ item,0)
return add2
}else{
let final = result
result = 0
return final
}
}
})(0)
var res = add2(1)(2)(3,4)()
var res = add3(1,2)(1,2)()
issue#41
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18(function(){
let list = []
window.add3= function(n){
if(n === undefined){
list.push(...arguments)
return add3
}else{
let final = add(...list)
list = []
return final
}
}
})()
var res = add2(1)(2)(3,4)()
var res = add3(1,2)(1,2)()
数组合并
请把两个数组 [‘A1’, ‘A2’, ‘B1’, ‘B2’, ‘C1’, ‘C2’, ‘D1’, ‘D2’] 和 [‘A’, ‘B’, ‘C’, ‘D’],合并为 [‘A1’, ‘A2’, ‘A’, ‘B1’, ‘B2’, ‘B’, ‘C1’, ‘C2’, ‘C’, ‘D1’, ‘D2’, ‘D’]。
方法: reduce(找出插入位置) /改造数组后合并后排序1
2
3
4
5
6
7// method 1
['A', 'B', 'C', 'D'].reduce(function(result, item){
// 注意此处的浅拷贝
let temp = [...result].reverse()
let idx = temp.length-temp.findIndex(x=>x.startsWith(item))
return [...result.slice(0, idx),item, ...result.slice(idx)]
}, ['A1', 'A2', 'B1', 'B2', 'C1', 'C2', 'D1', 'D2'] )
1 | // method 2 |
大数相加
当年我罗里吧嗦写的代码,现在用reduce一下子省去了很多代码量,而且可读性有了质的飞跃,强烈安利reduce。
细节要注意string 和 number转换和一些边界情况处理。
1 | var str1='31287' |
当a 等于多少成立
1 | // var a = ?; |
方法:
- toString
- valueOf
- 数组的join(数组调用toString会调用join)
- Object.defineProperty
- Symbol.toPrimitive
考察: 隐式转换涉及的方法 & 数据缓存(闭包/generator函数)
背景知识
如果原始类型的值和对象比较,对象会转为原始类型的值,再进行比较。
对象转换成原始类型的值,算法是 先调用valueOf方法;如果返回的还是对象,再接着调用 toString方法。
对于数组对象,toString 方法返回一个字符串,该字符串由数组中的每个元素的 toString() 返回值经调用 join() 方法连接(由逗号隔开)组成。
可以看到数组 toString 会调用本身的 join 方法。
ES6 还提供了 11 个内置的 Symbol 值,指向语言内部使用的方法。 Symbol.toPrimitive就是其中一个,它指向一个方法,表示该对象被转为原始类型的值时,会调用这个方法,返回该对象对应的原始类型值。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61// method 1
var a={
i:0,
toString(){
return ++a.i
}
}
// method 1.1 闭包
var a={
valueOf:(x=>()=>++x)(0)
}
// method 2.1
var a={
i:0,
valueOf(){
return ++a.i
}
}
// method 2.2
var a={
gen:(function *(){
yield 1;
yield 2;
yield 3;
})(),
valueOf(){
return this.gen.next().value
}
}
// method 3
var a=[1,2,3]
a.join = a.shift
if(a == 1 && a == 2 && a == 3){
console.log(1);
}
// method 4.1
var i=0
Object.defineProperty(window, 'a',{
get(){
return ++i
}
})
// method 4.2
Object.defineProperty(window, 'a', {
get: function() {
return this.value = this.value ? (this.value += 1) : 1;
}
});
// method 5
var a= {[Symbol.toPrimitive]: (x => ()=>++x)(0)}
if(a == 1 && a == 2 && a == 3) {
console.log('1');
}
说出结果1
考察:map 的callback传入的参数 和parseInt对于radix处理1
['1','2','3'].map(parseInt)
分析:
实际执行 parseInt(‘1’,0) –pareseInt(‘2’,1) – parseInt(‘3’,2)
得到[1, NaN, NaN]
说出结果2 和修改打印10 和 20
考察:具名自执行函数的变量为只读属性,不可修改,在内部类似const常量,不能被再次赋值1
2
3
4
5
6var b = 10;
(function b(){
// 'use strict' TypeError: Assignment to constant variable.
b = 20;
console.log(b);
})();
answer: 打印立即执行的b函数
ƒ b() {
b = 20;
console.log(b)
}
这样改,打印101
2
3
4
5var b = 10;
(function b(){
b = 20;
console.log(window.b);
})();
这样改,打印201
2
3
4
5
6
7
8
9
10
11
12var b = 10;
(function b(){
var b = 20;
console.log(b);
})();
var b = 10;
(function (){
b = 20;
console.log(b);
})();
手写防抖(2个版本)/节流(3个版本)
防抖动是将多次执行变为最后一次执行,节流是将多次执行变成每隔一段时间执行 。
防抖场景:用户在输入框不断输入;window 的resize/scroll
节流场景:鼠标连续点击;滚动页面时,每隔一段时间发一次 ajax 请求;监听滚动事件,比如是否滑到底部自动加载更多
防抖的实现重点,就是巧用setTimeout做缓存池,而且可以轻易地清除待执行的代码。防抖是维护一个计时器,规定在delay时间后触发函数,但是在delay时间内再次触发的话,都会清除当前的 timer 然后重新设置超时调用,即重新计时。
节流是通过判断是否到达一定时间来触发函数,超过则调用,不超过则使用计时器延后,每次函数触发会修改时间标记位
1 | // 版本1: 暴力版,会频繁创建定时器。首次会执行,最后一次会执行 |
1 | // 时间戳版本 节流 :第一次会执行 |
异步题目
改造下面的代码,使之输出0 - 9
1
2
3
4
5for (var i = 0; i< 10; i++){
setTimeout(() => {
console.log(i);
}, 1000)
}方法:let创建块级作用域/ setTimeout第三个参数/ 立即执行函数传递参数/ 闭包 / 用数组(取巧的方法)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41// method 1
for (let i = 0; i< 10; i++){
setTimeout(() => {
console.log(i);
}, 1000)
}
// method 2
for (var i = 0; i< 10; i++){
setTimeout((i) => {
console.log(i);
}, 1000,i)
}
// method 3
for (var i = 0; i< 10; i++){
(function(i){
setTimeout(() => {
console.log(i);
}, 1000)
})(i)
}
// method 4
for (var i = 0; i< 10; i++){
(()=>{
var temp = i // clousure
setTimeout(() => {
console.log(temp);
}, 1000)
})()
}
// method 5
var arr= []
for (var i = 0; i< 10; i++){
arr.push(i)
setTimeout(() => {
console.log(arr.shift());
}, 1000)
}
promise 执行顺序
核心 理解promise resolve后才将then 放入微任务,await a; 语句b 实际上是 Promise.resovle(a).then(b), a 如果不是返回promise,那么b会被马上放入微任务中,如果a返回了promise,这个promise resolved后b才被放入微任务中。
由于因为async await 本身就是promise+generator的语法糖。所以await后面的代码是microtask。
题目来源
原始
1 | async function async1() { |
变式1
1 | async function async1() { |
变式2
1 | async function async1() { |
变式3
1 | async function a1 () { |
promise 串行 并行 all race finally
all
1 | // promise all |
ps: Promise.all错误处理
当promise捕获到error 的时候,代码吃掉这个异常,返回resolve,约定特殊格式表示这个调用成功,catch后仍然返回一个resovle的promise1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27var p1 =new Promise(function(resolve,reject){
setTimeout(function(){
resolve(1);
},0)
});
var p2 = new Promise(function(resolve,reject){
setTimeout(function(){
resolve(2);
},200)
});
var p3 = new Promise(function(resolve,reject){
setTimeout(function(){
try{
console.log(XX.BBB);
}
catch(exp){
resolve("error");
}
},100)
});
Promise.all([p1, p2, p3]).then(function (results) {
console.log("success")
console.log(results);
}).catch(function(r){
console.log("err");
console.log(r);
});
race
1 | // 实现promise race |
finally
finally本质上是then方法的特例。finally方法总是会返回原来的值。1
2
3
4
5
6
7
8// promise fainally
Promise.prototype.finally = function (callback) {
let P = this.constructor;
return this.then(
value => P.resolve(callback()).then(() => value),
reason => P.resolve(callback()).then(() => { throw reason })
);
};
serial
1 | function serial(reqList){ |
parallel
1 | // 实现并行 |
拓展
实现1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17function machine() {
}
machine('ygy').execute()
// start ygy
machine('ygy').do('eat').execute();
// start ygy
// ygy eat
machine('ygy').wait(5).do('eat').execute();
// start ygy
// wait 5s(这里等待了5s)
// ygy eat
machine('ygy').waitFirst(5).do('eat').execute();
// wait 5s
// start ygy
// ygy eat
1 | // 本质 串行执行promise |
API详解
Array.prototype.reduce((callback,[initialValue])
reduce 为数组中的每一个元素依次执行回调函数,不包括数组中被删除或从未被赋值的元素,接受四个参数:初始值(或者上一次回调函数的返回值),当前元素值,当前索引,调用 reduce 的数组。callback (执行数组中每个值的函数,包含四个参数)
1、previousValue (上一次调用回调返回的值,或者是提供的初始值(initialValue))
2、currentValue (数组中当前被处理的元素)
3、index (当前元素在数组中的索引)
4、array (调用 reduce 的数组)initialValue (作为第一次调用 callback 的第一个参数。)
如果没有提供initialValue,reduce 会从索引1的地方开始执行 callback 方法,跳过第一个索引,prev是数组的第一个值。如果提供initialValue,从索引0开始,prevshi initialValue。
Point To MDNarrayObject.prototype.concat(arrayX,arrayX)
注意参数arrayX如果是数组,则将arrayX的成员添加到arrayObject,如果不是则,直接添加arrayX到arrayObjectarrayObject.prototype.map(callback, thisArg)
map 方法会给原数组中的每个元素都按顺序调用一次 callback 函数。callback 每次执行后的返回值(包括 undefined)组合起来形成一个新数组
函数会被 自动传入三个参数:数组元素,元素索引,原数组本身parseInt(string, radix))
radix:一个介于2和36之间的整数(数学系统的基础),当未指定基数时,不同的实现会产生不同的结果,通常将值默认为10。
返回解析后的整数值。 如果被解析参数的第一个字符无法被转化成数值类型,则返回 NaN
parseInt(‘123’, 5) // 将’123’看作5进制数,返回十进制数38 => 15^2 + 25^1 + 3*5^0 = 38
在基数为 undefined,或者基数为 0 或者没有指定的情况下,JavaScript 作如下处理:
如果字符串 string 以”0x”或者”0X”开头, 则基数是16 (16进制).
如果字符串 string 以”0”开头, 基数是8(八进制)或者10(十进制),那么具体是哪个基数由实现环境决定。ECMAScript 5 规定使用10,但是并不是所有的浏览器都遵循这个规定。因此,永远都要明确给出radix参数的值。
如果字符串 string 以其它任何值开头,则基数是10 (十进制)。
Point To MDNObject.prototype.valueOf()
你很少需要自己调用valueOf方法;当遇到要预期的原始值的对象时,JavaScript会自动调用它。
返回 指定对象的原始值。如果对象没有原始值,则valueOf将返回对象本身。JavaScript的许多内置对象都重写了该函数,以实现更适合自身的功能需要。
Point To MDN- Object.prototype.toString()
当该对象被表示为一个文本值时,或者一个对象以预期的字符串方式引用时自动调用。
返回指定对象的原始值。
如果此方法在自定义对象中未被覆盖,toString() 返回 “[object type]”,其中type是对象的类型。
Point To MDN参考
JS数组reduce()方法详解及高级技巧
每天一道面试题
从 (a==1&&a==2&&a==3) 成立中看javascript的隐式类型转换