八股文+面经总结

目录

ES6新特性

数据类型 

实现深拷贝的方式

CSS

HTML5新特性

CSS3新特性

怎么垂直水平居中

盒模型

display:none和visibility:hidden的区别

定位

css选择器

怎么实现拖拽

flex布局

响应式布局

两栏布局(左侧固定宽度,右侧自适应)

三栏布局

BFC

CSS动画

JS

判断数据类型的方法

let, var, const的区别

箭头函数

闭包

es6新特性

原型链

继承的几种方式和优缺点

事件代理(事件流)

防抖和节流

重绘和回流

模块化(import和require的区别)

href和src的区别(link和@import)(defer和async)

Iterator

forEach和map的区别

for…in和for…of的区别

some和every,find的区别

apply,call,bind的作用和区别

new做了什么

Generator

async/await

Promise

事件循环

js获取元素节点

js插入节点

垃圾回收

内存泄漏的几种情况

VUE

vue的特点

MVVM和MVC的区别

v-if和v-show的区别

组件和插件的区别

计算属性和侦听器的区别

vue的生命周期

vue2和vue3的区别

Mixin

Vuex

 vue传递数据的几种方式

vue-router

keep-alive

vue中的key有什么作用(key的作用,diff算法)

虚拟DOM

diff算法

vue响应式原理

vue3响应式原理

$nextTick

Webpack

webpack是什么

Loader和Plugin的区别,常见的有哪些

webpack的tree-shaking原理

浏览器

跨域

请求拦截器

history和hash模式的区别(前端路由)

浏览器缓存机制

Token,session,JWT,cookie,localstorage,sessionstorage

前端性能优化

多图优化

前端性能指标

如何减少白屏时间

浏览器输入url后的发生了什么

get和post的区别

Http状态码

HTTP和HTTPS的区别

HTTP1.0/HTTP1.1/HTTP2.0

前端安全及防范措施

Echarts

设计模式

TS

Interface和Type的区别

快速排序和归并排序

项目

常见前端题

红宝书


ES6新特性

Symbol,表示唯一的值,可以使用它来定义私有属性或方法

let,const

解构赋值

map,set

箭头函数,关键字class,class的继承

模块导入导出import,export

异步:Promise和Generator

数据类型 

Number       

                - 在js中所有的整数和浮点数都是Number

                - 数值并不是无限大,当超过一定范围后会显示近似值

                - Infinity 是一个特殊数值表示无穷

                - 在js中进行一些精度比较高的运算时要十分注意

                - NaN 也是一个特殊的数值,表示非法的数值

        类型转换

转换为数值

                1.使用Number()函数来转换

                    - 如果字符串不是数字,则会转换为NaN

                    - 如果是空串和纯空格的字符串,则转换为0

                    - 如果是布尔值

                        - true转换为1

                        - false转换为0

                    - null转换为0

                    - undefined转换为NaN

                2.parseInt()函数,将字符串转换为一个整数

                    解析时,会自左向右读取一个字符串,直到读取到非数字字符

                    可以用来取整

                3.parseFloat()函数,将字符串转换为浮点数

                    同上

String

        类型转换:

        toString()方法       null 和 undefined没有toString()

        String()函数       null 和 undefined直接转换为"null"和"undefined"

Undefined

Null

Symbol

typeof 变量名     //检查类型

实现深拷贝的方式

1.cloneDeep()

const _ = require('lodash')
        let obj = {
            name: "孙悟空",
            age: 18
        }
        const obj2 = _.cloneDeep(obj)

2. JSON实现

let obj = {
            name: "孙悟空",
            age: 18
        }
        const obj2 = JSON.parse(JSON.stringify(obj))

3. structuredClone()

let obj = {
            name: "孙悟空",
            age: 18
        }
        const obj2 = structuredClone(obj)

4. 递归实现

function deepClone(obj){
    let objClone = Array.isArray(obj)?[]:{};
    if(obj && typeof obj==="object"){
        for(key in obj){
            if(obj.hasOwnProperty(key)){
                //判断obj子元素是否为对象,如果是,递归复制
                if(obj[key]&&typeof obj[key] ==="object"){
                    objClone[key] = deepClone(obj[key]);
                }else{
                    //如果不是,简单复制
                    objClone[key] = obj[key];
                }
            }
        }
    }
    return objClone;
}    

CSS

HTML5新特性

新增元素选择器querySelectAll和querySelectAll;语义化标签header,footer,nav,selection,article;新增表单控件date,time,email,url,search,calendar等;本地存储localstorage和sessionStorage;媒体播放的audio和video标签。

CSS3新特性

伪元素选择器nth-child,弹性盒模型flex,媒体查询@media,颜色透明度rgba,圆角border-radius,阴影box-shadow,转换transform,平滑过渡transition,动画animation和@keyframes

怎么垂直水平居中

固定宽高时,高度为父元素高度减去自身高度后的一半,margin:0,auto

设置绝对定位后,利用margin:auto实现,要将top,left,right,bottom设置为固定值,比如0

或者使用top,left为50%,margin-left/top为负的自身一般的宽和高;或者利用transform,使translatex和translatey为负的50%

利用flex,使justify-content和align-item为center

盒模型

标准盒模型:宽度和高度为内容区的高度宽度,box-sizing:content-box

怪异盒模型:宽度和高度为内容区,内边距,边框的和,box-sizing:border-box

display:none和visibility:hidden的区别

display:none会使元素从渲染树中消失,导致元素的重排,子元素不会继承,子元素消失是因为父元素消失了,读屏器不会读取消失的元素。;visibility:hidden不会是元素从渲染树中消失,不会导致重排,会导致重绘,会继承,子元素消失是因为继承了父元素的属性,读屏器会读取消失的元素

定位

static:默认值,静态定位,相对于元素在文档流中的位置定位

relative:相对定位,相对于元素在文档流中的位置定位,不会脱离文档流

absolute:绝对定位,相对于第一个定位了的父元素进行定位,会脱离文档流

fixed:固定定位,相对于浏览器窗口进行定位,会脱离文档流

sticky:粘性定位,滚动范围达到阈值前相当于relative,达到阈值后相当于fixed定位

css选择器

!important>内联样式1000>id选择器100>类选择器10(属性选择器、伪类选择器)>元素选择器1(伪元素选择器)

怎么实现拖拽

利用mousedown,mousemove,mouseup三个事件,在按下鼠标时,在按下鼠标的处理函数中绑定mousemove事件,利用元素的left和top实现元素的移动,使left等于鼠标位置减去鼠标与元素边界的距离,绑定mouseup事件,当鼠标松开使,移除mousemove事件。

addeventlisener和on某某事件的区别

addeventlisener可以绑定多个事件,on某某只能绑定一个,add要通过remouvelisener删除,on通过将事件指定为null删除

flex布局

flex-direction: colum, row, colum-reverse, row-reverse

flex-wrap: wrap, nowrap,wrap-reverse(是否换行)

flex-flow: 以上两个属性的简写

justify-content: flex-start,flex-end,center,space-between(最后一个的后边和第一个的前边没有空白),space-around(空白均匀分布)

align-items: center,flex-start,flex-end,strentch(默认值),baseline(基线对齐)

align-content: 与justify-content一致,多一个stretch

子元素的属性:

flex-grow: 放大比例,默认0,为0时有剩余空间也不放大,如果所有项目都为1,则等分剩余空间。如果一个项目为2,其他为1,则前者比其他项的剩余空间多占一倍

flex-shrink: 缩小比例,默认1。如果所有的项目都为1,当空间不足时,都将等比例缩小,如果一个项目为0,其余为1,则空间不足时,前者不缩小。

flex-basis: 项目占据的主轴空间,根据这个计算主轴是否有多余空间,默认值auto,项目本来大小

flex:以上三种的属性的简写,默认0 1 auto,

order:定义项目的排列顺序,数值越小,排列越靠前

align-self:单个项目的对其方式,可以与其他项目不同,默认auto,继承父元素的align-items

响应式布局

媒体查询:通过max-width和min-witdth匹配不同区间的屏幕大小,编写多种样式,达到自适应效果。

rem/em:rem是根据根元素的字体大小为基本单位,比如html的font-size为16px,则1rem就是16px,em是根据元素自身的字体大小,如果没有,则根据父元素的字体大小为基本单位。比如使用js获取屏幕的宽度,然后根据宽度设置字体,单位统一使用rem,就可以使页面根据屏幕大小动态变化。

vm/vh:相对于视口宽度的百分比,如1vm就是1%的视口宽度

使用flex弹性布局

百分比:宽度高度内外边距是根据直接父元素比较,top,left,right,bottom是根据定位了的第一个父元素。

两栏布局(左侧固定宽度,右侧自适应)

左侧float:left,右侧margin-left:左侧元素的width

左侧left,右侧overflow:hidden

左侧绝对定位,父元素相对定位,右侧margin-left:左侧元素的width

右侧绝对定位,top:0,left:左侧元素的width

使用flex,左侧元素flex-grow:0,右侧元素flex-grow:1

使用表格布局,左侧元素和右侧元素设为table-cell

使用grid,父元素grid-template-colums:200px auto

三栏布局

使用flex,左右两侧宽度固定,中间flex:1(1 1 0%)

左右两侧绝对定位,top:0,左测left:0,右侧right:0,中间设置margin-left和margin-right

左侧左浮动,右侧右浮动,中间设置margin-left和margin-right,中间元素要放在最后,不然会把右边挤下去

圣杯布局:都设置左浮动,盒子顺序中左右,左侧右侧开启相对定位,左侧margin-leftt:-100%,把自己移上去,left:负自身宽度,右侧right:负自身宽度

双飞翼布局:盒子顺序中左右,中间用一个div包裹,都设置左浮动,左侧margin-left:-100%,右侧margin-left:-300px

使用table,都设置为table-cell

使用grid,grid-template-colums:300px auto 300px

BFC

bfc是块级格式化上下文,是一个独立渲染的区域,也就是说bfc不会影响外面的布局,可以防止外边距重叠和高度塌陷

开启bfc:float不为none;position为absolute或fixed;display为inline-block或flex或table-cell;overflow不为visible(hidden,auto,srcoll)

CSS动画

transition:包含transition-property,transition-duration,transition-timing-function,transition-delay

transfrom:包含rotate,scale,skew,translate

animation:定义了动作的每一帧@keyframes有什么效果,包括animation-name,animation-duration,animation-timing-function,animation-delay,amimation-iteration-count,animation-direction

JS

判断数据类型的方法

typeof 只能判断基本数据类型,除了null会返回object,判断引用类型时除了function,其他都返回object

instanceof只能判断引用类型,判断基本类型时会返回false

constructor,这种方法可以判断基本数据类型和引用数据类型,但是consturctor会发生变化

Object.propotype.toString().call(),这种方法可以判断基本类型和引用类型。

let, var, const的区别

var是es5的语法,存在变量提升,let和const是es6语法新提出的块级作用域概念,存在暂时性死区(变量未声明前的那段区域,使用变量会报错),没有变量提升机制。let和const声明的变量不能重复声明,而var可以重复声明。const声明的变量不可以修改,而var和let声明的变量可以修改,且声明的同时必须赋值。

map和set的区别

map

map是es6的一种新的集合类型,在实例化时传入一个可迭代对象,需要包含键值对数组,将其转换为键值对映射。map可以使用任何类型作为键,单独实例互不影响。键和值的对象内部属性可以修改,反不会影响引用关系。

map的方法:size,set(返回映射实例,因此可以链式调用),get,has,delete,clear

通过entries方法或[Symbol.iterator]属性得到每个键值对(结果是数组),entries是默认迭代器,可以对map直接展开。也可以使用foreach遍历,回调函数第一个参数是val,第二个是key。也可以通过for of遍历map.keys和map.values。

相较于object,map的内存占用和插入性能、删除性能更好,object的查找速度更快

weakmap

weakmap的键只能是object或者继承自object的类型,值的类型没有限制。弱的意思是键不存在正式的引用,不会阻止垃圾回收。weakmap没有clear方法,不可迭代键。

weakmap作用:创建私有变量,将dom节点与元数据关联

set

set可以使用任何数据类在作为值,值的内容或属性被修改时不影响set集合,set中的值不会重复。

set的方法:add(可以链式操作),has,size,delete,clear;add和delete操作是幂等的(同样的输入返回同样的结果)

可以通过for of来遍历set.values和set.keys(两者结果一样),values是默认迭代器,可以直接对set展开。entries方法产生包含两个元素的数组,这两个数组是集合的每个值的重复出现。也可以使用foreach来遍历set

map用于根据键来搜索值的情况,set适用于需要保持元素独特性的情况

箭头函数

箭头函数没有自己的this,他的this就是上下文的this,箭头函数是匿名函数,不能作为构造函数使用,不能new,箭头函数没有arguments,箭头函数也没有prototype属性,不能使用generator。

闭包

闭包就是能够在函数定义的作用域外,使用函数定义作用域内的局部变量,并且不会污染全局。

形成闭包的条件:存在函数的嵌套,内部函数使用了外部函数的变量,内部函数被返回。

定时器,事件监听器,ajax 请求,跨窗口通信,web workers 或者任何其他的异步/同步任务中,只要使用了回调函数,实际上就是使用闭包

es6新特性

let,const,symbol,箭头函数,map,set,weakmap等,class,class继承,导入导出。

原型链

在js中,每个构造函数身上都有个prototype属性,指向构造函数的原型对象,构造函数的原型对象身上有个constructor,指向构造函数自己。所有通过构造函数创建的实例对象身上都有个_proto_属性,指向自己构造函数的prototype对象,这就是原型链,原型链最终会指向Object.prototype,而Object的原型对象的proto会指向null。

继承的几种方式和优缺点

1. 原型链继承,将子构造函数的prototype属性指向父构造函数的实例,这种方法的缺点是所有子构造函数的实例公用相同的属性,修改其中一个,其他的也会发生变化。

2. 构造函数继承,在子构造函数中通过call调用父构造函数,这种方法的缺点是子构造函数的实例没法访问到父构造函数的prototype属性里的方法和属性。

3. 组合继承,原型链继承和构造函数继承的结合,这种方法的缺点是在子构造函数中会通过call调用一次父构造函数,在将父构造函数的实例赋值给子构造函数的prototype属性是,又会通过new()调用一次父构造函数,这样就调用了两次,性能不好。

4. 原型式继承,通过调用方法返回一个新的实例来继承,这个方法里定义了一个构造函数,构造函数的prototype指向传入的对象,然后返回这个构造函数的实例,使返回的实例拥有了传入对象所含有的属性和方法,或者直接使用Object.create()方法实现原型式继承。这种方法的缺点也是所有通过这个方法创建的实例,都公用相同的属性。

5. 寄生式继承,在原型式继承的基础上,使用一个增强函数,这个函数里定义了一个对象,来接收原型式继承里返回的实例,然后可以给这个对象添加新的方法,最后返回这个对象。通过这种方法继承的缺点是没有办法复用原始对象上的方法。

6.寄生组合继承,将寄生式继承与组合继承相结合,使用Object.create()里的第二个参数,第一个参数为父构造函数的prototype,第二个参数为子构造函数添加constructor属性,指向子构造函数自己。这种方法比较完美。

事件代理(事件流)

事件为什么要经历这三个阶段:因为点击屏幕某个地方时,可能涉及到不止一个DOM元素,这个点可能被不同的DOM节点包含,为了准确的找到触发事件的那个DOM元素,去触发子元素或者父元素身上的事件,就可以通过是否阻止冒泡来控制事件的触发。

触发一个事件会经历三个阶段:捕获、目标、冒泡。从最外层往里捕获,到触发事件的目标元素,然后再经历冒泡,从里往外触发事件,要想在捕获阶段触发事件,可以通过addEventListener绑定事件时,将第三个参数设置为true,取消冒泡可以通过event.stopPropagation()或者event.cancelBuble = true来设置,取消默认行为可以通过event.preventDefault()或者return false或者event.returnValue = false来设置。

事件代理就是借助事件冒泡,将事件绑定在父元素上,这样就不用为每个子元素都绑定事件,比如如果有一百个li,每个都要绑定,这会使浏览器性能下降。event.target表示触发事件的元素,也就是目标元素,event.currentTarget表示当前事件绑定的对象,也就是父元素。

防抖和节流

防抖就是防止抖动,避免事件的重复触发,等用户的高频操作结束,再进行事件触发操作。n秒内只触发一次,若n秒内被再次触发,则会重新计时。适用于多次触发但只有最后一次生效。(调整窗口大小)

防抖存在一个问题,就是事件会等到用户操作完成后一段时间再触发,如果一直操作会一直不触发,节流就是减少流量,将频繁触发的事件减少,并每隔一段时间执行。就是隔一段时间触发一次,期间的操作不会触发。n秒内执行一次,如果触发多次,只有一次生效。(轮播图)

重绘和回流

在渲染DOM时,浏览器将DOM分割为多个图层,对每个图层的节点计算样式结果,为每个节点生成图形和位置(重排,回流),将每个节点绘制填充到图层位图中(重绘),图层作为纹理上传至GPU,组合多个图层到页面上生成最终屏幕图像

重绘是一个元素外观的改变所触发的浏览器行为,例如改变outline,背景,颜色等属性,浏览器会根据元素的新属性重新绘制,重绘不会带来重新布局,所以不一定伴随回流

渲染树并不包含位置和大小信息,计算这些值的过程称为布局或回流,重绘不会引起回流,回流会导致重绘。

避免重绘回流:改变样式的操作合并成一次,不要一条一条修改,将dom离线后再修改,如display:none。

模块化(import和require的区别)

随着js的功能越来越复杂,模块化也变得更重要,模块化就是一个文件管理自己的属性和方法,实现一个功能,需要使用到某个功能时就导入某个模块,这样避免了全局变量的污染,方便代码的复用和维护,模块化最初是使用函数来实现,这样会污染全局变量,而且一个文件里的代码过多,会导致代码量越来越大,后来使用对象来实现模块化,将函数作为对象的方法,但这样对象的属性重名时会直接覆盖,而且可以通过对象修改属性,不安全也容易出错。后来使用立即执行函数和闭包来实现模块化,这样函数执行后就销毁,而且可以实现创建私有变量。不过这种方法也会使文件的代码量越来越大。

common js和es6都实现了模块化,前者使用require导入,使用exports和moudle.exports导出,require导入是同步的,导入的模块分为核心模块和文件模块,核心模块是node内置的模块,文件模块是自定义的模块,exports不能直接赋值,通过exports.x来导出,因为exports就是指向了moudle.exports,moudle.exports可以赋值,这两种导出方式都是通过复制导出的值,如果导出的值变化了,那么在另一个文件被导入的值不会变,需要重新导入。导入的模块会缓存

es6通过import和export来导入导出,import是异步导入,比require更快,可以使用{},来导入和导出多个需要的变量,default导出的变量导入时可以自定义名字。导出的是对变量的引用,所以变量更改时,导入的文件里的变量也会发生相应的变化。导入的模块不会缓存。

href和src的区别(link和@import)(defer和async)

href表示超文本引用,用来建立当前元素和文档之间的链接,常用于link和a标签,浏览器在处理link引入的css时,会并行下载该文档,并且不会停止对当前文档的处理。

src是source的缩写,src表示引入,引入的内容是页面必不可少的一部分,src引入的内容会嵌入到当前标签所在的位置,常用于img,script,iframe。在处理script引入的css时,浏览器会暂停渲染,直到这个资源加载完毕。因此建议将js脚本放在底部(可以使用defer和async进行异步并行加载,defer会延迟脚本的执行,等到页面解析后执行,按加载顺序执行;async在脚本下载完成后立即执行,谁先下载谁先执行)。

link是xhtml标签,在html文件里使用,除了加载css外,还可以加载其他事务,@import属于css范畴,在css文件内使用,只能加载css。link引用css时,页面载入时同时加载,@import需要页面完全载入以后加载,有浏览器兼容问题。link可以在文件任何位置使用,css只能在顶部。

Iterator

使得for...of可以遍历数组,map接使用xxx[Symbofl.iterator]()调用,返回的是一个对象,可以调用该对象的next()方法,返回的也是一个对象,包括value属性和done属性,当done为true时,表示已经遍历结束。对象上没有部署该方法。

实现iterator的思路是,函数将对应的数据结构传递进去,使用Object.keys()方法获取该对象的键值,初始index为0,该函数返回的是一个对象,对象上有next方法,next方法返回一个对象,当index小于keys的length时,返回value和done:false,当大于等于keys的length时,返回value:undefined和done:true.

forEach和map的区别

foreach的返回值是undefined,使用return语句没有作用,使用break会报错,会破坏原数组,当需要对数组进行改变时可以使用foreach。map的返回值是一个数组,每个元素是回调函数的返回值,不会破坏原数组,可以进行链式操作,当原数组不能改变,比如与组件绑定了,但需要依赖这个数组的数据得到新数组时使用map。他们都有第二个参数,用来表示this指向。

for…in和for…of的区别

for...in用于遍历可枚举对象(enumerable属性为true),遍历的是键值,结果是字符串,会遍历原型链上的属性,可以使用hasOwnPropoty()解决。for...of用于遍历可迭代对象,如数组,map,set,字符串,遍历的是值,要遍历普通对象需要搭配使用Object.keys()

some和every,find的区别

some会返回false和true,只要有一项满足,就返回true,every需要每一项都满足才会返回true,find会返回第一个满足的值,如果都不满足,返回undefined。

apply,call,bind的作用和区别

三者都可以为函数绑定this,第一个参数表示要绑定的this,第二个参数表示传参。

apply:参数是数组,立即执行

call:参数要一个个展开,立即执行

bind:参数要一个个展开,返回的是绑定this之后的新函数,可以多次传参

new做了什么

new操作符通过执行自定义构造函数或内置对象构造函数,生成对应的对象实例

创建了一个新对象,将新对象的proto属性指向了构造函数的prototype,将构造函数的this绑定为这个新对象,执行构造函数中的代码,如果构造函数没有返回对象,则返回这个新对象

Generator

Generator可以实现异步,早期的异步是通过回调函数,将后续的回调函数在异步函数内调用,后来使用事件和订阅/发布来实现,现在可以使用Promise实现,不过要通过then方法进行链式调用。generator可以在函数内部使用yeild来暂停函数,将函数的执行权交出去,调用函数时返回的是一个对象,可以调用对象的next方法,拿到yeild后面表达式的结果。next()方法返回的也是一个对象,有value属性和done属性,done为true时表示这个generator函数已经执行完毕。

实现generator的思路是,generator会将函数编译成一个switch的形式来达到分段执行。调用这个函数时,返回generator函数的结果,将一个匿名函数作为参数传递进去,generator函数返回一个对象,这个对象上有一个next()方法,next()方法调用传递进来的匿名参数,将当前的状态作为参数传递,来执行switch语句,当前的状态用一个对象保存,因此generator返回值是一个立即执行函数,表示状态的对象存在立即执行函数里作为私有变量,立即执行函数的返回值就是前面所说的generator要返回的对象,只是通过立即执行函数的返回值返回。

async/await

async和await是generator和yeild的语法糖,async修饰的函数相当于generator函数,await相当于yeild,await后面可以跟普通表达式,也可跟promise对象,await使函数暂停,交出函数的执行权,使函数外部的同步代码可以继续执行,当多个异步请求相互依赖对方的结果时,使用await比then更方便。

async/await的原理是:如果有一个异步请求,我们在函数里使用到了异步请求,将这个函数写成generator的形式,在请求的前面写yeild,然后用一个async函数包装,也就是将这个函数作为参数传递给asyncfunc里,在asyncfunc里调用这个函数,得到一个可迭代对象赋值给一个变量gen,asyncfunc里有一个next函数,递归调用该next函数,直到这个可迭代对象执行完毕,在next函数里,通过gen.next()拿到yeild后面的对象,并.value拿到promise,.then将结果传递给下一个next函数,下一个next函数可以利用这个结果,继续执行后面的yeild语句,这样就模拟了async和await。

使用场景:一些异步操作比如网络请求,定时器,如果后面的代码需要用到异步操作的结果就需要使用async和await,比promise的链式调用更简洁。

Promise

promise可以解决调用异步函数时依赖上一个异步回调函数的结果,出现回调地狱的问题;promise是一个构造函数,promiese有三个状态,pending,resolved(fulfilled),rejected,分别表示初始状态,成功状态,失败状态,状态只能改变一次,不会再次改变,只能同时存在一种状态。可以传一个函数作为参数,这个函数有两个参数,一个是resolve,一个是reject,分别可以将pending状态改为fulfilled状态或rejected状态。promise有三个实例方法,then(),catch(),finally(),他们都会返回一个新的promise,可以进行链式调用。then方法可以传递两个回调函数,一个在状态变为成功时执行,一个在状态变为失败时执行,catch()在状态变为失败时执行,finally()在最后执行,不论状态成功还是失败。

promise可以用于处理并发请求,有all, allSetled, any, race四种静态方法

promise.all([x,x,x])中每个promise实例都成功时,返回的新promise实例的值是一个数组,存储的是成功后的值。如果有一个失败,则会返回失败的结果。

promise.allSetled([x,x,x])中每个promise实例不论是否成功,返回的新promise实例的值是一个数组,状态是fulfilled。存储的是对象,里面是对应promise的状态和value值。

promise.any([x,x,x])返回的promise实例的值是第一个成功的promise实例的值。如果全都失败,则会输出错误信息。

promise.race([x,x,x])返回的promise实例的状态和值取决于最快的promise实例,不论成功或者失败。

promise实例的方法有then, catch, finally,catch用来处理reject,finnaly用于最后,不论是resolve还是reject。

手写promise思路:实例化promise时,里面的回调函数会立即调用,因此在构造函数里调用,并将resolve和reject传递给他,注意绑定this,构造函数需要三个属性,result用来存值,state用来表示状态,callback是一个数组,用于异步调用时,值还没存到result,但是then已经调用了,此时要把then里的回调函数存起来,待异步调用处理结束后,调用callback里所有的函数。resolve和reject注意要判断状态,存值之后,要改变状态。

事件循环

js是单线程的,因为浏览器器端主要负责和用户进行交互,渲染页面等,如果是多线程,就会引起一些难以处理的问题。比如同时有一个删除dom和对该dom添加内容的线程,此时就会产生混乱。浏览器端需要下载一些文件等资源时,同步会使任务阻塞,造成页面卡顿,这是异步就为我们解决了这个问题。

js事件循环是浏览器最开始执行scrpit中的代码,将一个个函数的执行上下文压入执行栈中,在执行代码时,遇到异步的代码时,会暂时挂起,将回调函数放进任务队列中,等执行栈中的代码执行完毕后,再去微任务中读取任务,微任务的代码执行完后,再去宏任务中读取代码,这个过程不断循环,直到任务队列为空。

对于dom事件,如果是在代码中调用的,比如button.click(),这个click是同步的代码,待他的回调函数执行完毕后,才会去执行微任务中的代码。但是如果是通过点击去触发这个事件,事件的回调会被作为宏任务放入宏任务队列中,这是区别要注意!!

宏任务:script,settimeout,setinterval,setimmediate,requestAnimationFrame(以最佳帧率重复运行函数)、ui 渲染,dom事件

setInterval 会按设定的时间间隔固定调用,其中 setInterval 里面的代码的执行时间也包含在内,所以实际间隔小于设定的时间间隔。而递归 setTimeout 是调用时才开始算时间,可以保证多次递归调用时的间隔相同。

如果当前 JavaScript 线程阻塞,轮到的 setInterval 无法执行,那么本次任务就会被丢弃。而 setTimeout 被阻塞后不会被丢弃,等到空闲时会继续执行,但无法保证执行间隔。

微任务:nextTick(这是异步任务中最早执行的任务),promise,object.observe(proxy代理),object.mutation()

js获取元素节点

document.getElementById(),通过id获取元素

document.getElementsByClassName,通过类名获取元素

document.querySelector(),通过选择器获取元素,非实时的,匹配的第一个元素

document.querySelectorAll(),通过选择器获取元素,非实时的,是一个伪数组,可以通过[].forEach.call()方法调用forEach,将伪数组作为参数传递给call()

element.getElementsByTagName(),通过标签名获取元素

element.childNodes(),获取当前元素的子节点(会包含空白元素,文本节点,注释节点等),伪数组

element.children(),只返回元素节点,不会返回文本节点,注释,空白等。

element.firstElementChild(),获取当前节点的第一个子元素

element.lastElementChild(),获取当前节点的最后一个子元素

element.previousElementSibling(),获取当前节点的前一个兄弟元素

element.nextElementSibling(),获取当前节点的后一个兄弟元素

element.parentNode(),获取当前节点的父节点

element.tagName(),获取当前元素的标签名

js插入节点

element.appendChild(),给一个元素添加子节点到末尾

element.adjacentElement(),给一个元素添加子节点或兄弟节点,第一个参数为要添加的位置,第二个参数为要添加的元素

        afterBegin(),添加子节点到子元素的顶部

        beforeBegin(),添加兄弟元素在自身前面

        afterEnd(),添加兄弟元素在自身后面

        beforeEnd(),添加子节点到子节点的尾部

element.innerHtml(),可以直接赋值,给元素添加内容,但会替换掉元素的子元素。、

垃圾回收

垃圾回收是一种内存自动管理机制,可以回收分配给程序的已经不再使用的内存,常见的垃圾回收算法有引用计数法和标记清除法。。该方法的局限性:当出现循环引用时,互相引用的对象不会被回收。

将资源(比如对象)的被引用次数保存起来,当被引用次数为零时释放

内存泄漏的几种情况

内存泄漏是指,应当被回收的对象没有被正常回收,变成常驻老生代的对象,导致内存占用越来越高。内存泄漏会导致应用程序速度变慢、高延时、崩溃等问题。

意外的全局变量,在函数中声明变量时没有使用let,var

事件监听器、定时器没有被关闭

DOM节点被删除后,仍然存在对元素的引用

闭包,比如闭包被返回后,使用到的外层函数的变量不会被回收

VUE

vue的特点

组件化,方便复用

虚拟DOM,更新视图

响应式编程(MVVM),连接视图和数据

单页面应用,局部刷新,不用每次跳转

MVVM和MVC的区别

mvc中三者的职责分开,基本都单向联系,mvvm中的viewmodel代替了mvc的controller,mvvm通过数据来显示视图层而不是节点操作,解决了mvc中大量的dom操作时页面渲染性能降低,加载速度变慢,影响用户体验

v-if和v-show的区别

使用v-if进行渲染的元素,涉及到元素的销毁和重建,而v-show只是简单的对css的display属性进行控制,不会进行销毁和重建,v-show不能用在template上,也不能配合使用v-else。v-if的切换开销更高,而v-show的渲染开销高,因此涉及到频繁的切换,就使用v-show更好,如果不涉及频繁切换,那么就使用v-if

组件和插件的区别

我理解的组件是页面的某一个部分,而插件是为了完成某个功能需要使用的,插件注册后,所有组件都可以使用,组件是.vue文件,降低了整个系统的耦合度,可以通过替换不同的组件快速完成需求,方便调试,出现问题时,可以直接使用排除法快速定位出错的组件,提高可维护性,方便复用。使用组件时,在components属性里注册,或者全局注册,插件需要在use之后使用。

计算属性和侦听器的区别

计算属性所依赖的数据发生了变化,计算属性才会变化,计算属性有返回值,而侦听属性没有,计算属性可以监听多个数据的变化,而一个侦听器只能监听一个数据,计算属性的返回的值会进行缓存,而侦听属性没有,侦听器可以获取到旧值和新值,而计算属性只能获取到新值,如果涉及到频繁的数据变化或者异步操作,那么使用侦听器更好,如果没有涉及到频繁的变化或者异步操作,计算属性会更方便

vue的生命周期

vue的生命周期函数有8个

initLifecycle/Event,往vm上挂载各种属性

触发beforecreate(),此时vue实例刚初始化,vm上只有一些初始属性和方法,data和el还获取不到

initInjection/initState,初始化注入和data响应性

created,此时可以访问data中的数据,但还没有挂载真实dom 在页面上,不能操作dom ,在这里可以进行数据初始化,可以进行异步操作,比如发送axios请求

最常用的是在 created 钩子函数中调用异步请求,因为在 created 钩子函数中调用异步请求有两个优点:

  • 第一点:能更快获取到服务端数据,减少页面 loading 时间;
  • 第二点:放在 created 中有助于一致性,因为ssr 不支持 beforeMount 、mounted 钩子函数。

执行$mount,执行mountComponent(),进行元素的挂载,compiler编译将template解析成render function

beforemount,虚拟dom 已经生成了,但还未挂载到页面上,可以访问data,不能操作dom 

执行render function,渲染vdom,执行_update(),挂载真实dom,并替换到dom tree中。

mounted,页面已经挂载,可以访问data,可以操作dom,在这里可以进行一些页面第一次渲染后的操作

以上四种钩子在整个生命周期内只会执行一次

当页面发生变化时,执行diff算法,比对改变是否需要触发UI更新

flushScheduleQueue遍历队列中所有的修改

beforeupdate,检测到数据更新后会执行的函数,此时页面上的数据还是显示的旧数据,这里可以进行数据变化后页面更新前的操作

执行watcher中的notify,通知所有依赖性更新UI

updated ,此时页面与data中的数据保持同步

组件销毁前,actived/deactivated(keep-alive),销毁,缓存,组件激活与失活

beforedestroy,实例销毁前的钩子,可以访问到data中的数据,可以操作dom,在这里可以进行一些事件解绑,定时器关闭等操作

销毁自身切递归销毁子组件和事件监听,remove()删除节点,watcher.teardown()清空依赖,vm.$off接触绑定

destroyed ,vue实例销毁,所有数据方法指令都不可用。

vue2和vue3的区别

生命周期有一部分变化,使用setup替代了beforecreat和created,基本上剩下的生命周期名称前都加上了on(beforeDestory->onBeforeUnmount,mounted->onUnmounted),在使用生命周期钩子时需要先引入,而vue2可以直接调用生命周期钩子。

vue3支持多根节点,vue2只能使用一个根节点,需要一个div包裹。

vue3是组合式API,将统一逻辑的内容写在一起,增强了代码的可读性,vue2是选项API,一个逻辑散乱在文件不同位置。

响应式原理不同,vue2中使用defineProperty,将属性转换为带有setter和getter的属性,在data上的属性才能被监听,监听不到属性的增加和删除,监听不到通过数组索引改变数组元素的值,监听不到数组长度的改变。需要通过$set或者splice或者object.design()实现。对于嵌套对象需要深层递归,浪费性能。而vue3使用Proxy,对对象的读取和修改进行拦截

vue3的打包优化:Tree-shaking

diff算法不同,vue2通过对比新旧虚拟DOM,返回一个patch对象,用来存储两个节点不同的地方,最后用patch去局部更新DOM,对每一个vnode进行比较会消耗性能。vue3在初始化时会给每一个虚拟节点添加一个patchFlags,diff算法只会比较patchFlags发生变化的vnode,进行更新试图,对于没有变化的元素做静态标记,在渲染时直接复用。

Mixin

MIXIN将组件的公共逻辑或者配置提取出来,那个组件需要使用,就直接将提取的这部分混入到组件内部,减少代码冗余度,方便后期维护。

与vuex的区别:vuex是公共状态管理,如果一个组件改变了vuex中的数据,那么其他引用了这个数据的组件也会变化。mixin中的数据和方法是独立的,组件之间使用后互不影响。

mixin包含了vue组件的常见逻辑结构,如data,methods,computed,mounted,created等。复用时,先执行mixin中生命函数中的代码,然后再执行组件内部的代码。

当mixin中的data与组件中的data冲突时,组件中的data会覆盖mixin中的。方法冲突时,组件的方法会覆盖mixin的方法。

Vuex

vuex是vue的状态管理模式,用于维护和管理多个组件共享的状态和数据,vuex 有5个属性,state,getter,mutation,action,module 

state:单一状态树,这个对象存储了组件共享的状态,辅助函数mapState([“数据名”])可以直接拿到数据

getter:类似于计算属性,依赖的数据变化时会发生变化,具有返回值。辅助函数mapGetter()

mutation:更改state的数据,每个函数的参数都有state,可以拿到state的数据并进行更改。辅助函数mapMutation(),mutation必须是同步的

action:vue通过dispatch 触发此对象中对应的方法,对数据进行一些处理后,通过commit 触发mutation 对应的方法,在这里可以进行异步操作。辅助函数mapAction()

moudle:随着store里的数据和方法越来越多,代码堆积量也越来越大,moudle可以对模块进行拆分,每个模块中都有自己的state,getter,moudle,mutation ,action。如果命名空间为true,使用时要加上模块名。

vuex 中的数据刷新后不在了,因为vue实例重新初始化了,可以使用浏览器的localstorage等来解决。

 vue传递数据的几种方式

1.父给子传递

第一种:props

父组件将要传递的数据写在子组件上,子组件通过props接收

2.子给父传递

第一种:自定义事件

给子组件绑定一个点击事件,事件触发后通过$emit()触发父组件的自定义事件(自定义事件可以直接通过v-on或者@绑定,也可以通过$on绑定),第一个参数为触发的自定义事件的名字,第二个参数为要传递给父组件的数据,该数据会作为父组件自定义事件的参数。

第二种:props

父组件给子组件一个函数,子组件通过props接收这个函数,子组件绑定一个点击事件,事件触发后,事件处理函数调用props接收的函数,并将要传递的数据作为参数,这样父组件中的函数就可以拿到数据并保存。

3.兄弟间传递

第一种:全局事件总线

为Vue的原型对象添加一个$bus,在main.js中Vue.prototype.$bus = this(就是当前vm),6想接收数据的组件在mounted给bus绑定自定义事件,事件的回调函数保留在组件内,提供数据的组件触发bus的自定义事件,把要传递的数据作为参数。

第二种:消息订阅与发布

 安装pubsub,想接收数据的组件在mounted中利用pubsub.subscribe订阅消息 ,回调函数留在组件自身,要传递数据的组件使用pubsub.publish提供数据,第一个参数发布哪个订阅消息,第二个参数是要传递的数据

第三种:vuex                                                                                      

vue-router

vue的路由管理器,有利于构建单页面也用,不用频繁跳转页面,只更新局部。

组件<router-link to=" "></router-link>to后面跟要跳转的路径,<router-link :to="{name:" "}"></router-link>,to后面跟路由的name属性值,实现路由跳转也可以通过this.$router.push()方法实现,参数可以是路径,也可以是对象形式。<router-view></router-view>路由的渲染位置。

active-class<router-link>组件的属性,用于指定组件被激活时的css样式的类名,也可以用于index.js文件里的router实例,使用linkActiveClass来指定。使用exact可以精确的定位被激活的组件。使用方法是在组件的active-class后面加上exact,或者使用linkExactActiveClass来指定。

动态路由可以通过query,params来实现,都是通过url传参,query刷新页面后参数还在,传参后的路径是/?属性名=属性值,params刷新页面后参数不在了,传参后的路径是/属性值。实现传参可以通过</router-link :to="{name:" ", query = {userid:123}">在to中通过query属性或者params属性以对象的形式指定。或者在push中通过this.$router.push("'/路径名/?属性名'+'属性值'")(params参数不用添加问号和属性名,如push("'/路径名/'+'属性值'")),或者以对象的形式传递。

路由守卫有三种类型,第一种是全局导航守卫router.beforeEach(),每个路由跳转前都会经过此路由守卫,在这里可以进行一些验证,然后通过next()放行;router.afterEach(),所有路由跳转后会执行此路由守卫,在这个函数里不能使用next(),router.beforeResolve(),和beforeEach()类似,也需要next()放行。第二种是路由独享守卫beforeEnter(),在路由配置对象中对应的routes对象里使用,拥有该方法的路由独享这个守卫。第三种是组件内路由守卫beforeRouteEnter()beforeRouteUpdate()beforeRouteLeave(),分别是对应组件进入时,更新时,跳转时使用。所有路由守卫除了afterEach(),每个守卫都有to,from,next三个属性,to是要到哪个路由,from是来自哪个路由,next用于放行。

$route对象是当前路由的信息,有path,query,params,name,meta等属性,$router是全局路由的实例,是Router构造方法的实例,有push(),go(),replace()等方法,实现路由的前进后退等。go()里可以传正数1,表示前进,负数-1表示后退,replace表示前进并替换当前路径,使用浏览器的回退按钮不能看到之前的画面。

可以在配置是使用redirect实现重定向,也可以解决404的问题。

通过watch监听路由参数的变化,可以通过给路由占位组件添加key达到路由复用时不会调用生命周期函数的问题。

history模型和hash模型的区别,hash模型的路径有#号符,而history没有

路由懒加载,将路由导入时写成箭头函数返回值的形式,这样在使用时才会加载对应的路由。

keep-alive

keep-alive是一个内置组件,它包裹的组件在不活动时不会被销毁,而是被缓存,比如在进入导航页后,点击某一列,进入详情页,退出详情页回到列表页时,不会重新发送请求获取列表,而是直接从缓存中读取。该组件不会渲染到真实DOM中。

属性:

        include:匹配的组件都会被缓存,字符串或正则表达式

        exclude:匹配的任意组件都不会被缓存,字符串或正则表达式

        max:能缓存的组件最大数量

两个生命周期:activated和deactivated,组件被缓存后不会触发created,mounted钩子函数。

按需控制keep-alive:在路由中设置keepAlive属性判断是否需要缓存,使用两个router-view,一个被keep-aclive包裹,一个不被包裹,两个的v-if属性相反,这需就可以按需缓存了。

原理:渲染组件时,执行render函数,获取默认插槽中的第一个组件节点,获取该组件的名称,优先获取name属性,如果不存在则获取tag,如果不在include或存在于exclude中则不缓存,直接返回vnode。如果需要缓存,则根据组件的key值去this.cache中查看是否有该值,如果有,则命中缓存,删掉原来的key,放在最后一个。如果没有命中,则将其push进缓存,如果配置了max并超过了max,则从缓存中删除第一个。

在mounted中观测include和exclude的变化,如果发生了变化,执行pruneCache函数,对this.cache对象进行遍历,取出每一项与新的缓存规则进行匹配,如果匹配不上,则调用pruneCacheEntry函数将其从 this.cache对象中删除。

vue中的key有什么作用(key的作用,diff算法)

vue会尽可能高效的渲染dom,也就是说通常会复用已有元素

key是dom对象的标识,当我们使用v-for指令时需要指定key,如果只是为了展示数据,列表的数据不会发生变化,key不会产生什么影响,如果涉及到数据的变化,需要更新DOM时,key就会产生很大的影响,如果不指定key,那么就相当于将index指定为key,那么vue会将index映射为key,使用index时,当我们在数据首部插入新数据时,由于index相同,但内容不同,那么旧的节点会被替换为新的,导致后面所有的节点都会被替换一次,造成资源的浪费,如果节点里面有输入框并且有输入的内容,那么input框会被直接拿来复用,导致本应该在第二行的文本框和内容在第一行显示,渲染出错的DOM;如果使用随机的key,那么在更新dom时,会发生毁灭性的销毁,删除所有旧节点,创建新的dom节点。

虚拟DOM

真实dom是一个对象,但他的元素非常多,如果页面的某个区域的数据发生了变化,去操作真实dom效率很低,性能会下降,更新真实dom时页面可能会出现一些混乱,不能随便去直接操作和改动真实dom,所以虚拟dom就因此出现了

虚拟dom也是一个对象,将真实DOM的内容抽取出来,以对象的形式模拟树形结构,使其更简洁明了。虚拟dom对象中包含了根节点的标签,key值,文本信息,节点属性等,还有elm属性存放真实DOM,同时有个children数组,存放子节点,子节点的结构也是一致的。通过对比两个虚拟节点进行更新,比操作真实dom更简单高效。

diff算法

diff算法通过比对两个虚拟dom,也就是oldvnode和vnode去判断需要更新的节点。

在更新虚拟DOM时,更新函数会调用patch()方法,会判断两个节点是否是同一个节点,如果不是,则直接销毁旧节点,渲染新节点,如果新旧节点相同,就执行patchVnode()方法,更新节点属性,然后判断节点的children,如果是文本节点,则直接替换内容,如果旧child为空,说明需要添加新节点,如果新child为空,说明要删除旧节点,如果新旧child都存在,那么进入updatechildren()

此时有四个指针分别指向旧节点的头和尾,新节点的头和尾,每一轮比较判断旧头节点和新头节点,旧尾节点和新尾节点,旧头节点和新尾节点,旧尾节点和新头节点是不是同一个(先判断key,再判断tag标签等),如果以上都不符合,则创建一个key->index的映射map,然后根据映射找到新节点对应的旧节点,如果找不到,就创建新节点,找到相应的节点后,如果是头指针,则往后移,如果是尾指针,则往前移,然后再一次按上述过程比较,直到头节点大于尾节点,此时旧节点的头节点大于尾节点,则创建新节点的头节点到尾节点之间的所有节点,如果新节点的头节点大于尾节点,则删除新旧节点之间的所有节点。

在vue3中,初始化vue会将不会更新的节点打上静态标记,在对比新旧vnode时,会跳过不会更新的节点,在vue3的diff算法中,会先找到旧vnode和新vnode的头指针和尾指针指向的第一个不相同的节点,如果旧的头指针大于尾指针,则创建新的头指针到尾指针中间的节点,反则删除旧的头指针到尾指针间的节点。如果新旧都有节点,则会根据新节点在旧vode中的位置,创建一个最长递增序列,移动不在序列中的元素,创建在旧vode索引为0的元素。

vue响应式原理

在vm实例初始化时,会为data中的数据进行响应式处理,也就是通过defineProperty()对属性进行转换为带有getter和setter方法的属性。具体步骤就是

调用observe(),如果不是对象则直接返回,如果是对象,返回Observer的实例化对象,将数据传入,每个Opserver实例有自己的dep实例;在Observer中,会判断所封装的数据是否是数组,如果是数组,则会改写数组的原型,将原型指向重写数组方法的对象,并调用observeArray()遍历数组的每一项,执行observe(),按上诉步骤实例化一个Observer,也就是说如果数组中有对象也会转换为响应式对象;如果是对象,则调用walk()方法,在walk方法中为对象的每一个属性调用defineReactive()方法。

在defineReactive()中,创建一个dep实例,如果shallow为false,则需要深度监视,递归调用observe()遍历属性的子属性,然后调用Object.defineProperty()方法,将obj上的属性转换为带有get和set的属性,在get()中调用dep.depend()收集依赖。

Watcher赋值给Dep.target,在访问数据时,就会把watcher加入到自己的subs中。在数据修改时,会触发set,派发更新,通过dep.notify()通知dep的所有watcher()进行更新。

vue3响应式原理

在vue3中,通过reactive将对象包装成一个Proxy代理的对象然后返回,用Proxy替代了defineProperty(),Proxy中使用get,set,deleteProperty拦截对象的读取,修改和删除,通过Reflect反射保证this的指向正确,使其指向代理后的对象,然后返回值或者修改值。

Proxy在get中通过track()收集依赖dep,在set,deleteProperty()中通过trigger()通知所有依赖进行更新。

$nextTick

由于Vue DOM更新是异步执行的,即修改数据时,视图不会立即更新,而是会监听数据变化,并缓存在同一事件循环中,等同一数据循环中的所有数据变化完成之后,再统一进行视图更新。为了确保得到更新后的DOM,所以设置了 Vue.nextTick(),就是在下次DOM更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的DOM。

Webpack

webpack是什么

在开发时会使用框架,ES6模块化语法,Less等CSS预处理器等语法进行开发,在浏览器中需要经过编译成浏览器能识别的CSS、JS语法才能运行,Webpack可以帮助我们完成这些事情,还能压缩代码,做兼容性处理,提升代码性能等。

webpack是一个静态模块的打包工具,他会在内部从一个或多个入口点构建一个依赖图,然后将项目中所需的每一个模块组合成一个或多个bundles,他们均为静态资源,可以直接在浏览器运行。

webpack功能:模块打包,将不同模块的文件打包整合在一起,保证他们之间的引用正确,执行有序。编译兼容,通过loader将浏览器无法识别的文件转换为浏览器可以识别的文件。能力扩展:通过plugin实现按需加载、代码压缩等功能。

Loader和Plugin的区别,常见的有哪些

Loader本质就是一个函数,该函数对接收到的内容进行转换,返回转换后的结果。所以Loader就是对其他类型的资源进行转译的预处理工作,处理那些非javascript文件。loader有两个属性,test:识别出哪些文件会被转换。use:定义在进行转换时,应该使用哪个loader。配置多个loader时,会下到上执行。

常见的loader有:

babel-loader:将代码转换为ES5。

ts-loader:将TS转换为JS

sass-loader:将scss/sass转换为CSS

style-loader:将模块导出的内容作为样式添加到DOM中

css-loader:加载css文件并解析import的css文件

less-loader:将less编译为css

node-loader:处理node.js插件

Plugin直译为插件,可以扩展webpack的功能,让webpack具有更多的灵活性。如打包优化,资源管理,注入环境变量。plugin会运行在webpack的不同阶段,贯穿整个编译周期。

常见的plugin:

clean-webpack-plugin:用于在打包前清理上一次项目生成的bundle文件

mini-css-extract-plugin:分离样式文件,css提取为独立文件

webpack-bundle-analyzer:可视化webpack输出文件的体积

speed-measure-webpack-plugin:可以看到每个loader和plugin执行耗时

optimize-css-assets-webpack-plugin:压缩css文件

css-minimizer-webpack-plugin:压缩css文件,用于webpack5

uglifyjs-webpack-plugin:压缩js文件

compression-webpack-plugin:启用gzip压缩

html-webpack-plugin:自动生成一个html文件,并且引用bundle.js文件

terser-webpack-plugin:可以压缩和去重

区别:除了以上的概念区别。loader运行在打包之前,plugin在整个编译周期都起作用。loader在module.rules中配置,类型为数组,每一项都是object,包含test, use属性;plugin在plugins中单独配置,类型为数组,每一项是一个plugin实例,参数通过构造函数传入。loader操作的是文件,对文件进行转换;plugin可以监听webpack运行周期中广播出的事件,在合适的时机通过webpack提供的api改变输出结果。

webpack的tree-shaking原理

webpack的tree-shaking在打包代码时,会将不会使用到的deed code删除

启动tree-shaking的三个条件:使用ESM规范编写模块代码,配置optimaization.usedExports为true,启动标记功能。启动配置优化功能,使mode为production

在打包时,会生成导出模块对应的dependency对象,记录到moudle对象的dependencies集合,并将其构建为moudleGraph中的每个moudle的exportinfo数组中的对象,在解析到导入语句时,会找到对应被导入模块对应的moudle的exportinfo数组,找到这个被导入的对象,并将其标记为已使用,这样所有被使用到的模块都会被打上已使用的标记,在生成代码的时候,会根据标记生成相应的代码,没有被使用到的模块会仅留下一个声明语句,等着被terser删除。

浏览器

跨域

什么是跨域?

跨域是一种安全机制,浏览器只接收来自同源的服务器响应的数据,如果不是同源,那么就会拦截服务器响应的数据,同源就是指协议,域名,端口号都要一致。

如何解决跨域“?

1.JSONP

利用script没有跨域限制的特点,给script添加src,地址是服务器的地址,并传递一个callback函数和要请求的数据,但是这种方法只能用于get请求。

2.前端代理

利用vue.config.js配置文件配置proxy,该方法利用的是服务器之间不存在跨域机制,由node服务器代为转发请求。该方法用于在开发的时候使用。通过axios.default.baseURL配置根路径,在config.js中在proxy里对根路径设置target,也就是目标地址,由node服务代为转发。

3.后端设置access-contrl-allow-origin

该方法只能用于支持xmlhttprequest的浏览器,ie9以下的不能使用

4.nginx

location 后面接根路径,花括号里设置服务器地址。

请求拦截器

请求拦截器:请求发出去之前都会经过请求拦截器,可以为请求带上token,或者设置加载动画

响应拦截器:可以对响应的数据进行处理,取消加载动画。

history和hash模式的区别(前端路由)

路径不同,hash模型有#号符,#号符后面的内容不会被包括在http请求中,对服务端没有影响,history的pushstate设置的新url可以是与当前url同源的任意url,而hash只能修改#后面的部分;pushState设置的新URL可以与当前URL一模一样,这样也会把记录添加到栈中;而hash设置的新值必须与原来不一样才会触发记录添加到栈中;pushState通过stateObject可以添加任意类型的数据到记录中;而hash只可添加短字符串;pushState可额外设置title属性供后续使用

hash:修改fragment不会触发网页重载,会改变浏览器的历史记录,会触发浏览器的onhashchange事件。

通过hash事件前端路由:通过监听hashchange事件来监听页面hash值的变化,通过解析hash的值来切换页面。

history:通过浏览器提供的原生history来实现路由,主要依赖history.pushState(将给定的DATA添加到当前标签页的历史记录栈中)和history.replaceState(将给定的data更新到历史记录栈中最新的一条记录中)方法和popstate事件(监听历史记录的变化)

这两种模式都可以实现前端路由,优点是无刷新切换内容,用户体验更佳,减轻服务端压力,缺点是初次加载耗时长,SEO效果不佳

浏览器缓存机制

缓存能够降低服务器端的压力,加快访问资源的速度,防止网络阻塞。常见的缓存有私有缓存(本地缓存)和代理缓存(协商缓存)

强缓存(本地缓存:浏览器第一次给服务器发送请求后,会把服务器返回的资源和对应的响应头缓存起来,服务器的响应状态码是200 OK,浏览器再次发送请求的时候,会先检查上一次保存的响应头中的Cache-Control字段,根据上次请求时间与cache-control的值计算过期时间,如果没有过期,则命中缓存,不再发送请求给服务器,如果没有命中,则会把请求发送给服务器,进入协商缓存。

Cache-Control: no-cache,不使用本地缓存,需要协商缓存;no-store,不使用缓存,每次都要发送请求,都要下载完整资源;public,可以被所有用户缓存,包括终端和cdn中间代理服务器;private,只能被终端用户缓存;max-age,从当前请请求开始,获取的响应被重用的时间;must-revalidate,当缓存过期时,需要去服务器验证缓存的有效性。

Expires:http:1.0出现的头信息,用于决定本地缓存策略的头,它是一个绝对时间,只要在Expires之前发送请求,本地缓存就有有效,如果同时出现Cache-Control:max-age,则max-age优先级更高。

协商缓存:如果服务器第一次返回的响应头没有Cache-Control和Expires,或者Cache-Control和Expires过期了,或者Cache-Control的属性设置为no-cache,则需要协商缓存。浏览器发送请求与服务器进行协商,询问服务器是否需要更新,服务器根据http头信息中的Last-Modified/If-Modified-SinceETag/If-None-Match来判断浏览器中的缓存是否过期,如果没有过期,则无需下载资源,服务器返回304 Not Modified,告诉浏览器直接从缓存中读取资源。如果过期了,则服务器返回最新的资源给客户端,状态码是200 OK

Last-Modified/If-Modified-Since:服务器第一次响应时,将资源的最新修改时间放在响应头的Last-Modified中返回。浏览器再次发送请求时,会将上一次服务器返回的最新修改时间放在请求头的If-Modified-Since发送给服务器。服务器便根据这个值判断资源是否在这个期间修改过。如果资源被修改过,服务器返回最新资源时,更新Last-Modified响应头内容。

ETag/If-None-Match:由于Last-Modified的单位是秒,如果在一秒内改变,那么缓存可能失效但服务器没有判断出来,或者资源修改过但又被还原,虽然内容没变,但更新时间变化了,服务器会将最新的资源返回,虽然没有变化。为了解决上述问题,ETag/If-None-Match不再依赖于修改时间,而是依赖于文件哈希值来精确判断,他们的流程类似,但ETag/If-None-Match是根据资源的摘要信息来判断(如md5 hash),服务器根据请求头中的If-None-Match与最新资源的摘要信息相比较。不论资源是否变化,都会在返回的响应头中放入ETag。但是这种方法的缺点是计算成本较大,对于大文件,只读取文件的部分内容,容易判断出错。另一个缺点是有计算误差,不同服务端可能采取不同的计算方式。对于使用服务器集群来处理请求的网站缓存命中率会降低。

强制缓存的优先级高于协商缓存,ETag优先级高于Last-Modified

缓存位置 :

        Service Worker:脱离了浏览器的窗体,无法直接访问DOM,有离线缓存、消息推送、网络代理的功能,独立于主javascript线程,设计完全异步,大量使用promise,不能访问dom, 不能使用XHR和localStorage

        Memory Cache:内存缓存,效率快,存活时间端短(渲染进程结束后,内存缓存就不在了)

        Disk Cache:磁盘缓存,效率慢,存储容量大时间长,适用于使用率高,体积大的文件

        Push Cache:推送缓存

Token,session,JWT,cookie,localstorage,sessionstorage

由于HTTP是无状态的,每次发起请求都是一个全新的请求,服务器不知道客户端的历史请求记录,cookie和session的主要目的是为了弥补http的无状态特性。

session:用户第一次登陆后,服务器开辟一块内存空间,存储session对象,存储结构为ConcurrentHashMap,同时会为用户生成唯一的标识,也就是session id,并通过响应头set-cookie: jsession=xxx字段,随着响应发送给给客户端,客户端每次发送请求的时候在cookie中携带session,服务端对session进行验证。但是随着用户的增多,每个用户都要存储一份session,这会对服务端产生压力和开销。如果服务器做了负载均衡,如果A服务器存储了session,某个时间内访问量激增,会转发到B服务器,但B服务器没有A的session,会导致session失效,或者将A的session复制到服务器,但又会浪费内存,如果存在一个公共服务器上,如果这台服务器出现故障,那么A和B都会失效。

token就可以解决这个问题,token根据一些用户信息进行加密后返回给客户端,简单token由uidtimesign(签名)组成,客户端将token存在localstorage里或cookie里,每次发送请求时在请求拦截器中为请求头加上token,服务端对token进行解密验证。session会有跨域问题,而token没有,因此token可以在跨域的时候使用(不使用cookie时)

refresh token用于刷新access token,如果没有refresh token,也可以刷新access token,但是每次刷新会很麻烦,要输入用户名与密码,refresh token未过期,access token过期,客户端发送请求申请新的access token,服务端返回新的access token。refresh token存在服务器端,只有申请新的access token时才会验证。

cookie是由服务端生成,发送给客户端,客户端每次发送请求时都浏览器都会自动带上cookie,cookie大小有限制,只有4kb,且不安全,所以不能存储密码等敏感信息。

cookie有session cookiespersistent cookies,会话cookie存储在内存,不会写入磁盘,当浏览器关闭时,cookie将永久消失。session cookies没有有效期max-age或者expires,浏览器可以使用会话还原,使cookie保持永久状态。永久性cookies不会在客户端关闭时过期,在max-age或expires有效期后过期。

cookies可以通过https协议通过加密的方式发送到服务器。secure为true时,cookie在http中无效。加密时也不能存敏感信息。

HttpOnly使cookie不可以被客户端脚本访问。如果不设置,6js脚本也可以获取到用户信息,导致cookie信息泄露,增加被跨站脚本攻击的威胁。攻击者可以通过重播窃取的cookie,伪装成用户或者敏感信息,进行跨站脚本攻击。

Domain和Path定义了cookie的作用域,即cookie应该发送给哪些URL,Domain如果不指定,默认为当前主机,如果指定了,则一般包含子域名。Path指定了匹配的路径,包含指定路径的url都会匹配。

JWTjson web token,跨域时使用。jwt中的信息是经过数字签名的,由headerpayloadsignature三部分组成。header通常由令牌的类型(即JWT)和使用的签名算法如(HS256或RSA)组成,然后通过base64url编码形成jwt的第一部分。payload包含一个有关用户或者其他数据的声明。signature是一个签名信息根据header和payload以及密钥生成。通过在请求头中使用Bearer模式添加JWT,jwt不需要查询数据库,因为jwt中包含了用户信息,但token还需要。

session使服务端有状态化,token使服务端无状态化

localstorage永久性的,关闭浏览器不会自动删除数据,除非主动删除

sessionstorage浏览器关闭时就会删除,刷新页面不会,复制标签页,sessionStorage也会被复制,通过window.open和改变localtion.href都可以获取到sessionstorage的数据。

sessionstorage和localstoreage通过setitem, getitem, removeitem, clear来添加,读取,删除和清空。

前端性能优化

减少http请求次数:文件过多,请求就越多,因此可以最小化网页必须加载的文件(压缩、小图片使用base64编码)和图像的数量(精灵图)。使用延迟加载技术,该技术仅在用户向下滚动到页面上的图像时发送服务器请求。缩小和合并css和javascript文件,删除不必要的代码,如不必要的字符,空格,注释等,将多个文件合并为一个。减少外部脚本的数量,使用内容分发网络(CDN),将静态资源缓存在cdn,用户请求时,资源会从最近的cdn返回给用户。

使用http2.0:采用二进制格式传输数据,而1.1是文本格式;对消息头采用Hpack进行压缩传输,1.1会携带大量的冗余头信息,浪费了宽带资源;异步连接多路复用,服务器不用按照请求的顺序发送响应;server push,服务器端能够更快的把资源推送到客户端。

设置浏览器缓存策略强缓存协商缓存

白屏时间做加载动画,增加用户体验

使用gzip压缩

css放在头部,javascript文件放在底部

使用字体图标代替图片图标

图片懒加载:对img标签的url添加到data-src属性,这样src属性就读不到,图片就不会加载了,然后根据判断条件来触发图片加载,触发图片懒加载的三个事件:scroll, resize, oritentionChange,事件触发时判断图片是否在是视口内,通过srollTop,offsetTop和clientHeight来判断,如果在视口内,也就是scrollTop+clientHeight>offsetTop,就将data-src赋给src,需要懒加载的图片全部加载完成后,移除监听的事件。使用background时也可以进行懒加载,为需要懒加载背景图片的元素添加css类选择器lazy,将background-image设置为none,当元素进入视口后,将lazy类移除。懒加载时需要防抖处理。

慎用全局变量,减少重绘回流节流防抖,少用闭包,减少内存泄漏

避免iframe嵌套网页

路由懒加载

按需加载

webpack优化:减少代码体积,提取第三方库代码,webpack dll优化

Tree Shaking 删除冗余代码

多图优化

选用正确的格式,如png,jpeg,svg,webp等

使用精灵图/雪碧图,减少http请求数,小图片使用base64编码

使用CDN

图片懒加载和预加载

前端性能指标

TTFB首包时间,资源请求到获取第一个字节之间的时间,包括重定向时间,service worker启动时间,dns查询时间,连接和tls协商,请求,直到响应的第一个字节到达的时间。

FCP首屏时间,首次内容绘制的时间,指页面从开始加载到页面内容的任何部分在屏幕上完成渲染的时间。

FP白屏时间,首次渲染的时间,页面第一次出现内容或颜色的时间

LCP视窗最大可见图片或文本块的渲染时间,如<img>,<svg>中的<image>元素,<video>,带<url>背景图的元素,块级元素有文本节点或者内联文本子元素。

FID首次输入延迟时间,测量用户第一次与页面交互到浏览器对交互作出响应,时间处理的时间。

性能测试工具Lighthouse,WebPageTest,PageSpeed Insights

如何减少白屏时间

DNS解析优化:dns预解析:在Link标签里通过dns-prefetch预解析;域名收敛:减少页面中的域名数量从而减少所需要的dns解析过程的耗时

TCP链接优化:通过preconnect在请求发送前预先执行一些操作,包括dns解析,tcp握手等

请求优化:使用http2协议

页面解析优化:服务端渲染;使用插件进行简单的预渲染;资源预加载:prefetch(浏览器会在空闲时加载)和preload(浏览器会立即加载)

vue优化:路由懒加载、开启gzip,进行cdn加速,开启vue服务渲染模式(两个入口文件,一个对应服务端,一个对应客户端)、删掉多余的console.log、添加Loading效果

浏览器输入url后的发生了什么

本地缓存:先检查是否命中缓存,如果命中了,则不用发送请求,直接到资源解析阶段,如果没有命中,需要发送请求

DNS解析:发送请求前,需要对url进行域名解析,先从客户端的主机检查是否有这个url的缓存,如果没有,就向本地域名服务器进行递归查询,本地域名服务器先检查是否有这个url的缓存,如果没有,本地域名服务器开始进行迭代查询,先向根域名服务器发送查询,根域名服务器告诉本地域名服务器下一次查询的顶级域名服务器的ip地址,本地域名服务器向顶级域名服务器查询,顶级域名服务器告诉本地域名服务器下一次查询的权限域名服务器的ip地址,本地域名服务器再向权限域名服务器查询,知道权限域名服务器把最后的结果告诉本地域名服务器,本地域名服务器把结果发起查询的主机。

建立TCP连接:客户端与服务器进行三次握手建立起TCP连接,客户端发送第一次握手,同步位SYN置1,seq=x,状态从CLOSED变为SYN-SEND,服务器收到后,发送确认报文段,ACK=1,SYN=1,确认号为x+1,seq=y,状态从LISTEN变为SYN-RCVD,客户端收到后,发送确认报文段,ACK=1,确认号为y+1,seq=x+1,状态从SYN-SEND变成ESTABLISHED,服务器收到后,状态也变为ESTABLISHED,连接建立。

为什么要三次握手:防止过期的连接请求发送到服务器,如果只有两次握手,服务器建立连接等待客户端发送数据,浪费资源,如果三次握手,客户端没有建立连接的请求,不理会服务器,服务器没有收到第三次握手的确认报文,就不会建立连接。

发送HTTP请求服务器返回数据

断开TCP连接:客户端断开TCP连接,向服务器发送报文,YIN=1,seq=u,状态从ESTABLISHED变为YIN-WAIT-1,服务器收到后,发出确认,ACK=1,ack=u+1,seq=v,状态从ESTABLISHED变为CLOSE-WAIT,客户端收到后,状态变为YIN-WAIT-2,期间服务器还可以发送数据。服务器没有数据发送时,发送连接释放报文段,YIN=1,seq=w,ack=u+1,ACK=1,状态变为LAST-ACK,客户端接收到后,发送确认报文段,ACK=1,seq=u+1,ack=w+1,状态变为TIME-WAIT,服务器收到后,状态变为CLOSED,客户端等待2MSL后状态变为CLOSED。

为什么要等待2MSL:为了防止最后一个确认报文段丢失了,服务器再次发送一次释放报文段,而客户端已经关闭了的情况。

客户端对资源进行解析:处理HTML标记并构造DOM树,当解析器发现非阻塞资源,比如图片,浏览器会请求这些资源并继续解析,当遇到一个css文件时,也可以继续解析,当遇到script标签时,会阻塞渲染并停止解析,除非有async或者defer属性。

构造CSSOM树,遍历CSS中的每个规则集,根据CSS选择器创建具有父、子、和兄弟关系的节点树。

将DOM树和CSSOM树组合成一个Render树,根据渲染树计算布局,计算每个节点的几何大小和位置,最后就是将各个节点绘制在屏幕上。

get和post的区别

get一般用来查询,post一般用于增加或修改,get请求使用url携带参数,但url的长度有限,post的参数在请求体中,能携带更多数据。

get请求的数据会保存在历史记录中,且能被缓存,但post请求不会,所以更安全,post请求回退会重新发送请求

get请求只能携带ascii码的数据,而post可以携带json等

get请求只会发送一次请求,而post会发送两次,第一次发送header,服务器返回100,第二次发送data,服务器返回200(firefox只发一次)

常用的Http请求头和响应头

请求头:

Accept:浏览器可以接受的服务为i返回的类型(text/html)

Accept-Encoding:浏览器的编码方式,通常指某种压缩方法

Accept-Language:浏览器接受的语言

Connection:持久连接还是非持久连接

Host:请求资源的主机号和端口号

Referer:告诉服务器请求从哪个页面过来的

User-Agent:客户端使用的操作系统和浏览器的名称和版本

Cache-Control:缓存策略

Cookie:存储一些用户信息

响应头:

Cache-Control

Content-Type:资源的类型和字符编码

Content-Encoding:资源的编码方式

Date:发送资源的时间

Expires:缓存的绝对时间

Last-Modefied:缓存相关

Etag:缓存相关

Access-Control-Allow-Origin

Access-Control-Allow-Methods

Http状态码

1XX:请求已被接受,继续处理

100 continue,继续,客户端应继续其请求

101 switching protocols,切换协议,服务器根据客户端的请求切换协议

2XX:表示请求已被成功接收、理解、接受

200 OK,请求成功,一般用于GET和POST请求

201 Created,已创建,成功请求并创建了新的资源

202 Accepted,已接受,已经接受请求,但未处理完成

203 Non-Authoritative Information,非授权信息,请求成功,但返回的meta信息不在原始的服务器,而是一个副本

204 No Content,无内容,服务器成功处理,但未返回内容

205 Reset Content,重置内容,服务器处理重构,客户端应重置文档视图

206 Partial Content,部分内容,服务器成功处理部分GET请求

3XX:重定向,信息不完整需要进一步补充

300 Multiple Choices,多种选择,请求的资源课包括多个位置,响应可返回一个资源特征与地址的列表用于客户端选择

301 Moved Permanently,永久移动。请求的资源已被永久的移动到新URL,返回信息会包括新的URL,浏览器会自动定向到新URL,今后的新请求都应使用新URL

302 Found,临时移动,与301类似,但资源只是临时被移动,客户端应继续使用原有URL

303 See Other,查看其他地址,与301类似,使用GET和POST请求查看

304 Not Modified,未修改,所请求的资源未修改,服务器不会返回任何资源。

305 Use Proxy,使用代理

307 Temporary Redirect,临时重定向,与302类似,使用GET请求重定向

4XX:客户端错误,请求有语法错误或请求无法实现

400 Bad Requqest,客户端请求的语法错误,服务器无法理解

401 Unauthorized,请求要求用户的身份认证

403 Forbidden,服务器理解请求客户端的请求,但是拒绝执行

404 Not Found,服务器无法根据客户端的请求找到资源。

5XX:服务器端错误

500 Internal Server Error,服务器内部错误,无法完成请求

501 Not Implemented,服务器不支持请求的功能,无法完成请求

503 Service Unavailable,由于超载或系统维护,服务器暂时的无法处理客户端的请求。

HTTP和HTTPS的区别

http协议使用明文传输数据,不提供任何方式的数据加密。HTTPS在HTTP的基础上加入了SSL/TLS协议,SSL/TLS依靠证书来验证服务器的身份,并为浏览器和服务器之间的通信加密,HTTPS协议可以理解为HTTP协议的升级,就是在HTTP的基础上增加了数据加密。

SSL:对称加密,非对称加密,摘要算法,数字签名

对称加密就是加密和解密使用的密钥都是同一个,需要保证密钥的安全

非对称加密存在两个密钥,一个叫公钥,一个叫私钥,两个密钥是不同的,公钥可以公开,私钥需要保密。

在https中采用的非对称加密+对称加密,也就是混合加密,就是将明文使用对称加密的密钥加密后,再使用对方的公钥加密,接受方使用私钥解密后再使用对称密钥解密得到明文。

HTTP1.0/HTTP1.1/HTTP2.0

http1.0每次与服务器交互,都需要重新建立连接,比如一个HTML里有3个其他的资源文件,那么需要建立4次TCP连接。

HTTP1.1支持长连接,在一个TCP连接上可以传送多个HTTP请求和响应,减少了建立和关闭连接的消耗和延迟,比如上面的例子,只用建立一个TCP连接。客户端可以不用等待上一次请求结果返回,就可以发出下一次请求,但服务端要按照顺序以此返回响应结果。HTTP1.1引入了更多的缓存控制策略(Etag, if-none-match, last-modify, if-modified-since)。

HTTP2.0多了许多特性:多路复用,客户端和浏览器都可以同时发送多个请求或回应,不用按顺序一一对应。二进制分帧,HTTP2采用二进制格式传输数据,而非文本格式。首部压缩,前一个请求发送了所有头部字段,第二个请求只需要发送差异数据,减少了冗余数据,降低开销。服务器推送,服务器会把一些客户端需要的资源一起推送到客户端。

前端安全及防范措施

跨站脚本攻击(xss):攻击者通过在目标网站上注入脚本,使之在浏览器上运行,利用这些恶意脚本,攻击者可以获取用户的敏感信息,如cookie、sessionid等

反射型xss:也叫非持久型,攻击者构造出特殊的URL,其中包含恶意script代码,用户点击恶意URL,服务器将恶意代码取出,拼接在HTML中返回给浏览器,浏览器执行,恶意代码也被执行,可能会被盗取cookie,发布用户没有发布的内容,或者删除一些用户不想删除的内容。

存储型xss:也叫持久型xss攻击,攻击者通过表单,评论等方式将恶意代码提交到服务器,服务器将数据保存到数据库中,当浏览器请求数据时,脚本从服务器上传回并执行

DOM型:这种方法跟反射型xss有点像,通过URL传入恶意代码,前端javascript取出url中的恶意代码并执行,达到攻击者的目的。

防御:

        设置HttpOnly:避免cookie被客户端恶意javascript窃取

        使用内容安全策略CSP,设置白名单

        避免使用innerHTML,outerHTML,而是使用textContent,setAttribute()等。

跨站请求伪造CSRF:利用用户在被攻击网站已经获取的注册凭证,绕过服务器的用户验证,达到冒充用户对被攻击的网站执行某些操作。用户访问恶意网站,恶意网站向用户登录过的一个目标网站发送请求,带上用户的cookie,利用服务器对客户端的信任,达成攻击目的。比如盗取用户资金。

防御:

        验证码,强制用户与网站进行交互

        在请求头中加入token验证字段,浏览器不会自动携带token去请求,且token可以携带一段加密的jwt用作身份认证,使携带cookie不能表明用户身份,网站拒绝请求。

Echarts

主要配置:

series:系列列表,指定图表的数据,图表的类型,可以多个图表重叠

xAxis:指定x轴的数据,设置x轴相关的配置

yAxis:设置y轴相关的配置

grid:网格配置,可以控制线型图,柱状图,图表大小

title:设置图表标题

tooltip:工具箱组件,可以另存为图片等功能

color:设置线条颜色

设计模式

工厂模式

单例模式

观察者模式

迭代器模式

TS

Interface和Type的区别

都可以起到对类型的限制和规范的作用,他们不关心实现细节,只规定和限制类或变量必须提供对应的属性和方法,都可以描述对象和函数。描述的类型都可以被class实现

区别:Type不可知定义后重新添加内容,而Interface则总是可以扩展新内容。Type并没有实际创建一个新的类型,而是创建。Interface的extends中同名字段的类型必须是兼容的。type可以描述一些基本类型、联合类型(如string | string[])、元素(如[number, string])。Interface可以被重复定义,并将合并所有声明的属性伪单个接口,而Type不可重复定义。Type可以使用in关键字动态生成属性,而interface的索引值必须是string或number内心,并不支持动态生成

快速排序和归并排序

选择排序思路:定义两个指针,left指向数组第一个元素,尾指针指向数组最后一个元素,定义一个key为数组第一个元素,然后在left小于right的时候进行循环,每一轮循环先从右往左找到第一个比key小的数,然后从左往右找到第一个比key大的元素,交换这两个元素的值,当left与right重叠时,循环结束,因为先从右往左找,所有这个重迭的数一定比key小,然后交换这个数和key的值,使key的左边都比他小,右边都比他大,然后分别对key的左右两边进行递归,直到左边或者右边长度为0或1。时间复杂度nlogn;最好logn ,最坏n^2

归并排序思路:同样设置头指针和尾指针,找到中间位置,然后递归的进行拆分,知道拆分的左右两边长度为1,然后对左右两边都定义头指针,依次比较大小,将小的数用一个辅助数组存起来,直到某一变遍历结束,如果有剩余的,直接放在辅助数组的最后,因为是有序的,然后将辅助数组复制到原来的数组上,这样先拆分,再进行有序数组的合并,最后实现排序。时间复杂度nlogn,空间n

项目

浏览器还原度检测插件:因为团队在搭建公司内部使用的组件库,需要通过录入规则去检测这些组件是否符合设计要求,比如按钮的边框,颜色,大小等是否正确,规则集录入是通过node搭建服务端,向服务端发送请求传入要检测的页面url,匹配目标元素的正则表达式以及规则和对比方法,服务器端通过url获取页面所有元素节点,筛选并计算元素属性,获取元素样式然后保存在elements数组中,提取正则表达式通过对比class属性来对elements进行过滤,然后根据对比方式的不同得到不规范样式,规则录入的目标就是找到合适的正则表达式能够匹配到我们想检测的元素,因为规则是业务那边提供的,然后这个插件就是当我们录入了规则集以后,怎么利用这些规则集快速拿到不规范样式的数量,在页面中的位置,以及它们的具体情况,因此插件的作用就是帮助业务人员快速定位不规范样式所在元素的位置,直观展示每种不规范样式的数量,他们的路径,以及预期的值,这个插件基本上是由我一个人独立完成,主要有我自己封装的滑动tabs标签,标签的标题区根据检测结果来展示,比如高度,宽度,背景颜色,边框圆角等等,每个标题都渲染了对应的不规范样式的数量,标题过长时点击左右两侧标题会自动滑动到中间位置,内容区会根据标题的切换而展示不同的内容。

滚动距离:元素到左边距离减去容器的一半加上自身宽度的一半

 var scrollLeftNum =itemList[i].offsetLeft - containerW / 2 + itemList[i].offsetWidth / 2; 
 container.scrollLeft = scrollLeftNum;

内容区的左边是echarts 图表和进度条组成,用来展示每种不规范样式的占比和数量最多的前五种样式,右边是一个滑动列表用来展示所有不规范样式的详情,比如路径、当前值和预期值,点击每个列表的定位按钮和高亮按钮,背景页面对应的元素会滑动到屏幕中间的位置并高亮,高亮是通过添加轮廓线实现的,同时检测结果、元素高亮状态等都做了数据持久化,而且不同标题,不同背景页面互不影响。

插件需要一个manifist.json文件配置插件页面和脚本,和背景页面通信是用chorme.runtime.onMessage.addListiener监听事件,chorme.runtime.sendMessage发送消息。

法务系统是提供给财经法务部工作人员使用,我主要参与了法务意见出具,法律评审功能模块的开发,是一个刚开始的项目,涉及到公司的保密,我主要负责法务意见出具的开发,使用了20多种组件,使用了并发请求去处理多个请求,因为当时有个页面的表单有7个下拉框需要发送请求过去下拉框数据,单独为每个请求进行处理然后将结果赋值给下拉框配置项会使代码量特别大,可读性变差,于是改进代码,使用promise.all去处理并发请求,统一处理返回结果,提高代码可读性

常见前端题

 介绍一下前端的学习经历,通过什么方式学习前端,看过什么书                                                            
 

红宝书

Array.from()可以将可迭代对象转换为数组,第二个参数是一个函数,类似于map(),第三个参数指定第二个函数的this;Array.of()可以将参数转换为数组,类似于Array.prototype.slice.call()


  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值