文章目录
前言
整理了我的前端路程,哈哈哈。一系列面试相关或者技术记录的文档1. 一些面试题
1.1 seo搜索引擎优化
语义化书写html代码
少使用iframe(搜索引擎抓取不到)
为图片加上alt属性
页面尽量不要做flash,视频
1.2 网络相关
1.2.1 同一域名资源浏览器访问,最多6-8个解决方案
- 域名分区:
将资源分散到不同的域名下(如使用二级子域名),但是会增加DNS域名解析时长 - Keep-Alive机制:
保持TCP连接不立即关闭,可以在一个连接上发送多个请求 - 通过优化前端性能优化减少请求
懒加载,cdn等
1.2.2 性能优化
- 减少htttp请求
- 服务器缓存(强缓存,协商缓存)
- 压缩格式化代码(减小请求体积)
- 把css放在顶部,js放在底部(不让js阻塞ui渲染)
- 资源外链(利于维护和缓存机制)
- 利用cdn
cdn一种虚拟网络,通过全国各地部署边缘服务器,利用中心平台的负载均衡,内容分发和调度。让用户可以访问距离他们最近的CDN节点获取所需内容。降低了网络阻塞,提高了网络速度和缓存命中率 - 若还考虑seo,使用ssr服务端渲染
1.2.3 图片优化
- img设置alt,提升用户体验
- 避免src为空
- 图片懒加载
- 减小图片尺寸
1.3 线程和进程,异步编程
一个程序一定会开启一个进程
多个进程之间存储空间独立,线程是进程的基本执行单位,任务运行必须依赖与线程,同进程下的线程共享地址空间
例子:浏览器的一个窗口就是一个进程,浏览器会包含一个主进程
每个进程下面包含多个线程,其中主要包含GUI渲染线程和Js引擎线程,js是单线程,因为如果是多线程就会有如同时操作dom,锁逻辑之类的。但是js可以通过事件循环机制执行异步编程。
当Gui渲染线程碰到js线程的时候,会把GUI渲染线程挂起,不然当渲染线程渲染后在处理js就可能导致不一致。这也是为什么js会阻塞html解析的原因,也是为什么要把js代码放到body底部的原因
1.3.1同步任务,异步任务
- 同步任务:自上向下运行,依赖于上一行执行结果。主线程首先执行同步代码
- 异步任务:执行时不会等待上一行代码完成,而是将这部分代码放入异步队列,继续执行后续的代码。
1.3.3 宏任务,微任务
异步任务分了宏任务和微任务,微任务先执行,宏任务后执行
- 宏任务:setTimeOut,setInterval,requestAnimationFrame,addEventLister
- 微任务:promise回调
1.3.2 执行栈,任务队列,事件循环机制
- 执行栈:先进后出
比如多个函数嵌套使用a {b {c {,那么栈中顺序a在栈底,c在栈顶,当c执行完,弹出c。最后a执行完弹出a - 任务队列(消息队列):先进先出
最先添加最先执行,任务队列就是用来存放异步方法的,分为了微任务队列和宏任务队列。 - 事件循环机制(Event Loop):
当主程序的执行栈(同步任务)执行完清空后。先去查询主程序执行(一个宏任务的调用)产生的微任务,执行微任务队列内的所有任务。执行后再去查询最先添加到宏任务队列里面的任务,将宏任务添加到执行栈中执行,执行完毕后立即去执行产生的微任务队列,微任务队列清空后再执行下一个宏任务。如此循环直到两个任务队列清空。
1.4 输入url到渲染界面过程
- 浏览器接受到url后,开启一个进程
- 提取url中协议,地址域名等,浏览器向dns服务器发送请求,dns进行解析。找到对应服务器ip地址
- 浏览器与服务器创建tcp三次握手协议
- 浏览器发送http请求,包含请求头等
- 服务器接受并返回,包括响应数据状态码等
- 浏览器分析状态码等,如果成功开始html解析(执行下述2.1流程)
- 链接结束,断开tcp链接
1.5 回收机制垃圾
垃圾回收机制是js管理内存的一个过程,当一个变量赋值给一个对象的时候就会被标记引用,当赋值为null或者在作用域通过其他方式删除后,之前的引用计数就会被删除,当引用计数为的时候就会被回收。
常见导致内存泄漏的类型:
- 全局对象不会被回收(var定义的变量
- 闭包导致的(变量引用不会被回收
1.6 单页面spa与多页面mpa区别
- 单页面是一个html,页面的跳转实际上是局部资源的刷新,不是整个界面的刷新。因为由js动态生成,不利于seo搜索殷勤优化
- 多页面是多个独立的页面,每个页面需要加载完整的html,css,js资源。页面跳转的时候都会重新加载,会导致白屏现象
1.7 首页白屏优化
- 按需加载,不要把加载的模块全部放到main.js
- 压缩图片来减小图像文件的大小
- 减少接口请求,压缩分割代码
- 使用cdn引入第三方插件
- 添加骨架屏和loading
1.8 大文件上传,上万条数据
- 大数据操作使用web Worker
- 大文件上传:使用切片方式.slice
import axios from 'axios';
methods: {
async uploadFile(file, uploadUrl, chunkSize = 1024 * 1024) {
const totalChunks = Math.ceil(file.size / chunkSize);
let uploadedChunks = 0;
// 创建分块上传的promise数组
const promises = [];
while (uploadedChunks < totalChunks) {
const chunk = file.slice(
uploadedChunks * chunkSize,
Math.min(file.size, (uploadedChunks + 1) * chunkSize)
);
const formData = new FormData();
formData.append('file', chunk);
formData.append('chunkIndex', uploadedChunks);
formData.append('totalChunks', totalChunks);
// 将每个分块的上传操作加入到promises数组中
promises.push(
axios.post(uploadUrl, formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
})
);
uploadedChunks++;
}
try {
// 并行上传所有分块
await Promise.all(promises);
console.log('所有分块上传成功');
} catch (error) {
console.error('上传失败', error);
}
}
}
1.9 图片懒加载(进入可视区域时再加载)
等到浏览器视图到图片区域的时候在设置src,而不是一开始设置页面加载的时候就导入所有图片。
大多数的原理是:当图片到浏览器窗口的距离,小于浏览器的高度 window.innerHeight就证明图片到可视区域了。
- 浏览器本身支持图片懒加载
<img src='xx.jpg' loading="lazy"/>
- 插件:vue-lazyload
- element的el-image:自带了懒加载属性lazy
1.10 前端安全相关
前端大多数情况能做的是数据传输加密,登录相关就是token鉴权:
//base64加密
const Base64 = require('js-base64').Base64
//rsa加密
import JsEncrypt from 'jsencrypt/bin/jsencrypt'
//md5加密
import md5 from 'js-md5'
Vue.prototype.$base64 = Base64
Vue.prototype.$jsEncrypt = JsEncrypt
Vue.prototype.$md5 = md5
1.11 浏览器兼容问题
因为浏览器内核不一样,会导致相同的一段代码在不同浏览器中呈现不同的页面效果。一般在样式层面和js渲染层面。
js相关:
- babel插件处理es6转es5
- 取消冒泡,默认等方法
// 取消冒泡 if (e.stopPropagation) { e.stopPropagation() } else { e.cancelBubble = true }
css相关:
- 引入重置默认样式插件:ormalize.css
- 引入添加默认前缀插件:postcss
ie浏览器的一些兼容问题:
- IE下event对象有x, y属性但没有pageX, pageY属性,而Firefox有pageX, pageY属性但没有x, y属性
- IE9以下版本不支持opacity属性,这需要在编写CSS时考虑到不同浏览器的支持情况
- 在访问frame对象时,IE和Firefox有不同的访问方式。例如,IE可以使用window.frameId或window.frameName来访问frame对象,而Firefox只能使用window.frameName。同时,两者都可以使用window.document.getElementById(“frameId”)
1.12 高阶函数,函数的柯里化
1.12.1 高阶函数
如果一个函数满足下面2个情况之一,那么该函数就是高阶函数
- 若函数a,接受的参数是一个函数
- 若函数a,调用的返回值是一个函数
常见的高阶函数:
- promise
- settimeout
- arr.map
1.12.2 高阶函数函数的柯里化
通过函数调用继续返回函数的方式,实现多次接受参数最后统一处理的函数编码形式
function sum(a){
return (b)=>{
return (c)=>{
return a+b+c
}
}
}
sum(1)(2)(3)
2.HTML
2.1 浏览器加载页面的顺序
- 获取html,解析成dom树
- 解析css,生成cssom树。来源包含:浏览器样式,通过link或@import引入外部CSS,style标签内联样式
- 解析js(defer和async可以改变执行时间,defer是文档加载解析完后执行,async是文件加载后立即执行)
- 结合dom树和cssom树生成布局树
- 布局和绘制(重排,重绘),布局过程涉及到分层
2.2 html5常见新特性
2.3 画布,水印
/**
* 设置水印
* @param width 画布宽度
* @param height 画布高度
* @param fillStyle 文字颜色
* @param font 字体样式
* @param insertDom 插入dom
* @param ZIndex 层级
* @param className 添加节点的class
* @param args 显示文本(多行)
*/
function set(
{
fillStyle = 'rgba(0, 0, 0, 0.1)',
font = 'normal 14px Microsoft YaHei',
insertDom = document.body,
ZIndex = 9996,
className = 'watermark',
rotate = 15
} = {}, ...args
) {
const canvas = document.createElement('canvas')
let lensArr = [...args].map(item => (item.length))
let sortArr = lensArr.sort((a, b) => (b - a))
let max = sortArr[0]
canvas.width = max * 14 + 164
canvas.height = rotate === 0 ? 126 : max * 14 * Math.sin(rotate * Math.PI / 180) + 112
canvas.style.display = 'none'
const context = canvas.getContext('2d')
// 控制文字的旋转角度和上下位置
context.rotate(-rotate * Math.PI / 180)
// context.translate(0, 0)
// 文字颜色
context.fillStyle = fillStyle
// 文字样式
context.font = font
args.forEach((text, index) => {
context.fillText(text, 0, index === 0 ? (canvas.height - 12) : (canvas.height + 40 * index), canvas.width - 100)
})
// 新建一个用于填充canvas水印的div标签,考虑到z-index对个别内容影响,故而不用body
const waterMark = document.createElement('div')
const styleStr = `left: 0px; top: 0px; width: 100%; height: 100%; position: absolute; z-index: ${ZIndex}; pointer-events: none; background-image: url(${canvas.toDataURL('image/png')}); background-repeat: repeat; mix-blend-mode: multiply;`
waterMark.setAttribute('style', styleStr)
waterMark.classList.add(`${className}`)
insertDom.appendChild(waterMark)
}
/**
* 关闭页面的水印,即要移除水印标签
*/
function close(insertDom = document.body, className = 'watermark') {
const waterMark = document.querySelector(`.${className}`)
// deleteFlag = true
insertDom.removeChild(waterMark)
}
const watermark = {
set,
close
}
export default watermark
2.4 拖拽
元素可拖拽:draggable="true"
开始拖拽事件:ondragstart=drag(event)
设置被拖数据的数据类型和值:dataTransfer.setData("type",data)
接收拖拽元素:ondragover="allowDrop(event)"
,方法中设置:event.preventDefault()
放置元素后:ondrop="drop(event)"
<body>
//接收拖拽的dom`在这里插入代码片`
<div id="div1" ondrop="drop(event)" ondragover="allowDrop(event)"></div>
//可以拖动的dom
<img id="drag1" src="/images/logo.png" draggable="true" ondragstart="drag(event)" width="336" height="69">
<script>
//开始拖拽
function drag(event){
event.dataTransfer.setData("Text",event.targrt.id)
};
//允许接收拖拽元素
function allowDrop(event){
event.preventDefault()
};
//拖拽后
function drop(event){
event.preventDefault();
var data=event.dataTransfer.getData("Text");
event.target.appendChild(document.getElementById(data));
}
</script>
</body>
2.5 地理位置
必须客户同意。
var x=document.getElementById("demo");
function getLocation(){
if (navigator.geolocation){
navigator.geolocation.getCurrentPosition(showPosition);
}else{
x.innerHTML="该浏览器不支持获取地理位置。";
}
}
function showPosition(position){
x.innerHTML="纬度: " + position.coords.latitude +
"<br>经度: " + position.coords.longitude;
}
- 音频,视频,插件
浏览器将使用第一个 可识别的格式:
<video width="320" height="240" controls>
<source src="movie.mp4" type="video/mp4">
<source src="movie.ogg" type="video/ogg">
您的浏览器不支持Video标签。
</video>
<audio controls>
<source src="horse.ogg" type="audio/ogg">
<source src="horse.mp3" type="audio/mpeg">
您的浏览器不支持 audio 元素。
</audio>
//<object> 元素定义了在 HTML 文档中嵌入的对象。
<object width="400" height="50" data="bookmark.swf"></object>
<object width="100%" height="500px" data="snippet.html"></object>
<object data="audi.jpeg"></object>
//<embed> 元素表示一个 HTML Embed 对象 。
<embed width="400" height="50" src="bookmark.swf">
<embed width="100%" height="500px" src="snippet.html">
3.CSS复习+Scss
3.1 link和import区别
link:属于html标签,在界面加载时会同步加载
import:属于css语法,界面加载后在加载。会导致样式闪烁
3.2 动画 ( @keyframes )
//设置动画名
div{
animation: myfirst 5s;
-moz-animation: myfirst 5s; /* Firefox */
-webkit-animation: myfirst 5s; /* Safari 和 Chrome */
-o-animation: myfirst 5s; /* Opera */
}
//设置动画效果
@keyframes myfirst{//可用半分比
from {background: red;}
to {background: yellow;}
}
@-moz-keyframes myfirst /* Firefox */{
from {background: red;}
to {background: yellow;}
}
@-webkit-keyframes myfirst /* Safari 和 Chrome */{
from {background: red;}
to {background: yellow;}
}
@-o-keyframes myfirst /* Opera */{
from {background: red;}
to {background: yellow;}
}
3.3 scss
4.js 复习
4.1 类型判断,转换
4.1.1 typeof
4.1.2 instanceof
4.1.3 Object.prototype.toString.call
4.1.4 转换成布尔Boolean
4.1.5 转换成数值Number
4.1.6 转换成字符串String
4.1.7 判断是不是数组,对象和类数组转换成数组
4.2 数字的相关方法
value.parseInt
valu.pareFloat
Math.PI
Math.ceil
Math.floor
Math.round
Math.random
- i++,++i计算:
变量后写++:计算后,变量本身在+
变量前写++:变量本身先加1,在计算
4.3 字符串转换比大小
字符串和字符串比较,会转换成编码比较
"23"<"3" // true
"23"<3 // false
运算符优先级:
1.括号()
2.一元运算符号 ++,–,!
3.乘除取余*,/,%
4.加减法 +,-
5.移位 << >>
6.关系 > < >= <=
7.相等 == !=
8.且 &&
9.或 ||
10.条件运算符?
11.赋值+=,=等
4.4 false,null,undifined与0的关系
console.log(false == 0); // true
console.log('' == 0); // true
console.log(0==undefined); // false
console.log(0== null); // false
console.log(null == undefined); // true
console.log(null === undefined); // false
4.5 数组常见方法,去重,解构
concat
pop
push
shift
unshift
reduce
sort
map
filter
foreach
some
every
- 数组去重
Array.from(new Set([1,2,3,4,3,4,5,6]))
- 数组最大最小值
Math.max(...[-10,-1,0,1,2,'100'])
Math.min(...['-10',-1,0,1,2,100])
- 数组解构
es6新增flat方法
4.6 堆和栈, 深拷贝浅拷贝
栈:操作系统自行分布,先进先出
基础数据类型,存放在栈内存中,占据固定大小的空间。内存地址大小的固定的
堆:程序手动分布,先进后出
引用数据类型的指针存在栈中,对象存在堆中
深拷贝:
赋值一份对象值存储到堆内存内,不是只赋值指针
4.7 作用域,this,闭包
作用域分为:全局作用域window,函数作用域,块级作用域(es6新增)
- 作用域链:保证对执行环境有权访问的所有变量和函数的有序访问
- 全局执行环境是window对象,每个函数都有自己的执行环境
- 内部环境可以通过作用域链访问所有的外部环境,但是外部环境不能访问内部环境的任何变量和函数
- 对象不构成单独的作用域
闭包:一个函数内部创建另外一个函数(内部函数使用外部函数活动对象,所以外部函数执行完后,作用域链被销毁,但是活动对象还在)
function out(a){
var b = 1
return function(c, d){
var e = a+b+c+d
console.log(e)
return {
b,
e
}
}
}
out(1)(1,1)
// 定义一个out函数,会创建它的执行环境。
// 调用函数后会创建它的活动对象,活动对象包含arguments,a为1,b为1
// 函数执行中,需要作用域链查找变量,此时作用域链第一位是out的活动对象,第二位是全局执行环境的活动对象
// 匿名函数返回后,会创建匿名函数的执行环境
// 调用函数后会创建它的活动对象,活动对象包含arguments,c为1,d为1,e为4
// 此时作用域链第一位是匿名函数的活动对象,第二位是out函数的活动对象,第三位是全局执行环境的活动对象
// 当out执行后,它的作用域链被销毁,但是它的活动对象没被销毁,因为匿名函数的作用域链任然在引用out的活动对象
this指向问题:this对象是在运行时基于函数执行环境定的
- 在全局函数中,this指向window
- 当函数作为对象方法被调用时,指向该对象
- 匿名函数执行环境具有全局性,this指向window
- new构造函数的this指向实例对象
- setTimeout普通函数写法指向window
- 箭头函数()=>指向定义时上层作用域中的this
4.8 面向对象相关
4.8.1 函数定义方式
1.函数声明式(会有函数提升)
function name(){}
2.函数表达式(匿名函数赋值给变量)
const name = function(){}
4.8.2 函数的四个内置属性,call,apply,bind
prototype,this,arguments,length
在特定的作用域下调用函数
var color = "bule"
function func(){
console.log(this.color)
}
var obj = {
color:"red"
}
// apply :第二个参数是参数数组
func.apply(obj,[parm1,parm2])
// call:参数要逐个列出
func.call(obj,parm1,,parm2)
// bind:创建函数实例
const colorFun = func.bind(obj)
color()
4.8.3 对象定义的方式
1.对象构造函数法
const person = new Object()
person.name = '1'
2.对象字面量法
const person = {
name:"1"
}
4.8.4 对象的数据属性,访问器属性。object.defineProperty
数据属性:
value:值
writable:能否修改属性值
enumerable:能否使用for in
configurable:能否使用delete删除属性
访问器属性:(必须通过object.defineProperty)
get:获取值触发
set:设置值
enumerable:能否使用for in
configurable:能否使用delete删除属性
设置属性
object.defineProperty(obj,'name',{
value:'123'
writable:false
})
object.defineProperties(obj,{
name:{
value:'123'
writable:false
},
color:{
get:function(){
return 'red'
},
set:function(value){
if(value=='bule'){
return 'yellow'
}
}
}
})
4.8.5 toString,valueOf方法
- toString方法将对象转换为字符串并返回字符串
var obj = {
a:1
}
console.log(obj.toString()) // [object Object]
console.log(['1',''].toString()) // 1,
- valueOf:返回对象的原始值
var obj = {
a:1,
valueOf:function(){
return this.a + 1
}
}
console.log(obj.valueOf()) // 2
4.8.6 New关键字
const person1 = new Person()
1.创建一个空对象
const person1 = {}
2.构造函数的作用域赋值给新对象,this指向新对象
Person.apply(person1,arguments)
2.执行构造函数代码(属性和方法加到this指向的对象内)
person1.属性=value
3.返回新对象
return person1
4.8.7 原型模式
// 构造函数
const person = function(){}
// 构造函数有个prototype属性,prototype指向原型对象,这句话是给原型对象内新增了值为123的name。原型对象内有一个constructor属性指向构造函数
person.prototype.name="123"
// 创建一个实例,实例中有一个_proto_指向原型对象
const person1 = new person()
// 实例中找不到name会去原型对象上找
console.log(person1)
console.log(person1.name)
4.8.8 原型链
让一个构造函数的原型等于另外一个构造函数的实例
- 原型链的原理依赖于对象的_proto_隐式属性
- 实例对象的_proto_指向原型对象=构造函数的prototype
- 原型对象的_proto_指向Object原型对象=Object.prototype
- Object对象的_proto_等于null,是终点
4.9 事件冒泡,事件捕获
事件冒泡:事件由最开始具体的元素接受想上传播。直到根节点
阻止冒泡:e.stopPropagation()
事件捕获:不大具体的节点应该更早接收到事件,具体的节点最后收到事件
DOM事件流(event flow )存在三个阶段:事件捕获阶段、处于目标阶段、事件冒泡阶段
// 第三个参数默认false,冒泡阶段执行,true为捕获阶段执行
event.addEventListener(事件类型,回调函数,true||false)
4.10 防抖,节流
防抖:一定时间内,触发多次函数,等待结束时间执行一次
一般用于输入框搜索,实时获取数据
var obj1 = {
id:null,
method1:function(){
console.log('666')
},
click1:function(){
clearTimeout(this.id);
var that=this;
this.id=setTimeout(function(){
that.method1()
},1000)
}
}
window.onresize = function(){obj1.click1()}
function debounce(fun,time){
let timeId = null;
return function(){
if(timeId){
clearTimeout(timeId);
}
var that = this;
timeId = setTimeout(function(){
timeId = null
fun.apply(that,arguments);
}, time)
}
}
window.addEventListener('resize',
debounce(function(){
console.log(window.innerWidth)
},1000)
)
节流:一定时间内,无论函数触发多少次,都只在开始触发一次
一般终于窗口缩放触发函数
function throttle(fn, time) {
let timeId = null;//声明一个定时器
return function () {
var width1 = this.innerWidth;
if (!timeId) {
var that = this;
timeId = setTimeout(function () {
fn.call(that,width1);
timeId = null;
}, time)
}
}
}
window.addEventListener('resize',
throttle(function(width){
console.log('输出第一次调整时候的窗口宽度',width)
},5000)
)
5.Es6
6.vue2
7.vue3和vue2区别
8.Node,npm,pnpm
8.1 npm和pnpm区别
- 包存放方式,空间占用
npm:通过在每个项目node_mudule下存放依赖,就算多个项目有相同依赖也是安装到项目的node_mudule内,所以空间占用大
pnpm:存放在全局的存储库,所以空间占用小 - 包获取方式
npm:在当前项目的node_mudule中查找
pnpm:通过符号链接查找存储库
8.2 npm和pnpm的workspace
- 什么是工作空间:
对单一存储库(也称为多包存储库、多项目存储库或单体存储库)的支持, 可以创建一个 workspace 以将多个项目合并到一个仓库中。
本质上就是在当前工作目录中可以通过npm/pnpm install将对应文件夹与当前工作目录的node_modules文件夹建立符号链接。
8.2.1 npm的工作空间
npm官网的workspaces
以truelore-public为例,truelore-public内部封装了一系列的功能
- 把truelore-public添加到业务系统中的packages文件夹中
- 将package.json中添加
{
"name": "my-workspaces-project",
"workspaces": ["packages/truelore-public"]
}
- 执行安装指令吧
npm install
- 使用对应功能
import { ZlDialog } from 'truelore-public'
8.2.2pnpm的工作空间
- 根目录下添加添加pnpm-workspace.yaml
packages: # 各个业务代码 - 'packages/*' # subtree/组件库 - 'libs/*'
- 将目录中添加相关的文件
- 在package.json文件中配置libs文件的组件依赖
"foo": "workspace:*", "bar": "workspace:~", "qar": "workspace:^", "zoo": "workspace:^1.5.0" 会被编译成 "foo": "1.5.0", "bar": "~1.5.0", "qar": "^1.5.0", "zoo": "^1.5.0"
- 执行安装指令
pnpm i
- 添加相关业务系统的运行和打包指令
9.vue2项目从0到1记录配置
10.webpack,vite
10.1 webpack
10.2 vite
10.3 webpack和vite区别:
- webpack是用node.js写的,vite是使用go写的
- webpack在构建大型项目时,需要将所有模块打包,这导致初次加载速度较慢。相比之下,vite利用浏览器对ES Module的原生支持(需要部署才能正常使用,本地是通过devServe),只打包和缓存实际改动的模块,从而极大提高了打包效率
- 热更新原理上,webapack整个模块链重新打包和替换,但是vite只会针对改动的模块进行更新
- webpack的插件,loader相关的生态体系比vite的健全