面试题系列
css、html篇---------
flex原理
父元素设置 flex 子元素:
order : 子元素值越高越往后排列,越小越往前。
flex-grow : 当子盒子在父盒子内部时 ,获取到父盒子的剩余空间,增大子盒子的占比(自己的宽度加剩余空间占比)。
Flex-shrink:当子盒子一起的大小超过父盒子的大小后,超出部分进行缩小的取值比例(真实比例计算:盒子1宽度shrink**😗*盒子2宽度shrink)
flex-basis : 用来设置盒子默认基准宽度,basis和width同时存在basis会把width干掉。(flex-basis会根据内部元素和flex-grow获取的宽度,与width作比较 ,三者比较情况不同不一样)
理解 1:flex:1的逻辑*就是用flex-basis把width干掉,然后再用flex-grow和flex-shrink增大的增大缩小的缩小,达成最终的效果。
理解 2 : 子元素 flex 不写,默认 0 ,1,auto。 flex1 : 实际 flex:1 1 0% ;
Align-self :
元素居中方式
margin : auto
position(居中元素宽高固定)
Postion 配合 transfrom
flex
BFC:块级格式化上下文。
作用:处理浮动脱离盒子问题 。(bfc就是页面上的一个独立容器,容器里面的子元素不会影响外面元素,也不会影响旁边元素)
解决方案:
.bfc {
//两种方式
//不推荐方式
overflow: hidden;
//推荐方式
display: flow-root;
}
实例:
<style>
.box {
border: 1px solid red;
}
.float {
width: 100px;
height: 100px;
background-color: red;
float: left;
}
.bfc {
/* overflow: hidden; */
display: flow-root;
}
</style>
<body>
<div class="box bfc">
<div class="float"></div>
<p class="bfc">我是一段问题</p>
</div>
</body>
grid布局
script标签 defer async
-
script
:HTML
暂停解析,下载JS
,执行JS
,在继续解析HTML
。 -
defer
:HTML
继续解析,并行下载JS
,HTML
解析完在执行JS
(不用把script
放到body
后面,我们在head
中<script defer>
让js
脚本并行加载会好点) -
async`:`HTML`继续解析,并行下载`JS`,执行`JS`(`加载完毕后立即执行`),在继续解析`HTML ,加载完毕后立即执行,这导致async属性下的脚本是乱序的,对于 script 有先后依赖关系的情况,并不适用
link标签preload和prefetch
preload
资源在当前页面使用,会优先加载prefetch
资源在未来页面使用,空闲时加载
<head>
<!-- 当前页面使用 -->
<link rel="preload" href="style.css" as="style" />
<link rel="preload" href="main.js" as="script" />
</head>
js篇-----------
instanceof与typeof
总体 :instanceof判断引用类型更加精确(instanceof是判断对象是否有(Array ,Function ,Object等类的构造函数是否与对象相等), typeof判断非object类型 ,返回类型字符串
typeof
会返回一个变量的基本类型,instanceof
返回的是一个布尔值instanceof
可以准确地判断复杂引用数据类型,但是不能正确判断基础数据类型- 而
typeof
也存在弊端,它虽然可以判断基础数据类型(null
除外),但是引用数据类型中,除了function
类型以外,其他的判断不清楚,比如 typeof [] 等于 object - 使用Object.prototype.toString判断类型更加精确
Object.prototype.toString({}) // "[object Object]"
Object.prototype.toString.call({}) // 同上结果,加上call也ok
Object.prototype.toString.call(1) // "[object Number]"
Object.prototype.toString.call('1') // "[object String]"
Object.prototype.toString.call(true) // "[object Boolean]"
Object.prototype.toString.call(function(){}) // "[object Function]"
Object.prototype.toString.call(null) //"[object Null]"
Object.prototype.toString.call(undefined) //"[object Undefined]"
Object.prototype.toString.call(/123/g) //"[object RegExp]"
Object.prototype.toString.call(new Date()) //"[object Date]"
Object.prototype.toString.call([]) //"[object Array]"
Object.prototype.toString.call(document) //"[object HTMLDocument]"
Object.prototype.toString.call(window) //"[object Window]"
- 手写完整的类型判断
function getType(obj){
//可以显示用typeof 判断null之外的基本数据类型
let type = typeof obj;
if (type !== "object") { // 先进行typeof判断,如果是基础数据类型,直接返回
return type;
}
// object 类型应为typeof判断不清楚 ,可以使用Object.prototype.toString.call()判断
return Object.prototype.toString.call(obj).replace(/^\[object (\S+)\]$/, '$1');
}
Promise相关
解决了之前函数回调地狱的问题
- 使用then来链式调用,代码可读性增强
- 三种状态 :fulfilled(成功) ,reject(失败) , pending (进行中)
- 特点
- 对象的状态不受外界影响,只有异步操作的结果,可以决定当前是哪一种状态
- 一旦状态改变(从
pending
变为fulfilled
和从pending
变为rejected
),就不会再变,任何时候都可以得到这个结果
- 特点
async/await单独理解
- 同步语法
- 执行
async
函数,返回的是promise
await
相当于promise
的then
try catch
可捕获异常(捕获 rejected 状态),代替了promise
的catch
await
后面跟Promise
对象:会阻断后续代码,等待状态变为fulfilled
,才获取结果并继续执行,await
后续跟非Promise
对象:会直接返回
promise
、Generator
、async/await
进行比较:
promise
和async/await
是专门用于处理异步操作的Generator
并不是为异步而设计出来的,它还有其他功能(对象迭代、控制输出、部署Interator
接口…)promise
编写代码相比Generator
、async
更为复杂化,且可读性也稍差Generator
、async
需要与promise
对象搭配处理异步情况async
实质是Generator
的语法糖,相当于会自动执行Generator
函数async
使用上更为简洁,将异步代码以同步的形式进行编写,是处理异步编程的最终方案
Generator
概要:Generator 函数是 ES6 提供的一种异步编程解决方案
说明:执行 Generator
函数会返回一个遍历器对象,可以依次遍历 Generator
函数内部的每一个状态
3个特征:
-
function
关键字与函数名之间有一个星号 -
函数体内部使用
yield
表达式,定义不同的内部状态 -
通过
next
方法才会遍历到下一个内部状态
//通过yield关键字可以暂停generator函数返回的遍历器对象的状态
function* helloWorldGenerator() {
yield 'hello';
yield 'world';
return 'ending';
}
var hw = helloWorldGenerator();
//上述存在三个状态:hello、world、return
This问题
this 特点: 1 严格模式和非严格模式有所不同(严格模式为undefined) 2 大多数情况函数调用决定了this的值 3 this关键字是函数运行时自动生成的对象,只能在函数内部使用,总是指向调用它的对象 4 this在函数执行过程中 , 一旦确认不可修改
绑定规则
- 默认绑定 : 非严格模式下 , 默认绑定到window全局对象 。
function func(){
//this 指向window
}
func()
- 隐式绑定 :函数内this 永远指向调用它的对象 ,没有就指向window或undfined
- 显示绑定 :通过call ,apply ,bind
- new绑定 : 指向创建的对象 ,如果有返回对象(只有返回的数据是对象才行) ,指向返回的对象
function Person(){
return {a:1}
}
const p = new Person()
// this 指向 {a:1}
箭头函数
- 在编译的时候确定了this指向
优先级:
- new绑定优先级 > 显示绑定优先级(call , bind , apply) > 隐式绑定优先级(obj.say()) > 默认绑定优先级
箭头函数
- 箭头函数不绑定
arguments
,可以使用...args
代替 - 箭头函数没有
prototype
属性,不能进行new
实例化 - 箭头函数不能通过
call
、apply
等绑定this
,因为箭头函数底层是使用bind
永久绑定this
了,bind
绑定过的this
不能修改 - 箭头函数的
this
指向创建时父级的this
- 箭头函数不能使用
yield
关键字,不能作为Generator
函数
总结:不适用箭头函数的场景
- 场景1:对象方法(this问题)
- 场景2:对象原型(this问题)
- 场景3:构造函数(不能使用new)
- 场景4:vue的生命周期和
method
(this问题)
js事件循环机制
文章:理解事件循环https://blog.csdn.net/weixin_45730243/article/details/125673846
文章:理解事件循环https://zhuanlan.zhihu.com/p/618003341
- 浏览器提供给js的线程是单线程的,在同步任务执行的同时,js线程并不会(阻塞),而是在等待异步任务准备的同时,JS 引擎去执行其他同步任务,等到异步任务准备好了,再去执行回调。
- 事件循环是由一个队列(微任务,宏任务)组成的,异步任务的回调遵循先进先出,在 JS 引擎空闲时会一轮一轮地被取出,所以被叫做循环。
微任务宏任务
宏任务 | 微任务 | |
---|---|---|
是否重新渲染页面 | 会 | 不会 |
是否需要其他异步线程的支持 | 需要 | 不需要 |
宏任务与微任务发起者 | 宿主(node、浏览器) | js引擎 |
具体事件 | script、setTimeout/setInterval、postMessage、MessageChannel、UI rending/UI事件、setImmediate与I/O(Node.js环境) | Promise.then()、await后面的代码、 MutaionObserver(html5新特性,监视 DOM 变动。DOM 的任何变动) 、proxy、process.nextTick(Node.js) |
- 同步任务 > 微任务 > 宏任务 :微任务和宏任务执行会放到消息队列,等待同步任务执行完毕之后执行。
- 微任务::process.nextTick(node 独有)、Promise.then()、Object.observe、MutationObserver
- 宏任务:script(整体代码、setTimout、setInterval、setImmediate(node 独有)、requestAnimationFrame(浏览器独有)、IO、UI render(浏览器独有)
setTimeout(() => {
console.log("1")
new Promise((resolve) => {
console.log("2")
resolve()
}).then(res => {
console.log("3")
})
})
new Promise((resolve) => {
setTimeout(() => {
console.log("4")
})
resolve()
}).then(res => {
console.log("5")
setTimeout(() => {
console.log("6")
})
})
setTimeout(() => {
console.log("7")
})
//5 1 2 3 4 7 6
1-1 1-3 1-5
1-4
1-2
2-1 2-3 2-5
2-4
2-2
setTimeout(() => {
console.log(1);
}, 0);
async function test1() {
console.log(2)
}
async function test2() {
//await后面一般跟一个Promise对象,如果后面不是Promise对象就会被转成Promise对象,await会暂停当前async //function的执行,等待await表达式后面的Promise处理完成后才会继续执行
await test1() // 后面执行代码 ,相当于.then
console.log(3)
}
test2()
new Promise((resolve) => {
console.log(4);
resolve(5);
new Promise((resolve) => {
console.log(6);
resolve(7);
}).then((res) => {
console.log(res);
});
}).then((res) => {
console.log(res);
});
//2463751
- 宏任务执行包含宏任务 :先把一个宏任务内容(包括微任务)执行完成 , 再执行下一个宏任务
- 微任务包含宏任务:先统一把所有的微任务执行完成再执行宏任务队列
- 如果有多个
setTimeout(() => {
console.log("1-1")
new Promise((resolve) => {
console.log("1-2")
resolve()
}).then(res => {
console.log("1-3")
})
})
setTimeout(() => {
console.log("1-4")
new Promise((resolve) => {
console.log("1-5")
resolve()
}).then(res => {
console.log("1-6")
})
})
//1-1 1-2 1-3 1-4 1-5 1-6
new Promise((resolve) => {
resolve()
}).then(res => {
console.log("2-1")
setTimeout(() => {
console.log("2-2")
})
})
new Promise((resolve) => {
resolve()
}).then(res => {
console.log("2-3")
setTimeout(() => {
console.log("2-4")
})
})
//2-1 2-3 2-2 2-4
原型链理解
1、原型链可以理解为原型和链。
2、首先说一下原型:
- 每一个函数都拥有一个显示原型 ,以属性的形式存在 ,prototype(一个对象)。
- 每一个对象都拥有隐式原型 ,proto(一个对象)
- 在js里面函数也是对象 ,所以函数也有一个隐式原型
3、在说下链:
- 一个对象有他自己本身的属性 ,和原型属性 , 当对象.属性的时候,优先获得本身属性,如果没有那就会往原型链去查找 ,而每一个原型又是一个对象 , 对象又有原型 ,所以会持续往下层寻找,直到找到为止!这个就是原型链。
4、如果使用一个构造函数 或者说类 ,实例一个对象那么
- 构造函数的显示原型 === 对象的隐试原型
5、原型链的终点是Object类或者说构造函数, Object的显示原型的隐式原型是null ,原型链的终点
Object.prototype.__proto__ = null
Object这个构造函数作为一个对象来说,他是由一个顶层类(或者说构造函数创建的)Function创建的。
- 一切对象都继承Object这个顶层对象
- 一切函数对象都是继承直Function构造函数
- Object直接继承至Function
作用域链
概念:子级可以向父级查找变量,逐级查找,找到为止。
- 全局作用域 : 全局作用域页面打开时创建 ,销毁时关闭
- 直接编写在 script 标签之中的JS代码,都是全局作用域;
- 引用单独js文件
- 有一个全局对象window(
- 前端所有的全局作用域变量作为window的对象属性保存
- 前端所有的全局作用域方法作为window的对象方法保存
- 局部作用域(函数作用域)
- 函数调用的时候创建, 只在函数作用域有效 ,每调用一次创建一个新作用域
- 作用域链:内层作用域->外层作用域->…->全局作用域
function类继承
- 原型链继承 : 存在问题?所有属性使用原型链继承,创建多个对象 , 修改原型上面的属性 ,都会修改!!!
function Person(){
this.name = "test"
}
function Person1(){
this.arr = [1,2]
}
Person.prototype = new Person1()
const p1 = new Person()
const p2 = new Person()
//存在问题
p1.arr.push(3)
console.log(p1 ,p2)
- 构造函数继承(借助 call): 存在问题 ? 属性可以私有 , 但是无法继承父类原型链属性。
function Person(){
this.name = "test"
}
//父类的原型链属性 ,无法被继承
Person.prototype.say=function(){
console.log("我是"+this.name)
}
function Person1(){
Person.call(this)
this.age = 20
}
const p1 = new Person1()
p1.say() // 报错
- 组合继承 :存在问题?方式一和方式二的问题都解决了,但是从上面代码我们也可以看到父构造函数执行了两次,造成了多构造一次的性能开销。
function Person(){
this.name = "test"
}
//父类的原型链属性 ,无法被继承
Person.prototype.say=function(){
console.log("我是"+this.name)
}
function Person1(){
Person.call(this) // 调用一次
this.age = 20
}
Person1.prototype = new Person() //调用2次
Person1.prototype.constructor = Person1 //手动挂载自己的构造器 ,因为上面把自己的构造器覆盖了
const p1 = new Person1()
p1.say() // 报错
console.log(Person1.prototype )
- 原型式继承 :主要借助
Object.create
方法实现普通对象的继承 存在问题?Object.create
方法实现的是浅拷贝,多个实例的引用类型属性指向相同的内存,存在篡改的可能- Object.create使用现有的对象来提供新创建的对象的
__proto __
,let obj = Object.create(ob) , 那么obj.proto ==ob
- Object.create使用现有的对象来提供新创建的对象的
// 现有的对象来提供新创建的对象的__proto__
const Obj = {
name:"test",
arr:[1,2]
}
const o1 = Object.create(Obj)
const o2 = Object.create(Obj)
o1.name ="haha"
o1.arr.push(3) //都修改了 ,和原型链继承类似
console.log(o1,o2)
- **寄生式继承 ** 缺点同上!!
let parent5 = {
name: "parent5",
friends: ["p1", "p2", "p3"],
getName: function() {
return this.name;
}
};
function clone(original) {
//添加原型属性
let clone = Object.create(original);
//额外添加属性
clone.getFriends = function() {
return this.friends;
};
return clone;
}
let person5 = clone(parent5);
console.log(person5.getName()); // parent5
console.log(person5.getFriends()); // ["p1", "p2", "p3"]
- 寄生组合式继承
// 父构造函数
function Parent() {
this.name = 'parent';
this.play = [1, 2, 3];
}
// 父构造函数原型
Parent.prototype.getName = function () {
return this.name;
}
//子构造函数
function Child() {
//调用子构造函数
Parent.call(this);
this.friends = 'child';
}
// 处理原型属性
function clone (Parent, Child) {
// 这里改用 Object.create 就可以减少组合继承中多进行一次构造的过程
Child.prototype = Object.create(Parent.prototype); //相当于组合式继承少调用了一次构造函数
///手动挂载自己的构造器 ,应为上面把自己的构造器覆盖了
Child.prototype.constructor = Child;
}
clone(Parent, Child);
let person = new Child();
console.log(person);
- extends :实际采用寄生组合式继承 ,使用babel进行传化 ,无缺点
防抖节流函数
- 防抖(debounce) : 重复执行 , 执行最后次 (防抖=回城)
- 实例:防止重复请求 ,抽奖系统 ,搜索输入框输入值(提交按钮 , 手机号验证 ,邮箱验证)
function debounce(fn, delay = 2000) {
//保存上一次定时器
let timer = null
//真正的执行函数
const _debounce = (...args) => {
//取消上一次定时器
if (timer) clearTimeout(timer)
//重新定义,延迟执行
timer = setTimeout(() => {
//调用回调事件
fn.apply(this,args)
}, delay)
}
return _debounce
}
//使用
document.getElementById("root").onclick = debounce(() => {
console.log("执行了")
})
- 节流(throttle)函数 :固定频率去执行函数**(节流=技能)**
- 实例 :监听页面滚动 ,鼠标移动事件 ,用户频繁点击 ,
function throttle(fn, delay) {
//第一次执行时间
let previous = 0;
// 使用闭包返回一个函数并且用到闭包函数外面的变量previous
return function () {
let args = arguments;
let now = new Date();
// 当前时间 - 上一次执行时间 大于 间隔时间
if (now - previous > delay) {
fn.apply(this, args);
//保存这一次执行时间 , 下一次执行判断使用
previous = now;
}
}
}
//使用
document.getElementById("root").onclick = throttle(() => { console.log("执行了") }, 3000);
手写深拷贝
-
浅拷贝 : 拷贝对象第一层 , 若第一层属性值为引用类型无法拷贝
Object.assign({},obj)
Array.prototype.slice()
,Array.prototype.concat()
- 拓展运算符{…obj}
-
深拷贝 :深层拷贝
- _.cloneDeep() : const _ = require(‘lodash’); (漏得洗)
- jQuery.extend()
- JSON.stringify() : 会忽略
undefined
、symbol
和函数
、和引用类型数据 - 手写循环递归
function deepClone(obj, hash = new WeakMap()) { if (obj === null) return obj; // 如果是null或者undefined我就不进行拷贝操作 if (obj instanceof Date) return new Date(obj); if (obj instanceof RegExp) return new RegExp(obj); // 可能是对象或者普通的值 如果是函数的话是不需要深拷贝 if (typeof obj !== "object") return obj; // 是对象的话就要进行深拷贝 if (hash.get(obj)) return hash.get(obj); let cloneObj = new obj.constructor(); // 找到的是所属类原型上的constructor,而原型上的 constructor指向的是当前类本身 hash.set(obj, cloneObj); for (let key in obj) { if (obj.hasOwnProperty(key)) { // 实现一个递归拷贝 cloneObj[key] = deepClone(obj[key], hash); } } return cloneObj; }
call apply bind
- 三者都可以改变函数的
this
对象指向 - 三者第一个参数都是
this
要指向的对象,如果如果没有这个参数或参数为undefined
或null
,则默认指向全局window
- 三者都可以传参,但是
apply
是数组,而call
是参数列表,且apply
和call
是一次性传入参数,而bind
可以分为多次传入 bind
是返回绑定this之后的函数,apply
、call
则是立即执行
造成内存泄露的方式
- 意外全局变量
function func(){
a = 1 //意外的全局变量 , 不会销毁
}
- 闭包
- 定时器或者监听函数未取消(定时器 , window)
- 没有清理的dom元素引用(元素被删除,由于一直保留了对这个元素的引用)
为什么需要堆栈
在 JavaScript 中,引擎需要用栈来维护程序执行时的上下文状态(即执行上下文),如果栈空间大了的话,所有数据存放在栈空间中,会影响到上下文切换的效率,从而影响整个程序的执行效率,所以占内存大的数据会放在堆空间中,引用它的地址来表示这个变量。
重绘(重排)与回流
总结: 回流必定会引起重绘,重绘一定不会引起回流。 回流会导致页面重排,影响性能。
回流:dom节点增删 ,元素位置变化 , 尺寸变化 ,窗口大小。(至少一次回流 , 初次渲染)
重绘:影响外观风格改变是重绘 。颜色 ,样式改变。
js线程相关
注意:JavaScript 只在一个线程上运行,不代表 JavaScript 引擎只有一个线程。事实上,JavaScript 引擎有多个线程,单个脚本只能在一个线程上运行(称为主线程),其他线程都是在后台配合。为了利用多核 CPU 的计算能力,HTML5 提出 Web Worker 标准,允许 JavaScript 脚本创建多个线程,但是子线程完全受主线程控制,且不得操作 DOM。所以,这个新标准并没有改变 JavaScript 单线程的本质。
闭包
概念:闭包就是能够读取其他函数内部变量的函数。(一个函数中包含另一个函数)
文章:https://www.jianshu.com/p/9de6f1e8a7fe
闭包形式:
- 1 函数返回值为函数
function a() {
var num = 0;
function b() {
console.log(++num);
};
return b;
};
var c = a();
c() // 1
c() //2 //下次再调用c()的时候num的值在内存中保存,所以每次都是在原有的基础上加1
// 全局变量c获取使用到了局部作用域a的内部变量,这是最常见的形式
#####
function two() {
var a = 1;
return function() {
a++;
console.log(a);
};
};
two()() //2
two()() //2 //每次都是调用two()都会重新执行一次,返回一个新的函数 , 再调用
- 2 内部函数赋给外部变量
let num;
function foo() {
const _num = 18;
function bar() {
return _num;
}
num = bar;
}
foo();
console.log(num()); // 18
- 3 通过立即执行函数行成独立作用域,保存变量(es6之后使用let,const替代)
for (var i = 1; i <= 5; i++) {
setTimeout(function timer() {
console.log(i);
}, i * 1000);
}
// 上述代码的预期输出结果是每隔一秒按顺序输出12345。
// 根据前面讨论过的事件循环机制,定时器任务会在同步任务执行完毕后再执行。因此此时的i已经变成6 会直接输出5个6(为什么是6?因为:循环流程是先定义 -> 加加 -> 判断是否符合 -> ok在执行循环内部)
// 解决这一问题的关键就是每次循环形成一个独立作用域,这样定时器中的操作执行时会访问对应作用域的变量。
#闭包解决
for (var i = 1; i <= 5; i++) {
// 包一层立即执行函数 并传入i 由于内部操作用到了i 因此会形成闭包
(function (i) {
setTimeout(function timer() {
console.log(i);
}, i * 1000);
})(i)
- 一般setTimeout的第一个参数是个函数,但是不能传值。如果想传值进去,可以调用一个函数返回一个内部函数的调用,将内部函数的调用传给setTimeout。
闭包的优缺点
高阶函数
-
接收参数为函数
-
返回值为函数
柯里化(一种特殊的高阶函数)
- 柯里化函数的优势就是参数复用
function sum(a){
return (b)=>{
return c=>{
return a+b+c
}
}
}
调用
sun(1)(2)(3)
使用场景
- 表单数据校验
//未使用柯里化
function check(targetString, reg) {
return reg.test(targetString);
}
check(/^1[34578]\d{9}$/, '14900000088');
check(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/, 'test@163.com');
//使用柯里化
function curring(reg) {
return (str) => {
return reg.test(str);
};
}
var checkPhone = curring(/^1[34578]\d{9}$/);
var checkEmail = curring(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/);
console.log(checkPhone("183888888")); // false
console.log(checkPhone("17654239819")); // true
- 请求工具函数封装(api封装)
- 请求状态码处理
- 函数柯里化求和
function total(...args) {
let sum = args.reduce((total, item) => total += item, 0)
const func = (sum) => total(...[sum, ...args])
func.toString = () => sum.toString()
return func
}
console.log(total(1)(2)(3) + "") // 6
console.log(total(1)(2)(3) + 1) //61
Js垃圾回收机制
概念词:
-
GC : 垃圾回收机制的简写。可以找到内存中的垃圾,并释放和回收垃圾。
-
可达对象 : 可以访问到的对象就是可达对象(可以通过引用、作用域链查找到)
-
垃圾回收方式 : ⑴引用计数 , ⑵标记清除法 ,⑶标记清理法
-
概念 :在JS中,我们创建变量的时候,JS引擎会自动给对象分配对应的内存空间,不需要我们手动分配,当代码执行完毕的时候,JS引擎也会自动地将你的程序,所占用的内存清理掉。
-
引用计数法 : 内部通过对数据的引用计数 , 来判断该数据的引用数是否为 0 ,决定是否为一个垃圾数据 , 如果为 0 ,GC开始工作 ,讲其内存空间进行释放再使用!
- 优点:
- 1 发现垃圾及时回收
- 2 最大程度减少程序暂停(内存爆满的时候会去找那些引用数值为0的对象释放其内存)
- 缺点:
- 1 无法回收循环引用的对象
- 2 维护引用(计数)开销大
- 优点:
-
**标记清除法:**将整个垃圾回收操作分为三个阶段:1 遍历所有的对象找到活动对象 ,进行标记操作 2 遍历所有的对象,找到那些没有标记的对象进行清除 3 回收相应空间
- 优点:
- 可以回收循环引用的对象空间
- 缺点:
- 容易产生碎片化空间(清除的空间不是连续的 , 需要整理合并),浪费空间
- 不能立即回收垃圾对象(回收慢)。
- 优点:
-
标记整理 : 和标记清除一样,在V8中也会被频繁使用:1,标记整理可以看做是标记清除的增强; 2,标记阶段的操作和标记清除一致;3,清除阶段之前会先执行整理,移动对象位置,使得他们在地址上是一个连续的空间
- 优点:
- 减少标记清除碎片空间
- 缺点
- 不会立即回收对象
- 优点:
-
V8对GC的优化 :分代式回收
- 新生代垃圾回收 : 临时分配的内存,存活时间短 (并行回收)
- 老生代垃圾回收 :回收是常驻内存,存活时间长 (增量标记与惰性回收)
注意:闭包不是内存泄露,闭包的数据是不可以被回收的
内存泄露场景
- dom监听事件 , 组件销毁未移除
- 组件定时器设置 , 组件销毁未移除
扩展:weakmap
和 weakset
都是弱引用,不会阻止垃圾回收机制回收对象。
const map = new Map()
function fn1() {
const obj = { x: 100 }
map.set('a', obj) // fn1执行完 map还引用着obj
}
fn1()
const wMap = new WeaMap() // 弱引用
function fn1() {
const obj = { x: 100 }
// fn1执行完 obj会被清理掉
wMap.set(obj, 100) // weakMap 的 key 只能是引用类型,字符串数字都不行
}
fn1()
const let var
①重复声明 ②变量提升 ③暂时性死区 ④块级作用域 ⑤window对象的属性和方法(全局作用域中)
- var允许重复声明,let、const不允许
- var有变量提升 , let 、 const 没有
- var没有暂时性死区 , let const 有
let a = 1
let b = 2
function func(){
console.log(b)
console.log(a)
let a = 3
}
func()
//1 和 Cannot access 'a' before initialization
- 全局作用域中,var声明的变量和function声明的函数,会自动变为window对象属性或方法,但const和let不会
let a = 1
console.log(a === window.a)
const b = 1
console.log(b === window.b)
var c = 1
console.log(c === window.c)
- var没有块级作用域,let和const有块级作用域
//let
for(let i = 1 ;i<4;i++){
console.log(i)
}
//1,2,3
console.log(i) //i 没有定义
//var
for(var i = 1 ;i<4;i++){
console.log(i)
}
//1,2,3
console.log(i) //4
模块化
- 解决命名冲突
- 提供复用性
- 提高代码可维护性
模块化进化路线
- 1 - 立即执行函数 (解决了命名冲突、污染全局作用域的问题)
- 2 - AMD 和CMD
// AMD
define(['./a', './b'], function(a, b) {
// 加载模块完毕可以使用
a.do()
b.do()
})
// CMD
define(function(require, exports, module) {
// 加载模块
// 可以把 require 写在函数体的任意地方实现延迟加载
var a = require('./a')
a.doSomething()
})
- CommonJS (最早在node环境使用 ,目前使用广泛 ,webpack(工程化)中能见到他)
// a.js文件
module.exports = {
a: 1
}
//或
exports.a = 1
// b.js文件
var module = require('./a.js')
module.a // -> log 1
- ES Module (会编译成
require/exports
来执行的)
Vue篇-------
为什么Vue3 比vue2快
proxy
响应式:深度监听,性能更好(获取到哪一层才触发响应式get
,不是一次性递归)PatchFlag
动态节点标记 (只比较动态节点)- 将静态节点,提升定义到父作用域,缓存起来。[空间换时间]
CacheHandler
事件缓存(把节点的时间缓存到一个数组)Tree-shaking
根据模板的内容动态import
不同的内容,不需要就不import
深入Vue(VM与VC)
理解vm与vc
-
Vue.extend({}),创建一个组件,组件的本质是一个VueComponent构造函数。
-
cosnt s = Vue.extend({options})可以简写为const s = {options}。简写也调用了Vue.extend。
-
写组件标签时,就会创建school组件实例对象,相当于new VueComponent({options})。(解析标签是调用)
-
每次调用Vue.extend,返回的都是一个新的VueComponent。内部sub变量接收VueComponent构造函数,返回sub。
//Vue.extend源码 Vue.extend = function (extendOptions: Object): Function { // 定义子构造函数 Sub const Sub = function VueComponent (options) { this._init(options) } //........... return Sub }
-
vc是可复用的vm。el是vm特有的。
-
VueComponent.prototype.proto === Vue.prototype所以vc能够访问的Vue原型上的方法。
代码理解
<body>
<div id="root"></div>
<script>
// 可以省略 Vue.extend() , 应为当模版解析vue标签的时候,会自动调用Vue.extend生成组件的构造函数并创建组件对象VC
var Btn = {
template: '<p>{{name}}</p>',
data: function () {
return {
name: "我是btn组件"
}
}
}
// 创建一个构造函数 ,用该构造函数创建VC(组件对象)
var App = Vue.extend({
template: '<div><h1>{{title}}</h1><p @click="handle">{{num}}</p><btn/></div>',
components: { btn: Btn },
data: function () {
return {
title: "app容器组件",
num: 10
}
},
methods: {
handle() {
this.num++
}
}
})
//创建vm实例对象
const vm = new Vue({
// 写法一
el: "#root",
// data: {
// age: 999,
// name: "李四"
// },
// template: '<div><btn/></div>',
// methods: {
// handle() {
// this.age++
// }
// },
// components: {
// Btn: Btn
// }
render: h => h(App)
})
//写法二
vm.$mount("#root")
</script>
</body>
react篇-----
fiber
- 组件树转为链表,可分段渲染
- 渲染时可以暂停,去执行其他高优先级任务,空闲时在继续渲染(
JS
是单线程的,JS
执行的时候没法去DOM
渲染) - 如何判断空闲?
requestIdleCallback
react setState优化
function handleClick() {
setAge(age + 1); // setAge(42 + 1)
setAge(age + 1); // setAge(42 + 1)
setAge(age + 1); // setAge(42 + 1)
}
//与
function handleClick() {
setAge(a => a + 1); // setAge(42 => 43)
setAge(a => a + 1); // setAge(43 => 44)
setAge(a => a + 1); // setAge(44 => 45)
}
a => a + 1
是你的更新函数。它接收一个待改变的 state,然后在函数体计算返回下一个 state。
React 把更新函数放入一个队列中。然后在下一次 render 时,更新函数将会以相同的顺序被调用。
理解 useRef、useMemo、useCallback
useRef 可用来存储一个引用值(不会受 re-render 影响),也可用来获取 dom 节点。
useMemo 用来缓存一个值。当依赖项为空数组时,缓存值永远不会变。当有依赖性时,每次 re-render 如果依赖改变,那么将重新执行函数,将新的函数返回值作为缓存的数据。
useCallback 是 useMemo 的语法糖,相当于返回一个函数。
const fn1 = useCallback(() => {
console.log(123);
}, [])
const fn2 = useMemo(() => () => {
console.log(123);
}, [])
使用memo注意
- 传递给子组件的函数 和 数据 分别使用useCallback 和useMemo包裹一下 , 应为会重新定义 , 地址改变 , 子组件也会重新刷新。
业务篇-------
代码codeView
代码标准
- 注释类
- 公共函数、样式加上注释
- 敏感注释处理
- 没用到的方式和生命周期, 删掉
- 测试代码,旧代码
- 核心业务描述等
- 风格类
- 如果特殊情况. 避免使用行类样式.
- 遵循基本标签嵌套规则
- css使用sass
- js代码
- 方法使用 ,比如用map必须要返回值
- 必要时使用模块化. 独立组件, 减少单文件代码量
- git hooks – pre commit
- 新项目统一使用ESLint作为代码规范检测工具,每次Git commit会判断代码是否符合规范,只有符合规范的才能被提交
切片上传+断点续传
- 获取上传文件
- 文件切片后存入数组 fileChunkList.push({ file: file.slice(cur, cur + size) });
- [断点续传] 生成文件hash [“文件hash值+当前切片索引值"] + 数据库判断已经上传的切片
- 根据文件切片列表生成请求列表
- 并发请求
- 待全部请求完成后发送合并请求
项目优化方案
-
路由懒加载:原理ES6的动态模块加载 ,通过路由懒加载 ,项目首页资源大幅度压缩 , 打开效率更高。
-
组件懒加载: 比如弹窗组件。react的和路由懒加载一样 ,使用组件时需要使用Suspense包裹。
-
异步组件
-
合理使用treeShaking : 原理 基于ES6的模块化特性 ,分析导入import ,只对export导出的变量生效, 无法通过静态分析判断一个变量是否被使用。
-
骨架屏:缩短白屏时间 。
-
长列表虚拟滚动。
-
WebWork优化长js任务 ,或者处理耗时计算任务
-
js的加载方式
- async(js资源与Dom没有依赖关系 ,如埋点)
- defer(js资源有依赖关系 ,加载完执行也会有先后顺序)
- Type = module, Vite就是利用浏览器支持原生es module ,开发时可以跳过打包 ,直接启动服务,编译代码
- link preload :渲染前加载 ,不会阻塞渲染。
- Link prefetch : 空闲是加载并缓存,将来使用。
-
图片的优化:
- 图片动态裁剪 :使用阿里云或七牛云 ,图片url后面添加参数
- 图片懒加载:1 、 先使用data-xxx属性绑定图片链接 2 、判断图片在屏幕可视区域内在加载
- 使用svg效果好 , 可以修改颜色样式等
- 使用小图使用base64 (图片转base64会变大 ,与浏览器请求次数来说,更有优势),写入html 减少http请求
-
打包开启gzip
-
打包代码分割(Code Splitting) + 配和异步组件实现
-
1、项目包含第三方依赖库以及自己写的代码,打包出的文件会比较大,在用户访问系统的时候,由于请求的资源比较大,所以会响应的比较慢,造成页面渲染缓慢,影响用户体验。
代码分割以后,chunk会相应的变小,用户访问时,只需返回此页面相关chunk(块的意思),再加上浏览器的并行请求策略,从而加快系统响应速度,优化用户体验。
2、由于将第三方依赖库和自己写的代码打包到了一起,一旦我们修改了其中的一部分代码,整个chunk(包括第三方依赖库,即使它没有发生任何变化)都会重新打包,生成带有新的hash值的chunk。原本在浏览器已经缓存了的chunk,会因为hash的变更,不会再使用之前缓存的chunk,而重新请求新的chunk。简言之,一处代码变更,要重新请求所有资源,缓存效果差。
代码分割以后,就可以将比较大的第三方依赖库分离到单独的chunk中,以后即使用户修改业务代码,只要不改变此库的版本或库代码,在浏览器端的此chunk的缓存会一直有效。剩余的比较小的第三方依赖库打包到同一个chunk中,将依赖库与业务代码彻底分离。由于更改依赖库的版本的概率比较小,所以可以有效利用缓存,提升响应速度。
-
使用插件 : SplitChunksPlugin
-
-
缓存组件:vue keepalive 、 react使用import {KeepAlive} from ‘react-activation’
vdom和diff算法
-
vdom
的核心概念很重要:h
、vnode
、patch
、diff
、key
-
vdom
存在的价值更重要,数据驱动视图,控制dom
操作 -
diff算法:新旧
vnode
对比,得出最小的更新范围,最后更新DOM。
浏览器篇—
浏览器存储
cookie : 一般由服务器生成 , 可以设置有效时间 ,大小4kb ,header协大 , 参与请求,不建议用来作为存储
localStorage : 不清理一直存在 , 大小5M
sessionStorage :页面关闭清理 , 大小5M
indexDB :
- 浏览器提供的本地数据库
- 属于非关系型数据库(键值对形式)
- 存储空间大(无限)
- 同源限制:每一个数据库对应创建它的域名。
- 异步(不会锁死浏览器)
- 支持事务(一系列操作步骤之中,只要有一步失败,整个事务就都取消,数据库回滚到事务发生之前的状态,不存在只改写一部分数据的情况)
cookie的安全性
属性 | 作用 |
---|---|
value | 如果用于保存用户登录态,应该将该值加密,不能使用明文的用户标识 |
http-only | 不能通过 JS 访问 Cookie ,减少 XSS 攻击 |
secure | 只能在协议为 HTTPS 的请求中携带 |
same-site | 规定浏览器不能在跨域请求中携带 Cookie ,减少 CSRF 攻击 |
Service Worker 也可以实现缓存
W3C 组织早在 2014 年 5 月就提出过 Service Worker 这样的一个 HTML5 API ,主要用来做持久的离线缓存。service worker是浏览器的一个高级特性,本质是一个web worker,是独立于网页运行的脚本
。 web worker这个api被造出来时,就是为了解放主线程
。因为,浏览器中的JavaScript都是运行在单一个线程上,随着web业务变得越来越复杂,js中耗时间、耗资源的运算过程则会导致各种程度的性能问题。 而web worker由于独立于主线程,则可以将一些复杂的逻辑交由它来去做,完成后再通过postMessage的方法告诉主线程。 service worker则是web worker的升级版本,相较于后者,前者拥有了持久离线缓存的能力。
Service Worker的特点
- 浏览器背后的独立线程、在后台运行的脚本
- 被install后就永远存在,除非被手动卸载
- 必须是https的协议才能使用。不过在本地调试时,在
http://localhost
和http://127.0.0.1
下也是可以跑起来的。 - 不能直接操纵dom:因为sw是个独立于网页运行的脚本。
- 可拦截请求和返回,缓存文件。sw可以通过fetch这个api,来拦截网络和处理网络请求,再配合cacheStorage来实现web页面的缓存管理以及与前端postMessage通信。
浏览器缓存机制
url:https://blog.csdn.net/qq_46658751/article/details/123433335
Service Worker
: 当Service Worker
没有命中缓存的时候,我们需要去调用fetch
函数获取数据Memory Cache
: 内存缓存 :主要包含的是当前页面中已经抓取到的资源,例如页面上已经下载的样式、脚本、图片等,打开页面以后,再次刷新页面,可以发现很多数据都来自于内存缓存。Disk Cache
: 磁盘缓存Push Cache
(推送缓存) :Push Cache
是HTTP/2
中的内容,当以上三种缓存都没有命中时,它才会被使用。并且缓存时间也很短暂,只在会话(Session
)中存在,一旦会话结束就被释放。- 网络请求 :如果所有缓存都没有命中的话,那么只能发起请求来获取资源了。
- 强缓存 :不会向服务器发送请求,直接从缓存中读取资源,在chrome控制台的Network选项中可以看到该请求返回200的状态码,并且Size显示from disk cache或from memory cache。
- 设置header :
- Expires ( HTTP/1) :缓存过期时间(是一个时间,存在客户端与服务器时间备不一致情况) , Last-modified结合使用。(判断该时间内 , 无需要发起请求)
- Cache-Control(HTTP/1.1 ,优先级高)
public
:表示响应可以被客户端和代理服务器缓存。private
: 表示响应只可以被客户端缓存。max-age=xxx
:缓存xxx秒就过期,需要重新请求(是一个时间段 , 比Expires好)。
- 协商缓存
- 强缓存 :不会向服务器发送请求,直接从缓存中读取资源,在chrome控制台的Network选项中可以看到该请求返回200的状态码,并且Size显示from disk cache或from memory cache。
网络请求缓存判断流程:
发送一次网络请求
- 判断不存在该缓存结果和缓存标识,则无缓存,直接向服务器发起一次请求 ,并缓存结果数据和标识(last-Modified或eTag)
- 存在该缓存结果和缓存标识,且该结果没有还没有失效,强制缓存生效,直接返回缓存结果 。
- 存在该缓存结果和缓存标识,但是结果已经失效,强制缓存失效,则使用协商缓存
- 强制缓存失效后开启协商缓存 , 浏览器携带缓存标识向服务器发起请求 ,服务器根据标识判断客户端的缓存当前是否还可以用!
- 如果还可以用 ,协商缓存生效,返回 304 状态码 和 not Modified , 浏览器依旧获取浏览器缓存结果
- 如果不可以用 , 协商缓存失效,返回请求结果 和 缓存规则及标识 , 浏览器直接获取请求结果 , 并缓存结果和标识。
- 怎么判断协商缓存失效 , 两种方式
- Last-Modified(第一次请求,服务端会给浏览器端缓存): 下次请求浏览器检测到有 Last-Modified 字段 , 把这个字段放在if-Modified-Since ,去服务端请求 , 服务端判断请求数据是否还生效。(有弊端 :没有对文件进行修改,可能还是会造成 Last-Modified 被修改)
- **Etag **: 服务端生成的hash值判断 , 下次请求把这个hash值放到 If-None-Match字段里 , 服务端判断当前hash是否一致 ,然后确定协商缓存是否失效 , 这种方式更加准确,但是性能要差点(因为取hash).
实际场景应用缓存策略:
- 频繁变动的资源::Cache-Control:no-cache 每次都请求 。
- **不长变动的资源( jquery-3.3.1.min.js, lodash.min.js ,图片,css):**Cache-Control: max-age=31536000(一年)
Http1/http1.1/http2.0+HTTPS
目前主要:HTTP1.1 是当前使用最为广泛的HTTP协议
http1
- 只有GET, POST 和 HEAD
- 缓存处理 :if-Modified-since + expires缓存判断标准
- 网络优化:HTTP1.0中,浏览器与服务器只保持短暂的连接,浏览器的每次请求都需要与服务器建立一个TCP连接,存在一些浪费带宽的现象,(每次发送完整请求数据)不支持断点续传 。
http1.1
- 新增OPTIONS、PUT、PATCH、DELETE、TRACE 和 CONNECT
- 缓存处理 :Entity tag,If-Unmodified-Since, If-Match, If-None-Match等更多可供选择的缓存头来控制缓存策略(多个缓存策略,最常用Etag)
- 网络优化:请求头引入了range头域,它允许只请求资源的某个部分。
- 错误状态码新增。
- 长连接 :HTTP 1.1支持长连接(PersistentConnection)和请求的流水线(Pipelining)处理。一个TCP连接上可以传送多个HTTP请求和响应,减少了建立和关闭连接的消耗和延迟,在HTTP1.1中默认开启Connection: keep-alive,一定程度上弥补了HTTP1.0每次请求都要创建新连接的缺点。通过设置http的请求头和响应头,保证本次数据请求结束之后,下一次请求仍可以重用这一通道,避免重新握手。
http2.0
- 采用二进制格式解析 :HTTP1.x的解析是基于文本(超文本传输协议)。基于文本协议的格式解析存在天然缺陷。
- 完全多路复用,而非有序并阻塞的、只需一个连接即可实现并行请求。
- 使用报头(header)压缩,通讯双方各自缓存一份header fields表,既避免了重复header的传输,又减小了需要传输的大小。
- 服务器推送 (一个通道持续向客户端发送数据资料)(实际中使用
websocket
)
https
-
在传输之前客户端(浏览器)与服务端(网站)之间进行一次握手,在握手过程中将确立双方加密传输数据的密码信息。
-
Http运行在TCP之上,所有传输的内容都是明文,HTTPS运行在SSL/TLS(使用了非对称加密,对称加密以及HASH算法)之上,SSL/TLS运行在TCP之上,所有传输的内容都经过加密的(防劫持)。
-
端口号不同:http 80 , https 443
三次握手
TCP(传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层(http是应用层)通信协议,采用三次握手建立连接,四次挥手终止连接。
HTTP (超文本传输协议)WWW标准 ,建立在TCP协议上的一种应用。
主要是浏览器与服务器建立连接 ,相互之间发送报文通信的过程 ,主要目的就是确认相互间能否接受到手机并确认对方接受的数据。
重复总结
- HTTP1.0
- 最基础的
HTTP
协议 - 支持基本的
GET
、POST
方法
- 最基础的
- HTTP1.1
- 缓存策略
cache-control
E-tag
- 支持长链接
Connection:keep-alive
一次TCP
连接多次请求 - 断点续传,状态码
206
- 支持新的方法
PUT DELETE
等,可用于Restful API
写法
- 缓存策略
- HTTP2.0
- 可压缩
header
,减少体积 - 多路复用,一次
TCP
连接中可以多个HTTP
并行请求 - 服务端推送(实际中使用
websocket
)
- 可压缩
连环问:HTTP协议和UDP协议有什么区别
HTTP
是应用层,TCP
、UDP
是传输层TCP
有连接(三次握手),有断开(四次挥手),传输稳定UDP
无连接,无断开不稳定传输,但效率高。如视频会议、语音通话
浏览器原理(加载一个页面流程)
资料相关
blibli
1、思学堂:https://space.bilibili.com/695517853
CSDN
1、前端啊
https://zhuanlan.zhihu.com/p/574913236 知乎
https://blog.csdn.net/weixin_61029090/article/details/130187720 面试题总结2 还可以