实现 add(1)(2)(3)
函数柯里化概念:柯里化(Currying)是把承受多个参数的函数转变为承受一个繁多参数的函数,并且返回承受余下的参数且返回后果的新函数的技术。
1)粗犷版
function add (a) {return function (b) {return function (c) {return a + b + c;}
}
}
console.log(add(1)(2)(3)); // 6
2)柯里化解决方案
- 参数长度固定
var add = function (m) {var temp = function (n) {return add(m + n);
}
temp.toString = function () {return m;}
return temp;
};
console.log(add(3)(4)(5)); // 12
console.log(add(3)(6)(9)(25)); // 43
对于 add(3)(4)(5),其执行过程如下:
- 先执行 add(3),此时 m =3,并且返回 temp 函数;
- 执行 temp(4),这个函数内执行 add(m+n),n 是此次传进来的数值 4,m 值还是上一步中的 3,所以 add(m+n)=add(3+4)=add(7),此时 m =7,并且返回 temp 函数
- 执行 temp(5),这个函数内执行 add(m+n),n 是此次传进来的数值 5,m 值还是上一步中的 7,所以 add(m+n)=add(7+5)=add(12),此时 m =12,并且返回 temp 函数
- 因为前面没有传入参数,等于返回的 temp 函数不被执行而是打印,理解 JS 的敌人都晓得对象的 toString 是批改对象转换字符串的办法,因而代码中 temp 函数的 toString 函数 return m 值,而 m 值是最初一步执行函数时的值 m =12,所以返回值是 12。
- 参数长度不固定
function add (...args) {
// 求和
return args.reduce((a, b) => a + b)
}
function currying (fn) {let args = []
return function temp (...newArgs) {if (newArgs.length) {
args = [
...args,
...newArgs
]
return temp
} else {let val = fn.apply(this, args)
args = [] // 保障再次调用时清空
return val
}
}
}
let addCurry = currying(add)
console.log(addCurry(1)(2)(3)(4, 5)()) //15
console.log(addCurry(1)(2)(3, 4, 5)()) //15
console.log(addCurry(1)(2, 3, 4, 5)()) //15
基于 Generator 函数实现 async/await 原理
外围:传递给我一个
Generator
函数,把函数中的内容基于Iterator
迭代器的特点一步步的执行
function readFile(file) {
return new Promise(resolve => {setTimeout(() => {resolve(file);
}, 1000);
})
};
function asyncFunc(generator) {const iterator = generator(); // 接下来要执行 next
// data 为第一次执行之后的返回后果,用于传给第二次执行
const next = (data) => {let { value, done} = iterator.next(data); // 第二次执行,并接管第一次的申请后果 data
if (done) return; // 执行结束 (到第三次) 间接返回
// 第一次执行 next 时,yield 返回的 promise 实例 赋值给了 value
value.then(data => {next(data); // 当第一次 value 执行结束且胜利时,执行下一步(并把第一次的后果传递下一步)
});
}
next();};
asyncFunc(function* () {
// 生成器函数:控制代码一步步执行
let data = yield readFile('a.js'); // 等这一步骤执行执行胜利之后,再往下走,没执行完的时候,间接返回
data = yield readFile(data + 'b.js');
return data;
})
Function.prototype.apply()
第一个参数是绑定的 this,默认为window
,第二个参数是数组或类数组
Function.prototype.apply = function(context = window, args) {if (typeof this !== 'function') {throw new TypeError('Type Error');
}
const fn = Symbol('fn');
context[fn] = this;
const res = context[fn](...args);
delete context[fn];
return res;
}
请实现一个 add 函数,满足以下性能
add(1); // 1
add(1)(2); // 3
add(1)(2)(3);// 6
add(1)(2, 3); // 6
add(1, 2)(3); // 6
add(1, 2, 3); // 6
function add(...args) {
// 在外部申明一个函数,利用闭包的个性保留并收集所有的参数值
let fn = function(...newArgs) {return add.apply(null, args.concat(newArgs))
}
// 利用 toString 隐式转换的个性,当最初执行时隐式转换,并计算最终的值返回
fn.toString = function() {return args.reduce((total,curr)=> total + curr)
}
return fn
}
考点:
- 应用闭包,同时要对 JavaScript 的作用域链(原型链)有深刻的了解
- 重写函数的
toSting()
办法
// 测试,调用 toString 办法触发求值
add(1).toString(); // 1
add(1)(2).toString(); // 3
add(1)(2)(3).toString();// 6
add(1)(2, 3).toString(); // 6
add(1, 2)(3).toString(); // 6
add(1, 2, 3).toString(); // 6
Promise 并行限度
就是实现有并行限度的 Promise 调度器问题
class Scheduler {constructor() {this.queue = [];
this.maxCount = 2;
this.runCounts = 0;
}
add(promiseCreator) {this.queue.push(promiseCreator);
}
taskStart() {for (let i = 0; i < this.maxCount; i++) {this.request();
}
}
request() {if (!this.queue || !this.queue.length || this.runCounts >= this.maxCount) {return;}
this.runCounts++;
this.queue.shift()().then(() => {
this.runCounts--;
this.request();});
}
}
const timeout = time => new Promise(resolve => {setTimeout(resolve, time);
})
const scheduler = new Scheduler();
const addTask = (time,order) => {scheduler.add(() => timeout(time).then(()=>console.log(order)))
}
addTask(1000, '1');
addTask(500, '2');
addTask(300, '3');
addTask(400, '4');
scheduler.taskStart()
// 2
// 3
// 1
// 4
实现一个 sleep 函数,比方 sleep(1000) 意味着期待 1000 毫秒
// 应用 promise 来实现 sleep
const sleep = (time) => {return new Promise(resolve => setTimeout(resolve, time))
}
sleep(1000).then(() => {// 这里写你的骚操作})
参考 前端进阶面试题具体解答
二叉树档次遍历
// 二叉树档次遍历
class Node {constructor(element, parent) {
this.parent = parent // 父节点
this.element = element // 以后存储内容
this.left = null // 左子树
this.right = null // 右子树
}
}
class BST {constructor(compare) {
this.root = null // 树根
this.size = 0 // 树中的节点个数
this.compare = compare || this.compare
}
compare(a,b) {return a - b}
add(element) {if(this.root === null) {this.root = new Node(element, null)
this.size++
return
}
// 获取根节点 用以后增加的进行判断 放右边还是放左边
let currentNode = this.root
let compare
let parent = null
while (currentNode) {compare = this.compare(element, currentNode.element)
parent = currentNode // 先将父亲保存起来
// currentNode 要不停的变动
if(compare > 0) {currentNode = currentNode.right} else if(compare < 0) {currentNode = currentNode.left} else {currentNode.element = element // 相等时 先笼罩后续解决}
}
let newNode = new Node(element, parent)
if(compare > 0) {parent.right = newNode} else if(compare < 0) {parent.left = newNode}
this.size++
}
// 档次遍历 队列
levelOrderTraversal(visitor) {if(this.root == null) {return}
let stack = [this.root]
let index = 0 // 指针 指向 0
let currentNode
while (currentNode = stack[index++]) {
// 反转二叉树
let tmp = currentNode.left
currentNode.left = currentNode.right
currentNode.right = tmp
visitor.visit(currentNode.element)
if(currentNode.left) {stack.push(currentNode.left)
}
if(currentNode.right) {stack.push(currentNode.right)
}
}
}
}
// 测试
var bst = new BST((a,b)=>a.age-b.age) // 模仿 sort 办法
// ![](http://img-repo.poetries.top/images/20210522203619.png)
// ![](http://img-repo.poetries.top/images/20210522211809.png)
bst.add({age: 10})
bst.add({age: 8})
bst.add({age:19})
bst.add({age:6})
bst.add({age: 15})
bst.add({age: 22})
bst.add({age: 20})
// 应用访问者模式
class Visitor {constructor() {this.visit = function (elem) {elem.age = elem.age*2}
}
}
// ![](http://img-repo.poetries.top/images/20210523095515.png)
console.log(bst.levelOrderTraversal(new Visitor()))
Promise
// 模仿实现 Promise
// Promise 利用三大伎俩解决回调天堂:// 1. 回调函数提早绑定
// 2. 返回值穿透
// 3. 谬误冒泡
// 定义三种状态
const PENDING = 'PENDING'; // 进行中
const FULFILLED = 'FULFILLED'; // 已胜利
const REJECTED = 'REJECTED'; // 已失败
class Promise {constructor(exector) {
// 初始化状态
this.status = PENDING;
// 将胜利、失败后果放在 this 上,便于 then、catch 拜访
this.value = undefined;
this.reason = undefined;
// 胜利态回调函数队列
this.onFulfilledCallbacks = [];
// 失败态回调函数队列
this.onRejectedCallbacks = [];
const resolve = value => {
// 只有进行中状态能力更改状态
if (this.status === PENDING) {
this.status = FULFILLED;
this.value = value;
// 胜利态函数顺次执行
this.onFulfilledCallbacks.forEach(fn => fn(this.value));
}
}
const reject = reason => {
// 只有进行中状态能力更改状态
if (this.status === PENDING) {
this.status = REJECTED;
this.reason = reason;
// 失败态函数顺次执行
this.onRejectedCallbacks.forEach(fn => fn(this.reason))
}
}
try {
// 立刻执行 executor
// 把外部的 resolve 和 reject 传入 executor,用户可调用 resolve 和 reject
exector(resolve, reject);
} catch(e) {
// executor 执行出错,将谬误内容 reject 抛出去
reject(e);
}
}
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function'? onRejected :
reason => {throw new Error(reason instanceof Error ? reason.message : reason) }
// 保留 this
const self = this;
return new Promise((resolve, reject) => {if (self.status === PENDING) {self.onFulfilledCallbacks.push(() => {
// try 捕捉谬误
try {
// 模仿微工作
setTimeout(() => {const result = onFulfilled(self.value);
// 分两种状况:// 1. 回调函数返回值是 Promise,执行 then 操作
// 2. 如果不是 Promise,调用新 Promise 的 resolve 函数
result instanceof Promise ? result.then(resolve, reject) : resolve(result);
})
} catch(e) {reject(e);
}
});
self.onRejectedCallbacks.push(() => {
// 以下同理
try {setTimeout(() => {const result = onRejected(self.reason);
// 不同点:此时是 reject
result instanceof Promise ? result.then(resolve, reject) : resolve(result);
})
} catch(e) {reject(e);
}
})
} else if (self.status === FULFILLED) {
try {setTimeout(() => {const result = onFulfilled(self.value);
result instanceof Promise ? result.then(resolve, reject) : resolve(result);
});
} catch(e) {reject(e);
}
} else if (self.status === REJECTED) {
try {setTimeout(() => {const result = onRejected(self.reason);
result instanceof Promise ? result.then(resolve, reject) : resolve(result);
})
} catch(e) {reject(e);
}
}
});
}
catch(onRejected) {return this.then(null, onRejected);
}
static resolve(value) {if (value instanceof Promise) {
// 如果是 Promise 实例,间接返回
return value;
} else {
// 如果不是 Promise 实例,返回一个新的 Promise 对象,状态为 FULFILLED
return new Promise((resolve, reject) => resolve(value));
}
}
static reject(reason) {return new Promise((resolve, reject) => {reject(reason);
})
}
static all(promiseArr) {
const len = promiseArr.length;
const values = new Array(len);
// 记录曾经胜利执行的 promise 个数
let count = 0;
return new Promise((resolve, reject) => {for (let i = 0; i < len; i++) {// Promise.resolve()解决,确保每一个都是 promise 实例
Promise.resolve(promiseArr[i]).then(
val => {values[i] = val;
count++;
// 如果全副执行完,返回 promise 的状态就能够扭转了
if (count === len) resolve(values);
},
err => reject(err),
);
}
})
}
static race(promiseArr) {return new Promise((resolve, reject) => {
promiseArr.forEach(p => {Promise.resolve(p).then(val => resolve(val),
err => reject(err),
)
})
})
}
}
分片思维解决大数据量渲染问题
题目形容: 渲染百万条构造简略的大数据时 怎么应用分片思维优化渲染
let ul = document.getElementById("container");
// 插入十万条数据
let total = 100000;
// 一次插入 20 条
let once = 20;
// 总页数
let page = total / once;
// 每条记录的索引
let index = 0;
// 循环加载数据
function loop(curTotal, curIndex) {if (curTotal <= 0) {return false;}
// 每页多少条
let pageCount = Math.min(curTotal, once);
window.requestAnimationFrame(function () {for (let i = 0; i < pageCount; i++) {let li = document.createElement("li");
li.innerText = curIndex + i + ":" + ~~(Math.random() * total);
ul.appendChild(li);
}
loop(curTotal - pageCount, curIndex + pageCount);
});
}
loop(total, index);
扩大思考:对于大数据量的简略 dom
构造渲染能够用分片思维解决 如果是简单的 dom
构造渲染如何解决?
这时候就须要应用 虚构列表 了,虚构列表和虚构表格在日常我的项目应用还是很多的
批改嵌套层级很深对象的 key
// 有一个嵌套档次很深的对象,key 都是 a_b 模式,须要改成 ab 的模式,留神不能用递归。const a = {
a_y: {
a_z: {y_x: 6},
b_c: 1
}
}
// {
// ay: {
// az: {
// yx: 6
// },
// bc: 1
// }
// }
办法 1:序列化 JSON.stringify + 正则匹配
const regularExpress = (obj) => {
try {const str = JSON.stringify(obj).replace(/_/g, "");
return JSON.parse(str);
} catch (error) {return obj;}
};;
办法 2:递归
const recursion = (obj) => {const keys = Object.keys(obj);
keys.forEach((key) => {const newKey = key.replace(/_/g, "");
obj[newKey] = recursion(obj[key]);
delete obj[key];
});
return obj;
};
实现一个治理本地缓存过期的函数
封装一个能够设置过期工夫的
localStorage
存储函数
class Storage{constructor(name){this.name = 'storage';}
// 设置缓存
setItem(params){
let obj = {
name:'', // 存入数据 属性
value:'',// 属性值
expires:"", // 过期工夫
startTime:new Date().getTime()// 记录何时将值存入缓存,毫秒级
}
let options = {};
// 将 obj 和传进来的 params 合并
Object.assign(options,obj,params);
if(options.expires){
// 如果 options.expires 设置了的话
// 以 options.name 为 key,options 为值放进去
localStorage.setItem(options.name,JSON.stringify(options));
}else{
// 如果 options.expires 没有设置,就判断一下 value 的类型
let type = Object.prototype.toString.call(options.value);
// 如果 value 是对象或者数组对象的类型,就先用 JSON.stringify 转一下,再存进去
if(Object.prototype.toString.call(options.value) == '[object Object]'){options.value = JSON.stringify(options.value);
}
if(Object.prototype.toString.call(options.value) == '[object Array]'){options.value = JSON.stringify(options.value);
}
localStorage.setItem(options.name,options.value);
}
}
// 拿到缓存
getItem(name){let item = localStorage.getItem(name);
// 先将拿到的试着进行 json 转为对象的模式
try{item = JSON.parse(item);
}catch(error){
// 如果不行就不是 json 的字符串,就间接返回
item = item;
}
// 如果有 startTime 的值,阐明设置了生效工夫
if(item.startTime){let date = new Date().getTime();
// 何时将值取出减去刚存入的工夫,与 item.expires 比拟,如果大于就是过期了,如果小于或等于就还没过期
if(date - item.startTime > item.expires){
// 缓存过期,革除缓存,返回 false
localStorage.removeItem(name);
return false;
}else{
// 缓存未过期,返回值
return item.value;
}
}else{
// 如果没有设置生效工夫,间接返回值
return item;
}
}
// 移出缓存
removeItem(name){localStorage.removeItem(name);
}
// 移出全副缓存
clear(){localStorage.clear();
}
}
用法
let storage = new Storage();
storage.setItem({
name:"name",
value:"ppp"
})
上面我把值取出来
let value = storage.getItem('name');
console.log('我是 value',value);
设置 5 秒过期
let storage = new Storage();
storage.setItem({
name:"name",
value:"ppp",
expires: 5000
})
// 过期后再取出来会变为 false
let value = storage.getItem('name');
console.log('我是 value',value);
判断括号字符串是否无效(小米)
题目形容
给定一个只包含 '(',')','{','}','[',']' 的字符串 s,判断字符串是否无效。无效字符串需满足:- 左括号必须用雷同类型的右括号闭合。- 左括号必须以正确的程序闭合。示例 1:输出:s = "()"
输入:true
示例 2:输出:s = "()[]{}"
输入:true
示例 3:输出:s = "(]"
输入:false
答案
const isValid = function (s) {if (s.length % 2 === 1) {return false;}
const regObj = {"{": "}",
"(": ")",
"[": "]",
};
let stack = [];
for (let i = 0; i < s.length; i++) {if (s[i] === "{" || s[i] === "(" || s[i] === "[") {stack.push(s[i]);
} else {const cur = stack.pop();
if (s[i] !== regObj[cur]) {return false;}
}
}
if (stack.length) {return false;}
return true;
};
实现 (5).add(3).minus(2) 性能
例:5 + 3 – 2,后果为 6
Number.prototype.add = function(n) {return this.valueOf() + n;
};
Number.prototype.minus = function(n) {return this.valueOf() - n;
};
实现 add(1)(2) =3
// 题意的答案
const add = (num1) => (num2)=> num2 + num1;
// 整了一个加强版 能够有限链式调用 add(1)(2)(3)(4)(5)....
function add(x) {
// 存储和
let sum = x;
// 函数调用会相加,而后每次都会返回这个函数自身
let tmp = function (y) {
sum = sum + y;
return tmp;
};
// 对象的 toString 必须是一个办法 在办法中返回了这个和
tmp.toString = () => sum
return tmp;
}
alert(add(1)(2)(3)(4)(5))
有限链式调用实现的关键在于 对象的 toString 办法 : 每个对象都有一个
toString()
办法,当该对象被示意为一个文本值时,或者一个对象以预期的字符串形式援用时主动调用。
也就是我在调用很屡次后,他们的后果会存在 add
函数中的 sum
变量上,当我 alert
的时候 add
会主动调用 toString
办法 打印出 sum,
也就是最终的后果
AJAX
const getJSON = function(url) {return new Promise((resolve, reject) => {const xhr = XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Mscrosoft.XMLHttp');
xhr.open('GET', url, false);
xhr.setRequestHeader('Accept', 'application/json');
xhr.onreadystatechange = function() {if (xhr.readyState !== 4) return;
if (xhr.status === 200 || xhr.status === 304) {resolve(xhr.responseText);
} else {reject(new Error(xhr.responseText));
}
}
xhr.send();})
}
树形构造转成列表(解决菜单)
[
{
id: 1,
text: '节点 1',
parentId: 0,
children: [
{
id:2,
text: '节点 1_1',
parentId:1
}
]
}
]
转成
[
{
id: 1,
text: '节点 1',
parentId: 0 // 这里用 0 示意为顶级节点
},
{
id: 2,
text: '节点 1_1',
parentId: 1 // 通过这个字段来确定子父级
}
...
]
实现代码如下:
function treeToList(data) {let res = [];
const dfs = (tree) => {tree.forEach((item) => {if (item.children) {dfs(item.children);
delete item.children;
}
res.push(item);
});
};
dfs(data);
return res;
}
异步串行 | 异步并行
// 字节面试题,实现一个异步加法
function asyncAdd(a, b, callback) {setTimeout(function () {callback(null, a + b);
}, 500);
}
// 解决方案
// 1. promisify
const promiseAdd = (a, b) => new Promise((resolve, reject) => {asyncAdd(a, b, (err, res) => {if (err) {reject(err)
} else {resolve(res)
}
})
})
// 2. 串行解决
async function serialSum(...args) {return args.reduce((task, now) => task.then(res => promiseAdd(res, now)), Promise.resolve(0))
}
// 3. 并行处理
async function parallelSum(...args) {if (args.length === 1) return args[0]
const tasks = []
for (let i = 0; i < args.length; i += 2) {tasks.push(promiseAdd(args[i], args[i + 1] || 0))
}
const results = await Promise.all(tasks)
return parallelSum(...results)
}
// 测试
(async () => {console.log('Running...');
const res1 = await serialSum(1, 2, 3, 4, 5, 8, 9, 10, 11, 12)
console.log(res1)
const res2 = await parallelSum(1, 2, 3, 4, 5, 8, 9, 10, 11, 12)
console.log(res2)
console.log('Done');
})()
实现 redux-thunk
redux-thunk
能够利用redux
中间件让redux
反对异步的action
// 如果 action 是个函数,就调用这个函数
// 如果 action 不是函数,就传给下一个中间件
// 发现 action 是函数就调用
const thunk = ({dispatch, getState}) => (next) => (action) => {if (typeof action === 'function') {return action(dispatch, getState);
}
return next(action);
};
export default thunk
判断是否是电话号码
function isPhone(tel) {var regx = /^1[34578]\d{9}$/;
return regx.test(tel);
}
实现类数组转化为数组
类数组转换为数组的办法有这样几种:
- 通过 call 调用数组的 slice 办法来实现转换
Array.prototype.slice.call(arrayLike);
- 通过 call 调用数组的 splice 办法来实现转换
Array.prototype.splice.call(arrayLike, 0);
- 通过 apply 调用数组的 concat 办法来实现转换
Array.prototype.concat.apply([], arrayLike);
- 通过 Array.from 办法来实现转换
Array.from(arrayLike);
二分查找
function search(arr, target, start, end) {
let targetIndex = -1;
let mid = Math.floor((start + end) / 2);
if (arr[mid] === target) {
targetIndex = mid;
return targetIndex;
}
if (start >= end) {return targetIndex;}
if (arr[mid] < target) {return search(arr, target, mid + 1, end);
} else {return search(arr, target, start, mid - 1);
}
}
// const dataArr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
// const position = search(dataArr, 6, 0, dataArr.length - 1);
// if (position !== -1) {// console.log(` 指标元素在数组中的地位:${position}`);
// } else {// console.log("指标元素不在数组中");
// }