实现 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)); // 12console.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)()) //15console.log(addCurry(1)(2)(3, 4, 5)()) //15console.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); // 1add(1)(2); // 3add(1)(2)(3);// 6add(1)(2, 3); // 6add(1, 2)(3); // 6add(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(); // 1add(1)(2).toString(); // 3add(1)(2)(3).toString();// 6add(1)(2, 3).toString(); // 6add(1, 2)(3).toString(); // 6add(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来实现 sleepconst 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办法// // 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 } }}// 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})
// 过期后再取出来会变为 falselet 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. promisifyconst 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("指标元素不在数组中");// }