01前端面试复盘

css

盒模型

盒模型有四个部分组成,分别是margin,border,padding,content。
标准盒模型和IE盒模型的区别在于设置width和height,标准盒模型的width和height属性的范围只包含了content;IE盒模型则包含了content、padding和border。
可以通过修改元素box-sizing属性来改变元素盒模型:
box-sizing:content-box标准盒模型;
box-sizing:border-box IE盒模型。

CSS3动画

属性有transform、transition和animation;

  • transform用来设置元素的形状改变,主要rotate(旋转)、scale(缩放)、skew(扭曲)、translate(移动)、matrix(矩阵变形);transform-origin基点—所有的变形都是基于基点,基点默认为元素的中心点;
  • transition是用来设置样式的属性值是如何从一种状态变平滑过渡到另外一种状态;transition-property(变换的属性,即那种形式的变换:大小、位置、扭曲等);transition-duration(变换延续的时间);transition-timing-function(变换的速率);transition-delay(变换的延时);
  • animation比较类似于flash中的逐帧动画,这种逐帧动画是由关键帧组成,很多个关键帧连续的播放就组成了动画,在CSS3中是由属性keyframes来完成逐帧动画的;animation-name用来设置动画的名称,可以同时赋值多个动画名称;animation-duration用来设置动画的持续时间;

transition和animation的区别

transition是过渡属性,强调过渡,它的实现需要触发一个时间才能执行动画。
animation是动画属性,它的实现不需要触发事件,设定好时间之后可以自己执行,且可以循环一个动画。

伪类和伪元素

伪类(冒号)是选择器的一种,它用于选择处于特定状态的元素,作用对象是整个元素;
伪元素(双冒号)作用于元素的一部分,并不存在于dom之中,只存在页面之中;
::after匹配出现在原有元素的实际内容之后的可样式化元素(在某元素之后插入某些内容)

canvas画布

用于图形的绘制,通过js来完成,默认情况写元素是没有边框和内容的;通常需要指定一个id属性(在脚本中引用),width和height属性定义画布的大小;在页面中可以使用多个元素;调用它的 getContext() 方法获取绘图上下文环境,参数必须为 2d

canvas和svg的区别

(1)从图像类别区分,Canvas是基于像素的位图,而SVG却是基于矢量图形。可以简单的把两者的区别看成photoshop与illustrator的区别。
(2)从渲染模式上来说,Canvas属于 即时模式,而SVG则是 保留模式。
(3)从结构上说,Canvas没有图层的概念,所有的修改整个画布都要重新渲染,而SVG则可以对单独的标签进行修改。
(4)从操作对象上说,Canvas是基于HTML canvas标签,通过宿主提供的Javascript API对整个画布进行操作的,而SVG则是基于XML元素的。
(5)从功能上讲,SVG发布日期较早,所以功能相对Canvas比较完善。
(6)关于动画,Canvas更适合做基于位图的动画,而SVG则适合图表的展示。

水平垂直居中的实现

  • 利用绝对定位,先将元素的左上角通过top:50%和left:50%定位到页面的中心,然后再通过translate来调整元素的中心点到页面的中心。该方法需要考虑浏览器兼容问题。
.parent {
    position: relative;
}
 
.child {
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%,-50%);
}
  • 利用绝对定位,设置四个方向的值都为0,并将margin设置为auto,由于宽高固定,因此对应方向实现平分,可以实现水平和垂直方向上的居中。该方法适用于盒子有宽高的情况:
.parent {
    position: relative;
}
 
.child {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    margin: auto;
}
  • 利用绝对定位,先将元素的左上角通过top:50%和left:50%定位到页面的中心,然后再通过margin负值来调整元素的中心点到页面的中心。该方法适用于盒子宽高已知的情况;
.parent {
    position: relative;
}
 
.child {
    position: absolute;
    top: 50%;
    left: 50%;
    margin-top: -50px;     /* 自身 height 的一半 */
    margin-left: -50px;    /* 自身 width 的一半 */
}
  • 使用flex布局,通过align-items:center和justify-content:center设置容器的垂直和水平方向上为居中对齐,然后它的子元素也可以实现垂直和水平的居中。该方法要考虑兼容的问题,该方法在移动端用的较多:
.parent {
    display: flex;
    justify-content:center;
    align-items:center;
}

重排和重绘

  • 重绘:某些元素的外观被改变。
  • 重新生成布局,重新排列元素。
  • 重绘不一定导致重排,但重排一定会导致重绘。
    下面情况会发生重排:
  • 页面初始渲染,这是开销最大的一次重排
  • 添加/删除可见的DOM元素
  • 改变元素位置
  • 改变元素尺寸,比如边距、填充、边框、宽度和高度等
  • 改变元素内容,比如文字数量,图片大小等
  • 改变元素字体大小
  • 改变浏览器窗口尺寸,比如resize事件发生时
  • 激活CSS伪类(例如::hover)
  • 设置 style 属性的值,因为通过设置style属性改变结点样式的话,每一次设置都会触发一次reflow
  • 查询某些属性或调用某些计算方法:offsetWidth、offsetHeight等,除此之外,当我们调用 getComputedStyle方法,或者IE里的 currentStyle 时,也会触发重排,原理是一样的,都为求一个“即时性”和“准确性”。
  • 重排优化建议
    减少重排范围、减少重排次数(样式集中改变、分离读写操作、将dom离线、使用absolute或fixed脱离文档流、优化动画)

JavaScript

JavaScript数据类型

  • 栈:原始数据类型(Undefined、Null、Boolean、Number、String),因频繁使用数据,所以存储于栈中;
  • 堆:引用数据类型(对象、数组和函数)脱离,如果存储于栈中,会影响程序的运行性能;引用数据类型在栈中存指针,指针会指向该实体的起始地址,当解释器寻找该引用值时,会检索栈中的地址,取得地址之后从堆中获得实体。

函数声明(变量)提升

JavaScript函数里的所有声明(只是声明,但不涉及赋值)都被提前到函数体的顶部,而变量赋值操作留在原来的位置。
从JavaScript底层运行机制分析,JavaScript的运行阶段分为预编译阶段和执行阶段,声明提前是在JavaScript引擎的预编译时进行,是在代码开始运行之前。
注意: 同名情况下,函数声明提升优先级要高于变量声明提升,且提升后该函数声明定义不会被提升后的同名变量声明所覆盖,但是会被后续顺序执行的同名变量赋值所覆盖。

  1. 解析和预编译过程中的声明提升可以提高性能,让函数可以在执行时预先为变量分配栈空间;
  2. 声明提升还可以提高JS代码的容错性,使一些不规范的代码也可以正常执行。

event loop运行机制

  • 宏任务:常见的定时器,用户交互事件等等,包含
    script(整体代码)
    setTimeout
    setInterval
    I/O
    UI交互事件
    postMessage
    MessageChannel
    setImmediate(Node.js 环境)
  • 微任务:Promise相关任务,MutationObserver等,包含
    Promise.then
    Object.observe
    MutaionObserver
    process.nextTick(Node.js 环境)

在事件循环中,每进行一次循环操作称为 tick,每一次 tick 的任务处理模型是比较复杂的,但关键步骤如下:

  • 执行一个宏任务(栈中没有就从事件队列中获取)
  • 执行过程中如果遇到微任务,就将它添加到微任务的任务队列中
  • 宏任务执行完毕后,立即执行当前微任务队列中的所有微任务(依次执行)
  • 当前宏任务执行完毕,开始检查渲染,然后GUI线程接管渲染
  • 渲染完毕后,JS线程继续接管,开始下一个宏任务(从事件队列中获取)
    在这里插入图片描述
    宏任务和微任务以及event loop,利用银行办理流程介绍了宏任务和微任务。

防抖节流

防抖和节流目的:都是优化高频率触发执行js代码的一种手段

  • 防抖:在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。(单位时间内事件触发会被重置,避免事件被误伤触发多次)
  • 节流:规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效。(单位时间内事件只能触发一次)
    函数防抖是某一段时间内只执行一次,而函数节流是间隔时间执行。
    函数防抖的应用场景
    连续的事件,只需触发一次回调的场景有:
  • 搜索框搜索输入。只需用户最后一次输入完,再发送请求
  • 手机号、邮箱验证输入检测
  • 窗口大小Resize。只需窗口调整完成后,计算窗口大小。防止重复渲染。
    函数节流的应用场景
    间隔一段时间执行一次回调的场景有:
  • 滚动加载,加载更多或滚到底部监听
  • 谷歌搜索框,搜索联想功能
  • 高频点击提交,表单重复提交

数组有哪些原生方法

  1. 数组和字符串的转换方法:toString()、toLocalString()、join() 其中 join() 方法可以指定转换为字符串时的分隔符。
  2. 数组尾部操作的方法 pop() 和 push(),push 方法可以传入多个参数。
    数组首部操作的方法 shift() 和 unshift() 重排序的方法 reverse() 和 sort(),sort() 方法可以传入一个函数来进行比较,传入前后两个值,如果返回值为正数,则交换两个参数的位置。
  3. 数组连接的方法 concat() ,返回的是拼接好的数组,不影响原数组。
  4. 数组截取办法 slice(),用于截取数组中的一部分返回,不影响原数组。
  5. 数组插入方法 splice(),影响原数组查找特定项的索引的方法,indexOf() 和 lastIndexOf() 迭代方法 every()、some()、filter()、map() 和 forEach() 方法
  6. 数组归并方法 reduce() 和 reduceRight() 方法

原型和原型链

ECMA-262把原型链定义为ECMAScript的主要继承方式。其基本思想是通过原型继承多个引用类型的属性和方法。
构造函数、原型和实例的关系:每个构造函数都有一个原型对象,原型有一个属性指回构造函数,而实例有一个内部指针指向原型。如果原型是另一个类型的实例呢?那就意味着这个原型本身有一个内部指针指向另一个原型,相应的另一个原型也有一个指针指向另一个构造函数,这样就在实例和原型之间就构造了一条原型链。这就是原型链的基本构想。
原型链

ES6

  1. 类(class)是es6中的基础性语法糖结构,实际使用的是原型和构造函数的概念。注意: 函数声明可以提升,但类定义不能,而且不能重复声明;函数受函数作用域限制,而类受块作用域限制。
  • constructor关键字用于在类定义内部创建类的构造函数。方法名constructor会告诉解释器在使用new操作符创建类的新实例时,应该调用这个函数。构造函数的定义不是必需的,不定义构造函数相当于将构造函数定义为空函数。
  • 注意: 构造函数与类构造函数的主要区别是,调用类构造函数必须使用new操作符。而普通构造函数如果不适用new调用,那么就会以全局的this(通常是window)作为内部对象。调用类构造函数时如果忘了使用new,则会跑出错误。
  1. **箭头函数和普通函数的区别:**箭头函数不能使用arguments、super和new.target,也不能用作构造函数。此外,箭头函数没有prototype属性。箭头函数不会创建自己的this, 所以它没有自己的this,它只会在自己作用域的上一层继承this。所以箭头函数中this的指向在它在定义时已经确定了,之后不会改变。
  2. 对象和数组的解构: 对象的解构是利用属性名;数组的解构是利用位置(坐标)一一对应的。数组的解构可用于变量声明、变量赋值;不定元素变量(剩余元素变量)必须是解构的最后一个变量,其后面不能再有别的变量,否则会抛出语法错误。
let color = ['red', 'green', 'blue'];
let [firstColor, ...secondColor, error] = color; 
// Uncaught SyntaxError: Rest element must be last element
  1. proxy实现的功能
    Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。proxy代替Object.defineProperty()方法实现数据响应式。
let pro = new Proxy(target,handler);
//target参数表示所要拦截的目标对象
//handler参数是用来定制拦截行为的

之所以 Vue3.0 要使用 Proxy 替换原本的 API 原因在于 Proxy 无需一层层递归为每个属性添加代理,一次即可完成以上操作,性能上更好,并且原本的实现有一些数据更新不能监听到,但是 Proxy 可以完美监听到任何方式的数据改变,唯一缺陷就是浏览器的兼容性不好。

promise的理解

Promise 是一个对象,它代表了一个异步操作的最终完成或者失败。
Promise解决异步操作导致的回调地狱,现在可以通过回调绑定到返回的Promise上,形成Promise链。
一个 Promise 必然处于以下几种状态之一:

  • 待定(pending): 初始状态,既没有被兑现,也没有被拒绝。
  • 已兑现(fulfilled): 意味着操作成功完成。
  • 已拒绝(rejected): 意味着操作失败。
    待定状态的 Promise 对象要么会通过一个值被兑现(fulfilled),要么会通过一个原因(错误)被拒绝(rejected)。当这些情况之一发生时,我们用 promise 的 then 方法排列起来的相关处理程序就会被调用。如果 promise 在一个相应的处理程序被绑定时就已经被兑现或被拒绝了,那么这个处理程序就会被调用,因此在完成异步操作和绑定处理方法之间不会存在竞争状态。

Promise.all()

Promise.all可以将多个Promise实例包装成一个新的Promise实例。同时,成功和失败的返回值是不同的,成功的时候返回的是一个结果数组,而失败的时候则返回最先被reject失败状态的值。

Promise.all中传入的是数组,返回的也是是数组,并且会将进行映射,传入的promise对象返回的值是按照顺序在数组中排列的,但是注意的是他们执行的顺序并不是按照顺序的,除非可迭代对象为空。

需要注意,Promise.all获得的成功结果的数组里面的数据顺序和Promise.all接收到的数组顺序是一致的,这样当遇到发送多个请求并根据请求顺序获取和使用数据的场景,就可以使用Promise.all来解决。

面向对象

递归的形式以及停止的条件

  • 直接递归,例如斐波那契数列数列
  • 尾递归
  • 间接递归
    递归的终止条件一般定义在递归函数内部,在递归调用前要做一个条件判断,根据判断的结果选择是继续调用自身,还是return;返回终止递归。
    终止的条件:
    1、判断递归的次数是否达到某一限定值
    2、判断运算的结果是否达到某个范围等,根据设计的目的来选择

商城的列表页跳转到商品的详情页,详情页数据接口很慢,前端可以怎么优化用户体验?

  1. 优化简要版
  • 懒加载:获取首屏数据,后边的数据进行滑动加载请求
    (1)首先不要将图片地址放到src属性中,而是放到其他属性(data-original)中。
    (2)页面加载完成后,根据srcollTop判断图片是否在用户的视野内,如果在,则将data-original属性中的值取出存放到src属性中。
    (3)在滚动事件中重复判断图片是否进入视野,如果进入,则将data-original属性中的值取出来存放到src属性中。
  • 利用骨架屏提升用户体验
  • ProloadJS预加载:使用PreloadJS库,PreloadJS提供了一种预加载内容的一致方式,以便在html应用程序中使用。预加载可以使用HTML标签以及xhr来完成。默认情况下,ProloadJS会尝试使用xhr加载内容,因为它提供了对进度和完成事件有更好的支持,但是由于跨域问题,使用基于标记的加载可能更好。
  • 除了添加前端loading和超时404页面外,接口部分可以添加接口缓存和接口的预加载
    (1)使用workbox对数据进行缓存,缓存优先
    (2)使用orm对本地离线数据进行缓存,优先请求本地
    (3)采用预加载,再进入到详情页阶段使用quicklink预加载详情页
    (4)使用nodejs作为中间层将详情页数据缓存至Redis等,上面的方法可以根据业务需求选择组合使用。
  1. 优化详细版
  • 当网络过慢时在获取数据前的处理
    其中cnd在dns阶段,dom渲染在processing onload阶段
    上图从promot for unload到unload的过程这么多步骤,在用户体验来说,一个页面从加载到展示超过4秒,就会有一种非常直观的卡顿现象,其中load对应的位置是onload事件结束后,才开始构建dom树,但是用户不一定是关心当前页面是否完成了资源的下载;往往是一个页面开始出现可见元素开始FCP首次内容绘制或者是FC首次绘制此时用户视觉体验开始,到TTI(可交互时间),可交互元素的出现,意味着用户交互体验开始,这时候用户就可以愉快的浏览使用我们的页面啦;
    所以这个问题的主要是需要缩短到达TTI和FCP的时间,但是这里已知进入我们详情页面时,接口数据返回速度很慢的,FCP和FC以及加快到达TTI,就需要页面预处理。
  • 页面数据缓存处理
    (1)第一次进入详情页面,可以使用骨架图进行模拟FC展示,并且骨架图,可使用背景图且行内样式的方式对首次进入详情页面进行展示,对于请求过慢的详情接口使用worker进程,对详情的接口请求丢到另外一个工作线程进行请求,页面渲染其他已返回数据的元素;当很慢的数据回来后,需要对页面根据id签名为key进行webp或者缩略图商品图的cnd路径localstroage的缓存,商品id的签名由放在cookie并设置成httponly
    (2)非第一次进入详情页时,前端可通过特定的接口请求回来对应的商品id签名的cookieid,读取localstroage的商品图片的缓存数据,这样对于第一次骨架图的展示时间就可以缩短,快速到达TTI与用户交互的时间,再通过worker数据,进行高清图片的切换。
  • 数据缓存和过期缓存数据的处理主体流程在这里插入图片描述
  • 页面重绘重排处理
    在这里插入图片描述
    触发重排的操作主要是几何因素:
    (1)页面首次进入的渲染
    (2)浏览器resize
    (3)元素位置和尺寸发生改变的时候
    (4)可见元素的增删
    (5)内容发生改变
    (6)字体的font改变
    (7)css伪类激活
    尽量减少上面这些产生重绘重排的操作
    比如说,这里产生很大的重绘重排主要发生在worker回来的数据替换页面中的图片src这一步
// 该节点为img标签的父节点
const imgParent = docucment.getElementById('imgParent'); 
// 克隆当前需要替换img标签的父元素下所有的标签
const newImgParent = imgParent.cloneNode(true); 
const imgParentParent = docucment.getElementById('imgParentParent');
for(let i = 0; i < newImgParent.children.length; i++) { 
// 批量获取完所有img标签后, 再进行重绘
  newImgParent.children[i].src = worker.img[i].path;
}
// 通过img父节点的父节点, 来替换整个img父节点
// 包括对应的所有子节点, 只进行一次重绘操作
imgParentParent.replaceChild(newImgParent, imgParent); 

vue

双向数据绑定的原理

Vue实现双向数据绑定是采用数据劫持和发布者-订阅者模式。数据劫持是利用ES5的Object.defineProperty(obj,key,val)方法来劫持每个属性的getter和setter,在数据变动是发布消息给订阅者,从而触发相应的回调来更新视图。
在这里插入图片描述

虚拟DOM

  • 虚拟 DOM 提升性能的点在于 DOM 发生变化的时候,通过 diff 算法比对 JavaScript 原生对象,计算出需要变更的 DOM,然后只对变化的 DOM 进行操作,而不是更新整个视图。
  • 虚拟DOM就是一个普通的JavaScript对象,包含了tag、props、children三个属性。
  • Dom diff 则是通过JS层面的计算,返回一个patch对象,即补丁对象,在通过特定的操作解析patch对象,完成页面的重新渲染。

vue-router

VueRouter如何实现根据页面跳转的:

  • vue拿到地址栏的路径(如/user/user-list)
  • 根据routes的配置,拿到components
  • 将替换成相应的components
  • 找不到的话会报错,当然一般都会配置404
    history模式和hash模式:
  • hsah : 默认 hash 模式 —— 使用 URL 的 hash 来模拟一个完整的 URL,URL 改变时,页面不会重新加载。( url 有 # ,不好看)
  • history : 可选 history 模式,充分利用 history.pushState API 来完成 URL 跳转而无须重新加载页面。( url 没有 # ,好看)
    访问路由: 组件内通过this.$route访问当前路由;通过this.$router访问所有路由。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值