HTML相关
有哪些是块状元素和内联元素?
HTML可以将元素分类(display)方式分为行内元素(display:inline)、块状元素(display:block)和行内块状元素(display:inline-block)三种
块状元素特点:如div、p、nav、aside、header、footer、section、article、ul-li、address等等
- 能够识别宽高
- margin和padding的上下左右均对其有效
- 可以自动换行
- 多个块状元素标签写在一起,默认排列方式为从上至下
内联元素的含义:如span、b、i、img、a等等
- 设置宽高无效
- 对margin仅设置左右方向有效,上下无效;padding设置上下左右都有效,即会撑大空间
- 不会自动进行换行
CSS相关
重绘(Repaint)和回流(Reflow)
当元素的样式发生变化时,浏览器需要触发更新,重新绘制元素。这个过程中,有两种类型的操作,即重绘与回流。
- 重绘(repaint): 当元素样式的改变不影响布局时,浏览器将使用重绘对元素进行更新,此时由于只需要UI层面的重新像素绘制,因此 损耗较少
- 回流(reflow): 当元素的尺寸、结构或触发某些属性时,浏览器会重新渲染页面,称为回流。此时,浏览器需要重新经过计算,计算后还需要重新页面布局,因此是较重的操作。会触发回流的操作:
- 重绘和回流是渲染步骤中的一小节,但是这两个步骤对于性能影响很大。
- 重绘是当节点需要更改外观而不会影响布局的,比如改变
color就叫称为重绘 - 回流是布局或者几何属性需要改变就称为回流。
- 回流必定会发生重绘,重绘不一定会引发回流。回流所需的成本比重绘高的多,改变深层次的节点很可能导致父节点的一系列回流。
所以以下几个动作可能会导致性能问题:
- 改变
window大小 - 改变字体
- 添加或删除样式
- 文字改变
- 定位或者浮动
- 盒模型
很多人不知道的是,重绘和回流其实和 Event loop 有关。
- 当
Event loop执行完Microtasks后,会判断document是否需要更新。- 因为浏览器是60Hz的刷新率,每16ms才会更新一次。 - 然后判断是否有
resize或者scroll,有的话会去触发事件,所以resize和scroll事件也是至少 16ms 才会触发一次,并且自带节流功能。 - 判断是否触发了
media query - 更新动画并且发送事件
- 判断是否有全屏操作事件
- 执行
requestAnimationFrame回调 - 执行
IntersectionObserver回调,该方法用于判断元素是否可见,可以用于懒加载上,但是兼容性不好 - 更新界面
- 以上就是一帧中可能会做的事情。如果在一帧中有空闲时间,就会去执行
requestIdleCallback回调。
减少重绘和回流
使用
translate替代top
<div class="test"></div>
<style>
.test {
position: absolute;
top: 10px;
width: 100px;
height: 100px;
background: red;
}
</style>
<script>
setTimeout(() => {
// 引起回流
document.querySelector('.test').style.top = '100px'
}, 1000)
</script>
- 使用
visibility替换display: none,因为前者只会引起重绘,后者会引发回流(改变了布局) - 把
DOM离线后修改,比如:先把DOM给display:none(有一次Reflow),然后你修改100次,然后再把它显示出来 - 不要把
DOM结点的属性值放在一个循环里当成循环里的变量
for(let i = 0; i < 1000; i++) {
// 获取 offsetTop 会导致回流,因为需要去获取正确的值
console.log(document.querySelector('.test').style.offsetTop)
}
- 不要使用
table布局,可能很小的一个小改动会造成整个table的重新布局 动画实现的速度的选择,动画速度越快,回流次数越多,也可以选择使用requestAnimationFrame CSS选择符从右往左匹配查找,避免DOM深度过深- 将频繁运行的动画变为图层,图层能够阻止该节点回流影响别的元素。比如对于
video标签,浏览器会自动将该节点变为图层。
盒子模型
JavaScript相关
值类型
包含:undefined、boolean、number、string、symbol
引用类型
包含:[] 、 {} 、 function 、 null
值类型和引用类型的区别
值类型:值类型的值放在栈内存中(另一种说法:指针直接指向值)
引用类型:引用类型的值放在堆内存中(另一种书法:指针指向值的地址)
// 值类型
var a = 100
var b = a
b = 200
console.log(a) // 100
// 引用类型
var obj1 = {a:100}
var obj2 = obj1
obj2.a = 200
console.log(obj1.a) // 200
判断类型
typeof可判断哪些类型?
- 识别所有值类型
- 识别函数
- 判断是否是引用类型(不可再细分)
深拷贝
/**
* 深拷贝
*/
const obj1 = {
age: 20,
name: 'xxx',
address: {
city: 'beijing'
},
arr: ['a', 'b', 'c']
}
const obj2 = deepClone(obj1)
obj2.address.city = 'shanghai'
obj2.arr[0] = 'a1'
console.log(obj1.address.city)
console.log(obj1.arr[0])
/**
* 深拷贝
* @param {Object} obj 要拷贝的对象
*/
function deepClone(obj = {}) {
if (typeof obj !== 'object' || obj == null) {
// obj 是 null ,或者不是对象和数组,直接返回
return obj
}
// 初始化返回结果
let result
if (obj instanceof Array) {
result = []
} else {
result = {}
}
for (let key in obj) {
// 保证 key 不是原型的属性
if (obj.hasOwnProperty(key)) {
// 递归调用!!!
result[key] = deepClone(obj[key])
}
}
// 返回结果
return result
}
原型和原型链
ES5-ES6原型,原型链
所有的引用类型(数组、对象、函数)都具有对象特性,即可自由扩展属性(除了“null”)
const obj = {}
obj.a = 100
const arr = []
arr.a = 200
function fn() {}
fn.a = 300
所有的引用类型(数组、对象、函数)都有一个 proto 属性(隐式原型属性),属性值是一个普通的对象
所有的函数和class,都有一个 prototype(显式原型)属性,属性值也是一个普通的对象
所有的引用类型(数组、对象、函数), proto 属性值(隐式原型属性)指向它的构造函数的“prototype”属性值
原型链:当试图得到一个对象的某个属性时,如果这个对象本身没有这个属性,那么会去它的_proto_(即它的构造函数的 prototype(显式原型))中寻找
执行上下文(简称:上下文)
概念:变量或函数的上下文决定了它们可以访问到哪些数据,以及它们的行为。
特点:每个上下文都有一个关联的变量对象,这个对象里包含了这个上下文中定义的所有变量和函数
全局上下文是最外层的上下文,就是window对象
本人通俗的理解:上下文就是作用域的问题。内部可以使用外部的属性或者方法,但是外部没办法使用内部的属性或者方法
this指向的问题
所有场景中this所取的值是在函数执行的时候决定的,而不是定义的时候
箭头函数中,this指向的是定义箭头函数
function fn1(){
console.log(this)
}
fn1 // 打印结果:window
------------------------------------------------------
fn1.call({ x: 100 }) // 打印结果:{ x: 100 }
------------------------------------------------------
const fn2 = fn1.bind({ x: 200 })
fn2() // 打印结果: { x: 200 }
------------------------------------------------------
const zhangsan = {
name: '张三',
sayHi(){
console.log(this)
// this 即当前对象
}
wait(){
setTimeout(function() {
console.log(this)
// this指向window
})
}
}
--------------------------------------------------------
const zhangsan = {
name: '张三',
sayHi(){
console.log(this)
// this 即当前对象
}
wait(){
setTimeout(() => {
console.log(this)
// this指向当前对象
})
}
}
----------------------------------------------------------
class People {
constructor(name){
this.name = name
this.age = 20
}
sayHi(){
console.log(this)
}
}
const zhangsan = new People('张三')
zhangsan.sayHi() // 打印结果: zhangsan对象
手写bind
Function.prototype.newBind = function () {
// 将参数拆解为数组
console.log('arguments',arguments)
const newArr = Array.prototype.slice.call(arguments);
console.log("newArr", newArr);
// 获取this(数组第一项)
const newThis = newArr.shift();
// xxx.newBind(....)中的xxx
const self = this;
return function () {
return self.apply(newThis, newArr);
};
};
function fn1(a, b) {
console.log("this", this);
console.log(a, b);
return "Hello";
}
const fn2 = fn1.newBind({ x: 100 }, 10, 20);
fn2();
闭包
概念:闭包指的是那些使用了另外一个函数作用域内的变量的函数,通常是在嵌套函数中实现的。
闭包:自由变量的查找,是在函数定义的地方,向上级作用作用域查找,不是在执行的地方
function create(){
const a = 100
return function () {
console.log(a)
}
}
const a = 200
fn = create()
fn() // 输出结果:100
--------------------分割线-------------------
function print(fn) {
const a = 100
fn()
}
const a = 200
function fn(){
console.log(a)
}
print(fn) // 输出结果:200
实际开发中闭包的应用
- 隐藏数据
- 做一个简单的cache工具
function createCache() {
const data = {};
return {
set: function (key, value) {
data[key] = value;
},
get: function (key) {
return data[key];
},
};
}
const newData = createCache();
newData.set("a", 100);
console.log(newData.get("a"));
异步和单线程
js是单线程语言,只能同时做一件事
异步和同步的区别
- 基于js是单线程语言
- 异步不会阻塞代码执行
- 同步会阻塞代码执行
异步应用场景
- 网络请求,如ajax图片加载
- 定时任务,如setTimeout
/* Promise图片加载实例 */
function loadImg(src) {
return new Promise((resolve, reject) => {
const img = document.createElement("img");
img.onload = () => {
resolve(img);
};
img.onerror = () => {
reject(new Error("图片加载失败"));
};
img.src = src;
});
}
const url1 =
"https://gimg2.baidu.com/image_search/src=http%3A%2F%2F1812.img.pp.sohu.com.cn%2Fimages%2Fblog%2F2009%2F11%2F18%2F18%2F8%2F125b6560a6ag214.jpg&refer=http%3A%2F%2F1812.img.pp.sohu.com.cn&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1625236813&t=b636a3bec755a4639ad07f59b28fe2cc";
const url2 =
"https://gimg2.baidu.com/image_search/src=http%3A%2F%2F2c.zol-img.com.cn%2Fproduct%2F124_500x2000%2F748%2FceZOdKgDAFsq2.jpg&refer=http%3A%2F%2F2c.zol-img.com.cn&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1625236813&t=0ec6f945dc883b312daef0050bbdc290";
loadImg(url1)
.then((img) => {
console.log(img.width);
return img; // 普通对象
})
.then((img) => {
console.log(img.height);
return loadImg(url2); // Promise对象
})
.then((img) => {
console.log(img.width);
return img;
})
.then((img) => {
console.log(img.height);
})
.catch((ex) => console.log(ex));
event loop(事件循环/事件轮询)
微任务和宏任务
微任务:setTimeout setInterval Ajax Dom事件
宏任务:Promise async/await
微任务和宏任务区别
- 微任务执行时机比宏任务要早
- 微任务在DOM渲染前触发,宏任务在DOM渲染后触发。
- 微任务是ES6规定的,宏任务是浏览器规定的。
React框架
异步组件
- import()
- React.lazy
- React.Suspense
性能优化
React中默认:父组件有更新,子组件则无条件也更新!!!
- shouldComponentUpdate(简称SCU)
shouldComponentUpdate(nextProps, nextState){
if(nextProps.a !== this.props.a) {
return true
}
return false
}
- PureComponent
- memo
- immutable.js
Redux使用
React原理
函数式编程
- 一种编程范式
- 纯函数
- 不可变值
更新中