手写 Promise
const PENDING = "pending";const RESOLVED = "resolved";const REJECTED = "rejected";function MyPromise(fn) { // 保留初始化状态 var self = this; // 初始化状态 this.state = PENDING; // 用于保留 resolve 或者 rejected 传入的值 this.value = null; // 用于保留 resolve 的回调函数 this.resolvedCallbacks = []; // 用于保留 reject 的回调函数 this.rejectedCallbacks = []; // 状态转变为 resolved 办法 function resolve(value) { // 判断传入元素是否为 Promise 值,如果是,则状态扭转必须期待前一个状态扭转后再进行扭转 if (value instanceof MyPromise) { return value.then(resolve, reject); } // 保障代码的执行程序为本轮事件循环的开端 setTimeout(() => { // 只有状态为 pending 时能力转变, if (self.state === PENDING) { // 批改状态 self.state = RESOLVED; // 设置传入的值 self.value = value; // 执行回调函数 self.resolvedCallbacks.forEach(callback => { callback(value); }); } }, 0); } // 状态转变为 rejected 办法 function reject(value) { // 保障代码的执行程序为本轮事件循环的开端 setTimeout(() => { // 只有状态为 pending 时能力转变 if (self.state === PENDING) { // 批改状态 self.state = REJECTED; // 设置传入的值 self.value = value; // 执行回调函数 self.rejectedCallbacks.forEach(callback => { callback(value); }); } }, 0); } // 将两个办法传入函数执行 try { fn(resolve, reject); } catch (e) { // 遇到谬误时,捕捉谬误,执行 reject 函数 reject(e); }}MyPromise.prototype.then = function(onResolved, onRejected) { // 首先判断两个参数是否为函数类型,因为这两个参数是可选参数 onResolved = typeof onResolved === "function" ? onResolved : function(value) { return value; }; onRejected = typeof onRejected === "function" ? onRejected : function(error) { throw error; }; // 如果是期待状态,则将函数退出对应列表中 if (this.state === PENDING) { this.resolvedCallbacks.push(onResolved); this.rejectedCallbacks.push(onRejected); } // 如果状态曾经凝固,则间接执行对应状态的函数 if (this.state === RESOLVED) { onResolved(this.value); } if (this.state === REJECTED) { onRejected(this.value); }};
手写 call 函数
call 函数的实现步骤:
- 判断调用对象是否为函数,即便咱们是定义在函数的原型上的,然而可能呈现应用 call 等形式调用的状况。
- 判断传入上下文对象是否存在,如果不存在,则设置为 window 。
- 解决传入的参数,截取第一个参数后的所有参数。
- 将函数作为上下文对象的一个属性。
- 应用上下文对象来调用这个办法,并保留返回后果。
- 删除方才新增的属性。
- 返回后果。
// call函数实现Function.prototype.myCall = function(context) { // 判断调用对象 if (typeof this !== "function") { console.error("type error"); } // 获取参数 let args = [...arguments].slice(1), result = null; // 判断 context 是否传入,如果未传入则设置为 window context = context || window; // 将调用函数设为对象的办法 context.fn = this; // 调用函数 result = context.fn(...args); // 将属性删除 delete context.fn; return result;};
实现节流函数(throttle)
节流函数原理:指频繁触发事件时,只会在指定的时间段内执行事件回调,即触发事件间隔大于等于指定的工夫才会执行回调函数。总结起来就是: 事件,依照一段时间的距离来进行触发 。
像dom的拖拽,如果用消抖的话,就会呈现卡顿的感觉,因为只在进行的时候执行了一次,这个时候就应该用节流,在肯定工夫内屡次执行,会晦涩很多
手写简版
应用工夫戳的节流函数会在第一次触发事件时立刻执行,当前每过 wait 秒之后才执行一次,并且最初一次触发事件不会被执行
工夫戳形式:
// func是用户传入须要防抖的函数// wait是等待时间const throttle = (func, wait = 50) => { // 上一次执行该函数的工夫 let lastTime = 0 return function(...args) { // 以后工夫 let now = +new Date() // 将以后工夫和上一次执行函数工夫比照 // 如果差值大于设置的等待时间就执行函数 if (now - lastTime > wait) { lastTime = now func.apply(this, args) } }}setInterval( throttle(() => { console.log(1) }, 500), 1)
定时器形式:
应用定时器的节流函数在第一次触发时不会执行,而是在 delay 秒之后才执行,当最初一次进行触发后,还会再执行一次函数
function throttle(func, delay){ var timer = null; returnfunction(){ var context = this; var args = arguments; if(!timer){ timer = setTimeout(function(){ func.apply(context, args); timer = null; },delay); } }}
实用场景:
DOM
元素的拖拽性能实现(mousemove
)- 搜寻联想(
keyup
) - 计算鼠标挪动的间隔(
mousemove
) Canvas
模仿画板性能(mousemove
)- 监听滚动事件判断是否到页面底部主动加载更多
- 拖拽场景:固定工夫内只执行一次,避免超高频次触发地位变动
- 缩放场景:监控浏览器
resize
- 动画场景:防止短时间内屡次触发动画引起性能问题
总结
- 函数防抖 :将几次操作合并为一次操作进行。原理是保护一个计时器,规定在delay工夫后触发函数,然而在delay工夫内再次触发的话,就会勾销之前的计时器而从新设置。这样一来,只有最初一次操作能被触发。
- 函数节流 :使得肯定工夫内只触发一次函数。原理是通过判断是否达到肯定工夫来触发函数。
Array.prototype.forEach()
Array.prototype.forEach = function(callback, thisArg) { if (this == null) { throw new TypeError('this is null or not defined'); } if (typeof callback !== "function") { throw new TypeError(callback + ' is not a function'); } const O = Object(this); const len = O.length >>> 0; let k = 0; while (k < len) { if (k in O) { callback.call(thisArg, O[k], k, O); } k++; }}
Promise.race
Promise.race = function(promiseArr) { return new Promise((resolve, reject) => { promiseArr.forEach(p => { // 如果不是Promise实例须要转化为Promise实例 Promise.resolve(p).then( val => resolve(val), err => reject(err), ) }) })}
实现非负大整数相加
JavaScript对数值有范畴的限度,限度如下:
Number.MAX_VALUE // 1.7976931348623157e+308Number.MAX_SAFE_INTEGER // 9007199254740991Number.MIN_VALUE // 5e-324Number.MIN_SAFE_INTEGER // -9007199254740991
如果想要对一个超大的整数(> Number.MAX_SAFE_INTEGER
)进行加法运算,然而又想输入个别模式,那么应用 + 是无奈达到的,一旦数字超过 Number.MAX_SAFE_INTEGER
数字会被立刻转换为迷信计数法,并且数字精度相比以前将会有误差。
实现一个算法进行大数的相加:
function sumBigNumber(a, b) { let res = ''; let temp = 0; a = a.split(''); b = b.split(''); while (a.length || b.length || temp) { temp += ~~a.pop() + ~~b.pop(); res = (temp % 10) + res; temp = temp > 9 } return res.replace(/^0+/, '');}
其次要的思路如下:
- 首先用字符串的形式来保留大数,这样数字在数学示意上就不会发生变化
- 初始化res,temp来保留两头的计算结果,并将两个字符串转化为数组,以便进行每一位的加法运算
- 将两个数组的对应的位进行相加,两个数相加的后果可能大于10,所以可能要仅为,对10进行取余操作,将后果保留在以后位
- 判断以后位是否大于9,也就是是否会进位,若是则将temp赋值为true,因为在加法运算中,true会主动隐式转化为1,以便于下一次相加
- 反复上述操作,直至计算完结
参考 前端进阶面试题具体解答
JSONP
script标签不遵循同源协定,能够用来进行跨域申请,长处就是兼容性好但仅限于GET申请
const jsonp = ({ url, params, callbackName }) => { const generateUrl = () => { let dataSrc = ''; for (let key in params) { if (Object.prototype.hasOwnProperty.call(params, key)) { dataSrc += `${key}=${params[key]}&`; } } dataSrc += `callback=${callbackName}`; return `${url}?${dataSrc}`; } return new Promise((resolve, reject) => { const scriptEle = document.createElement('script'); scriptEle.src = generateUrl(); document.body.appendChild(scriptEle); window[callbackName] = data => { resolve(data); document.removeChild(scriptEle); } })}
实现一个call
call做了什么:
- 将函数设为对象的属性
- 执行&删除这个函数
- 指定this到函数并传入给定参数执行函数
- 如果不传入参数,默认指向为 window
// 模仿 call bar.mycall(null);//实现一个call办法:Function.prototype.myCall = function(context) { //此处没有思考context非object状况 context.fn = this; let args = []; for (let i = 1, len = arguments.length; i < len; i++) { args.push(arguments[i]); } context.fn(...args); let result = context.fn(...args); delete context.fn; return result;};
应用Promise封装AJAX申请
// promise 封装实现:function getJSON(url) { // 创立一个 promise 对象 let promise = new Promise(function(resolve, reject) { let xhr = new XMLHttpRequest(); // 新建一个 http 申请 xhr.open("GET", url, true); // 设置状态的监听函数 xhr.onreadystatechange = function() { if (this.readyState !== 4) return; // 当申请胜利或失败时,扭转 promise 的状态 if (this.status === 200) { resolve(this.response); } else { reject(new Error(this.statusText)); } }; // 设置谬误监听函数 xhr.onerror = function() { reject(new Error(this.statusText)); }; // 设置响应的数据类型 xhr.responseType = "json"; // 设置申请头信息 xhr.setRequestHeader("Accept", "application/json"); // 发送 http 申请 xhr.send(null); }); return promise;}
实现一个治理本地缓存过期的函数
封装一个能够设置过期工夫的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);
实现一个JS函数柯里化
事后解决的思维,利用闭包的机制
- 柯里化的定义:接管一部分参数,返回一个函数接管残余参数,接管足够参数后,执行原函数
- 函数柯里化的次要作用和特点就是
参数复用
、提前返回
和提早执行
- 柯里化把屡次传入的参数合并,柯里化是一个高阶函数
- 每次都返回一个新函数
- 每次入参都是一个
当柯里化函数接管到足够参数后,就会执行原函数,如何去确定何时达到足够的参数呢?
有两种思路:
- 通过函数的
length
属性,获取函数的形参个数,形参的个数就是所需的参数个数 - 在调用柯里化工具函数时,手动指定所需的参数个数
将这两点联合一下,实现一个简略 curry
函数
通用版
// 写法1function curry(fn, args) { var length = fn.length; var args = args || []; return function(){ newArgs = args.concat(Array.prototype.slice.call(arguments)); if (newArgs.length < length) { return curry.call(this,fn,newArgs); }else{ return fn.apply(this,newArgs); } }}
// 写法2// 分批传入参数// redux 源码的compose也是用了相似柯里化的操作const curry = (fn, arr = []) => {// arr就是咱们要收集每次调用时传入的参数 let len = fn.length; // 函数的长度,就是参数的个数 return function(...args) { let newArgs = [...arr, ...args] // 收集每次传入的参数 // 如果传入的参数个数等于咱们指定的函数参数个数,就执行指定的真正函数 if(newArgs.length === len) { return fn(...newArgs) } else { // 递归收集参数 return curry(fn, newArgs) } }}
// 测试function multiFn(a, b, c) { return a * b * c;}var multi = curry(multiFn);multi(2)(3)(4);multi(2,3,4);multi(2)(3,4);multi(2,3)(4)
ES6写法
const curry = (fn, arr = []) => (...args) => ( arg => arg.length === fn.length ? fn(...arg) : curry(fn, arg))([...arr, ...args])
// 测试let curryTest=curry((a,b,c,d)=>a+b+c+d)curryTest(1,2,3)(4) //返回10curryTest(1,2)(4)(3) //返回10curryTest(1,2)(3,4) //返回10
// 柯里化求值// 指定的函数function sum(a,b,c,d,e) { return a + b + c + d + e}// 传入指定的函数,执行一次let newSum = curry(sum)// 柯里化 每次入参都是一个参数newSum(1)(2)(3)(4)(5)// 偏函数newSum(1)(2)(3,4,5)
// 柯里化简略利用// 判断类型,参数多少个,就执行多少次收集function isType(type, val) { return Object.prototype.toString.call(val) === `[object ${type}]`}let newType = curry(isType)// 相当于把函数参数一个个传了,把第一次先缓存起来let isString = newType('String')let isNumber = newType('Number')isString('hello world')isNumber(999)
实现每隔一秒打印 1,2,3,4
// 应用闭包实现for (var i = 0; i < 5; i++) { (function(i) { setTimeout(function() { console.log(i); }, i * 1000); })(i);}// 应用 let 块级作用域for (let i = 0; i < 5; i++) { setTimeout(function() { console.log(i); }, i * 1000);}
版本号排序的办法
题目形容:有一组版本号如下 ['0.1.1', '2.3.3', '0.302.1', '4.2', '4.3.5', '4.3.4.5']
。当初须要对其进行排序,排序的后果为 ['4.3.5','4.3.4.5','2.3.3','0.302.1','0.1.1']
arr.sort((a, b) => { let i = 0; const arr1 = a.split("."); const arr2 = b.split("."); while (true) { const s1 = arr1[i]; const s2 = arr2[i]; i++; if (s1 === undefined || s2 === undefined) { return arr2.length - arr1.length; } if (s1 === s2) continue; return s2 - s1; }});console.log(arr);
二叉树深度遍历
// 二叉树深度遍历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++ } // 前序遍历 preorderTraversal(visitor) { const traversal = node=>{ if(node === null) return visitor.visit(node.element) traversal(node.left) traversal(node.right) } traversal(this.root) } // 中序遍历 inorderTraversal(visitor) { const traversal = node=>{ if(node === null) return traversal(node.left) visitor.visit(node.element) traversal(node.right) } traversal(this.root) } // 后序遍历 posterorderTraversal(visitor) { const traversal = node=>{ if(node === null) return traversal(node.left) traversal(node.right) visitor.visit(node.element) } traversal(this.root) } // 反转二叉树:无论先序、中序、后序、层级都能够反转 invertTree() { const traversal = node=>{ if(node === null) return let temp = node.left node.left = node.right node.right = temp traversal(node.left) traversal(node.right) } traversal(this.root) return this.root }}
先序遍历
二叉树的遍历形式
// 测试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})// 先序遍历// console.log(bst.preorderTraversal(),'先序遍历')// console.log(bst.inorderTraversal(),'中序遍历')// // console.log(bst.posterorderTraversal(),'后序遍历')// 深度遍历:先序遍历、中序遍历、后续遍历// 广度遍历:档次遍历(同层级遍历)// 都可拿到树中的节点// 应用访问者模式class Visitor { constructor() { this.visit = function (elem) { elem.age = elem.age*2 } }}// bst.posterorderTraversal({// visit(elem) {// elem.age = elem.age*10// }// })// 不能通过索引操作 拿到节点去操作// bst.posterorderTraversal(new Visitor())console.log(bst.invertTree(),'反转二叉树')
查找字符串中呈现最多的字符和个数
例: abbcccddddd -> 字符最多的是d,呈现了5次
let str = "abcabcabcbbccccc";let num = 0;let char = ''; // 使其依照肯定的秩序排列str = str.split('').sort().join('');// "aaabbbbbcccccccc"// 定义正则表达式let re = /(\w)\1+/g;str.replace(re,($0,$1) => { if(num < $0.length){ num = $0.length; char = $1; }});console.log(`字符最多的是${char},呈现了${num}次`);
封装异步的fetch,应用async await形式来应用
(async () => { class HttpRequestUtil { async get(url) { const res = await fetch(url); const data = await res.json(); return data; } async post(url, data) { const res = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); const result = await res.json(); return result; } async put(url, data) { const res = await fetch(url, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, data: JSON.stringify(data) }); const result = await res.json(); return result; } async delete(url, data) { const res = await fetch(url, { method: 'DELETE', headers: { 'Content-Type': 'application/json' }, data: JSON.stringify(data) }); const result = await res.json(); return result; } } const httpRequestUtil = new HttpRequestUtil(); const res = await httpRequestUtil.get('http://golderbrother.cn/'); console.log(res);})();
实现数组的乱序输入
次要的实现思路就是:
- 取出数组的第一个元素,随机产生一个索引值,将该第一个元素和这个索引对应的元素进行替换。
- 第二次取出数据数组第二个元素,随机产生一个除了索引为1的之外的索引值,并将第二个元素与该索引值对应的元素进行替换
- 依照下面的法则执行,直到遍历实现
var arr = [1,2,3,4,5,6,7,8,9,10];for (var i = 0; i < arr.length; i++) { const randomIndex = Math.round(Math.random() * (arr.length - 1 - i)) + i; [arr[i], arr[randomIndex]] = [arr[randomIndex], arr[i]];}console.log(arr)
还有一办法就是倒序遍历:
var arr = [1,2,3,4,5,6,7,8,9,10];let length = arr.length, randomIndex, temp; while (length) { randomIndex = Math.floor(Math.random() * length--); temp = arr[length]; arr[length] = arr[randomIndex]; arr[randomIndex] = temp; }console.log(arr)
验证是否是邮箱
function isEmail(email) { var regx = /^([a-zA-Z0-9_\-])+@([a-zA-Z0-9_\-])+(\.[a-zA-Z0-9_\-])+$/; return regx.test(email);}
实现有并行限度的 Promise 调度器
题目形容:JS 实现一个带并发限度的异步调度器 Scheduler
,保障同时运行的工作最多有两个
addTask(1000,"1"); addTask(500,"2"); addTask(300,"3"); addTask(400,"4"); 的输入程序是:2 3 1 4 整个的残缺执行流程:一开始1、2两个工作开始执行500ms时,2工作执行结束,输入2,工作3开始执行800ms时,3工作执行结束,输入3,工作4开始执行1000ms时,1工作执行结束,输入1,此时只剩下4工作在执行1200ms时,4工作执行结束,输入4
实现代码如下:
class Scheduler { constructor(limit) { this.queue = []; this.maxCount = limit; this.runCounts = 0; } add(time, order) { const promiseCreator = () => { return new Promise((resolve, reject) => { setTimeout(() => { console.log(order); resolve(); }, time); }); }; 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 scheduler = new Scheduler(2);const addTask = (time, order) => { scheduler.add(time, order);};addTask(1000, "1");addTask(500, "2");addTask(300, "3");addTask(400, "4");scheduler.taskStart();
对象数组列表转成树形构造(解决菜单)
[ { id: 1, text: '节点1', parentId: 0 //这里用0示意为顶级节点 }, { id: 2, text: '节点1_1', parentId: 1 //通过这个字段来确定子父级 } ...]转成[ { id: 1, text: '节点1', parentId: 0, children: [ { id:2, text: '节点1_1', parentId:1 } ] }]
实现代码如下:
function listToTree(data) { let temp = {}; let treeData = []; for (let i = 0; i < data.length; i++) { temp[data[i].id] = data[i]; } for (let i in temp) { if (+temp[i].parentId != 0) { if (!temp[temp[i].parentId].children) { temp[temp[i].parentId].children = []; } temp[temp[i].parentId].children.push(temp[i]); } else { treeData.push(temp[i]); } } return treeData;}