前端二面必会手写面试题汇总
- 2022-12-20 浙江
本文字数:14913 字
阅读完需:约 49 分钟
实现 Vue reactive 响应式
// Dep moduleclass Dep { static stack = [] static target = null deps = null
constructor() { this.deps = new Set() }
depend() { if (Dep.target) { this.deps.add(Dep.target) } }
notify() { this.deps.forEach(w => w.update()) }
static pushTarget(t) { if (this.target) { this.stack.push(this.target) } this.target = t }
static popTarget() { this.target = this.stack.pop() }}
// reactivefunction reactive(o) { if (o && typeof o === 'object') { Object.keys(o).forEach(k => { defineReactive(o, k, o[k]) }) } return o}
function defineReactive(obj, k, val) { let dep = new Dep() Object.defineProperty(obj, k, { get() { dep.depend() return val }, set(newVal) { val = newVal dep.notify() } }) if (val && typeof val === 'object') { reactive(val) }}
// watcherclass Watcher { constructor(effect) { this.effect = effect this.update() }
update() { Dep.pushTarget(this) this.value = this.effect() Dep.popTarget() return this.value }}
// 测试代码const data = reactive({ msg: 'aaa'})
new Watcher(() => { console.log('===> effect', data.msg);})
setTimeout(() => { data.msg = 'hello'}, 1000)
使用 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;}
实现数组元素求和
arr=[1,2,3,4,5,6,7,8,9,10],求和
let arr=[1,2,3,4,5,6,7,8,9,10]let sum = arr.reduce( (total,i) => total += i,0);console.log(sum);
arr=[1,2,3,[[4,5],6],7,8,9],求和
var = arr=[1,2,3,[[4,5],6],7,8,9]let arr= arr.toString().split(',').reduce( (total,i) => total += Number(i),0);console.log(arr);
递归实现:
let arr = [1, 2, 3, 4, 5, 6]
function add(arr) { if (arr.length == 1) return arr[0] return arr[0] + add(arr.slice(1)) }console.log(add(arr)) // 21
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); } })}
实现一个双向绑定
defineProperty 版本
// 数据const data = { text: 'default'};const input = document.getElementById('input');const span = document.getElementById('span');// 数据劫持Object.defineProperty(data, 'text', { // 数据变化 --> 修改视图 set(newVal) { input.value = newVal; span.innerHTML = newVal; }});// 视图更改 --> 数据变化input.addEventListener('keyup', function(e) { data.text = e.target.value;});
proxy 版本
// 数据const data = { text: 'default'};const input = document.getElementById('input');const span = document.getElementById('span');// 数据劫持const handler = { set(target, key, value) { target[key] = value; // 数据变化 --> 修改视图 input.value = value; span.innerHTML = value; return value; }};const proxy = new Proxy(data, handler);
// 视图更改 --> 数据变化input.addEventListener('keyup', function(e) { proxy.text = e.target.value;});
实现一个拖拽
<style> html, body { margin: 0; height: 100%; } #box { width: 100px; height: 100px; background-color: red; position: absolute; top: 100px; left: 100px; }</style>
<div id="box"></div>
window.onload = function () { var box = document.getElementById('box'); box.onmousedown = function (ev) { var oEvent = ev || window.event; // 兼容火狐,火狐下没有window.event var distanceX = oEvent.clientX - box.offsetLeft; // 鼠标到可视区左边的距离 - box到页面左边的距离 var distanceY = oEvent.clientY - box.offsetTop; document.onmousemove = function (ev) { var oEvent = ev || window.event; var left = oEvent.clientX - distanceX; var top = oEvent.clientY - distanceY; if (left <= 0) { left = 0; } else if (left >= document.documentElement.clientWidth - box.offsetWidth) { left = document.documentElement.clientWidth - box.offsetWidth; } if (top <= 0) { top = 0; } else if (top >= document.documentElement.clientHeight - box.offsetHeight) { top = document.documentElement.clientHeight - box.offsetHeight; } box.style.left = left + 'px'; box.style.top = top + 'px'; } box.onmouseup = function () { document.onmousemove = null; box.onmouseup = null; } }}
参考 前端进阶面试题详细解答
小孩报数问题
有 30 个小孩儿,编号从 1-30,围成一圈依此报数,1、2、3 数到 3 的小孩儿退出这个圈, 然后下一个小孩 重新报数 1、2、3,问最后剩下的那个小孩儿的编号是多少?
function childNum(num, count){ let allplayer = []; for(let i = 0; i < num; i++){ allplayer[i] = i + 1; }
let exitCount = 0; // 离开人数 let counter = 0; // 记录报数 let curIndex = 0; // 当前下标
while(exitCount < num - 1){ if(allplayer[curIndex] !== 0) counter++;
if(counter == count){ allplayer[curIndex] = 0; counter = 0; exitCount++; } curIndex++; if(curIndex == num){ curIndex = 0 }; } for(i = 0; i < num; i++){ if(allplayer[i] !== 0){ return allplayer[i] } }}childNum(30, 3)
实现 Ajax
步骤
创建
XMLHttpRequest实例发出 HTTP 请求
服务器返回 XML 格式的字符串
JS 解析 XML,并更新局部页面
不过随着历史进程的推进,XML 已经被淘汰,取而代之的是 JSON。
了解了属性和方法之后,根据 AJAX 的步骤,手写最简单的 GET 请求。
实现 Array.isArray 方法
Array.myIsArray = function(o) { return Object.prototype.toString.call(Object(o)) === '[object Array]';};
console.log(Array.myIsArray([])); // true
实现 async/await
分析
// generator生成器 生成迭代器iterator
// 默认这样写的类数组是不能被迭代的,缺少迭代方法let likeArray = {'0': 1, '1': 2, '2': 3, '3': 4, length: 4}
// // 使用迭代器使得可以展开数组// // Symbol有很多元编程方法,可以改js本身功能// likeArray[Symbol.iterator] = function () {// // 迭代器是一个对象 对象中有next方法 每次调用next 都需要返回一个对象 {value,done}// let index = 0// return {// next: ()=>{// // 会自动调用这个方法// console.log('index',index)// return {// // this 指向likeArray// value: this[index],// done: index++ === this.length// }// }// }// }// let arr = [...likeArray]
// console.log('arr', arr)
// 使用生成器返回迭代器// likeArray[Symbol.iterator] = function *() {// let index = 0// while (index != this.length) {// yield this[index++]// }// }// let arr = [...likeArray]
// console.log('arr', arr)
// 生成器 碰到yield就会暂停// function *read(params) {// yield 1;// yield 2;// }// 生成器返回的是迭代器// let it = read()// console.log(it.next())// console.log(it.next())// console.log(it.next())
// 通过generator来优化promise(promise的缺点是不停的链式调用)const fs = require('fs')const path = require('path')// const co = require('co') // 帮我们执行generator
const promisify = fn=>{ return (...args)=>{ return new Promise((resolve,reject)=>{ fn(...args, (err,data)=>{ if(err) { reject(err) } resolve(data) }) }) }}
// promise化let asyncReadFile = promisify(fs.readFile)
function * read() { let content1 = yield asyncReadFile(path.join(__dirname,'./data/name.txt'),'utf8') let content2 = yield asyncReadFile(path.join(__dirname,'./data/' + content1),'utf8') return content2}
// 这样写太繁琐 需要借助co来实现// let re = read()// let {value,done} = re.next()// value.then(data=>{// // 除了第一次传参没有意义外 剩下的传参都赋予了上一次的返回值 // let {value,done} = re.next(data) // value.then(d=>{// let {value,done} = re.next(d)// console.log(value,done)// })// }).catch(err=>{// re.throw(err) // 手动抛出错误 可以被try catch捕获// })
// 实现co原理function co(it) {// it 迭代器 return new Promise((resolve,reject)=>{ // 异步迭代 需要根据函数来实现 function next(data) { // 递归得有中止条件 let {value,done} = it.next(data) if(done) { resolve(value) // 直接让promise变成成功 用当前返回的结果 } else { // Promise.resolve(value).then(data=>{ // next(data) // }).catch(err=>{ // reject(err) // }) // 简写 Promise.resolve(value).then(next,reject) } } // 首次调用 next() })}
co(read()).then(d=>{ console.log(d)}).catch(err=>{ console.log(err,'--')})
整体看一下结构
function asyncToGenerator(generatorFunc) { return function() { const gen = generatorFunc.apply(this, arguments) return new Promise((resolve, reject) => { function step(key, arg) { let generatorResult try { generatorResult = gen[key](arg) } catch (error) { return reject(error) } const { value, done } = generatorResult if (done) { return resolve(value) } else { return Promise.resolve(value).then(val => step('next', val), err => step('throw', err)) } } step("next") }) }}
分析
function asyncToGenerator(generatorFunc) { // 返回的是一个新的函数 return function() {
// 先调用generator函数 生成迭代器 // 对应 var gen = testG() const gen = generatorFunc.apply(this, arguments)
// 返回一个promise 因为外部是用.then的方式 或者await的方式去使用这个函数的返回值的 // var test = asyncToGenerator(testG) // test().then(res => console.log(res)) return new Promise((resolve, reject) => {
// 内部定义一个step函数 用来一步一步的跨过yield的阻碍 // key有next和throw两种取值,分别对应了gen的next和throw方法 // arg参数则是用来把promise resolve出来的值交给下一个yield function step(key, arg) { let generatorResult
// 这个方法需要包裹在try catch中 // 如果报错了 就把promise给reject掉 外部通过.catch可以获取到错误 try { generatorResult = gen[key](arg) } catch (error) { return reject(error) }
// gen.next() 得到的结果是一个 { value, done } 的结构 const { value, done } = generatorResult
if (done) { // 如果已经完成了 就直接resolve这个promise // 这个done是在最后一次调用next后才会为true // 以本文的例子来说 此时的结果是 { done: true, value: 'success' } // 这个value也就是generator函数最后的返回值 return resolve(value) } else { // 除了最后结束的时候外,每次调用gen.next() // 其实是返回 { value: Promise, done: false } 的结构, // 这里要注意的是Promise.resolve可以接受一个promise为参数 // 并且这个promise参数被resolve的时候,这个then才会被调用 return Promise.resolve( // 这个value对应的是yield后面的promise value ).then( // value这个promise被resove的时候,就会执行next // 并且只要done不是true的时候 就会递归的往下解开promise // 对应gen.next().value.then(value => { // gen.next(value).value.then(value2 => { // gen.next() // // // 此时done为true了 整个promise被resolve了 // // 最外部的test().then(res => console.log(res))的then就开始执行了 // }) // }) function onResolve(val) { step("next", val) }, // 如果promise被reject了 就再次进入step函数 // 不同的是,这次的try catch中调用的是gen.throw(err) // 那么自然就被catch到 然后把promise给reject掉啦 function onReject(err) { step("throw", err) }, ) } } step("next") }) }}
Object.is
Object.is解决的主要是这两个问题:
+0 === -0 // trueNaN === NaN // false
const is= (x, y) => { if (x === y) { // +0和-0应该不相等 return x !== 0 || y !== 0 || 1/x === 1/y; } else { return x !== x && y !== y; }}
滚动加载
原理就是监听页面滚动事件,分析 clientHeight、scrollTop、scrollHeight 三者的属性关系。
window.addEventListener('scroll', function() { const clientHeight = document.documentElement.clientHeight; const scrollTop = document.documentElement.scrollTop; const scrollHeight = document.documentElement.scrollHeight; if (clientHeight + scrollTop >= scrollHeight) { // 检测到滚动至页面底部,进行后续操作 // ... }}, false);
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), ) }) }) }}
实现类的继承
实现类的继承-简版
类的继承在几年前是重点内容,有 n 种继承方式各有优劣,es6 普及后越来越不重要,那么多种写法有点『回字有四样写法』的意思,如果还想深入理解的去看红宝书即可,我们目前只实现一种最理想的继承方式。
// 寄生组合继承function Parent(name) { this.name = name}Parent.prototype.say = function() { console.log(this.name + ` say`);}Parent.prototype.play = function() { console.log(this.name + ` play`);}
function Child(name, parent) { // 将父类的构造函数绑定在子类上 Parent.call(this, parent) this.name = name}
/** 1. 这一步不用Child.prototype = Parent.prototype的原因是怕共享内存,修改父类原型对象就会影响子类 2. 不用Child.prototype = new Parent()的原因是会调用2次父类的构造方法(另一次是call),会存在一份多余的父类实例属性3. Object.create是创建了父类原型的副本,与父类原型完全隔离*/Child.prototype = Object.create(Parent.prototype);Child.prototype.say = function() { console.log(this.name + ` say`);}
// 注意记得把子类的构造指向子类本身Child.prototype.constructor = Child;
// 测试var parent = new Parent('parent');parent.say()
var child = new Child('child');child.say() child.play(); // 继承父类的方法
ES5 实现继承-详细
第一种方式是借助 call 实现继承
function Parent1(){ this.name = 'parent1';}function Child1(){ Parent1.call(this); this.type = 'child1' }console.log(new Child1);
这样写的时候子类虽然能够拿到父类的属性值,但是问题是父类中一旦存在方法那么子类无法继承。那么引出下面的方法
第二种方式借助原型链实现继承:
function Parent2() { this.name = 'parent2'; this.play = [1, 2, 3] } function Child2() { this.type = 'child2'; } Child2.prototype = new Parent2();
console.log(new Child2());
看似没有问题,父类的方法和属性都能够访问,但实际上有一个潜在的不足。举个例子:
var s1 = new Child2(); var s2 = new Child2(); s1.play.push(4); console.log(s1.play, s2.play); // [1,2,3,4] [1,2,3,4]
明明我只改变了 s1 的 play 属性,为什么 s2 也跟着变了呢?很简单,因为两个实例使用的是同一个原型对象
第三种方式:将前两种组合:
function Parent3 () { this.name = 'parent3'; this.play = [1, 2, 3]; } function Child3() { Parent3.call(this); this.type = 'child3'; } Child3.prototype = new Parent3(); var s3 = new Child3(); var s4 = new Child3(); s3.play.push(4); console.log(s3.play, s4.play); // [1,2,3,4] [1,2,3]
之前的问题都得以解决。但是这里又徒增了一个新问题,那就是 Parent3 的构造函数会多执行了一次(
Child3.prototype = new Parent3();)。这是我们不愿看到的。那么如何解决这个问题?
第四种方式: 组合继承的优化 1
function Parent4 () { this.name = 'parent4'; this.play = [1, 2, 3]; } function Child4() { Parent4.call(this); this.type = 'child4'; } Child4.prototype = Parent4.prototype;
这里让将父类原型对象直接给到子类,父类构造函数只执行一次,而且父类属性和方法均能访问,但是我们来测试一下
var s3 = new Child4(); var s4 = new Child4(); console.log(s3)
子类实例的构造函数是 Parent4,显然这是不对的,应该是 Child4。
第五种方式(最推荐使用):优化 2
function Parent5 () { this.name = 'parent5'; this.play = [1, 2, 3]; } function Child5() { Parent5.call(this); this.type = 'child5'; } Child5.prototype = Object.create(Parent5.prototype); Child5.prototype.constructor = Child5;
这是最推荐的一种方式,接近完美的继承。
实现简单路由
// hash路由class Route{ constructor(){ // 路由存储对象 this.routes = {} // 当前hash this.currentHash = '' // 绑定this,避免监听时this指向改变 this.freshRoute = this.freshRoute.bind(this) // 监听 window.addEventListener('load', this.freshRoute, false) window.addEventListener('hashchange', this.freshRoute, false) } // 存储 storeRoute (path, cb) { this.routes[path] = cb || function () {} } // 更新 freshRoute () { this.currentHash = location.hash.slice(1) || '/' this.routes[this.currentHash]() }}
实现一下 hash 路由
基础的html代码:
<html> <style> html, body { margin: 0; height: 100%; } ul { list-style: none; margin: 0; padding: 0; display: flex; justify-content: center; } .box { width: 100%; height: 100%; background-color: red; } </style> <body> <ul> <li> <a href="#red">红色</a> </li> <li> <a href="#green">绿色</a> </li> <li> <a href="#purple">紫色</a> </li> </ul> </body></html>
简单实现:
<script> const box = document.getElementsByClassName('box')[0]; const hash = location.hash window.onhashchange = function (e) { const color = hash.slice(1) box.style.background = color }</script>
封装成一个 class:
<script> const box = document.getElementsByClassName('box')[0]; const hash = location.hash class HashRouter { constructor (hashStr, cb) { this.hashStr = hashStr this.cb = cb this.watchHash() this.watch = this.watchHash.bind(this) window.addEventListener('hashchange', this.watch) } watchHash () { let hash = window.location.hash.slice(1) this.hashStr = hash this.cb(hash) } } new HashRouter('red', (color) => { box.style.background = color })</script>
手写常见排序
冒泡排序
冒泡排序的原理如下,从第一个元素开始,把当前元素和下一个索引元素进行比较。如果当前元素大,那么就交换位置,重复操作直到比较到最后一个元素,那么此时最后一个元素就是该数组中最大的数。下一轮重复以上操作,但是此时最后一个元素已经是最大数了,所以不需要再比较最后一个元素,只需要比较到
length - 1的位置。
function bubbleSort(list) { var n = list.length; if (!n) return [];
for (var i = 0; i < n; i++) { // 注意这里需要 n - i - 1 for (var j = 0; j < n - i - 1; j++) { if (list[j] > list[j + 1]) { var temp = list[j + 1]; list[j + 1] = list[j]; list[j] = temp; } } } return list;}
快速排序
快排的原理如下。随机选取一个数组中的值作为基准值,从左至右取值与基准值对比大小。比基准值小的放数组左边,大的放右边,对比完成后将基准值和第一个比基准值大的值交换位置。然后将数组以基准值的位置分为两部分,继续递归以上操作
ffunction quickSort(arr) { if (arr.length<=1){ return arr; } var baseIndex = Math.floor(arr.length/2);//向下取整,选取基准点 var base = arr.splice(baseIndex,1)[0];//取出基准点的值, // splice 通过删除或替换现有元素或者原地添加新的元素来修改数组,并以数组形式返回被修改的内容。此方法会改变原数组。 // slice方法返回一个新的数组对象,不会更改原数组 //这里不能直接base=arr[baseIndex],因为base代表的每次都删除的那个数 var left=[]; var right=[]; for (var i = 0; i<arr.length; i++){ // 这里的length是变化的,因为splice会改变原数组。 if (arr[i] < base){ left.push(arr[i]);//比基准点小的放在左边数组, } }else{ right.push(arr[i]);//比基准点大的放在右边数组, } return quickSort(left).concat([base],quickSort(right));}
选择排序
function selectSort(arr) { // 缓存数组长度 const len = arr.length; // 定义 minIndex,缓存当前区间最小值的索引,注意是索引 let minIndex; // i 是当前排序区间的起点 for (let i = 0; i < len - 1; i++) { // 初始化 minIndex 为当前区间第一个元素 minIndex = i; // i、j分别定义当前区间的上下界,i是左边界,j是右边界 for (let j = i; j < len; j++) { // 若 j 处的数据项比当前最小值还要小,则更新最小值索引为 j if (arr[j] < arr[minIndex]) { minIndex = j; } } // 如果 minIndex 对应元素不是目前的头部元素,则交换两者 if (minIndex !== i) { [arr[i], arr[minIndex]] = [arr[minIndex], arr[i]]; } } return arr;}// console.log(selectSort([3, 6, 2, 4, 1]));
插入排序
function insertSort(arr) { for (let i = 1; i < arr.length; i++) { let j = i; let target = arr[j]; while (j > 0 && arr[j - 1] > target) { arr[j] = arr[j - 1]; j--; } arr[j] = target; } return arr;}// console.log(insertSort([3, 6, 2, 4, 1]));
实现节流函数(throttle)
防抖函数原理:规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效。
// 手写简化版
// 节流函数const throttle = (fn, delay = 500) => { let flag = true; return (...args) => { if (!flag) return; flag = false; setTimeout(() => { fn.apply(this, args); flag = true; }, delay); };};
适用场景:
拖拽场景:固定时间内只执行一次,防止超高频次触发位置变动
缩放场景:监控浏览器 resize
动画场景:避免短时间内多次触发动画引起性能问题
实现模板字符串解析功能
let template = '我是{{name}},年龄{{age}},性别{{sex}}';let data = { name: '姓名', age: 18}render(template, data); // 我是姓名,年龄18,性别undefined
function render(template, data) { const reg = /\{\{(\w+)\}\}/; // 模板字符串正则 if (reg.test(template)) { // 判断模板里是否有模板字符串 const name = reg.exec(template)[1]; // 查找当前模板里第一个模板字符串的字段 template = template.replace(reg, data[name]); // 将第一个模板字符串渲染 return render(template, data); // 递归的渲染并返回渲染后的结构 } return template; // 如果模板没有模板字符串直接返回}
reduce 用法汇总
语法
array.reduce(function(total, currentValue, currentIndex, arr), initialValue);/* total: 必需。初始值, 或者计算结束后的返回值。 currentValue: 必需。当前元素。 currentIndex: 可选。当前元素的索引; arr: 可选。当前元素所属的数组对象。 initialValue: 可选。传递给函数的初始值,相当于total的初始值。*/
reduceRight()该方法用法与reduce()其实是相同的,只是遍历的顺序相反,它是从数组的最后一项开始,向前遍历到第一项
1. 数组求和
const arr = [12, 34, 23];const sum = arr.reduce((total, num) => total + num);
// 设定初始值求和const arr = [12, 34, 23];const sum = arr.reduce((total, num) => total + num, 10); // 以10为初始值求和
// 对象数组求和var result = [ { subject: 'math', score: 88 }, { subject: 'chinese', score: 95 }, { subject: 'english', score: 80 }];const sum = result.reduce((accumulator, cur) => accumulator + cur.score, 0); const sum = result.reduce((accumulator, cur) => accumulator + cur.score, -10); // 总分扣除10分
2. 数组最大值
const a = [23,123,342,12];const max = a.reduce((pre,next)=>pre>cur?pre:cur,0); // 342
3. 数组转对象
var streams = [{name: '技术', id: 1}, {name: '设计', id: 2}];var obj = streams.reduce((accumulator, cur) => {accumulator[cur.id] = cur; return accumulator;}, {});
4. 扁平一个二维数组
var arr = [[1, 2, 8], [3, 4, 9], [5, 6, 10]];var res = arr.reduce((x, y) => x.concat(y), []);
5. 数组去重
实现的基本原理如下:
① 初始化一个空数组② 将需要去重处理的数组中的第1项在初始化数组中查找,如果找不到(空数组中肯定找不到),就将该项添加到初始化数组中③ 将需要去重处理的数组中的第2项在初始化数组中查找,如果找不到,就将该项继续添加到初始化数组中④ ……⑤ 将需要去重处理的数组中的第n项在初始化数组中查找,如果找不到,就将该项继续添加到初始化数组中⑥ 将这个初始化数组返回
var newArr = arr.reduce(function (prev, cur) { prev.indexOf(cur) === -1 && prev.push(cur); return prev;},[]);
6. 对象数组去重
const dedup = (data, getKey = () => { }) => { const dateMap = data.reduce((pre, cur) => { const key = getKey(cur) if (!pre[key]) { pre[key] = cur } return pre }, {}) return Object.values(dateMap)}
7. 求字符串中字母出现的次数
const str = 'sfhjasfjgfasjuwqrqadqeiqsajsdaiwqdaklldflas-cmxzmnha';
const res = str.split('').reduce((pre,next)=>{ pre[next] ? pre[next]++ : pre[next] = 1 return pre },{})
// 结果-: 1a: 8c: 1d: 4e: 1f: 4g: 1h: 2i: 2j: 4k: 1l: 3m: 2n: 1q: 5r: 1s: 6u: 1w: 2x: 1z: 1
8. compose 函数
redux compose源码实现
function compose(...funs) { if (funs.length === 0) { return arg => arg; } if (funs.length === 1) { return funs[0]; } return funs.reduce((a, b) => (...arg) => a(b(...arg)))}
还未添加个人签名 2022-07-31 加入
还未添加个人简介








评论