immutable如何优化react项目性能
- react组件接收新的state和props之后默认会发生渲染,即使前后两次值相同
- 使用immutable处理过后的state或props是一个不可变类型的对象,在修改过后原值保留,并且返回一个引用地址完全不同的新值
- 而react为了提高性能,推出了PureComponent和useMemo,如果单纯使用一定会造成子组件的props为复杂类型情况下引用地址改变但不触发render的bug
- 所以在PureComponent和useMemo情况下,使用Immutable即可在解决重复渲染问题的同时避免引用对象深层改变而浅比较不更新的bug,原因是Immutable内部实现的算法,确保引用类型子节点更新之后,所以相关父节点都更新一遍,而原来引用顺利迁移,这个多出来的父节点更新足以让PureComponent和memo感知到想关props传入值的前后变化引起render
[图片上传失败...(image-f9c96c-1611840489556)]
- 参考链接:深入解析Immutable及 React 中实践
- 思考:如果不使用PureComponent或者memo,那么用Immutable是不是没有意义?——不适用PureComponent或者memo的情况下,每次this.setState时候,如果用到原来的state并且state中的键对应值为复杂类型,都要把该复杂类型深拷贝一份再进行操作,会增加操作上的繁琐,但使用Immutable.js的值,可以放心地操作,如果使用immer则可以更简洁地操作state
React fiber的本质
- 参考链接:程墨大佬讲解react fiber
- 个人理解:react15之前,存储虚拟dom的结构是类似数组的树形数据,采用深度优先的先序遍历算法进行diff对比,这就使得一旦开始遍历便无法中断,否则树形结构难以找到上次中断的索引,如果dom树结构庞大,则diff算法消耗时间过长(超过16.66ms)就会造成卡顿。所以为了解决这个弊端,react推出了fiber,简单说,fiber就是原虚拟dom转化后的可执行最小事务的数据结构,整体fiber采用了双向链表的数据格式,使得可以暂停再继续,并且灵活运用了requestIdleCallback机制,向浏览器申请时间分片,拆分更小的执行过程,尽量避免长时间阻塞js渲染进行,提高了刷新流畅度。
CSS中实现水平垂直居中的几种方式:
1、子绝父相,top和left分别50%
- 第一种偏移方式:tranform: translate(-50%, -50%)
<style>
.father {
width: 200px;
height: 200px;
border: 1px solid green;
position: relative;
}
.son {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 50px;
height: 50px;
border: 2px solid firebrick;
}
</style>
</head>
<body>
<div class="father">
<div class="son"></div>
</div>
</body>
- 第二种偏移方式:margin-top: -自身高度;margin-left: -自身宽度
position: absolute;
top: 50%;
left: 50%;
margin-top: -25px;
margin-left: -25px;
[图片上传失败...(image-749d4e-1611840489556)]
- 第三种偏移方式,要求必须得有宽高,但不关心宽高为多少
.son {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
margin: auto;
2、displya:flex,align-items:center, justy-content:center
<style>
.father {
display: flex;
align-items: center;
justify-content: center;
width: 200px;
height: 200px;
border: 1px solid green;
}
.son {
width: 50px;
height: 50px;
border: 2px solid firebrick;
}
</style>
</head>
<body>
<div class="father">
<div class="son"></div>
</div>
[图片上传失败...(image-f7a14c-1611840489556)]
3、display:table-cell;vertical-align:middle;text-align:-webkit-center;
<style>
.father {
display: table-cell;
text-align: center;
vertical-align: middle;
width: 200px;
height: 200px;
border: 1px solid green;
}
.son {
width: 50px;
height: 50px;
border: 2px solid firebrick;
}
</style>
</head>
<body>
<div class="father">
<div class="son"></div>
</div>
[图片上传失败...(image-25a931-1611840489556)]
4、js方式
[图片上传失败...(image-3ef185-1611840489556)]
扩展知识:dom.getBoundingClientRect()
<div class="father">
<div class="son" id="box"></div>
</div>
<script>
// var box = document.getElementById('box')
console.dir(box.getBoundingClientRect())
</script>
[图片上传失败...(image-8a38e7-1611840489556)]
盒子模型有几种,区别?
- 盒子模型中类:标准盒模型,怪异(IE)盒模型,flex伸缩盒模型,col响应式盒模型(未了解)
1、标准盒模型:
- box-sizing: content-box:翻译过来就是给盒子指定的宽和高只是限定了内容大小,盒子的实际宽高要加上padding和border
- [图片上传失败...(image-809fc4-1611840489556)]
2、IE盒模型(怪异盒模型)
- box-sizing: border-box:盒子指定的宽高就是盒子的实际宽高了,如果设置了padding或者border会自动将内容区域压缩
- [图片上传失败...(image-bcef2-1611840489556)]
3、弹性伸缩盒模型
[图片上传失败...(image-3aa4f5-1611840489556)]
4、多列布局:常用于pad
[图片上传失败...(image-95bfa5-1611840489556)]
典型布局案例
圣杯布局:左固定右固定,中间自适应
<style>
html,
body {
height: 100%;
overflow: hidden;
}
.container {
height: 100%;
padding: 0 200px;
}
.left,
.right,
.center {
float: left;
}
.center {
width: 100%;
height: 100%;
background: lightsalmon;
}
.left,
.right {
width: 200px;
min-height: 200px;
background: lightgreen;
}
.left {
margin-left: -100%;
position: relative;
left: -200px;
}
.right {
margin-right: -200px;
}
</style>
</head>
<body>
<div class="container">
<div class="center"></div>
<div class="left"></div>
<div class="right"></div>
</div>
[图片上传失败...(image-7728af-1611840489556)]
关键点:CSS属性计算.left {
margin-left: calc(-100% - 200px);
相当于:
** **/position: relative;/
/left: -200px;/}
[图片上传失败...(image-69c8d7-1611840489556)]可以达到同样效果
双飞翼布局:左固定右固定,中间自适应
<style>
html,
body {
height: 100%;
overflow: hidden;
}
.container {
width: 100%;
}
.container,
.left,
.right {
float: left;
}
.container .center {
min-height: 400px;
background: lightsalmon;
margin: 0 200px;
}
.left,
.right {
width: 200px;
min-height: 200px;
background: lightgreen;
}
.left {
margin-left: -100%;
}
.right {
margin-left: -200px;
}
</style>
</head>
<body>
<div class="container">
<div class="center"></div>
</div>
<div class="left"></div>
<div class="right"></div>
</body>
[图片上传失败...(image-5e1fda-1611840489556)]
flex实现圣杯布局
<style>
html,
body {
overflow: hidden;
}
.container {
display: flex;
justify-content: space-between;
height: 100%;
}
.left,
.right {
flex: 0 0 200px;
height: 200px;
background: lightgreen;
}
.center {
flex: 1;
height: 100%;
min-height: 400px;
background: lightsalmon;
}
</style>
</head>
<body>
<div class="container">
<div class="left"></div>
<div class="center"></div>
<div class="right"></div>
</div>
</body>
[图片上传失败...(image-39c3b6-1611840489556)]
定位实现圣杯布局
<style>
html,
body {
height: 100%;
overflow: hidden;
}
.container {
position: relative;
}
.center {
margin: 0 200px;
min-height: 400px;
background: lightsalmon;
}
.left,
.right {
width: 200px;
min-height: 200px;
background: lightgreen;
position: absolute;
}
.left {
top: 0;
left: 0;
}
.right {
top: 0;
right: 0;
}
</style>
</head>
<body>
<div class="container">
<div class="center"></div>
<div class="left"></div>
<div class="right"></div>
</div>
[图片上传失败...(image-94f6fb-1611840489556)]
js闭包变量回收问题
- 一般,无论是否涉及到闭包,只要变量在当前作用域中不被引用则就被销毁
- 而如果变量通过各种方式被间接引用到了全局作用域上,则会成为全局变量被保留下来,不会被销毁
js变量回收规则:
在js中定义的全局变量是不会被销毁的,因为随时都可能会用到这个变量,所以不能被销毁。
具体引用关系的不会被销毁
如果一个对象不被引用,那么这个对象就会被回收;
如果两个对象互相引用,但是没有被第3个对象所引用,那么这两个互相引用的对象也会被回收。
通过下面两段代码进行对比分析:
function a(){
var b= 10;
return function(){
b++;
console.log(num);
}
}
a()(); //11
a()(); //11
分析:
在函数a中返回了一个匿名函数,在这个匿名函数中我们num++了一下,然后在函数外面执行了这个匿名函数函数,
现在num是11,然后又执行了一次这个函数,你们应该是12吧,为什么不是呢?
原因:
js为了让没有必要的变量保存在内存中,(我们写的任何变量都是需要内存空间的)在不需要这个变量的时候它就
会被销毁。所以每次执行一遍 a()() 则变量b就会被销毁。下次再执行,就会重新声明变量b,所以两次输出都
是11。
function a(){
var b = 0;
return function(){
b ++;
console.log(b);
}
}
var d = a();
d();//1
d();//2
原因分析:
函数a 被 变量d 引用,更准确的说是 函数a 里面的 匿名函数 被变量d所引用。
因为变量d 保存的是函数a执行完成后的值,而函数a执行完,返回了那个匿名函数,所以变量d等于匿名函数。
-匿名函数因为使用了 函数a 中的 变量b 并且还被 变量 d所引用,所以就形成了一个闭包。
只要这个变量d不等于null的话,那么那个变量b会一直保存到变量d中不会被销毁。
所以两次执行的结果不一样
作者:小本YuDL链接:https://www.jianshu.com/p/00c747510df5来源:简书著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
i++ 和 ++i的区别
- i++,返回i,下次返回i+1
- ++i,返回i+1,下次返回i+1
- [图片上传失败...(image-7e6811-1611840489556)]
手写深克隆函数
/**
* 手写一个深克隆函数
* */
function deepClone (obj) {
if( obj === null ) return null
if( typeof obj !== 'object') return obj
// 正则表达式
if( obj instanceof RegExp) return new RegExp(obj)
// 日期对象
if ( obj instanceof Date) return new Date(obj)
// 函数
if ( obj instanceof Function) return new Function(obj)
// 数组
if (Array.isArray(obj)) return obj.map(item => deepClone(item))
// 普通数据类型的处理
const newObj = {}
for(let i in obj) {
if(obj.hasOwnProperty(i)) {
newObj[i] = deepClone(obj[i])
}
}
return newObj
}
let obj = {
f1: function (a) {
console.log(a)
},
d2: new Date(),
r3: /\d+/g,
n4: null,
c5: {
a: 23
},
d6: [1,3,4, {5: 5}]
}
const obj2 = deepClone(obj)
console.log(obj === obj2)
console.log(obj2, obj)
console.log(obj.c5 === obj2.c5)
注意:isArray方法只存在于Array对象上
事件循环常考题
[图片上传失败...(image-43e777-1611840489556)]浏览器中:[图片上传失败...(image-6878fc-1611840489556)] node环境中:[图片上传失败...(image-beca6-1611840489556)]
关键:await async2如果async2是一个async函数,则会被当成new Promise((res, rej) => {
// aync2函数体内的代码在这里执行
}).then( () => {
// await async2语句之后的所有代码进入这个回调中执行,但顺序并不按照微任务队列的push顺序进行
} )
- [图片上传失败...(image-9ce58b-1611840489556)]
indexOf和取反操作符的应用~
let A = ['a', 'b', 'c']
function replaceArr(item, str){
let i = A.indexOf(item);
~i && (A[i] = str)
}
replaceArr('a', '苹果')
replaceArr('b', '香蕉')
类型转换和覆盖原型链上方法
类型转换原则
1、对象和字符串== 会将对象转换为字符串,调用toString
2、null == undefined null和undefined相等,但是和其他值不相等
3、NaN和NaN是不相等的
4、其余全部转化为数字
[图片上传失败...(image-cf5cc8-1611840489556)]
Vue中实现双向数据绑定原理
Object.defineProperty实现双向数据绑定
<body>
姓名:<span id="spanName"></span><br>
<input type="text" id="inpName">
<script>
let obj = {
name: ''
}
let newObj = {
...obj
}
Object.defineProperty(obj, 'name', {
get () {
return newObj.name
},
set (v) {
newObj.name = v
observe()
}
})
function observe () {
spanName.innerHTML = newObj.name
}
inpName.oninput = function (e) {
console.log(e.target.value)
obj.name = e.target.value
}
</script>
</body>
- 关键点:对原obj对象同时进行赋值和取值操作的时候,需要另一个newObj做中继,否则会在obj的get方法取值时候造成无限循环导致堆栈溢出
- 切忌这样写,否则会造成死循环:
- [图片上传失败...(image-957915-1611840489556)]
Proxy实现数据双向绑定
- 相比于Object.defineProperty优势:1、不需要克隆生成一个新的对象 2、不需要分别设置监听,而是可以给目标对象中的所有属性统一设置监听
<body>
姓名:<span id="spanName"></span><br>
<input type="text" id="inpName">
<script>
let obj = {
name: ''
}
obj = new Proxy(obj, {
get (target, p, receiver) {
console.log(target, p, receiver, 'get')
return target[p]
},
set (target, p, value, receiver) {
console.log(target, p, value, receiver, 'set')
target[p] = value
observe()
}
})
inpName.oninput = function (e) {
obj.name = e.target.value
}
function observe () {
spanName.innerHTML = obj.name
}
</script>
</body>
session和cookie的区别
[图片上传失败...(image-96c001-1611840489556)]
- session设置的过期时间和cookie的过期时间一致
前端算法
数组去重
/**
* 数组去重实现方案
* @type {number[]}
*/
let arr = [12,45,6,23,4,76,12,23,4,12,14,34,23]
// 1 new Set()方式
let setArr = [...new Set(arr)]
// reduce方式
let reduceArr = arr.reduce((previousValue, currentValue) => {
return ~previousValue.indexOf(currentValue) ? previousValue : previousValue.concat(currentValue)
}, [])
// 传统遍历方式
let forArr = []
for(let val of arr ) {
if(forArr.indexOf(val) === -1) forArr.push(val)
}
// 使用对象方式
let obj = {}
let objArr = []
arr.forEach(item => {
obj[item] = item
objArr = Object.values(obj)
})
obj = null // 把当前使用的堆内存销毁
// 先排序后正则处理
let ary = arr.sort((a, b) => a-b).join('@') + '@'
let reg = /(\d+@)\1*/g // \1*代表前面分组中的重复部分出现0次或多次(可重复也可不重复)
let regArr = []
ary.replace(reg, (val, group1) => {
// console.log(val, group1) 4@4@ 4@
regArr.push(Number(group1.slice(0, group1.length-1)))
})
console.log(regArr)
排序
1、冒泡排序
let arr = [1,14,3,16,28,39,4]
/**
* 冒泡排序
*/
function bubble (arr) {
for(let i = 0, len=arr.length; i<len-1; i++) {
for( let j = 1; j<len-1-i; j++ ) {
if(arr[j] > arr[j+1]) {
console.log(arr[j], arr[j+1])
let tmp = arr[j]
arr[j] = arr[j+1]
arr[j+1] = tmp
}
}
}
}
bubble(arr)
console.log(arr) // [ 1, 3, 4, 14, 16, 28, 39 ]
2、插入排序
let arr = [2, 26, 1, 24, 38, 12]
/**
* 插入排序算法
* */
function insert (arr) {
let handle = []
handle.push(arr[0])
for (let i = 1, len = arr.length; i < len; i++) {
let A = arr[i] // 即将被比较的项
for (let j = handle.length - 1; j >= 0; j--) {
let B = handle[j]
console.log(A, B, j)
if (A > B) {
handle.splice(j+1, 0, A)
break
}
console.log(handle, 'handle')
if (j === 0) handle.unshift(A)
}
}
return handle
}
console.log(insert(arr)) // [ 1, 2, 12, 24, 26, 38 ]
分析过程:[图片上传失败...(image-1d5620-1611840489556)][图片上传失败...(image-93c0fe-1611840489556)]
3、快速排序
- 目前最快速的排序
const arr = [1,3,12,8,14,54,23,6]
/**
* 快速排序算法
* */
function quick (arr) {
// 结束递归的条件
if(arr.length <= 1) return arr
// 1、找到中间项数组的索引,在原数组中将其移除
const middleIndex = Math.floor(arr.length/2)
// let middleValue = arr[middleIndex]
// arr.splice(middleIndex, 1)
let middleValue = arr.splice(middleIndex, 1)[0]
// 2、准备左右两个数组,循环数组中剩余的每一项,比中间项小的放左边,比中间项大的放右边
const leftArr = [],
rightArr = []
arr.forEach(item => {
item < middleValue ? leftArr.push(item) : rightArr.push(item)
})
// 递归方式处理左右两边数组,返回值可以直接返回期望的数据格式
console.log(leftArr, rightArr)
return quick(leftArr).concat(middleValue, quick(rightArr))
}
console.log(quick(arr))
数组扁平化的几种实现方案
let arr = [1,34,34,23,45,2,[3,455,67],[34,65,2,[23,7,23,[8,56,23,8,[4,2,7]]]]]
// 1、ES6中的flat方法 console.log(arr.flat(Infinity)) // 不传参数默认拍平1级,Infinity拍平全部
// 2、some方法
// while (arr.some(item => Array.isArray(item))) {
// arr = [].concat(...arr)
// }
// console.log(arr)
// 3、toString方法
// let regArr = arr.toString().split(',').map(item => parseInt(item))
// console.log(regArr)
// 4、JSON.stringify+正则表达式方法
// let JSONArr = JSON.stringify(arr).replace(/(\[|\])/g, '').split(',').map(item => parseFloat(item))
// console.log(JSONArr)
// 5、手动实现flat
(function () {
function myFlat () {
const result = []
const fn = arr => {
for (let i=0;i<arr.length;i++) {
const item = arr[i]
if(Array.isArray(item)) {
fn(item)
continue
}
result.push(item)
}
}
fn(this)
return result
}
Array.prototype.myFlat = myFlat
})()
console.log(arr.myFlat())
返回指定值的连续累加情况
/**
* 给定一个数字,找出从1开始的整数连续想加等于该数字的所有组合
* */
function find (number) {
const result = []
// 由于中位数之后的数字想加不可能,所以找出中位数,排除其余情况
let middleIndex = Math.ceil(number / 2)
// const arr = Array.from({ length: middleIndex }, (item, index) => index + 1)
for (let i = 1; i <= middleIndex; i++) {
for (let j = 2; ; j++) {
let total = (2*i + j - 1) * j / 2 // 从i开始,连续累加j次 i+(i+1)+(i+2)+...+(i+j-1) === (i+(i+j-1))*j/2
if (total > number) {
break
}else if (total === number) {
result.push(create(i, j))
console.log(i, j)
break
}
}
}
return result
}
/**
* 传入i,j,生成累加数组
* @param i
* @param j
*/
function create (i, j) {
// const res = []
// for(let k = 0; k<j; k++) {
// res.push(i+k)
// }
// return res
return Array.from({length: j}, (item, index) => i+index)
}
console.log(find(15))
算法学习参考链接:
聊一聊PWA
- PWA是渐进式Web应用程序
- ProgressiveWebApp
- PWA就是一种网页应用,它可以离线使用,可以变成独立应用安装到系统中。它非常的轻量化,在多平台上面拥有一致的界面。
- 参考链接:PWA概念简介和发展情况
- 使用PWA的Services Workers和cache storage实现拦截请求和响应
- [图片上传失败...(image-4139f1-1611840489556)]
- CSDN的Services workers代码:
/*
*
* Push Notifications codelab
* Copyright 2015 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*
*/
/* eslint-env browser, serviceworker, es6 */
'use strict';
/*服务工作线程*/
// 已经发送数据给应用服务器 此时监听服务器返回数据的情况
var linkUrl = ''
var username = ''
// 点击推来的消息的某一条的监听
self.addEventListener('notificationclick', function(event) {
// console.log('[Service Worker] Notification click Received.');
// console.log('[Service Worker]点击其中一条推来的消息完成 关闭点过的通知 打开详情地址');
event.notification.close();
// 打开详情地址
//console.log('linkurl===',linkUrl)
if(event && event.notification && event.notification.data){
linkUrl = event.notification.data
} else {
linkUrl = 'https://www.csdn.net/'
}
// console.log('event notification====',event.notification)
// console.log('action====sw',linkUrl)
fetch('https://statistic.csdn.net/notification?notify=open&username='+username, {mode: 'cors'})
//_hmt.push(['_trackEvent', 'open', '消息', '', '打开推送弹框'])
// 点击后创建image标签
event.waitUntil(
clients.openWindow(linkUrl)
);
// event.waitUntil确保浏览器不会在显示新窗口前终止服务工作线程
});
网友评论