闭包是什么?闭包是能够读取其他函数内部变量的函数
柯里化是什么?柯里化是把一个多个参数的函数转化为单参数函数的方法
闭包的用途:闭包的主要用途是为了不污染全局变量,用闭包的局部变量来做一些库提供给开发者使用,在平时的开发中,也可以用来缓存一些数据,例如你需要对比上一次的值和下一次进入该函数的值决定是否进行操作时,就可以使用闭包。
柯里化的用途:柯里化是闭包的高级应用,柯里化的主要用途是用来减少重复代码的编写,同时使代码更加清晰,例如在对moogodb进行操作时,我们可以利用柯里化来封装我们的crud。例如下面的代码
// db.js 只是负责连接数据库, 向外暴露一个连接信息
const mysql = require('mysql')
// 创建连接池
const db = mysql.createPool({
host: 'localhost',
port: 3306,
user: 'root',
password: 'root',
database: 'root'
})
exports.db = db
const { db } = require('./db')
const select = function (sql) {
return function (params = []) {
return new Promise((resolve, reject) => {
db.query(sql, params, (err, data) => {
if (err) return reject(err)
resolve(data)
})
})
}
}
const db = require('./db_crud')
const selectUser = db.select('SELECT * FROM `student` LIMIT ?, 10')
const deleteById = db.delete('DELETE FROM `users` WHERE `id`=?')
module.exports = {
selectUser: selectUser,
deleteById: deleteById
}
使用时,直接导入该模块
const abc=require(...)
acc.selectUser([参数]).then(res=>{console.log(res}) 即可
事件循环分为javascript的事件循环(也是浏览器的事件循环,因为js本身不实现事件循环机制)和nodejs的事件循环,两者还是有一定区别的。一般来说问的都是js的事件循环,事件循环在开发中也是很常用的。
由来:由于JS是单线程,但是我们的业务通常不可能都是同步进行,一定需要异步的操作,所以就有了事件循环来解决该问题。
在js中将事件循环分为宏任务和微任务(区别于同步任务),都会被挂起异步执行,那么二者有什么区别呢?在js中的任务是要排队的,所以执行栈里面就会排满了任务,同步任务就会从上到下依次执行,而当遇到宏任务和微任务时就会挂起,那么什么时候开始执行呢?宏任务会单独开辟一个任务队列将宏任务塞进去,微任务并不会开辟新的任务队列,而是将微任务放到当前队列的末尾去执行。
补充一句:在js中的异步都是通过回调来进行的,那为什么还有promise async等等,这里就涉及到了回调的一些缺点,如果异步的太多就会形成大量的回调嵌套回调的情况,也就是回调地狱,就会让代码的可读性很差,心智负担也比较大,所以才有了promise等api。
在js中哪些是宏任务,哪些是微任务?
宏任务:定时器,事件绑定,ajax,回调函数
微任务:promise async await process.nextTick MutationObserver
为什么要有微任务? 因为宏任务开销较大
尝试下下面的例题:
console.log(1);
document.addEventListener("14", function () {
console.log(14);
});
new Promise(function (resolve) {
resolve();
console.log(2);
setTimeout(function () {
console.log(3);
}, 0);
Promise.resolve().then(function () {
console.log(4);
setTimeout(function () {
console.log(5);
}, 0);
setTimeout(function () {
(async function () {
console.log(6);
return function () {
console.log(7);
};
})().then(function (fn) {
console.log(8);
fn();
});
}, 0);
});
new Promise(function (resolve) {
console.log(9);
resolve();
}).then(function () {
new Promise(function (resolve, reject) {
console.log(10);
reject();
})
.then(function () {
setTimeout(function () {
console.log(11);
}, 0);
console.log(12);
})
.catch(function () {
console.log(13);
var evt = new Event("14");
document.dispatchEvent(evt);
});
});
});
setTimeout(function () {
console.log(15);
Promise.resolve().then(function () {
console.log(16);
});
}, 0);
//答案是1 2 9 4 10 13 14 3 15 16 5 6 8 7
项目中,经常会使用settimeout来对一些需要异步的动作进行处理。
补充下nodejs中的事件循环:
NodeJs基于V8引擎,所以在表现形式上,大体上是一致的,当接收到请求时, 就将这个请求作为事件放入队列, 然后继续接收其它请求, 直到没有请求时(主线程空闲时), 开始循环事件队列, 这里要判断, 如果是非I/O任务 就直接处理, 如果是I/O任务 就从 线程池 中拿出一个线程来处理这个事件, 并指定回调函数, 然后继续循环队列其它事件。在每次运行的事件循环之间,Node.js 检查它是否在等待任何异步 I/O 或计时器,如果没有的话,则完全关闭。
Node.js 的 Event Loop 并不是浏览器那种一次执行一个宏任务,然后执行所有的微任务,而是执行完一定数量的 Timers 宏任务,再去执行所有微任务,然后再执行一定数量的 Pending 的宏任务,然后再去执行所有微任务,感兴趣的自行了解吧。
虚拟DOM是什么,虚拟DOM本质上就是一串JSON,简单来说就是制定一套规则来描述真实DOM。
浏览器渲染引擎流程:创建DOM树=》创建StyleRules =》创建Render树=》布局Layout=》绘制Painting
Web界面由DOM树(树的意思是数据结构)来构建,当其中一部分发生变化时,其实就是对应某个DOM节点发生了变化。
虚拟DOM就是为了解决浏览器性能问题而被设计出来的。如前,若一次操作中有10次更新DOM的动作,虚拟DOM不会立即操作DOM,而是将这10次更新的diff内容保存到本地一个JS对象中,最终将这个JS对象一次性attch到DOM树上,再进行后续操作,避免大量无谓的计算量。所以用JS对象模拟DOM节点的好处是,页面的更新可以先全部反映在JS对象(虚拟DOM)上,操作内存中的JS对象的速度显然要更快,等更新完成后,再将最终的JS对象映射成真实的DOM,交由浏览器去绘制。
毕竟虚拟DOM到真实DOM的绘制增加了一层,实际上单比较某个节点变化来说虚拟DOM的性能是比直接渲染要差的,所以也就是说虚拟DOM无法极致的性能优化。
diff算法和Key值的作用
1: 同级元素进行diff
2: 单节点和多节点diff
3: diff算法优先级问题(递归和遍历的区别)
vue.js 则是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()
来劫持各个属性的setter
,getter
,在数据变动时发布消息给订阅者,触发相应的监听回调。
Object.defineProperty的问题:这是MVVM的基础,基本思路就是遍历每一个属性,然后使用Object.defineProperty将这个属性设置为响应式的(即我能监听到他的改动)。遇到普通数据属性,直接处理,遇到对象,遍历属性之后递归进去处理属性,遇到数组,递归进去处理数组元素(console.log)。遍历完就到处理了,也就是Object.defineProperty部分了,对于一个对象,我们可以用这个来改写它属性的getter/setter,这样,当你改属性的值我就有办法监听到。但是对于数组就有问题了。
很多时候我们操作数组是采用push、pop、splice、unshift等方法来操作的,光是push你就没办法监听,更不要说pop后你设置的getter/setter就直接没了。
所以,Vue的方法是,改写数组的push、pop等8个方法,让他们在执行之后通知我数组更新了(这种方法带来的后果就是你不能直接修改数组的长度或者通过下标去修改数组。参见官网)。这样改进之后我就不需要对数组元素进行响应式处理,只是遇到数组的时候把数组的方法变异即可。于是在用户使用数组的push、pop等方法会改变数组本身的方法时,可以监听到数组变动。
Vue3的变化:
diff算法静态标记
cacheHandlers 事件侦听器缓存
主要讲一下vue3的双向数据绑定:vue3使用了新的jsApi,Proxy来代替原本的方案。刚才已经说过了vue2的双向绑定的缺点了,Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。proxy无法兼容全部浏览器,无法polyfill
通过虚拟DOM将需要keep-alive的虚拟DOM缓存起来,下次DOM渲染时再扔回去。在 created钩子函数调用时将需要缓存的 VNode 节点保存在 this.cache 中/在 render(页面渲染) 时,如果 VNode 的 name 符合缓存条件(可以用 include 以及 exclude 控制),则会从 this.cache 中取出之前缓存的 VNode实例进行渲染。
手动刷新
const throttle = (fn, wait, op = {}) => {
let timer = null;
let pre = 0;
return () => {
let now = Date.now();
if(now - pre > wait) {
if(pre == 0 && !op.begin) {
pre = now;
return
}
if(timer) {
clearTimeout(timer);
timer = null;
}
fn();
pre = now;
} else if(!timer && op.end) {
timer = settimeout(() => {
fn();
timer = null;
}, wait);
}
}
}
const sendRequests = (reqs, max, callback = () => { }) => {
let waitList = [];
let currentNum = 0;
let NumReqDone = 0;
const results = new Array(reqs.length).fill(false);
const init = () => {
reqs.forEach((element, index) => {
request(index, element);
});
}
const request = async (index, reqUrl) => {
if (currentNum >= max) {
await new Promise(resolve => waitList.push(resolve))
}
reqHandler(index, reqUrl);
}
const reqHandler = async (index, reqUrl) => {
currentNum++;
try {
const result = await fetch(reqUrl);
results[index] = result;
} catch (err) {
results[index] = err;
} finally {
currentNum--;
NumReqDone++;
if (waitList.length) {
waitList[0]();
waitList.shift();
}
if (NumReqDone === max) {
callback(results);
}
}
}
init()
}
const allRequest = [
"https://dog-facts-api.herokuapp.com/api/v1/resources/dogs?index=1",
"https://dog-facts-api.herokuapp.com/api/v1/resources/dogs?index=2",
"https://dog-facts-api.herokuapp.com/api/v1/resources/dogs?index=3"
];
sendRequests(allRequest, 2, (res) => console.log(res))
const arr = [{
id: 2,
name: '部门B',
parentId: 0
},
{
id: 3,
name: '部门C',
parentId: 1
},
{
id: 1,
name: '部门A',
parentId: 2
},
{
id: 4,
name: '部门D',
parentId: 1
},
{
id: 5,
name: '部门E',
parentId: 2
},
{
id: 6,
name: '部门F',
parentId: 3
},
{
id: 7,
name: '部门G',
parentId: 2
},
{
id: 8,
name: '部门H',
parentId: 4
}
];
const transTree = (list, pId) => {
const loop = (pId) => {
let res = [];
let i = 0;
while (i < list.length) {
let item = list[i];
i++;
if (item.pid !== pId) continue
item.children = loop(item.id);
res.push(item);
}
return res;
}
return loop(pId);
}
const transTree = (list, pId) => {
const loop = (pId) => {
return list.reduce((pre, cur) => {
if(cur.pid === pId) {
cur.children = loop(cur.id);
pre.push(cur);
};
return pre;
},[])
}
return loop(pId);
}
“ababac” —— “ababa”
“aaabbbcceeff” —— “aaabbb”
const changeStr = (str) => {
let obj = {};
const _str = str.split("");
_str.forEach(item => {
if (obj[item]) {
obj[item]++;
} else {
obj[item] = 1;
}
})
var _obj = Object.values(obj).sort();
var min = _obj[0];
for (let key in obj) {
if (obj[key] <= min) {
var reg = new RegExp(key, "g")
str = str.replace(reg, "")
}
}
return str;
}
console.log(changeStr("aaabbbcceeff"));
//将数字(整数)转为汉字,从零到一亿亿,需要小数的可自行截取小数点后面的数字直接替换对应arr1的读法就行了
const convertToChinaNum = (num) => {
var arr1 = new Array('零', '一', '二', '三', '四', '五', '六', '七', '八', '九');
var arr2 = new Array('', '十', '百', '千', '万', '十', '百', '千', '亿', '十', '百', '千','万', '十', '百', '千','亿');//可继续追加更高位转换值
if(!num || isNaN(num)){
return "零";
}
var english = num.toString().split("")
var result = "";
for (var i = 0; i < english.length; i++) {
var des_i = english.length - 1 - i;//倒序排列设值
result = arr2[i] + result;
var arr1_index = english[des_i];
result = arr1[arr1_index] + result;
}
//将【零千、零百】换成【零】 【十零】换成【十】
result = result.replace(/零(千|百|十)/g, '零').replace(/十零/g, '十');
//合并中间多个零为一个零
result = result.replace(/零+/g, '零');
//将【零亿】换成【亿】【零万】换成【万】
result = result.replace(/零亿/g, '亿').replace(/零万/g, '万');
//将【亿万】换成【亿】
result = result.replace(/亿万/g, '亿');
//移除末尾的零
result = result.replace(/零+$/, '')
//将【零一十】换成【零十】
//result = result.replace(/零一十/g, '零十');//貌似正规读法是零一十
//将【一十】换成【十】
result = result.replace(/^一十/g, '十');
return result;
}
前端工程化是一种思想,任何事物的出现都是基于需求的,面对日益复杂的前端项目,工程化就是类似工厂流水线,优化前端的开发工作,例如,解决代码冗余,项目可维护性,提升版本迭代速度等等一系列的问题。前端工程化的概念也就是在这中情况下被提出了,为了更加便捷的完成项目的开发部署等工作,前端工程化可以分成四个方面来说,分别为模块化
、组件化
、规范化
和自动化
。目前常见的前端工程化有:脚手架,代码模块化,组件库,webpack等打包工具,docker,CICD,github等等
结合项目来说:
结合上面的事件循环,其实就是在问你Nodejs的事件循环机制。
看这个吧
还有这个
简单讲讲:利用循环和观察者不断循环 轮询状态执行callback函数。
libuv 采用了 异步 (asynchronous), 事件驱动 (event-driven)的编程风格, 其主要任务是为开人员提供了一套事件循环和基于I/O(或其他活动)通知的回调函数, libuv 提供了一套核心的工具集, 例如定时器, 非阻塞网络编程的支持, 异步访问文件系统, 子进程以及其他功能.
// 比如这个函数输入一个1,那么要求函数返回A
const A = [1, 2, 3];
const B = [4, 5, 6];
const C = [7, 8, 9];
const test = (num) => {
const newArr = [A, B, C];
let i = 0;
while (i < newArr.length) {
if (newArr[i].includes(num)) return newArr[i];
i++;
}
return [];
}
console.log(test(5));
了解过。vue3是怎么变快的
熟练背诵并使用
函数式编程思想,三大原则,Redux
转盘的大小和样式。
转盘的旋转速度和方向。
转盘的选项和概率。
转盘的声音和动画效果。
转盘的交互和事件处理。
在与业务方协调时,需要确定转盘的具体功能和需求,以及转盘的使用场景和目的。例如,转盘是用于抽奖、抽红包、抽优惠券等,还是用于其他目的。
前端防刷的方法有很多,例如:
防止恶意刷取:可以使用验证码、IP限制、用户行为分析等方法。
防止机器刷取:可以使用反爬虫技术、限制请求频率、限制请求次数等方法。
虚拟列表的实现,实际上就是在首屏加载的时候,只加载可视区域
内需要的列表项,当滚动发生时,动态通过计算获得可视区域
内的列表项,并将非可视区域
内存在的列表项删除。
可视区域
起始数据索引(startIndex
)可视区域
结束数据索引(endIndex
)可视区域的
数据,并渲染到页面中startIndex
对应的数据在整个列表中的偏移位置startOffset
并设置到列表上
<div class="virtual-list-container">
<div class="virtual-list-place">div>
<div class="virtual-list">
...
....
div>
div>
接着,监听virtual-list-container的scroll事件,获取滚动位置scrollTop
假定可视区域高度固定,称之为screenHeight
假定列表每项高度固定,称之为itemSize
假定列表数据称之为listData
假定当前滚动位置称之为scrollTop
则可推算出:
列表总高度listHeight = listData.length * itemSize
可显示的列表项数visibleCount = Math.ceil(screenHeight / itemSize)
数据的起始索引startIndex = Math.floor(scrollTop / itemSize)
数据的结束索引endIndex = startIndex + visibleCount
列表显示数据为visibleData = listData.slice(startIndex,endIndex)
当滚动后,由于渲染区域相对于可视区域已经发生了偏移,此时我需要获取一个偏移量startOffset,通过样式控制将渲染区域偏移至可视区域中。
偏移量startOffset = scrollTop - (scrollTop % itemSize);
列表项高度不固定(VUE 版代码实现)
预估高度
先行渲染,然后获取真实高度并缓存。<template>
<div ref="listContainerRef" class="virtual-list-container" :style="{height}" @scroll="onScroll">
<div ref="listPlaceRef" class="virtual-list-place"></div>
<div ref="listRef" class="virtual-list">
<div
ref="itemsRef"
class="virtual-list-item"
v-for="(item, itemIndex) in visibleList"
:key="item._key"
:id="item._key"
>
<slot name="default" :item="item" :index="itemIndex"></slot>
</div>
</div>
<slot name="footer"></slot>
</div>
</template>
<script>
export default {
props: {
// 首尾缓存比例 = (首或者尾高度 + 滚动视窗高度) / 滚动视窗高度
// 自动高度开启时需要调大缓存区比例(视窗内能容纳的越多该值应该越大)
bufferScale: {
type: Number,
default: 0.4,
requred: false
},
// 数据
dataSource: {
type: Array,
requred: true
},
// 滚动视窗高度
height: {
type: String,
default: '100%',
requred: false
},
// (非必填)列表每一项高度(不填时需开启itemAutoHeight)
itemHeight: {
type: Number,
default: 60,
requred: false
},
// 是否自动计算每一项高度
itemAutoHeight: {
type: Boolean,
default: false
},
// 到达底部回调
onReachBottom: Function
},
data () {
return {
screenHeight: 0,
startIndex: 0,
endIndex: 0,
positions: []
}
},
computed: {
_listData () {
return this.dataSource.map((item, index) => ({
...item,
_key: `${index}`
}))
},
anchorPoint () {
return this.positions.length ? this.positions[this.startIndex] : null
},
visibleCount () {
return Math.ceil(this.screenHeight / this.itemHeight)
},
aboveCount () {
return Math.min(this.startIndex, Math.ceil(this.bufferScale * this.visibleCount))
},
belowCount () {
return Math.min(this._listData.length - this.endIndex, Math.ceil(this.bufferScale * this.visibleCount))
},
visibleList () {
const startIndex = this.startIndex - this.aboveCount
const endIndex = this.endIndex + this.belowCount
return this._listData.slice(startIndex, endIndex)
}
},
mounted () {
this.init()
},
updated () {
// 列表数据长度不等于缓存长度
if (this._listData.length !== this.positions.length) {
this.initPositions()
}
this.$nextTick(function () {
if (!this.$refs.itemsRef || !this.$refs.itemsRef.length) {
return
}
// 获取真实元素大小,修改对应的尺寸缓存
if (this.itemAutoHeight) {
this.updateItemsSize()
}
// 更新列表总高度
const height = this.positions[this.positions.length - 1].bottom
this.$refs.listPlaceRef.style.height = height + 'px'
// 更新真实偏移量
this.setStartOffset()
})
},
methods: {
init () {
this.initPositions()
this.screenHeight = this.$refs.listContainerRef.clientHeight
this.startIndex = 0
this.endIndex = this.visibleCount
this.setStartOffset()
},
initPositions () {
this.positions = this._listData.map((_, index) => ({
index,
height: this.itemHeight,
top: index * this.itemHeight,
bottom: (index + 1) * this.itemHeight
})
)
},
updateItemsSize () {
const nodes = this.$refs.itemsRef
nodes.forEach((node) => {
const rect = node.getBoundingClientRect()
const height = rect.height
const index = +node.id.split('_')[0]
const oldHeight = this.positions[index].height
const dValue = oldHeight - height
if (dValue) {
this.positions[index].bottom = this.positions[index].bottom - dValue
this.positions[index].height = height
this.positions[index].over = true
for (let k = index + 1; k < this.positions.length; k++) {
this.positions[k].top = this.positions[k - 1].bottom
this.positions[k].bottom = this.positions[k].bottom - dValue
}
}
})
},
setStartOffset () {
let startOffset
if (this.startIndex >= 1) {
const size = this.positions[this.startIndex].top - (this.positions[this.startIndex - this.aboveCount] ? this.positions[this.startIndex - this.aboveCount].top : 0)
startOffset = this.positions[this.startIndex - 1].bottom - size
} else {
startOffset = 0
}
this.startOffset = startOffset
this.$refs.listRef.style.transform = `translate3d(0,${startOffset}px,0)`
},
getStartIndex (scrollTop = 0) {
return this.binarySearch(this.positions, scrollTop)
},
binarySearch (list, value) {
let start = 0
let end = list.length - 1
let tempIndex = null
while (start <= end) {
const midIndex = parseInt((start + end) / 2)
const midValue = list[midIndex].bottom
if (midValue === value) {
return midIndex + 1
} else if (midValue < value) {
start = midIndex + 1
} else if (midValue > value) {
if (tempIndex === null || tempIndex > midIndex) {
tempIndex = midIndex
}
end = end - 1
}
}
return tempIndex
},
onScroll () {
const scrollTop = this.$refs.listContainerRef.scrollTop
const scrollHeight = this.$refs.listContainerRef.scrollHeight
if (scrollTop > this.anchorPoint.bottom || scrollTop < this.anchorPoint.top) {
this.startIndex = this.getStartIndex(scrollTop)
this.endIndex = this.startIndex + this.visibleCount
this.setStartOffset()
}
if (scrollTop + this.screenHeight > scrollHeight - 50) {
this.onReachBottom && this.onReachBottom()
}
}
}
}
</script>
<style>
.virtual-list-container {
overflow: auto;
position: relative;
}
.virtual-list-place {
position: absolute;
left: 0;
top: 0;
right: 0;
z-index: -1;
}
.virtual-list {
position: absolute;
left: 0;
top: 0;
right: 0;
z-index: 1;
}
</style>
参考文章:https://blog.csdn.net/qq_42036203/article/details/125990867
diff算法的区别,响应式的区别(definedProperty,proxy,setter,getter)
useState useEffect useRef useMemo useCalllback useSelect useDispatch useRequest useReducer useVirtualList(虚拟列表)
import { useVirtualList } from 'ahooks';
const { list, containerProps, wrapperProps } = useVirtualList(Array.from(Array(99999).keys()), {
overscan: 30, // 视区上、下额外展示的 dom 节点数量
itemHeight: 60, // 行高度,静态高度可以直接写入像素值,动态高度可传入函数
});
{ list.map(item => {item.data} ) }
useState 不要直接操作state进行push pop等操作
不要在循环中使用hooks
useReducer 是用于提高应用性能的,当更新逻辑比较复杂时,我们应该考虑使用useReducer,思想类似redux,和redux的区别是不能全局进行状态管理,多组件共享数据
useReducer 接受两个参数reducer函数和初始化state,返回值为最新的state和dispatch函数(用来触发reducer函数,计算对应的state)。dispatch可以传递
const [state, dispatch] = useReducer(reducer, initialArg, init);
组件外和组件内let a = 1;点击事件更改a,渲染的a都不会发生变化,useState可以重新渲染页面
exec execFile spawn fork
Flex布局 定位布局 浮动布局 都可以实现
flex 1 代表撑满剩下的flex布局的空间
分别有:Flex-grow Flex-shrink Flex-basis
Flex-grow
flex-grow属性定义项目的放大比例,默认为0,即如果存在剩余空间,也不放大
如果所有项目的flex-grow属性都为1,则它们将等分剩余空间(如果有的话)。如果一个项目的flex-grow属性为2,其他项目都为1,则前者占据的剩余空间将比其他项多一倍。
Flex-shrink
flex-shrink属性定义了项目的缩小比例,默认为1,即如果空间不足,该项目将缩小。flex-shrink属性定义了项目的缩小比例,默认为1,即如果空间不足,该项目将缩小。
如果所有项目的flex-shrink属性都为1,当空间不足时,都将等比例缩小。如果一个项目的flex-shrink属性为0,其他项目都为1,则空间不足时,前者不缩小。
Flex-basis
flex-basis属性定义了在分配多余空间之前,项目占据的主轴空间(main size)。浏览器根据这个属性,计算主轴是否有多余空间。它的默认值为auto,即项目的本来大小。
它可以设为跟width或height属性一样的值(比如350px),则项目将占据固定空间。
.parent{
display:flex;
height:300px;
justify-content:space-between;
}
.left{
background-color:pink;
width:30%;
}
.center{
background-color:black;
width:30%;
}
.right{
background-color:red;
width:30%;
}
xss (跨站脚本攻击)CSRF攻击(跨站请求伪造)
csp是什么?nginx 中同源策略 配置允许哪些第三方访问
https加密算法
// 输入 [['A', 'B', ...], [1, 2], ['a', 'b'], ...]
// 输出 ['A1a', 'A1b', ....]
let res = arr.reduce((prev, cur) => {
if (!Array.isArray(prev) || !Array.isArray(cur)) {
return
}
if (prev.length === 0) {
return cur
}
if (cur.length === 0) {
return prev
}
const emptyVal = []
prev.forEach(val => {
cur.forEach(item => {
emptyVal.push(`${val}${item}`)
})
})
return emptyVal
}, [])
console.log(res);
pre = 'abcde123'
now = '1abc123'
a前面插入了1,c后面删除了de
function compareStrings(pre, now) {
let diff = JsDiff.diffChars(pre, now);
let result = [];
diff.forEach(function(part) {
let type = part.added ? '插入' : part.removed ? '删除' : '保持不变';
if (type !== '保持不变') {
result.push('在pre的第' + part.index + '个位置' + type + '了' + part.value);
}
});
return result;
}
let pre = 'abcde123';
let now = '1abc123';
let result = compareStrings(pre, now);
result.forEach(function(part) {
console.log(part);
});
//在pre的第0个位置插入了1
//在pre的第3个位置删除了d
//在pre的第4个位置删除了e
function compareStrings(pre, now) {
let result = [];
let i = 0;
let j = 0;
let pre_len = pre.length;
let now_len = now.length;
while (i < pre_len && j < now_len) {
if (pre[i] === now[j]) {
i++;
j++;
} else {
let pre_end = i;
let now_end = j;
while (pre_end < pre_len && pre[pre_end] !== now[j]) {
pre_end++;
}
while (now_end < now_len && now[now_end] !== pre[i]) {
now_end++;
}
if (pre_end - i < now_end - j) {
result.push('在pre的第' + i + '个位置删除了' + pre.substring(i, pre_end));
i = pre_end;
} else {
result.push('在pre的第' + i + '个位置插入了' + now.substring(j, now_end));
j = now_end;
}
}
}
while (i < pre_len) {
result.push('在pre的第' + i + '个位置删除了' + pre[i]);
i++;
}
while (j < now_len) {
result.push('在pre的第' + i + '个位置插入了' + now[j]);
j++;
}
return result;
}
let pre = 'abcde123';
let now = '1abc123';
let result = compareStrings(pre, now);
result.forEach(function(part) {
console.log(part);
});
//在pre的第0个位置插入了1
//在pre的第3个位置删除了de
使用load和BEFOREUNLOAD进行crashed监控
对用户行为和使用chrome工具进行分析
top命令是Linux下常用的性能分析工具,能够实时显示系统中各个进程的资源占用状况,类似于Windows的任务管理器。下面详细介绍它的使用方法。top是一个动态显示过程,即可以通过用户按键来不断刷新当前状态.如果在前台执行该命令,它将独占前台,直到用户终止该程序为止.比较准确的说,top命令提供了实时的对系统处理器的状态监视.它将显示系统中CPU最“敏感”的任务列表.该命令可以按CPU使用.内存使用和执行时间对任务进行排序;而且该命令的很多特性都可以通过交互式命令或者在个人定制文件中进行设定.
301 永久重定向
302 临时重定向
304 客户端发送附带条件的请求时(if-matched,if-modified-since,if-none-match,if-range,if-unmodified-since任一个)服务器端允许请求访问资源,但因发生请求未满足条件的情况后,直接返回304Modified(服务器端资源未改变,可直接使用客户端未过期的缓存)。304状态码返回时,不包含任何响应的主体部分。304虽然被划分在3xx类别中,但是和重定向没有关系。
function sleep(delay) {
var start = (new Date()).getTime();
while ((new Date()).getTime() - start < delay) {
continue;
}
}
function test() {
console.log('111');
sleep(2000);
console.log('222');
}
function sleep(ms, callback) {
setTimeout(callback, ms)
}
sleep(2000, () => {
console.log("sleep")
})
const sleep = time => {
return new Promise(resolve => setTimeout(resolve, time))
}
sleep(1000).then(()=>{ console.log(1) })
function sleepGenerator(time) {
yield new Promise(function(resolve, reject){
setTimeout(resolve, time);
})
}
sleepGenerator(1000).next().value.then(()=>{console.log(1)})
function sleep(time) {
return new Promise(resolve =>
setTimeout(resolve, time))
}
async function output() {
let out = await sleep(1000);
console.log(1);
return out;
}
output();
// 节流
const throttle = (callback, time) => {
let flag = true;
return (...args) => {
if(!flag) return;
flag = false;
setTimeout(() => {
callback.apply(this, args);
flag = true;
}, time);
}
}
// 节流二
const throttle = (callback, time) => {
let prev = Date.now();
return (...args) => {
let now = Date.now();
if(now - prev >= time) {
callback.apply(this, args);
prev = Date.now();
}
}
}
// 节流三
function throttle(func, wait) {
var timer = null;
var startTime = Date.now();
return function(){
var curTime = Date.now();
var remaining = wait-(curTime-startTime);
var context = this;
var args = arguments;
clearTimeout(timer);
if(remaining<=0){
func.apply(context, args);
startTime = Date.now();
}else{
timer = setTimeout(fun, remaining); //保证最后一次一定会被执行
}
}
}
// 防抖
function debounce(func, wait, immediate) {
var timeout, result;
const debounced = (...args) => {
if (timeout) clearTimeout(timeout);
if (immediate) {
var callNow = !timeout;
timeout = setTimeout(function() {
result = func.apply(this, args)
}, wait);
if (callNow) result = func.apply(context, args);
} else {
timeout = setTimeout(function() {
result = func.apply(context, args)
}, wait);
}
return result;
}
debounced.cancel = function(){
cleatTimeout(timeout);
timeout = null;
}
return debounced;
}
var b = 10;
(function b(){
b = 20;
console.log(b);
})();
// [Function: b] NFE大致规则1.只能在函数体内访问,2.函数名是自由变量,可以理解为常量,不可变。
async function async1() {
console.log('1');
await async2();
console.log('2');
}
async function async2() {
console.log('3');
}
console.log('4');
setTimeout(function() {
console.log('5');
}, 0);
async1();
new Promise(function(resolve) {
console.log('6');
resolve();
}).then(function() {
console.log('7');
});
console.log('8');
// 41368275
BFC
全称:Block Formatting Context
, 名为 “块级格式化上下文”。
W3C
官方解释为:BFC
它决定了元素如何对其内容进行定位,以及与其它元素的关系和相互作用,当涉及到可视化布局时,Block Formatting Context
提供了一个环境,HTML
在这个环境中按照一定的规则进行布局。
简单来说就是,BFC
是一个完全独立的空间(布局环境),让空间里的子元素不会影响到外面的布局。那么怎么使用BFC
呢,BFC
可以看做是一个CSS
元素属性
// 可以构成BFC属性
overflow: hidden
display: inline-block
position: absolute
position: fixed
display: table-cell
display: flex
为什么需要webpack,因为目前浏览器只识别html,css,js,但是在我们开发中基本都是使用vue react等框架以及sass ES6语法等等
那么webpack能做什么,核心功能就是构建包,将代码转换为浏览器可识别的代码,同时webpack可以帮助我们压缩打包的体积,并且在打包过程中做一些事情,同时还可以支持开发中的热更新
打包过程大体上分为4步
webpack构建流程(原理)
从启动构建到输出结果一系列过程:
(1)初始化参数:解析webpack配置参数,合并shell传入和webpack.config.js文件配置的参数,形成最后的配置结果。
(2)开始编译:上一步得到的参数初始化compiler对象,注册所有配置的插件,插件监听webpack构建生命周期的事件节点,做出相应的反应,执行对象的 run 方法开始执行编译。
(3)确定入口:从配置的entry入口,开始解析文件构建AST语法树,找出依赖,递归下去。
(4)编译模块:递归中根据文件类型和loader配置,调用所有配置的loader对文件进行转换,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理。
(5)完成模块编译并输出:递归完事后,得到每个文件结果,包含每个模块以及他们之间的依赖关系,根据entry配置生成代码块chunk。
(6)输出完成:输出所有的chunk到文件系统。
注意:在构建生命周期中有一系列插件在做合适的时机做合适事情,比如UglifyPlugin会在loader转换递归完对结果使用UglifyJs压缩覆盖之前的结果。
常用loader
加载scc style-loader、css-loader、less-loader和sass-loader
(文件打包解析css less sass)
加载图片和字体等文件 raw-loader、file-loader 、url-loader
加载数据xml和csv csv-loader xml-loade
r (打包加载解析csv和xml文件数据)
校验测试:mocha-loader、jshint-loader、eslint-loader
.编译:babel-loader、coffee-loader、ts-loade
常用plugin
webpack插件,采用不同的plugin完成各类不同的性需求,热更新,css去重之类的问题
1.ProvidePlugin:自动加载模块,代替require和import
2.html-webpack-plugin可以根据模板自动生成html代码,并自动引用css和js文件
3.extract-text-webpack-plugin 将js文件中引用的样式单独抽离成css文件
4.DefinePlugin 编译时配置全局变量,这对开发模式和发布模式的构建允许不同的行为非常有用。
5.HotModuleReplacementPlugin 热更新
6.optimize-css-assets-webpack-plugin 不同组件中重复的css可以快速去重
7.webpack-bundle-analyzer 一个webpack的bundle文件分析工具,将bundle文件以可交互缩放的treemap的形式展示。
8.compression-webpack-plugin 生产环境可采用gzip压缩JS和CSS
9.happypack:通过多进程模型,来加速代码构建
10.clean-wenpack-plugin 清理每次打包下没有使用的文件
javascript中继承的方法和理解
浏览器从输入url开始发生了什么
浏览器的工作原理
Generator 函数是协程在 ES6 的实现,最大特点就是可以交出函数的执行权(即暂停执行)。
总结下来就是:
console.log(typeof typeof typeof null); // string
console.log(typeof console.log(1)); // 1 undefined
第一个typeof null 为字符串object 后面所有都是string
第二个console.log 本身是一个函数没有返回值 所以是undefined
var name = '123';
var obj = {
name: '456',
print: function() {
function a() {
console.log(this.name);
}
a();
}
}
obj.print(); // undefined
【代码题】
删除链表的一个节点/**
* Definition for singly-linked list.
* function ListNode(val) {
* this.val = val;
* this.next = null;
* }
*/
/**
* @param {ListNode} head
* @param {number} val
* @return {ListNode}
*/
var deleteNode = function(head, val) {
// 定义虚拟节点
const res = new ListNode(-1);
// 虚拟节点连接到head
res.next = head;
// 定义p指针,最开始指向虚拟节点
let p = res;
// 从虚拟节点开始遍历链表
while (p?.next) {
// 如果下一个值等于val,则删除下一个值
if (p.next.val === val)
p.next = p.next.next;
p = p.next;
}
return res.next;
};
缓存淘汰算法:内存容量是有限的,当你要缓存的数据超出容量,就得有部分数据删除,这时候哪些数据删除,哪些数据保留,就是LRU算法和LFU算法,FU强调的是访问次数,而LRU强调的是访问时间。
选择内存中最近最久未使用的页面予以淘汰,如果我们想要实现缓存机制 – 满足最近最少使用淘汰原则,我们就可以使用LRU算法缓存机制。如:vue 中 keep-alive 中就用到了此算法。
⭐LRU:即Least Recently Used(最近最少使用算法)。把长期不使用的数据被认定为无用数据,在缓存容量满了后,会优先删除这些被认定的数据。
var LRUCache = function(capacity) {
this.cache = new Map();
this.capacity = capacity;
};
LRUCache.prototype.get = function(key) {
if(this.cache.has(key)) {
let temp = this.cache.get(key);
this.cache.delete(key);
this.cache.set(key, temp);
return temp;
}
return -1;
}
LRUCache.prototype.put = function(key, value) {
if(this.cache.has(key)) {
this.cache.delete(key);
} else if(this.cache.size >= this.capacity) {
this.cache.delete(this.cache.keys().next().value);
};
this.cache.set(key, value);
}
【面经】5年前端-历时一个月收获7个offer