- 🏆🏆🏆 小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
- 📣📣 欢迎大家多多点赞,关注!!!
前言
- 最近几天收集了不少关于前端的面试题以及一些基础算法,后续会持续更新...
浏览器解析渲染页面
这个图就是Webkit解析渲染页面的过程。
- 解析HTML形成DOM树
- 解析CSS形成CSSOM 树
- 合并DOM树和CSSOM树形成渲染树
- 浏览器开始渲染并绘制页面
这个过程涉及两个比较重要的概念回流和重绘,DOM结点都是以盒模型形式存在,需要浏览器去计算位置和宽度等,这个过程就是回流。等到页面的宽高,大小,颜色等属性确定下来后,浏览器开始绘制内容,这个过程叫做重绘。浏览器刚打开页面一定要经过这两个过程的,但是这个过程非常非常非常消耗性能,所以我们应该尽量减少页面的回流和重绘
性能优化之回流重绘
回流
当Render Tree中部分或全部元素的尺寸、结构、或某些属性发生改变时,浏览器重新渲染部分或全部文档的过程称为回流。
会导致回流的操作:
- 页面首次渲染
- 浏览器窗口大小发生改变
- 元素尺寸或位置发生改变
- 元素内容变化(文字数量或图片大小等等)
- 元素字体大小变化
- 添加或者删除可见的DOM元素
- 激活CSS伪类(例如::hover)
- 查询某些属性或调用某些方法
一些常用且会导致回流的属性和方法:
- clientWidth、clientHeight、clientTop、clientLeft
- offsetWidth、offsetHeight、offsetTop、offsetLeft
- scrollWidth、scrollHeight、scrollTop、scrollLeft
- scrollIntoView()、scrollIntoViewIfNeeded()
- getComputedStyle()
- getBoundingClientRect()
- scrollTo()
重绘
当页面中元素样式的改变并不影响它在文档流中的位置时(例如:color、background-color、visibility等),浏览器会将新样式赋予给元素并重新绘制它,这个过程称为重绘。
优化
CSS
- 避免使用table布局。
- 尽可能在DOM树的最末端改变class。
- 避免设置多层内联样式。
- 将动画效果应用到position属性为absolute或fixed的元素上。
- 避免使用CSS表达式(例如:calc())。
JavaScript
- 避免频繁操作样式,最好一次性重写style属性,或者将样式列表定义为class并一次性更改class属性。
- 避免频繁操作DOM,创建一个documentFragment,在它上面应用所有DOM操作,最后再把它添加到文档中。
- 也可以先为元素设置display: none,操作结束后再把它显示出来。因为在display属性为none的元素上进行的DOM操作不会引发回流和重绘。
- 避免频繁读取会引发回流/重绘的属性,如果确实需要多次使用,就用一个变量缓存起来。
- 对具有复杂动画的元素使用绝对定位,使它脱离文档流,否则会引起父元素及后续元素频繁回流。
JS的解析
JS的解析是由浏览器的JS引擎完成的。由于JavaScript是单进程运行,也就是说一个时间只能干一件事,干这件事情时其他事情都有排队,但是有些人物比较耗时(例如IO操作),所以将任务分为同步任务和异步任务,所有的同步任务放在主线程上执行,形成执行栈,而异步任务等待,当执行栈被清空时才去看看异步任务有没有东西要搞,有再提取到主线程执行,这样往复循环(冤冤相报何时了,阿弥陀佛),就形成了Event Loop事件循环,下面来看看大人物
举出闭包实际场景运用的例子
常见的防抖节流
//防抖
// 设置延时器,短时间高频率触发只有最后一次触发成功
function debounce (fn, delay= 300) {
let timer;
return function() {
const args = arguments;
if(timer) {
clearTimeout(timer);
}
timer = setTimeout( () => {
fn.apply(this, args);
},delay);
}
}
// 节流
// 设置状态锁,短时间高频率触发只有第一次触发成功
function throttle(fn, delay) {
let flag = true;
return () => {
if(!flag) return;
flag = false;
timer = setTimeout( ()=> {
fn();
flag = true;
},delay)
}
}
vue Diff算法原理总结
- 首先判断是否是真实节点,oldVnode是真实节点,代表是初次渲染,直接用新的dom替换el
- 如果是虚拟dom,就是更新过程,采用diff算法。首先去比较新老节点的tag,如果不一致,就用新的节点替换老的节点
- 如果旧节点是一个文本节点,直接替换老的节点的内容就ok
- 如果标签tag一致,并且不是文本节点,首先尽心属性替换,然后比对子元素 为了节点复用,所以直接把旧的虚拟dom对应真实dom赋值给新的虚拟dom的el属性
- **新老节点都有子节点,采用双指针方式进行比对,同节点判断tag和key完全相同为同一个节点 进行节点复用
-
- 头和头相等比较
- 尾和尾相等比较
-
-
- samenode同节点的时候传入两个新老子节点patch(oldChild, newChild)
-
-
- 乱序情况--上面都不符合 先遍历旧的子节点数组形成key值映射的map对象,然后根据新子节点数组循环 按照key值和位置关系移动以及新增节点 最后删除多余的旧子节点 如果移动旧节点同样需要patch(oldChild, newchild)
- 新的有老的没有子节点,直接将子元素虚拟节点转化为真实节点插入
- 新的没有老的有,直接清空el的innnerhtml
手写Promise
class MyPromise {
constructor(fn) {
this.state = 'pending';
this.successFun = [];
this.failFun = [];
let resolve = val => {
if(this.state !== 'pending') return;
this.state = 'success';
setTimeout(()=> {
this.successFun.forEach(item => item.call(this, val))
})
};
let reject = err => {
if(this.state !== 'pending') return;
this.state = 'fail';
setTimeout(()=> {
this.failFun.forEach(item => item.call(this, val))
})
};
// 调用函数
try {
fn(resolve, reject);
} catch (error) {
reject(error);
}
}
then(resolveCallback,rejectCallback) {
resolveCallback = typeOf resolveCallback !== 'function' ? v => v : resolveCallback;
rejectCallback = typeOf rejectCallback !== 'function' ? v => v : rejectCallback;
//保持链式调用 继续返回Promise
return new Mypromise((resolve,reject) => {
//将回调注册到successfun事件集合里面
this.successFun.push((val)=> {
try {
let x = resolveCallback(val);
// 如果回调函数结果是普通值 那么就resolve出去给下一个then链式调用 如果是一个promise对象(代表又是一个异步) 那么调用x的then方法 将resolve和reject传进去 等到x内部的异步 执行完毕的时候(状态完成)就会自动执行传入的resolve 这样就控制了链式调用的顺序
x instanceof MyPromise ? x.then(resolve, reject): resolve(x);
}
catch(err) {
reject(err)
}
})
})
this.failFun.push((val) => {
try {
// 执行回调函数
let x = rejectCallback(val);
x instanceof Mypromise ? x.then(resolve, reject) : reject(x);
} catch (error) {
reject(error);
}
});
}
}
手写all和race
static all(promiseArr) {
let result = [];
let conunt = 0;
return new Mypromise((resolve, reject) => {
for (let i = 0; i < promiseArr.length;i++) {
Promise.resolve(promiseArr[i].then(
res => {
// 不能直接push,控制顺序一一对应
result[i] = res;
if(count === promiseArr.length) {
resolve(result);
}
},
err => {
reject(err);
}
)
}
})
}
//race
static race(promiseArr) {
return new MyPromise((resolve,reject) => {
for (let i = 0; i < promiseArr.length; i++) {
Promise.resolve(promiseArr[i].then(
res => {
resolve(res)
},
err => {
resject(err)
}
))
}
})
}
寄生组合继承
function Parent (name) {
this.name = name;
this.say = () => {
console.log(1111);
}
}
Parent.prototype.play = () = {
console.log(222)
}
function Child(name) {
Parent.call(this);
this.name = name;
}
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;
归并排序
function mergeSort(arr) {
var len = arr.length;
if(len < 2) {
return arr
}
var middle = Marg.floor(len/2);
var left = arr.slice(0, middle);
var right = arr.slice(midddle);
return merge(mergeSort(left), mergeSort(right))
}
function merge (left, right) {
var result = []
while(left.length > 0 && right.length > 0) {
if(left[0] < right[0]) {
result.push(left.shift())
} else {
result.push(right.shift())
}
}
while(left.length) result.push(left.shift())
while(right.length) result.push(right.shift())
return result
}
冒泡排序
function bubbleSort(arr) {
var len = arr.length;
for (let i = 0; i < len; i++ ) {
for(let j = i + 1; j< len; j++) {
if(arr[i] > arr[j]) {
var temp = arr[i];
arr[i] = arr[j]
arr[j] = temp
}
}
}
return arr
}
快速排序
function quickSort(arr) {
let len = arr.length;
if(len <= 1) return arr;
let privotIndex = Math.floor(len/2);
let privot = arr.splice(prvotIndex, 1)[0];
let left = [];
let right = [];
for (let i = 0; i < len; i++){
if(arr[i] < privot) {
left.push(arr[i])
} else {
right.push(arr[i])
}
}
return quickSort(left).concat([privot],quickSort(right));
}
找到一个数组中第一个没出现的最小正整数
// o(n^2)
function firstMissingPos(nums) {
let i = 0;
let res = 1;
while(i < nums.length) {
if(num[i] == res){
res++;
i=0;
} else {
i++;
}
}
return res
}
// o(n)
function firstMissingPos(nums) {
let set = new Set();
for(let i = 0; i < nums.length; i++) {
set.add(nums[i])
}
for(let i = 1; i < nums.length + 1; i++) {
if(!set.has(i)) {
return i;
}
}
}
Proxy与Object.defineProperty的优劣对比?
Proxy的优势如下:
- Proxy可以直接监听对象而非属性
- Proxy可以直接监听数组的变化
- Proxy有多达13种拦截方法,不限于apply、ownKeys、deleteProperty、has等等是Object.defineProperty不具备的
- Proxy返回的是一个新对象,我们可以只操作新的对象达到目的,而Object.defineProperty只能遍历对象属性直接修改
- Proxy作为新标准将受到浏览器厂商重点持续的性能优化,也就是传说中的新标准的性能红利
Object.defineProperty的优势如下:
- 兼容性好,支持IE9
compose
function compose(...fn) {
if(!fn.length) return v=> v;
if(fn.length === 1 ) return fn[0];
return fn.reduce(
(pre, cur) => {
...args => pre(cur(...args));
}
)
}
数组扁平化
//递归
function flatter(arr) {
if(!arr.length) return;
return arr.reduce(
(pre, cur) =>
Array.isArray(cur) ? [...pre, flatter(cur)] : [...pre, cur];
)
}
//迭代
function flatter(arr) {
if (!arr.length) return;
while (arr.some((item) => Array.isArray(item))) {
arr = [].concat(...arr);
}
return arr;
}
通过时间分片方式解决简单dom结构大数据量渲染
let ul = document.createElement('ul');
let total = 100000;
let once = 20;
let index = 0;
let page = total/once;
function loop(curTotal, curIndex) {
if(curTotal <= 0) {
return false
}
//每页多少条
let pageCount = Math.min(curTotal, once);
window.requestAnimationFrame(()=>{
for(let i = 0; i < pageCount; i++) {
let li = document.createElement('li')l;
li.innerText = curindex+i;
ul.appenChild(li)
}
loop(curTotal - pageCount, curindex + pageCount)
})
}
loop(total, index);
一维数组&树形结构互转
//转树
const arrayToTree = (arr, pid) => {
return arr.reduce((res, current) => {
if (current['pid'] === pid) {
current.children = arrayToTree(arr, current['id']);
return res.concat(current);
}
return res;
}, []);
};
console.log(arrayToTree(list, 0))
//转数组
let list = [];
function getList(treeData){
treeData.map((item,index)=>{
let { node_id, name, parent_id} = item
list.push({ node_id, name, parent_id})
if(item.children.length>0){
getList(item.children)
}
})
}
a = [1, 2, 3, 4, 5, 6] 右移动 n 位, n >=0 , 求移动后的数组
function moveElement(arr, n) {
if(Math.abs(n)>arr.length) n = n%arr.length
return arr.slice(-n).concat(arr.slice(0,-n))
}
// moveElement(arr, 9)
// moveElement(arr, 0)
// moveElement(arr, -9)
取数组交集
// 两个数组的交集
const interSection = (arr1, arr2) => {
const s = new Set(arr2);
return [... new Set(arr1)].filter(x => s.has(x));
}
//多个数组的交集
const interSection = (arr) => {
return arr.reduce((per,cur) => per.filter(item => cur.includs(item)))
}