admin管理员组文章数量:1646251
1. css
1.1 flex布局?常用来干啥?常见的属性;grid布局?【必背】
- Flex-direction --属性决定主轴的方向
- Flex-wrap – 属性决定项目排不下时如何换行
- justify-content – 水平对齐
- align-items – 垂直对齐
- Flex-flow – flex-direction 和 flex-wrap 的简写形式
- align-content – 决定了多根主轴的对齐方式
grid是网格布局,可以做垂直居中布局用
.parent {
display: grid;
place-items: center;
}
详情》
1.2 vue scoped 是怎么实现的,dom 上的哈希是如何和 style 中的哈希对应起来的,又是如何保证每次生成的哈希不变的【必背】
给一个组件中的所有dom添加了一个独一无二的动态属性
,然后,给CSS选择器
额外添加一个对应的属性选择器
来选择该组件中的dom
,这种做法使得样式只作用于含有该属性的dom——组件内部dom。
link
1.3 两列布局,左边固定宽度,右边是剩余的宽度【必背】
- float
- position:absolute
详情
1.4 不定宽高的元素如何水平垂直居中?【必背】
【字节】
<div class='content'>
<div class='son'>
内容区
</div>
</div>
// 方法1绝对定位
.content{
position: relative; //重点
background:black;
width:100px;
height:200px;
.son{
position:absolute;
left: 50%;
top: 50%;
transform: translate(-50%,-50%);
background:red;
}
}
// 方法2 flex
.content{
width:100px;
height:200px;
display: flex;
justify-content: center;
align-items: center;
background:black;
.son{
background:red;
}
}
1.5 如果实现一个三栏布局,需要三栏占同样的宽度,放多个元素时会自动换行,有哪些做法【必背】
1.flex
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>三栏布局</title>
<style>
.parent {
background: rgb(96 96 96);
display: flex;
Flex-wrap: wrap;
height: 400px;
}
.son {
box-sizing: border-box;
background: #ddd;
width: 33.33%;
border-right: 1px solid #000;
border-top: 1px solid #000;
}
</style>
</head>
<body>
<div class="parent">
<div class="son">子元素</div>
<div class="son">子元素</div>
<div class="son">子元素</div>
<div class="son">子元素</div>
</div>
</body>
</html>
效果:
- float
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>三栏布局</title>
<style>
.parent {
background: rgb(96 96 96);
height: 400px;
}
.son {
box-sizing: border-box;
background: #ddd;
float: left;
height: 100px;
width: 33.33%;
border-right: 1px solid #000;
border-top: 1px solid #000;
}
.son::after {
content: "";
display: block;
height: 0;
clear: both;
visibility: hidden;
}
</style>
</head>
<body>
<div class="parent">
<div class="son">子元素</div>
<div class="son">子元素</div>
<div class="son">子元素</div>
<div class="son">子元素</div>
</div>
</body>
</html>
效果:
三栏布局
1.5 用css写出3种隐藏元素的方法
{ display: none; } /* 不占据空间,无法点击 */
{ visibility: hidden; } /* 占据空间,无法点击 */
{ position: absolute; top: -999em;} /* 不占据空间,无法点击 */
{ height: 0; overflow: hidden; } /* 不占据空间,无法点击 */
{ opacity: 0; filter:Alpha(opacity=0); } /* 占据空间,可以点击 */
{ position: relative; top: -999em; } /* 占据空间,无法点击 */
{ position: absolute; visibility: hidden; } /* 不占据空间,无法点击 */
{ position: absolute; opacity: 0; filter:Alpha(opacity=0);} /* 不占据空间,可以点击 */
1.6 有如下布局,问 first-child last-child 宽度分别是多少【必背】
.container {
display: flex;
width: 600px;
height: 300px;
}
.a {
width: 500px;
flex-shrink: 1;
}
.b {
width: 400px;
flex-shrink: 2;
}
很明显,a.width + b.width已经大于父元素的宽度, flex-shrink会让子元素进行压缩,以适应父元素的宽度,值越大压缩的越严重。
- a + b 超出父元素宽度 为 500 + 400 - 600 = 300
- a压缩后的宽度 = a的宽度500 - a需要压缩的宽度【300*(500 * 1/(500 * 1 + 400 * 2))
- 即 a = 500 - 115 = 385
- b压缩后的宽度 = b的宽度400 - b需要压缩的宽度【300*(400 * 2/(500 * 1 + 400 * 2))】
- 即 b = 400 - 185 = 215
知识点>
1.7 一个梯型如何实现
原理就是对一个高度为0px的正方形的div的border-top进行增粗
详情
1.8 CSS 实现一个扇形
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>菜鸟教程(runoob)</title>
<style>
.normal{
width: 0;
height: 0;
border-width: 50px;
border-style: solid; // 边框为实线
border-color: #f00 transparent transparent;
border-radius: 50px
}
</style>
</head>
<body>
<p class="normal">扇形</p>
</body>
</html>
把上面的border-radius: 50px
删除就是三角形了
border-color
边框的颜色 写了4项时 : 上 / 右 / 下 / 左
边框的颜色 写了3项时 : 上 / 左右 / 下
边框的颜色 写了2项时 : 上下 / 左右
1.9 怎么适配移动端和 pc 端 ,rem 具体实现【必背】
【网易灵犀,360】
常见单位:
-
px:绝对单位,页面按精确像素展示
-
em:相对单位,基准点为父节点字体的大小,如果自身定义了 font-size 按自身来计算(浏览器默认字体是 16px),整个页面内 1em 不是一个固定的值
-
rem:相对单位,可理解为”root em”, 相对根节点 html 的字体大小来计算,CSS3 新加属性,chrome/firefox/IE9+支持
-
vw:viewpoint width,视窗宽度,1vw 等于视窗宽度的 1%
-
vh:viewpoint height,视窗高度,1vh 等于视窗高度的 1%
-
vmin:vw 和 vh 中较小的那个
-
vmax:vw 和 vh 中较大的那个
-
%:百分比
移动端适配是用 rem 还是 vw?分别的原理是什么?你们用什么方案?
rem是相对于根节点html的字体大小,vm是视窗的宽度。
用的是rem,具体实现:
<!DOCTYPE html>
<html>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<head>
<meta charset="utf-8">
<title>三栏布局</title>
<style>
.renew {
margin: 60px 0;
}
.renew ul {
margin-top: 70px;
padding-left: 24px;
}
.renew li {
border-left: 1px solid #4cc1e9;
}
.renew dl {
padding-left: 50px;
padding-bottom: 40px;
}
.renew dt {
color: #4cc1e9;
font-size: 26px;
padding-top: 30px;
margin-bottom: 10px;
}
.renew dd {
font-size: 24px;
color: #666;
line-height: 44px;
}
</style>
</head>
<body>
<div class="renew" id="renew">
<ul>
<li>
<dl>
<dt>优质体验</dt>
<dd>按需创建服务,满足不同用户需求</dd>
<dd>可随时取消服务,用户自主选择</dd>
</dl>
</li>
<li>
<dl>
<dt>自动扣费</dt>
<dd>定时扣费,自动完成,</dd>
<dd>免去多次主动付费的繁琐操作</dd>
</dl>
</li>
</ul>
</div>
</body>
<script>
(function (win, doc) {
if (!win.addEventListener) return;
var html = document.documentElement;
function setFont() {
var cliWidth = html.clientWidth;
// if(cliWidth > 750){
// cliWidth = 750;
// }
html.style.fontSize = cliWidth / 750 + 'px';
}
win.addEventListener('resize', setFont, false)
doc.addEventListener('DOMContentLoaded', setFont, false)
})(window, document);
</script>
</html>
从上面可以看到,屏幕为750px的话,1rem就是1px,在设计图也为750px的情况下,那么.renew原来是margin: 60px 0;
,就要换算为margin: 60rem 0;
现在假如屏幕为1500px了,那么1rem就是2px,那么.renew原来是margin: 60rem 0;
,60*2 = 120,就相当于margin: 120px 0;
,可以看到宽度是同比增大的。
1.10 重排重绘的区别以及优化方法【必背】
【玄武科技,网易灵犀】
link
1.11 介绍一下浏览器的合成层
【网易灵犀】
详情>
1.12 !important在什么场景用,css选择器权重是如何计算的【必背】
【网易传媒】
需要样式覆盖的场景,优先级最高,比内联还高
内联 1000
id 100
类 10
标签 1
详情
1.13 盒模型的边距叠加,如何解决盒子塌陷,如何创建BFC【必背】
【360,字节】
什么是BFC=》
什么是盒模型》
边距叠加解决:
- 可以给父元素添加 border-top | padding-top 来分隔父子元素
- 也可将父元素设为 BFC;
塌陷
当父元素没设置足够大小的时候,而子元素设置了浮动的属性,子元素就会跳出父元素的边界(脱离文档流),尤其是当父元素的高度为auto时,而父元素中又没有其它非浮动的可见元素时,父盒子的高度就会直接塌陷为零, 我们称这是CSS高度塌陷。
- 最简单,直接,粗暴的方法就是盒子大小写死
- 给外部的父盒子也添加浮动,让其也脱离标准文档流
- 给父盒子添加overflow属性
- after伪类清除浮动
.mobileBackColor:after{
clear: both;
content: "";
width: 0;
height: 0;
display: block;
visibility: hidden;
}
创建BFC
- 根元素
- 浮动元素:float 除 none 以外的值
- 绝对定位元素:position (absolute、fixed)
- display 为 inline-block、table-cells、flex
- 表格类元素
- overflow 除了 visible 以外的值 (hidden、auto、scroll)【最常用】
1.14 移动端有没有遇到过滑动穿透的问题
【360,Fordeal】
link
1.15 安卓和IOS兼容性,举个例子说说
【网易游戏,360】
详情>
1.16 如何通过自定义样式修改组件视图?【必背】
- /deep/
- 加id,然后用id选择器来写样式,注意style不要加scoped
1.17 animation 和 transition 有什么区别
transition(过渡)
transition: property duration timing-function delay;
值 | 描述 |
---|---|
transition-property | 定设置过渡效果的 CSS 属性的名称。 |
transition-duration | 规定完成过渡效果需要多少秒或毫秒。 |
transition-timing-function | 规定速度效果的速度曲线。 |
transition-delay | 定义过渡效果何时开始。 |
animate(动画)
animation: 1s 1s rainbow linear 3 forwards normal;
- animation-name 指定要绑定到选择器的
关键帧
的名称
(动画名称) - animation-duration 动画指定需要多少秒或毫秒
完成
- animation-timing-function 设置动画将如何完成一个
周期
(linear/ease/ease-in/ease-out/ease-in-out) - animation-delay 设置动画在启动前的
延迟间隔
(多长时间后动画开始执行)。 - animation-iteration-count 定义动画的
播放次数
(数字或infinite无限次)。 - animation-direction 指定是否应该
轮流反向
播放动画。
a:hover{
animation-name: rainbow;
animation-duration: 1s;
animation-timing-function: linear;
animation-delay: 1s;
animation-fill-mode:forwards;
animation-direction: normal;
animation-iteration-count: 3;
}
@keyframes rainbow {
0% { background: #c00 }
50% { background: orange }
区别
是否需要触发
- transition需要伪类、事件
触发才执行
;某一个元素指定了Transiton,那么当其某个属性改变的时候就会按照Transition指定的方式进行过渡,transition是一次性
的,不能重复
发生,除非一再触发transition ; - animation
不需要
触发,页面加载
就可以执行
;可以设置循环次数、infinite实现无限次循环。
中间过程
- transition只能设置动画
初始值
和结束值
,中间
的过程无法控制
。 - animation可以控制到
每一帧
,结合@keyframes
使用,可以通过百分比
来划分帧,(0%-100% 或者使用from{} to{});
1.18 animation实战
写个动画,一个盒子,开始时缩放是 0,50%时是 1,100%时是 0,开始结束都是慢速,持续 2 秒,延迟 2 秒,结束后固定在结束的效果
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>菜鸟教程(runoob)</title>
<style>
div
{
width:100px;
height:100px;
background:red;
position:relative;
animation-name:mymove; //动画名
animation-duration:2s; //动画持续时间
animation-timing-function: ease-in-out;//动画如何运动,动画以低速开始和结束。
animation-delay: 2s;
animation-fill-mode:forwards; //动画停留在结束位置
}
@keyframes mymove
{
0% {top:0px;transform: scale(0,0)}
50% {top:100px;transform: scale(1,1);}
100% {top:200px;transform: scale(0,0);}
}
</style>
</head>
<body>
<div></div>
</body>
</html>
2.js
1.1 js的数据类型都有哪些,有什么区别,为什么基本数据类型存到栈但是引用数据类型存到堆【必背】
【360】
数据类型
堆栈
为什么
1.2 数据类型常用的判断方式都有哪些
【360,映客直播,有米科技】
1.3 平时有用哪些数据结构,用在哪里
- input数字输入框: 设置type为Number
- props接收父组件的传参:设置类型type
- array: 获取列表数组给数据初始化为空数组
详情
1.4 Set、Map、Symbol数据结构需要学习【必背】
WeakMap与Map类似,但有几点区别:
1、WeakMap只接受对象作为key,如果设置其他类型的数据作为key,会报错。
2、WeakMap的key所引用的对象都是弱引用,只要对象的其他引用被删除,垃圾回收机制就会释放该对象占用的内存,从而避免内存泄漏。【内存泄漏有哪些场景?如何查找?—这是个问题】
3、由于WeakMap的成员随时可能被垃圾回收机制回收,成员的数量不稳定,所以没有size属性。
4、没有clear()方法
5、不能遍历
map ,set需要了解
map和obj的区别
详情,基础知识>
详情>
1.5 Symbol 有了解吗,迭代器有了解吗,哪些是可迭代的【必背,看下原理】
Symbol
遍历Array可以采用下标循环,遍历Map和Set就无法使用下标。为了统一集合类型,ES6标准引入了新的iterable类型来使得它们也支持遍历,Array、Map和Set都属于iterable类型。
迭代器
- 迭代器
(iterator)
是一种接口,为各种不同的数据结构提供统一的访问机制。也就是说,只要数据结构种部署了该接口,那么它就可以遍历 - ES6还引入了一个新的遍历命令与之搭配:
for...of
循环遍历,具有iterable类型的集合可以通过新的for … of循环来遍历。
迭代器和遍历的区别
-
迭代:迭代强调的是依次取数据的过程,并不保证取多少,也不保证把所有的数据都取完
-
遍历:遍历强调的是要把整个数据依次全部取出
原生具备iterator接口的数据结构:
Array
Agruments
Set
Map
String
TypedArray
NodeList
底层机制
- 数据结构中,(以数组为例),iterator接口表现为一个函数
Array.prototype.Symbol.iterator()
- 它创建一个指针对象iterator,指向数据结构的起始位置
- 其内部有一个
next()
方法用于遍历 - 第一次调用对象的
next
方法,指针自动指向数据结构的第一个成员 - 不断调用
next
方法,指针一直向后移动,直到指向最后一个成员 - 每次
next
方法返回一个包含value
和done
属性的对象
generator
就是一个返回值为 iterator
的函数,iterator
也是一种对象,不过它有着专为迭代而设计的接口。它有next
方法.
该方法返回一个包含 value
和 done
两个属性的对象 (下称 result
)。
前者是迭代的值,后者是表明迭代是否完成的标志 – 布尔值: true
表示迭代完成,false
表示没有。iterator
内部有指向迭代位置的指针,每次调用next
, 自动移动指针并返回相应的 result
。
function* foo() { // 用*来创建Generator函数
yield 'result1'
yield 'result2'
yield 'result3'
}
const gen = foo()
console.log(gen.next().value) //result1
console.log(gen.next().value) //result2
console.log(gen.next().value) //result3
Generator介绍
Generator原理
什么是迭代器,JS如何实现迭代器
1.6 用 Set 获取两个数组的交集,如何做【必背】
let arr1 = [1,2,3]
let arr2 = [1,2,4,5]
// 并集
let arr3 = arr1.concat(arr2);
console.log([...new Set(arr3)]); //[1, 2, 3, 4, 5]
// 交集
const jioaji = (a, b) => {
const s = new Set(b);
return [...new Set(a)].filter(x => s.has(x));
};
console.log(jioaji(arr1, arr2)); // [1, 2]
//数组取差集
function chaji(arr1, arr2) {
let set1 = new Set(arr1);
let set2 = new Set(arr2);
let subset = [];
for (let item of set1) {
if (!set2.has(item)) {
subset.push(item);
}
}
return subset;
}
console.log(chaji(arr2, arr1)); // [4, 5]
1.7 如何实现new一个对象【必背】
详情
1.8 累加reduce【必背】
var arr = [1, 2, 3, 4];
var sum = arr.reduce(function(prev, cur, index, arr) {
console.log(prev, cur, index, arr)
return prev + cur; //重点是这里要return
})
console.log(sum); //10
上面的累加里面分别打印:
- 1 2 1 [1, 2, 3, 4]
- 3 3 2 [1, 2, 3, 4]
- 6 4 3 [1, 2, 3, 4]
- 10
1.9 数组和对象的api【必背】
【佰锐科技】
数组
1. pop: ƒ pop() //pop() 方法用于删除数组的最后一个元素并返回删除的元素。【修改原数组】
1. push: ƒ push() //push() 方法可向数组的末尾添加一个或多个元素,并返回新的长度。【修改原数组】
1. reverse: ƒ reverse()//reverse() 方法用于颠倒数组中元素的顺序。【修改原数组】
1. shift: ƒ shift() //shift() 方法用于把数组的第一个元素从其中删除,并返回第一个元素的值。【修改原数组】
1. slice: ƒ slice() //slice() 方法可从已有的数组中返回选定的元素。【不修改原数组】
1. sort: ƒ sort()//sort() 方法用于对数组的元素进行排序。【修改原数组】
1. splice: ƒ splice()//splice() 方法用于添加或删除数组中的元素。【修改原数组】
1. unshift: ƒ unshift()//unshift() 方法可向数组的开头添加一个或更多元素,并返回新的长度。【修改原数组】
1. values: ƒ values()
1. some: ƒ some()
1. toLocaleString: ƒ toLocaleString()
1. toString: ƒ toString()
1. reduce: ƒ reduce()
1. concat: ƒ concat()
1. every: ƒ every()
1. filter: ƒ filter()
1. find: ƒ find()
1. findIndex: ƒ findIndex()
1. flat: ƒ flat()
1. forEach: ƒ forEach()
1. includes: ƒ includes()
1. indexOf: ƒ indexOf()
1. join: ƒ join()
1. keys: ƒ keys()
1. lastIndexOf: ƒ lastIndexOf()
1. length: 0
1. map: ƒ map()
对象
1. hasOwnProperty: ƒ hasOwnProperty()//------
1. toString: ƒ toString() //------
1. valueOf: ƒ valueOf() //------
1. isPrototypeOf: ƒ isPrototypeOf()
1. toLocaleString: ƒ toLocaleString()
1.10 垃圾回收【必背,重点】
【虾皮,蓝湖】
引用计数:
-
当声明了一个变量并且将一个引用类型赋值给该变量的时候这个值的引用次数就为 1
-
如果同一个值又被赋给另一个变量,那么引用数加 1
-
如果该变量的值被其他的值覆盖了,则引用次数减 1
-
当这个值的引用次数变为 0 的时候,说明没有变量在使用,这个值没法被访问了,回收空间,垃圾回收器会在运行的时候清理掉引用次数为 0 的值占用的内存
缺点是有循环引用的问题,所以出来了标记清除法来解决。
标记清除法:
其中有个重要的概念是可达性
,标识从根节点
开始是否能够到达(引用)的值,如果不能的话就是垃圾,所以它解决了循环引用的问题
-
标记阶段:把所有
活动
对象(能从根节点到达的值)做上标记
。 -
清除阶段:把
没有
标记(也就是非活动对象)销毁
。
缺点是会导致内存内存碎片:
标记清除算法有一个很大的缺点,就是在清除之后,剩余的对象内存位置是不变的,也会导致空闲内存空间是不连续的,出现了 内存碎片
(如下图),并且由于剩余空闲内存不是一整块,它是由不同大小内存组成的内存列表,这就牵扯出了内存分配的问题
解决:
标记整理(Mark-Compact)算法 就可以有效地解决,它的标记阶段和标记清除算法没有什么不同,只是标记结束后,标记整理算法会将活着的对象(即不需要清理的对象)向内存的一端移动,最后清理掉边界的内存
垃圾回收
你真的了解垃圾回收机制吗
简单了解JavaScript垃圾回收机制
1.11 v8 GC 原理【必背,重点】
V8采用了一种分代回收的机制,将内存分为两个生代:新生代 和老生代 。
- 新生代中的对象为存活时间较短的对象
- 老生代中的对象为存活时间较长或常驻内存的对象
- 分别对新老生代采用不同的垃圾回收算法来提高效率
- 对象最开始都会先被分配到新生代(如果新生代内存空间不够,直接分配到老生代)
- 新生代中的对象会在满足某些条件后,被移动到老生代,这个过程也叫晋升,
新生代:
新生代采用Scavenge垃圾回收算法,在算法实现时主要采用Cheney算法。
Cheney算法将内存一分为二,叫做semispace,一块处于使用状态,一块处于闲置状态。
处于使用状态的semispace称为From空间,处于闲置状态的semispace称为To空间。
- step1. 在From空间中分配了3个对象A、B、C
- step2. GC进来判断对象B没有其他引用,可以回收,对象A和C依然为活跃对象
- step3. 将活跃对象A、C从From空间复制到To空间
- step4. 清空From空间的全部内存
- step5. 交换From空间和To空间,这样From空间就变成空的空间了,而TO空间存放了A,C
- step6. 在From空间中又新增了2个对象D、E
- step7. 下一轮GC进来发现对象D没有引用了,做标记
- step8. 将活跃对象A、C、E从From空间复制到To空间
- step9. 清空From空间全部内存
- step10. 继续交换From空间和To空间,开始下一轮
进行From和To交换,就是为了让活跃对象始终保持在一块semispace中,另一块semispace始终保持空闲的状态。
Scavenge由于只复制存活
的对象,并且对于生命周期短
的场景存活对象只占少部分,所以它在时间效率上有优异的体现。Scavenge的缺点是只能使用堆内存的一半
,这是由划分空间和复制机制所决定的。
晋升:
对象从新生代移动到老生代的过程叫作晋升
当一个对象经过多次复制仍然存活时,它就会被认为是生命周期较长的对象。这种较长生命周期的对象随后会被移动到老生代中,采用新的算法进行管理。
对象晋升的条件主要有两个:
- 对象从From空间复制到To空间时,会检查它的内存地址来判断这个对象是否已经
经历过一次Scavenge
回收。如果已经经历过了,会将该对象从From空间移动到老生代空间中,如果没有,则复制到To空间。总结来说,如果一个对象是第二次经历从From空间复制到To空间,那么这个对象会被移动到老生代中。 - 当要从From空间复制一个对象到To空间时,如果To空间已经使用了超过25%,则这个对象直接晋升到老生代中。设置25%这个阈值的原因是当这次Scavenge回收完成后,这个To空间会变为From空间,接下来的内存分配将在这个空间中进行。如果占比过高,会影响后续的内存分配。
老生代
在老生代中,存活对象占较大比重,如果继续采用
Scavenge
算法进行管理,就会存在两个问题:
- 由于存活对象较多,复制存活对象的效率会很低。
- 采用Scavenge算法会浪费一半内存,由于老生代所占堆内存远大于新生代,所以浪费会很严重。
所以,V8在老生代中主要采用了**Mark-Sweep(标记清除)和Mark-Compact(标记整理)**相结合的方式进行垃圾回收。
总结
V8的垃圾回收机制分为新生代
和老生代
。
新生代主要使用Scavenge
进行管理,主要实现是Cheney
算法,将内存
平均分为两块
,使用
空间叫From
,闲置
空间叫To
,新对象都先分配到From空间中,在空间快要占满时
将存活对象复制到To
空间中,然后清空From
的内存空间,此时,调换
From空间和To空间,继续进行内存分配,当满足那两个条件时对象会从新生代晋升到老生代。
老生代主要采用标记清除
和标记整理
。两者不同的地方是,标记清除在垃圾回收后会产生碎片内存(因为剩余的对象内存位置是不变的)
,而标记整理在清除前会进行一步整理,将存活
对象向一侧移动
,随后清空边界的另一侧内存,这样空闲的内存都是连续的,但是带来的问题就是速度会慢一些。
1.12 原型链 以及如何判断属性是否为对象自己的?
【字节】
一句话解释什么是原型
原型属于对象的一个属性,即prototype,原型让原型链上的对象能继承属性。
- 封装:无需关注函数的内部执行,给定一个入参,自然会有一个输出结果
- 继承:提供多种继承方法
- 多态:执行同一操作且作用于不同对象时,返回不同的结果
参考
判断属性是否为对象自己的
详情>
var b = {name:1}
b.hasOwnProperty('name') // true
原型链
所有的JS对象都有一个__proto__属性,指向它的原型对象。当查找对象的属性时,在对象本身没有找到,就会查找对象的原型,以及该对象的原型的原型,层层向上查找,直到原型链的顶端null为止,找到了就返回,这样的一种查找关系,就称为原型链。
原型实战 【可略】
var webName = "long";
// Pig的构造函数
function Pig(name, age) {
this.name = name;
this.age = age;
}
// 创建一个Pig的实例,小猪佩奇
var Peppa = new Pig('Peppa', 5); //{name: 'Peppa', age: 5}
普通对象不存在prototype属性
console.log(Pig.prototype) //{constructor: ƒ} 函数的显式原型是个对象
console.log(Peppa.prototype) // undefined 实例不存在prototype属性,只有函数才有
- 第一层隐式原型都指向它的同类型
- 函数的显式原型是一个对象
- 对象不存在显式原型
函数
的显式原型 等于实例
的隐式原型
console.log(Pig.__proto__) //ƒ () { [native code] } 第一层隐式原型都指向它的同类型,函数指向函数
console.log(Pig.prototype) // {constructor: ƒ} 函数的显式原型是一个对象
console.log(Peppa.__proto__) // {constructor: ƒ} 隐式原型都指向它的同类型,对象指向对象
console.log(Pig.prototype.prototype) //undefined 对象不存在显式原型
Pig.prototype === Peppa.__proto__ //true
Pig.__proto__ === Function.prototype //true
构造函数
console.log(Pig.__proto__) //ƒ () { [native code] } 第一层隐式原型都指向它的同类型,函数指向函数
console.log(Pig.constructor) // Function() { [native code] } 函数的构造函数就是Function
//ƒ Pig(name, age) { this.name = name; this.age = age;} 当前实例的构造函数就是Pig本身
console.log(Peppa.constructor)
console.log(Pig.prototype) // {constructor: ƒ} 函数的显式原型是一个对象
console.log(Pig.prototype.constructor) // Pig本身
Pig.prototype.constructor === Pig //true
拓展
console.log(Pig.__proto__) //ƒ () { [native code] } 第一层隐式原型都指向它的同类型,函数指向函数
//{constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, …}
console.log(Pig.__proto__.__proto__)
console.log(Pig.__proto__.__proto__.__proto__) //null 找到顶层了
console.log(Pig.__proto__.__proto__.__proto__.__proto__) //报错,Cannot read properties of null (reading '__proto__')
Peppa.__proto__//{constructor: ƒ}
Peppa.__proto__.__proto__//{constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, …}
Peppa.__proto__.__proto__.__proto__//null
1.13 介绍一下前端的继承方式【必背,重点】
【蓝湖】
下面这个的输出是什么,this 指向谁
class A {
constructor() {
console.log(this.name,this);
}
}
class B extends A {
constructor() {
super(); //会先执行A里面的constructor,this会指向当前B,而此时name还没有赋值为undefined
this.name = "B";
console.log(this)//{name: 'B'}
}
}
const b = new B();
A里面打印undefined {},B里面打印{name: ‘B’}
详情
1.14 js 事件循环模型;【必背,重点】
【字节,美团,网易游戏,网易传媒,腾讯音乐】
进程
就好比工厂的车间,它代表CPU所能处理的单个任务。进程
之间相互独立,任一时刻,CPU总是运行一个进程
,其他进程
处于非运行状态。 CPU使用时间片轮转进度算法来实现同时运行多个进程
线程
就好比车间里的工人,一个进程
可以包括多个线程
,多个线程
共享进程
资源。
进程
是cpu资源分配的最小单位(是能拥有资源和独立运行的最小单位)线程
是cpu调度的最小单位(线程是建立在进程的基础上的一次程序运行单位,一个进程中可以有多个线程)- 不同
进程
之间也可以通信,不过代价较大 单线程
与多线程
,都是指在一个进程
内的单和多
浏览器渲染进程包含:
- GUI渲染线程: 渲染页面
- JS引擎线程: 解析和执行javascript脚本程序
- 事件触发线程: 鼠标点击
- 定时触发器线程
- 异步http请求线程
为什么 javascript 是单线程的:
- 首先是历史原因,在创建 js 这门语言时,多进程多线程的架构并不流行,硬件支持并不好。
- 其次是因为多线程的
复杂
性,多线程操作需要加锁
,编码的复杂性会增高。 - 而且,如果
同时操作 DOM
,在多线程不加锁的情况下,最终会导致 DOM渲染的结果不可预期
。
执行顺序:
-
首先,
整体的script
(作为第一个宏任务
)开始执行的时候,会把所有代码分为同步任务、异步任务
两部分 -
同步
任务会直接进入主线程
依次执行 -
异步
任务会再分为宏
任务和微
任务 -
宏任务进入到Event Table中,并在里面注册回调函数,每当指定的事件完成时,Event Table会将这个函数移到Event Queue(事件队列)中
-
微任务也会进入到另一个Event Table中,并在里面注册回调函数,每当指定的事件完成时,Event Table会将这个函数移到Event Queue中
-
当主线程内的任务执行完毕,
主线程为空
时,会检查微任务
的Event Queue,如果有任务,就全部执行,如果没有就执行下一个宏任务 -
上述过程会不断重复,这就是Event Loop,比较完整的事件循环
1.15 异步执行顺序【必背】
async function async1() {
console.log("async1 start"); // 2 同步
await async2();
console.log("async1 end"); // 6 异步,微任务(await后面的相当于被promise封装过的.then)
}
async function async2() {
console.log("async2"); // 3 同步
}
console.log("script start"); // 1 同步
setTimeout(() => {
console.log("setTimeout"); // 8 宏任务
}, 0);
async1();
new Promise((resolve) => {
console.log("promise1"); // 4 同步
resolve();
}).then(() => {
console.log("promise2"); // 7 任务
});
console.log("script end"); // 5 同步
- script start
- async1 start
- async2
- promise1
- script end
- async1 end
- promise2
- setTimeout
【网易游戏】
setImmediate(() => {
console.log(1);
});
setTimeout(function(){
console.log(2);
});
new Promise(function(resolve){
console.log(3);
resolve();
console.log(4);
}).then(function(){
console.log(5);
});
console.log(6);
process.nextTick(function(){
console.log(7);
});
console.log(8);
// 3 4 6 8 5 7 1 2
- 微任务: Promise,process.nextTick
- setImmediate优先于setTimeout执行。
1.16 async和defer的区别【必背】
那么什么场景下可以用到这些属性呢?
比如有些第三方库,要求在header
中引入(这就意味着第三方插件库会优先加载),但是我们并不需要在页面加载之初
就用
到这个插件。那么我们可以加上defer
属性使之最后
加载。
同理,如果页面加载的同时
需要用到第三方插件
,那么我们引用的时候可以加async属性,这样网站内的资源就可以与插件资源异步加载
详情1 重点>
详情2>
详情3 可略
1.17 0.1 + 0.2 为什么不等于 0.3,为什么会有误差,如何解决【必背】
【网易游戏】
由于浮点数精度问题造成的,JavaScript采用的是
IEEE754的64位双精度
版本,相加的时候需要转换为2进制,0.1用二进制是无限循环小数,而在JavaScript中只能存储52位小数,那么0.1
的小数位在第52位时就需要判读进位,相加就出问题了。
parseFloat //将字符串转换成数字,转换成10进制
toFixed //保留几位小数
(0.1 + 0.2).toFixed(10) // '0.3000000000' 字符串
parseFloat((0.1 + 0.2).toFixed(10)) // 0.3 将字符串转换成数字
地址
1.18 作用域【必背】
代码区块的有效范围
【美团】
var a = 3
function func() {
console.log(a);
a = 10;
console.log(a);
}
func();
console.log(a);
打印 3 10 10,func里面的a是全局的a
var a = 3;
function func(a) {
console.log(a);
a = 10;
console.log(a);
}
func();
console.log(a);
打印 undefined 10 3
上面的式子相当于:
var a = 3;
function func() {
var a;
console.log(a);
a = 10;
console.log(a);
}
func();
console.log(a);
打印 undefined 10 3
第一个 a 打印的值是undefined 而不是3。因为我们在 func() 中定义了变量 a,用 var 定义的变量会提升到当前作用域的顶部(即当前函数作用域顶部),但是赋值还在原地,所以是undefined。a声明在函数作用域中,所以它会先在当前作用域查找a的声明,查到了有var,那么a在这里面的作用域就被限定在函数里面函数作用域。
第二个 打印10,因为它只会在当前作用域查找a。
第三个a 打印的值是 3 因为函数作用域中的a和全局的a是独立的了
let a = 3;
function func(a) {
a = 10;
console.log(a);
}
func();
console.log(a);
会报错,因为func里面相当于隐士声明了:var a = 10
,全局中又用了let声明,所以会报错:重复声明
function bar() {
console.log(project);
}
function foo() {
var project = 'foo';
bar();
}
var project = 'global';
foo();
// global bar定义在全局中,所以bar中拿到是全局的project
1.19 闭包【必背】
【腾讯广告】
一个函数使用了另外一个函数作用域中的变量
基础
详情>
1.20 作用域/闭包实战【必背】
【腾讯广告】
for(var i = 0; i < 3; i++){
setTimeout(() => {
console.log(i);
}, 1000);
}
打印: 2 2 2
要把上面搞成打印0 ,1 ,2 有什么方式
方式1 let
for(let i = 0; i < 3; i++){
setTimeout(() => {
console.log(i);
}, 1000);
}
方式2 自执行函数/闭包
for (var i = 0; i < 3; i++) {
(function(s) {
setTimeout(() => {
console.log(s)
}, 1000)
})(i)
}
方式3 利用 setTimeout 函数的第三个参数,会作为回调函数的第一个参数传入
for (var i = 0; i < 3; i++) {
setTimeout(console.log(i), 1000, i)
}
方式4 利用try catch
for(var i = 0; i < 3; i++){
try{
throw i;
}catch(i){
setTimeout(() => { console.log(i); },1000)
}
}
方式5 闭包
for (var i = 0; i < 3; i++) {
timeoutPromise(i);
}
function timeoutPromise(i) {
setTimeout(() => {
console.log(i);
}, 1000);
}
1.21.this的指向【必背】
详情>
1.22 es6新特性,箭头函数(什么情况不使用),proxy,装饰器,可选链,如何遍历一个对象【必背】
es6新特性
箭头函数(什么情况不使用)
装饰器,注意区分下迭代器
decorator相当于一个高阶函数,接收一个函数,返回一个被装饰后的函数。
目前需要安装插件来支持:transform-decorators-legacy
@transform
class My {
// ...
}
function transform(target) {
target.home = '湖南'
}
console.log(My.home) // 湖南
上面代码中,@transform就是一个修饰器。它修改了My
这个类的行为,为它加上了属性home
。transform
函数的参数target
是My
类本身。
详情>
可选链,对象的层级属性简写
如何遍历一个对象
1.23 obj读取数据快的原因,js是怎么实现的【可略】
详情
1.24 数组怎么乱序【必背】
var arr = [1,2,3,4,5,6,7,8,9,10]
function s(a,b){
return Math.random()>0.5 ? 1:-1;
}
arr.sort(s)
console.log(arr) //[1, 5, 6, 7, 10, 8, 2, 4, 3, 9]
1.25 Object.create(null)和直接创建一个{}有什么区别【必背】
var a = Object.create(null)
- 创建的是一个完全的空对象,这个对象继承任何原型方法和属性
console.log(a.toString); // undefined
a.hasOwnProperty() // a.hasOwnProperty is not a function
var b = {}
- 会有对象默认的一系列方法和属性
Object.create(b).toString //function toString() { [native code] }
b.hasOwnProperty() // undefined
在vue中之所以用Object.create(null)方法创建对象,我个人认为,可能有这几种考虑:
1、使用Object.create(null)创建的对象,是一个完全空白的对象,不会继承任何属性和方法,在后续使用中不需要考虑命名冲突的问题
。
2、重写对象的原型方法或者给对象添加新方法,能够保证后续调用时代码风格的统一
1.26 异步加载js的方式都有哪些 【必背】
【360】
- defer
- async ( 多数第三方框架采用此方案 )
- 创建 script ,插⼊到 DOM 中,加载完毕后 callBack
- esmodule的import
1.27 加载css和js时会阻塞dom渲染吗【必背】
【360】
js:
加载或者执行js时会阻塞
对标签
的解析
,也就是阻塞了dom树
的形成
,只有等到js执行完毕,浏览器才会继续解析标签。没有dom树,浏览器就无法渲染,所以当加载很大的js文件时,可以看到页面很长时间是一片空白
之所以会阻塞对标签的解析是因为加载的js中可能会创建,删除节点
等,这些操作会对dom树产生影响,如果不阻塞,等浏览器解析完标签生成dom树后,js修改了某些节点,那么浏览器又得重新解析
,然后生成dom树,性能比较差
css:
这可能也是浏览器的一种优化机制。因为加载css的时候,可能会修改DOM节点的样式,如果css加载不阻塞DOM树渲染的话,那么等到css加载完之后,DOM树可能又得重新重绘或者回流了,这就造成了一些没有必要的损耗。所以先把DOM树的结构先解析完,把可以做的工作做完,然后ss加载完之后,再根据最终的样式来渲染DOM树,这种做法性能方面确实会比较好一点。
- css并不会阻塞DOM树的
解析
。 - css加载会阻塞DOM树
渲染
。
1.29 判断一个对象是否是循环引用对象
【腾讯广告】
不知道
1.30 jsBrigdge底层通讯的原理【必背】
详情
3.Es6
3.1 ES6 语法用过哪些,都有哪些常用的特性【必背】
【360】
let / const、箭头函数、解构赋值、默认参数、扩展运算符、类、字符串模板、数组新增的高阶函数、Promise
详情
3.2 介绍一下 promise,它为啥叫 promise【必背,有空手写promise】
【360,映客】
概念:Promise早期是流行于社区的一个
异步编程
的解决方案
,15年es6发布的时候被正式纳入了规范
从语法上讲,promise
是一个对象
,从它可以获取异步操作
的消息
;
从本意上讲,它是承诺
,承诺它过一段时间
一定会给你一个结果
。
promise有三种状态:pending(等待态),fulfiled(成功态),rejected(失败态) ;
状态一旦改变,就不会再变。创造promise实例后,它会立即执行。
Promise都有哪些方法:
- all :同时执行多个请求,返回结果集;只要有失败,则失败
let Promise1 = new Promise(function(resolve, reject){}) let Promise2 = new Promise(function(resolve, reject){}) let Promise3 = new Promise(function(resolve, reject){}) let p = Promise.all([Promise1, Promise2, Promise3]) p.then(funciton(){ // 三个都成功则成功 }, function(){ // 只要有失败,则失败 })
- rance: 返回执行最快的那个请求的结果
//请求某个图片资源 function requestImg(){ var p = new Promise((resolve, reject) => { var img = new Image(); img.onload = function(){ resolve(img); } img.src = '图片的路径'; }); return p; } //延时函数,用于给请求计时 function timeout(){ var p = new Promise((resolve, reject) => { setTimeout(() => { reject('图片请求超时'); }, 5000); }); return p; } Promise.race([requestImg(), timeout()]).then((data) =>{ console.log(data); }).catch((err) => { console.log(err); });
解决哪些问题:
- 回调地狱,代码难以维护, 常常第一个的函数的输出是第二个函数的输入这种现象
- promise可以支持多个并发的请求,获取并发请求中的数据
- 这个promise可以解决异步的问题,本身不能说promise是异步的
Promise使用,难点是手写promise
3.3 如何把一个请求封装成promise【必背】
【佰锐科技】
直接在请求的外层加一个promise:
function getData( content ) {
return new Promise(function( reslove,reject ) {
setTimeout(function(){ // 模拟请求
if(content>4){
resolve(content) //模拟请求成功
}else{
reject('小于4') // 模拟请求失败
}
},1000)
})
}
3.4 如何中断promise【必背】
promise 被中断了,但是 promise 并没有终止,网络请求依然可能返回
中断调用链
:返回一个空的promise,这样就一直处于pending,从而阻止了后面的状态更改(即不会进入then和catch)中断Promise
:利用race实现超时就进reject
link
3.5 promise执行顺序【必背】
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1)
throw new Error('p1失败信息')
}, 2000)
})
const p2 = p1
.then((val) => {
console.log(val, 'p2成功返回的')
return val + 1
})
.catch((err) => {
console.log(err, 'p2失败返回的')
return err
})
Promise.all([p2, Promise.reject(3)])
.then((val2) => {
console.log(val2, 'all成功返回的')
})
.catch((err2) => {
console.log(err2, 'all失败返回的')
})
先打印:3 'all失败返回的'
2秒后报错: VM463:4 Uncaught Error: p1失败信息 at <anonymous>:4:11
接着打印:1 'p2成功返回的'
分析:
Promise.all只要有失败的就只会返回失败的,Promise.reject(3)是失败的,且执行更快,所以走catch,err2为3。
2s后执行p1里面的resolve(1),状态为成功,promise只要状态变了就不会再更改状态,所以resolve(1)后面throw new Error只会被浏览器捕捉,而不会被当作失败返回出去。所以p1最终是成功的,p2链式调用了p1,所以走p2的.then
3.6 promise 执行
【字节】
async function async1() {
console.log('async1start')
await async2()
// 等所有的同步任务执行完才会执行异步任务
console.log('async1end')
}
async function async2() {
console.log('async2')
}
console.log('scriptstart')
setTimeout(function () {
console.log('setTimeout')
}, 0)
async1()
new Promise(function (resolve) {
console.log('promise1')
resolve()
})
.then(function () {
console.log('promise1then')
return 'promise1end'
})
.then((res) => {
console.log(res)
})
.then((res) => {
console.log(res)
})
console.log('scriptend')
//打印如下:
scriptstart
async1start
async2 //同步任务
promise1
scriptend
async1end // await会等所有同步任务执行完后,才继续执行后面的任务
promise1then //
promise1end
undefined // 前面没有return 则默认返回undefined
setTimeout // promise.then里面是微任务,promise.then.then还是微任务,宏任务会等所有微任务执行完再执行。
3.7 如何限制 Promise 请求并发数【必背,只能死记了】
初始化:
- 设置最大并发量
max
- 封装请求函数
fn
- 设置并发池数组
pool
- 设置我们要执行的请求地址数组
URLS
- 设置剩余的请求地址数组
urls
执行逻辑:
- 一开始,
urls
肯定和URLS
一致 - 然后开始循环往
pool
中添加请求任务,直到pool达到限制max
才退出循环- 取出每个请求任务,放入到
pool
中(执行fn
返回的其实就是一个promise请求) - 当任务执行完后,就需要从
pool
出栈
- 取出每个请求任务,放入到
- 循环到达一定程度的时候,
pool
拥堵任务会达到限制max
,这个时候就要退出循环,需要把pool
中的任务执行1个,以达到小于限制max
- 例如:并发池已经有5个任务了,已经达到了最大限制,此时,就需要取出并发池中执行最快的任务
- 将最快的任务执行完,那么并发池就只有4个任务了,此时又可以从剩余的任务池 取出任务到并发池了
- 添加完之后,又达到限制了,又要执行并发池中最快的任务,才能小于限制,这样一直递归
//promise并发限制
class PromisePool {
constructor(max, fn) {
this.max = max //最大并发量
this.fn = fn //自定义的请求函数
this.pool = [] //并发池
this.urls = [] //剩余的请求地址
}
start(urls) {
this.urls = urls //一开始剩余的就是全部的数量
while (this.pool.length < this.max) {
//一开始会走这里,循环把并发池塞满,遍历完才会走后面代码
let url = this.urls.shift() //头部出栈,会改变原数组
this.setTask(url)
}
// 当超过并发池最大值时,url经过上面的一次次出栈,数量已经减少了max项了
// 利用Promise.race方法来获得并发池中某任务完成的信号
let race = Promise.race(this.pool)
return this.run(race)
}
setTask(url) {
if (!url) return
let task = this.fn(url) // 执行任务,返回一个promise对象,promise等待一定时机后才会resolve
this.pool.push(task) // 将该任务推入pool并发池中,然后并发池就开始有一个promise,2个promise...
console.log(`\x1B[43m ${url} 开始,当前并发数:${this.pool.length}`)
task.then(res => {
// 经过一定时间后,执行请求结束将该Promise任务从并发池中移除
// 所以这一段时间内,可能pool中就拥堵了好几个promise任务
this.pool.splice(this.pool.indexOf(task), 1)
console.log(`\x1B[43m ${url} 结束,当前并发数:${this.pool.length}`)
})
}
// 这个地方就是不断通过递归的方式,每当并发池跑完一个任务,就再塞入一个任务
run(race) {
race.then(res => {
// 每当并发池跑完一个任务,就再塞入一个任务
// 例如:并发池已经有5个任务了,已经达到了最大限制,此时,就需要取出并发池中执行最快的任务,
// 将它执行完,那么并发池就只有4个任务了,此时又可以从剩余的任务池 取出任务到并发池了
// 添加完之后,又要执行并发池中最快的任务,这样一直递归
let url = this.urls.shift()
this.setTask(url)
return this.run(Promise.race(this.pool))
})
}
}
//test
const URLS = ['bytedance', 'tencent', 'alibaba', 'microsoft', 'apple', 'hulu', 'amazon']
//自定义请求函数
var requestFn = url => {
return new Promise(resolve => {
setTimeout(() => {
resolve(`任务${url}完成`)
}, 1000)
}).then(res => {
console.log('外部逻辑', res)
})
}
const pool = new PromisePool(5, requestFn) //并发数为5
pool.start(URLS)
3.8 执行 【可略】
【字节】
function Person() {}
let p = new Person()
//{constructor: ƒ}
console.log(p.__proto__)
//{constructor: ƒ}
console.log(Person.prototype)
//{constructor: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ...}
console.log(p.__proto__.__proto__)
//ƒ Object() { [native code] }
console.log(p.__proto__.__proto__.constructor)
//{constructor: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ...}
console.log(p.__proto__.__proto__.constructor.prototype)
//ƒ Object() { [native code] }
console.log(p.__proto__.__proto__.constructor.prototype.constructor)
//ƒ Function() { [native code] }
console.log(p.__proto__.__proto__.constructor.prototype.constructor.constructor)
//ƒ Function() { [native code] }
console.log(p.__proto__.__proto__.constructor.prototype.constructor.constructor.constructor)
3.9 await和promise的关系,分别的应用场景有哪些【必背】
【360】
async
- 可以让函数变成异步的
- async是ES7新出的特性,表明当前函数是异步函数,不会阻塞线程导致后续代码停止运行。
- async 表示函数里有异步操作
- await 表示紧跟在后面的表达式需要等待结果。
- 返回的是一个
promise
对象,状态为resolved
,参数是return
的值
async function asyncFn() { //将这个函数变成异步的
return '1' // 必须将结果return出去
}
asyncFn().then(result => {
console.log(result);
})
console.log('2');
// 依次打印 2 1
async
定义的函数内部会默认返回一个promise
对象,如果函数内部抛出异常或者是返回reject
,都会使函数的promise
状态为失败reject
。
async function e() {
throw new Error('has Error');
}
e().then(success => console.log('成功', success))
.catch(error => console.log('失败', error)); //会走这里
async
函数接收到返回的值,发现不是异常
或者reject
,则判定成功,这里可以return
各种数据类型的值,false
,NaN
,undefined
…总之,都是resolve
但是返回如下结果会使async
函数判定失败reject
- 内部含有直接使用并且未声明的变量或者函数。
- 内部抛出一个错误
throw new Error
或者返回reject
状态return Promise.reject('执行失败')
- 函数方法执行出错(🌰:Object使用push())等等…
还有一点,在async
里,必须要将结果return
回来,不然的话不管是执行reject
还是resolved
的值都为undefine
,建议使用箭头函数。
await:
await
意思是async wait(异步等待)。这个关键字只能在使用async
定义的函数里面使用。任何async
函数都会默认返回promise
,并且这个promise
解析的值都将会是这个函数的返回值,而async
函数必须等到内部所有的await
命令的Promise
对象执行完,才会发生状态改变。
async function awaitReturn() {
return await 1
}
awaitReturn()
.then((success) => console.log('成功', success))
.catch((error) => console.log('失败', error))
在这个函数里,有一个await
函数,async会等到await 1
这一步执行完了才会返回promise
状态,判定resolved
。
await会依次等待执行
const timeoutFn = function (timeout) {
return new Promise(function (resolve) {
return setTimeout(resolve, timeout)
})
}
async function fn() {
await timeoutFn(1000)
await timeoutFn(2000) // 等上面的await执行完才执行
return '完成' // 等上面的await执行完才执行
}
fn().then((success) => console.log(success)) // 3秒后打印完成
返回reject后,后面的代码就不会执行了
let last
async function throwError() {
await Promise.reject('error')
last = await '没有执行' // 上面reject导致这里不会执行
}
throwError()
.then((success) => console.log('成功', last))
.catch((error) => console.log('失败', last)) // 失败 undefined
上面的代码 可以通过try catch来捕获
let last
async function throwError() {
try {
await Promise.reject('error')
last = await '没有执行'
} catch (error) {
console.log('has Error stop') // has Error stop
}
}
throwError()
.then((success) => console.log('成功', last)) //成功 undefined
.catch((error) => console.log('失败', last))
3.10 说一下es6的class【必背】
constructor
constructor 方法是类的构造函数
,是一个默认方法
一个类必须有 constructor 方法,如果没有显式定义,一个默认的 consructor 方法会被默认添加
。所以即使你没有添加构造函数,也是会有一个默认的构造函数。一般 constructor
方法返回实例对象 this
,但是也可以指定 constructor 方法返回一个全新的对象,让返回的实例对象不是该类的实例。
class A {
constructor() {
this.name = 'long'
}
}
var b = new A()
console.log(b) // {name: 'long'}
super
class继承
中,子类
必须在constructor
方法中调用super
方法,否则新建实例时会报错
。这是因为子类自己的this对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法。如果不调用super方法,子类就得不到this对象。
super 这个关键字,既可以当做函数
使用,也可以当做对象
使用。这两种情况下,它的用法完全不用。
当做函数使用:-----------------------------------
class A {}
class B extends A {
constructor() {
super(); // ES6 要求,子类的构造函数必须执行一次 super 函数,否则会报错。
}
}
注:super 代表了父类的构造函数。super 虽然代表了父类 A 的构造函数,但是返回的是子类 B 的实例,即 super 内部的 this 指的是 B,因此 super() 在这里相当于 A.prototype.constructor.call(this, props)
。
class A {
constructor() {
console.log(new.target.name); // new.target 指向当前正在执行的函数
}
}
class B extends A {
constructor {
super();
}
}
new A(); // A
new B(); // B
可以看到,在 super() 执行时,它指向的是 子类 B 的构造函数,而不是父类 A 的构造函数。也就是说,super() 内部的 this 指向的是 B。
当做对象使用-----------------------------------
在普通方法中,指向父类的原型对象;在静态方法中,指向父类。
class A {
c() {
return 2;
}
}
class B extends A {
constructor() {
super();
console.log(super.c()); // 2
}
}
let b = new B();
上面代码中,子类 B 当中的 super.c(),就是将 super 当作一个对象使用。这时,super 在普通方法之中,指向 A.prototype,所以 super.c() 就相当于 A.prototype.c()。
通过 super 调用父类的方法时,super 会绑定子类的 this。
详情
静态方法
类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上static
关键字,就表示该方法不会
被实例继承
,而是直接通过类
来调用,这就称为“静态方法”。
class Foo {
static classMethod() { // 静态方法
return 'hello';
}
}
var foo = new Foo();
Foo.classMethod() // 'hello'
foo.classMethod()
// TypeError: foo.classMethod is not a function
上面代码中,Foo
类的classMethod
方法前有static
关键字,表明该方法是一个静态方法,可以直接在Foo
类上调用(Foo.classMethod()
),而不是在Foo
类的实例上调用。如果在实例上调用静态方法,会抛出一个错误,表示不存在该方法。
注意,如果静态方法包含
this
关键字,这个this
指的是类,而不是实例。
class Foo {
static bar() {
this.baz();
}
static baz() { //这个方法是静态方法,可以直接Foo.bar()调用
console.log('hello');
}
baz() { // 这个方法只能同过new的实例调用
console.log('world');
}
}
Foo.bar() // hello
上面代码中,静态方法bar
调用了this.baz
,这里的this
指的是Foo
类,而不是Foo
的实例,等同于调用Foo.baz
。另外,从这个例子还可以看出,静态方法可以与非静态方法重名。
父类的静态方法,可以被子类继承。
class Foo {
static classMethod() {
return 'hello';
}
}
class Bar extends Foo {
}
Bar.classMethod() // 'hello'
静态方法也是可以从
super
对象上调用的。
class Foo {
static classMethod() {
return 'hello';
}
}
class Bar extends Foo {
static classMethod() {
return super.classMethod() + ', too';
}
}
Bar.classMethod() // "hello, too"
详情
静态属性
静态属性指的是 Class 本身的属性,即Class.propName
,而不是定义在实例对象(this
)上的属性。
class MyClass {
static myStaticProp = 42; //静态属性
constructor() {
console.log(MyClass.myStaticProp); // 42
}
}
MyClass.prop = 2; // 这样也能添加静态属性
上面的写法为Foo
类定义了一个静态属性prop
。
目前,只有这种写法可行,因为 ES6 明确规定,Class 内部只有静态方法,没有静态属性。现在有一个提案提供了类的静态属性,写法是在实例属性法的前面,加上static
关键字。Es7纳入了规范(好像)
3.11 下面这个 class 的四个属性分别属于这个 class 的什么,fn 和 f 有什么区别【必背】
class A {
static a = 1;
b = 2;
f = () => {console.log('f函数')};
fn() {console.log('fn函数')}
}
var b = new A()
console.log(b) // {b: 2, f: ƒ},查看b的原型发现有[fn: ƒ fn()]
console.log(A.a) // 1
console.log(A.b) // undefined 需要通过实例才能调用
console.log(A.fn()) // A.fn is not a function 需要通过实例才能调用
console.log(A.f()) // A.fn is not a function 需要通过实例才能调用
从上面可以看到:
- static是静态属性,只能通过A构造函数直接调用,不能继承,所以a不能继承,而b,f,fn都继承了
- A如果想调用自身的非静态属性,只能通过new A后的实例来调用。
- 函数表达式f直接挂载到实例上(类似于b)是
成员函数
,函数声明的格式fn会挂载到原型上,是属性
3.12 esmodule 介绍一下,它和 commonjs 的区别,主要的优势是什么,还接触过其他的模块化方案么【必背,重点】
【360】
背景
在之前的javascript中是没有模块化概念的。如果要进行模块化操作,需要引入第三方的类库。随着技术的发展,前后端分离,前端的业务变的越来越复杂化。直至ES6带来了模块化,才让javascript第一次支持了module。ES6的模块化分为导出(export)
与导入(import)
两个模块。
export的用法
在ES6中每一个模
块即是一个文件
,在文件中定义的变量,函数,对象在外部
是无法获取
的。如果你希望外部可以读取模块当中的内容,就必须使用export
来对其进行暴露
(输出)。
先来看个例子,来对一个变量进行模块化。
//我们先来创建一个test.js文件,来对这一个变量进行输出:
export let myName="laowang";
//然后可以创建一个index.js文件,以import的形式将这个变量进行引入:
import {myName} from "./test.js";
console.log(myName);//laowang
如果要输出多个变量
可以将这些变量包装成对象
进行模块化输出:
let myName="laowang";
let myAge=90;
let myfn=function(){
return "我是"+myName+"!今年"+myAge+"岁了"
}
export {
myName,
myAge,
myfn
}
******************************接收的代码调整为*********************
import {myfn,myAge,myName} from "./test.js";
console.log(myfn());//我是laowang!今年90岁了
console.log(myAge);//90
console.log(myName);//laowang
如果你不想暴露模块当中的变量名字,可以通过as来进行操作:
let myName="laowang";
let myAge=90;
let myfn=function(){
return "我是"+myName+"!今年"+myAge+"岁了"
}
export {
myName as name,
myAge as age,
myfn as fn
}
/******************************接收的代码调整为**********************/
import {fn,age,name} from "./test.js";
console.log(fn());//我是laowang!今年90岁了
console.log(age);//90
console.log(name);//laowang
也可以直接导入整个模块,将上面的接收代码修改为:
import * as info from "./test.js";//通过*来批量接收,as 来指定接收的名字
console.log(info.fn());//我是laowang!今年90岁了
console.log(info.age);//90
console.log(info.name);//laowang
默认导出(default export) 一个模块只能有一个默认导出,对于默认导出,导入的名称可以和导出的名称不一致。
/******************************导出**********************/
export default function(){
return "默认导出一个方法"
}
/******************************引入**********************/
import myFn from "./test.js";//注意这里默认导出不需要用{}。
console.log(myFn());//默认导出一个方法
可以将所有需要导出的变量放入一个对象中,然后通过default export进行导出
/******************************导出**********************/
export default {
myFn(){
return "默认导出一个方法"
},
myName:"laowang"
}
/******************************引入**********************/
import myObj from "./test.js";
console.log(myObj.myFn(),myObj.myName);//默认导出一个方法 laowang
同样也支持混合导出
/******************************导出**********************/
export default function(){
return "默认导出一个方法"
}
export var myName="laowang";
/******************************引入**********************/
import myFn,{myName} from "./test.js";
console.log(myFn(),myName);//默认导出一个方法 laowang
commonJS的用法:
// 定义模块math.js
var basicNum = 0;
function add(a, b) {
return a + b;
}
module.exports = { //在这里写上需要向外暴露的函数、变量
add: add,
basicNum: basicNum
}
// 引用自定义的模块时,参数包含路径,可省略.js
var math = require('./math'); // 引入是的导出的整个对象
math.add(2, 5);
// 引用核心模块时,不需要带路径
var http = require('http');
http.createService(...).listen(3000);
commonJS和esmodule的区别:
区别 | commonJS | esmodule |
---|---|---|
加载模块的方式 | 同步 | 异步 |
模块输出 | 值的拷贝 | 值的引用 |
加载时机 | 运行时加载 | 编译时输出 |
this指向 | 指向当前模块 | 指向undefined |
加载内容 | 整个模块,即将所有的方法全部加载进来 | 可以单独加载其中的某个方法 |
- commonJS用
同步
的方式加载模块
。在服务端
,模块文件都存在本地磁盘
,读取
非常快
,所以这样做不会有问题。但是在浏览器端,限于网络原因,更合理的方案是使用异步加载。 - CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
- CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
ES6的模块不是对象,import
命令会被 JavaScript 引擎静态分析
,在编译
时就引入模块代码
,而不是在代码运行时加载,所以无法实现条件加载
。也正因为这个,使得静态分析成为可能。/
也由于有了静态分析,treeshaking才成为可能。
详情
3.13 CMD和AMD的区别【必背】
AMD是异步的,AMD规范采用异步方式加载模块,模块的加载不影响它后面语句的运行。
用require.config()指定引用路径等,用define()定义模块,用require()加载模块。
3.14 模块化的作用【必背】
- 传统
script
标签的代码加载容易导致全局作用域污染
,而且要维系一系列script的书写顺序,项目一大,维护起来越来越困难。 - 模块系统通过声明式的暴露和引用模块使得各个
模块之间
的依赖
变得明显
。 - 方便代码复用。
3.15 懒加载加载的的时机是什么时候【必背】
【字节】
发生在执行(import('xxModule'))
的时候,它会单独打成一个包,采用动态加载的方式,具体过程:当用户触发其加载的动作时,会动态的在head
标签中创建一个script
标签,然后发送一个http
请求,加载模块,模块加载完成以后自动执行其中的代码,主要的工作有两个,更改缓存中模块的状态,另一个就是执行模块代码。
4. vue
4.1 mvvm
MVVM的核心是数据驱动
即ViewModel,ViewModel
是View和Model的关系映射
。
MVVM本质就是基于操作数据
来操作视图
进而操作DOM
,借助于MVVM无需直接
操作DOM,开发者只需编写ViewModel
中有业务
,使得View完全实现自动化
。
4.2 SPA页面和传统页面的区别
【佰锐科技】
详情>
4.3 怎么理解 Vue 的单向数据流?
Vue 的单向数据流:
所有的 prop
都使得其父子 prop
之间形成了一个单向下行绑定:父级 prop
的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外改变父级组件的状态,从而导致你的应用的数据流向难以理解。
额外的,每次父级组件发生更新时,子组件中所有的 prop
都将会刷新为最新的值。这意味着你不应该在一个子组件内部改变 prop
。如果你这样做了,Vue 会在浏览器的控制台中发出警告。
为什么不能子组件直接修改父组件传来的值呢?
父组件的值可能会不断发生变化,那么如果我们子组件对父组件传来的值比如说 props 有一个 number,子组件收到了 number=1,在收到后,子组件直接改变number 的值为 5,去做些事情,但还未做时父组件数据更新了,传过来一个值 3,也就是说子组件刚将其变为 5,父组件又把它变成了 3,可能影响子组件的使用。
在子组件中直接用 v-model 绑定父组件传过来的数据是不合理的,如果希望修改父组件传给子组件的值:
(1)在子组件 data 中创建一个变量获取 props 中的值,再改变这个 data 中的值。
(2)子组件使用 $emit 发出一个事件,让父组件接收去修改这个值。
4.4 输入一个二叉树和两个 node,输出这两个 node 的最近公共祖先【可略】
【蓝湖】
不会
4.5 双向绑定原理【必背,重点】
【字节】
详情>
4.6 自定义组件的双向绑定怎么写【必背】
有两种方式:
1.采用sync修饰符
sync说明:当一个子组件改变了一个 prop 的值时,这个变化也会同步到父组件中所绑定
父组件:
<button @click="showAddress">显示弹窗</button>
<receivingModal :showModal.sync="showModal" ></receivingModal>
...
data: {
return {
showModal : false
}
},
methods:{
addAddressModal(){
this.showModal = true
},
}
子组件
<Modal title="添加商户配置" v-model="visible">
...
</Modal>
props: {
showModal: {
type: Boolean,
default: false,
},
//利用computed,读取的时候读传递进来的值,修改的时候,直接触发修改父组件的传递进来的属性
computed: {
visible: {
set(val) {
//直接更新父组件对应的属性,省去了父组件接收的事件
return this.$emit('update:showModal', val)
},
get() {
return this.showModal
}
}
},
2.采用v-model
父组件:
<PortBtnTab v-model="currentCustom"></PortBtnTab>
data() {
return {
currentCustom: 0,
}
},
子组件:
<template>
<Button :key="index" @click="handleTabClick(index)">
{{ item.name }}
</Button>
</template>
<script>
export default {
model: {
prop: 'currentCustom', //对应父组件v-model的key currentCustom
event: 'change' // 随便命名事件,对应下面$emit即可
},
props: {
currentCustom: { // 接收,注意,不能直接修改它
type: Number,
required: true
},
},
methods: {
handleTabClick(index) {
//事件和上面的event保持一致,index值会传递到父组件的currentCustom属性上
this.$emit('change', index)
},
}
}
</script>
4.7 写个自定义 v-model【必背】
注意,自定义指令有以下几个钩子函数:
-
bind:
只调用一次
,指令第一次绑定
到元素
时调用,可以定义一个在绑定时
执行一次
的初始化
动作。例如点击按钮触发事件可以用这个 -
inserted: 被绑定元素
插入父节点
时调用(父节点存在即可调用,不必存在于 document 中),例如直接格式化时间可以用这个。 -
update: 被绑定元素所在的模板
更新时
调用,而不论绑定值是否变化。通过比较更新前后的绑定值。 -
componentUpdated: 被绑定元素所在模板完成一次
更新周期时
调用。 -
unbind: 只调用
一次
, 指令与元素解绑时
调用。
钩子函数的参数:
el
:指令所绑定的元素,可以用来直接操作 DOM 。binding
:一个对象,包含以下属性:name
:指令名,不包括v-
前缀。bingding.name
value
:指令的绑定值,例如:v-my-directive="1 + 1"
中,绑定值为2
。oldValue
:指令绑定的前一个值,仅在update
和componentUpdated
钩子中可用。无论值是否改变都可用。expression
:字符串形式的指令表达式。例如v-my-directive="1 + 1"
中,表达式为"1 + 1"
。arg
:传给指令的参数,可选。例如v-my-directive:foo
中,参数为"foo"
。modifiers
:一个包含修饰符的对象。例如:v-my-directive.foo.bar
中,修饰符对象为{ foo: true, bar: true }
。
vnode
:Vue 编译生成的虚拟节点也就是虚拟dom。oldVnode
:上一个虚拟节点,仅在update
和componentUpdated
钩子中可用。
v-modal原理
实现:
gloabelDirective.js
export default {
install(Vue) {
Vue.directive('Model', {
inserted(el, binding, vnode) {
// 当前dom节点 绑定的数据 虚拟节点,vnode可以用来修改组件绑定数据
// binding.value就是绑定的inpval的值
console.log(el, binding, vnode, vnode.context.form)
},
update (el, binding, vnode) {
//将用到了Model指令的组件data下的inpval属性更新
vnode.context.inpval = binding.value
},
})
}
}
mian.js引入
import gloabelDirective from './gloabelDirective.js'
Vue.use(gloabelDirective)
使用
<Input v-Model="inpval"></Input>
....
data() {
return {
inpval: ''
}
}
4.8 如果希望DOM中的一个值和js中的变量双向绑定,使用原生js可以怎么做
【大疆】
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>双向</title>
</head>
<body>
<input id="inpVal" type="text" />
<span id="msg"></span>
<script>
var obj = { test: '' }
Object.defineProperty(obj, 'test', {
set(val) {
document.querySelector('#msg').innerHTML = val
}
})
document.querySelector('#inpVal').onkeyup = function(e) {
obj.test = e.target.value
}
</script>
</body>
</html>
4.9 Vue 组件之间的通信方式都有哪些,用过 eventbus吗,eventbus 的思想是什么【必背,手写多看几遍】
【网易传媒】 写一个EventBus,包含emit/on/off
组件通信方式
eventbus思想是发布订阅模式,手写eventBus
4.10 写一个发布订阅模式的on/emit/off
【百度】
7.1 如果需要把订阅者执行成功和失败的方法分开,需要怎么做
7.2 如果希望失败的可追溯,找到是哪个订阅者的报错,需要怎么做
7.3 实现一下before和after方法,可以添加一些前置的和后置的订阅者
7.4 现在希望给所有的订阅者加打点上报的功能,并且提供全局的开关,需要如何设计
7.5 如果需要给某一个订阅者单独加一个打点,需要如何设计
4.11 如果想给一个对象上的所有方法在执行时加一些打点上报的功能,如何做
【百度】
4.12 attrs和$listener 有了解吗
$attrs和$listener
4.13 生命周期【必背】
【佰锐科技】
Vue 生命周期有哪些?都是做什么的?
beforeCreate 的时候能拿到 Vue 实例?
不能,此时data,method,props都拿不到
组件销毁的时候调用的是哪个 API?
生命周期>
updated 什么情况下会触发:
无论是组件本身的数据变更,还是从父组件接收到的 props
或者从vuex
里面拿到的数据有变更,都会触发虚拟 DOM
重新渲染和打补丁,并在之后调用 updated
。
例子:
<template>
<div>
<div v-for="(item, index) in list" :key="index">{{item}}</div>
</div>
</template>
<script>
export default {
data () {
return {
list: [1, 1, 1]
}
},
created () {
setTimeout(_ => {
this.list = [2, 2, 2]
}, 1000)
},
updated () {
console.log('App.vue finish re-render')
}
}
</script>
页面首先渲染了3个1的列表,1s后页面重绘为3个2,并打出App.vue finish re-render的日志。
4.14 什么情况下会触发组件销毁【必背】
什么情况下会触发组件销毁
- v-if会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。
- 切换路由的时候,(没有使用keep-alive时的路由切换)
- 比如页面关闭
- 还可以改变key值来销毁组件,这属于手动销毁组件
销毁的时候会卸载自定义事件和原生事件么?
会的
4.15 传统前端开发和框架开发的区别是什么【必背】
组件化
:Vue.js 可以进行组件化开发,便于封装和复用响应式
:数据变动更新视图,视图变得更新数据hash路由
:相比传统的页面通过超链接实现页面的切换和跳转,Vue 使用路由不会刷新页面。虚拟DOM
:不直接操作dom- span
4.16 响应式原理,Watcher 的 cleanDeps 是做什么的
【网易灵犀,奥凯信息服务公司,字节】
不会啊
详情>
4.17 Vue2 的数据响应式有两个缺陷,你知道是哪两个缺陷么,为什么会有这样的缺陷,如何解决【必背】
1. 无法监听新增属性
在使用Vue2.0
的时候,如果给对象添加新属性的时候,往往需要调用$set
, 这是因为Object.defineProperty
只能监听已存在的属性,而新增的属性无法监听,而通过$set
相当于手动给对象新增了属性,然后再触发数据响应。但是对于Vue3.0
来说,因为使用了Proxy
, 在他的set
钩子函数中是可以监听到新增属性的,所以就不再需要使用$set
解决
vm.set(this.list, "key", value)
2. 无法监听对象属性的删除
其实与$set
解决的问题类似,Vue2.0
是无法监听到属性被删除的,所以提供了$delete
用于删除属性,但是对于Proxy
,是可以监听删除操作的,所以就不需要再使用$delete
了
解决
vm.delete(this.list, "key")
3. 无法监听数组下标或者length的变化
Object.defineProperty
的另一个缺陷,无法监听数组(只能监听对象)的变化。 Vue是可以检测到数组变化(是因为对数组进行了原型链重写),但是只有以下八种方法。
以下方式不能检测:
- 当你利用索引直接设置一个数组项时,例如:
vm.list[indexOfItem] = newValue
- 当你修改数组的长度时,例如:
vm.list.length = newLength
解决办法
vm.list.splice(indexOfItem, 1, newValue) //针对第一种情况
vm.list.splice(newLength) //针对第二种情况
以下方式可以检测:
push()
pop()
shift()
unshift()
splice()
sort()
reverse()
其实作者在这里用了一些奇技淫巧,把无法监听数组的情况hack掉了,以下是方法示例。使用了函数劫持
的方式,重写
了数组的方法,Vue将data
中的数组
进行了原型链重写
,指向了自己定义
的数组原型方法
。这样当调用数组api时,可以通知依赖更新。如果数组中包含着引用类型,会对数组中的引用类型再次递归遍历进行监控。这样就实现了监测数组变化。
const aryMethods = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'];
const arrayAugmentations = [];
aryMethods.forEach((method)=> {
// 这里是原生Array的原型方法
let original = Array.prototype[method];
// 将push, pop等封装好的方法定义在对象arrayAugmentations的属性上
// 注意:是属性而非原型属性
arrayAugmentations[method] = function () {
console.log('我被改变啦!');
// 这里就可以写一些相关的更新函数,触发视图更新
// 调用对应的原生方法并返回结果
return original.apply(this, arguments);
};
});
let list = ['a', 'b', 'c'];
// 将我们要监听的数组的原型指针指向上面定义的空数组对象
// 别忘了这个空数组的属性上定义了我们封装好的push等方法
if(Array.isArray(list)){ //如果是数组,重写原型
list.__proto__ = arrayAugmentations;
}
list.push('d'); // 我被改变啦! 4
// 这里的list2没有被重新定义原型指针,所以就正常输出
let list2 = ['a', 'b', 'c'];
list2.push('d'); // 4
由于只针对了八种方法进行了hack,所以其他数组的属性也是检测不到的
4.18 nextTick用途和原理【必背】
在下次DOM更新循环结束
之后执行延迟回调
。在这里面的代码会等到dom更新以后
再执行
原理:
简单来说,一开始就会收集每个写在nextTick中的函数放到一个callback数组中,再设置一个pengding状态,初始化为false,来标识dom是否更新了,当dom更新后,pengding改为true,此时就会遍历callback,依次执行里面的函数。如果浏览器支持Promise,那么就用Promise.then的方式来延迟函数调用
简单使用
原理
4.19 keep-alive 的原理是什么,如果不用它的话怎么自己实现类似的效果【必背】
了解keep-alive
有时间可以看-原理
4.20 v-show 与 v-if 的区别【必背】
link
4.21 slot了解吗?有哪几种类型【必背】
【佰锐科技】
详情>
4.22 vue和react的区别【必背】
【360】
对比项 | vue | react |
---|---|---|
设计思想 | 渐进式框架,采用自底向上增量开发的设计。不需要一上手就把所有东西全用上,例如vuex想用就用 | react主张函数式编程,所以推崇纯组件,数据不可变,单向数据流 |
编写语法 | 保留了html、css、js分离的写法 | 直接就是一个渲染函数 |
构建工具 | CLI 脚手架,vite | create-react-app |
数据绑定 | 双向数据绑定的mvvm框架 | react是单向数据流,react中属性是不允许更改的,状态是允许更改的,可以通过setState来进行更改 |
指令 | 提供 | react中没有v-for指令,所以循环渲染的时候需要用到map()方法来渲染视图 |
原生渲染native | vue和Weex进行官方合作 | react native |
生命周期 | 4个 | 5个(getInitialState:为实例挂载初始状态) |
vue的优点是什么
详情
【小红书,荔枝】
4.23 虚拟 DOM【必背,必背】
【佰锐科技,奥凯信息】
一个用来描述真实DOM的javaScript对象
虚拟 DOM和真实DOM的区别
两者的区别如下:
-
虚拟DOM
不会进行重排
与重绘
操作,而真实DOM会频繁重排与重绘 -
虚拟DOM的总损耗是
“虚拟DOM增删改
+真实DOM差异增删改
+排版与重绘”
。真实DOM的总损耗是“真实DOM完全增删改+排版与重绘”
拿之前文章举过的例子:
传统的原生api或jQuery去操作DOM时,浏览器会从构建DOM树开始从头到尾执行一遍流程
当你在一次操作时,需要更新10个DOM节点,浏览器没这么智能,收到第一个更新DOM请求后,并不知道后续还有9次更新操作,因此会马上执行流程,最终执行10次流程
而通过VNode,同样更新10个DOM节点,虚拟DOM不会立即操作DOM,而是将这10次更新的diff内容
保存到本地的一个js对象
中,最终将这个js对象一次性attach到DOM树
上,避免大量的无谓计算
优缺点:
真实DOM的优势:
- 易用,直接
缺点:
-
效率低,解析速度慢,内存占用量过高
-
性能差:频繁操作真实DOM,会增加重绘与重排
使用虚拟DOM的优势如下:
-
简单方便:如果使用手动操作真实DOM来完成页面,繁琐又容易出错,在大规模应用下维护起来也很困难
-
性能方面:使用Virtual DOM,能够有效避免真实DOM树频繁更新,减少多次引起重绘与重排,提高性能
-
跨平台:虚拟DOM是普通js对象, 有了跨平台的能力,一套代码多端运行
缺点:
-
在一些性能要求极高的应用中虚拟 DOM 无法进行针对性的极致优化
-
首次渲染大量DOM时,由于多了一层虚拟DOM的计算,速度比正常稍慢
虚拟 DOM 实现原理
构建虚拟dom树时用了什么算法
diff算法
4.24 vue的v-for的key是怎么回事?diff的过程【必背】
【美团,奥凯信息,金山,网易传媒】
key
key 是为 Vue 中 vnode 的唯一标记,通过这个 key,我们的 diff 操作可以更快速准确。
diff算法时,新旧节点两两四次的快捷比对时,如果没有匹配到,就需要通过旧节点的key
值列表查找,如果找到了说明是已有的节点,只是位置不对,那就移动节点位置即可。如果没有找到说明是新增的节点(创建对应的真实Dom
,插入到旧节点里start
对应真实Dom
的前面。),从而提高整个diff
比对的性能。
link
diff使用的算法是:
diff使用的是双端比较的算法
- 比较的时候,是以oldNode为基准,在oldNode上进行改动。
- 新旧节点比较的时候,先同级比较,再比较各自的子节点。
- 如果新节点有子节点,而旧节点没有子节点,那么就需要在oldNode上新增对应部分。
- 如果新节点有没有子节点,而旧节点有子节点,那么就需要在oldNode上删除对应部分。
- 如果新旧节点都有子节点,那么新旧节点就找到各自的开始和结束位置进行两两比较
- 新开始-旧开始,就都把开始位置后移一位
- 新结束-旧结束,就都把开始位置前移一位
- 新开始-旧结束,就都把新开始位置后移一位,旧结束位置前移一位
- 新结束-旧开始,就都把新结束位置前移一位,旧结束位置后移一位
- 两两比较后,如果还是没有匹配到的话,同过key来查找,如果能找到就说明节点存在,只是位置移动了,那么把它添加到oldNode的开始位置,然后把key所在的oldNode的原来的位置设置为undefined,后续再两两比较时就跳过该节点。
详情>
4.25 vue 和 react diff对比【可略】
【cvte】
详情
4.26 组件中 data 为什么是一个函数?【必背】
因为组件可以被复用,如果data是对象的话,对象又是引用类型, 那么其中一个引入的组件data改变了就会同步影响其它组件的data,造成数据混乱。而如果是函数的话,就形成了闭包,各个引用的组件就互不影响了。
详情
4.27 vue 和 react 都看过哪些部分源码【有时间可看】
react只是用过,研究的比较少。
vue我之前写过大概10篇源码相关的文章,比如响应式,diff算法,生命周期,parse函数。(具体看自己写的掘金文章)
4.28 Vue 的单文件开发模式,这个解析 vue-loader 是如何实现的。【必背,重点】
【知乎】
通过 vue-loader, webpack 可以将 .vue 文件 转化为 浏览器可识别的javascript。
vue-loader 的工作流程, 简单来说,分为以下几个步骤:
- 将一个 .vue 文件 切割成 template、script、styles 三个部分。
- template 部分 通过 compile 生成 render、 staticRenderFns。
- 获取 script 部分 返回的配置项对象 scriptExports。
- styles 部分,会通过 css-loader、vue-style-loader, 添加到 head 中, 或者通过 css-loader、MiniCssExtractPlugin 提取到一个 公共的css文件 中。
- 使用 vue-loader 提供的 normalizeComponent 方法, 合并 scriptExports、render、staticRenderFns, 返回 构建vue组件需要的配置项对象 - options, 即 {data, props, methods, render, staticRenderFns…} 。
详情
4.29 vuex【必背】
【金山,字节】
用于组件间通信的状态管理模式
-
vuex详情>
-
哪些情况用 vuex ?
场景:登录,注册(需要根据登录用户信息来展示菜单和用户名)
- 多个视图依赖于同一状态。
- 频繁跨组件间通信
-
除了 vuex ,还有哪些组件间通信方式?详情>
-
你是如何处理兄弟组件间通信的?
- vuex
- eventBus
- localStorage,sessionStorage
4.30 讲讲vue的计算属性为什么说是无害的【必背】
【字节】
这个无害是相对于watch和methods,计算属性能减少性能的损耗
我们可以将同一函数定义为一个方法而不是一个计算属性。两种方式的最终结果确实是完全相同的。然而,不同的是计算属性是基于它们的依赖进行缓存的。计算属性只有在它的相关依赖发生改变时才会重新求值。这就意味着只要依赖的data还没有发生改变,多次访问计算属性会立即返回之前的计算结果,而不必再次执行函数。
而方法是每次都会执行, watch在依赖的data没有改变也会执行。
4.31 computed 和 watch 是什么原理【必背,需要看下原理】
【网易灵犀】
watch总结:
这里说明了为什么watch 和 this.$watch 的实现是一致的, 以及简单解释它们的原理就是为需要观察的数据创建并收集user-watcher。
当数据改变是通知到user-watcher 将新值和旧值传递给用户自己定义的回调函数。
最后分析了定义watch时会被使用到的三个参数: sync, immediate, deep 它们的实现原理。
简单说明它们的实现原理就是: sync是不将watcher加入到nextTick队列而是同步的更新, immediate 是立即以得到的值执行一次回调函数, deep是递归的对它的子值进行依赖收集。
computed总结:
为什么计算属性有缓存功能?
因为当计算属性经过计算后, 内部的标志位会表明已经计算过了。再次访问时会直接读取计算后的值;
为什么计算属性内的响应式数据发生变更后,计算属性会重新计算?
因为内部的响应式数据会收集computed-watcher, 变更后通知计算属性要进行计算, 也会通知页面重新渲染, 渲染时会读取到重新计算后的值。
4.32 如果 data 里有一个对象,不希望它被深层监听,需要怎么做【必背】
【网易灵犀】
-
使用v-once
-
利用
Object.freeze()
提升性能。
Object.freeze()
可以冻结一个对象,冻结之后不能向这个对象添加新的属性,不能修改其已有属性的值,不能删除已有属性,以及不能修改该对象已有属性的可枚举性、可配置性、可写性。该方法返回被冻结的对象。
4.34 Vue 从修改属性到渲染到页面上都经历了什么,浏览器渲染机制【必背】
【美团,便利蜂,腾讯音乐,字节】
浏览器渲染流程是怎样的:
DNS 解析
: 将域名解析成 IP 地址TCP 连接
:TCP 三次握手- 发送
HTTP 请求
- 服务器处理请求并
返回 HTTP 报文
- 浏览器解析渲染页面,构建
dom树
,样式树
,渲染树
,layout布局,painting绘制 断开连接
:TCP 四次挥手
详情->
将template渲染成dom的流程:
- 把模板编译为
render函数
- 实例进行挂载, 根据根节点render函数的调用,递归的生成
虚拟dom
- 对比虚拟dom,渲染到
真实dom
- 组件内部
data发生变化
,组件和子组件引用data作为props重新调用render函数,生成虚拟dom, 返回到步骤3
修改属性重新渲染的逻辑:
- 触发setter方法
- 派发更新notify
- 触发render函数
- 对比虚拟dom,渲染到真实dom
详情>
4.35 观察者模式 【必背】
【便利蜂,虾皮】
和发布订阅有些类似,最大的区别就是,观察者模式是观察者直接观察目标对象,没有中介层。而发布订阅模式多了一个中介层,发布者和订阅者依赖于中介层,并不直接通信。
观察者模式与发布订阅模式相比,耦合度更高,通常用来实现一些响应式的效果。在观察者模式中,只有两个主体,分别是目标对象Subject
,观察者Observer
。
- 观察者需
Observer
要实现update
方法,供目标对象调用。update
方法中可以执行自定义的业务代码。 - 目标对象
Subject
也通常被叫做被观察者或主题,它的职能很单一,可以理解为,它只管理一种事件。Subject
需要维护自身的观察者数组observerList
,当自身发生变化时,通过调用自身的notify
方法,依次通知每一个观察者执行update
方法。
按照这种定义,我们可以实现一个简单版本的观察者模式。
// 观察者
class Observer {
// 构造器 cb 回调函数,收到目标对象通知时执行
constructor(cb){
if (typeof cb === 'function') {
this.cb = cb
} else {
throw new Error('Observer构造器必须传入函数类型!')
}
}
// 被目标对象通知时执行
update() {
this.cb()
}
}
// 目标对象
class Subject {
constructor() {
// 维护观察者列表
this.observerList = []
}
// 添加一个观察者 Observer实例
addObserver(observer) {
this.observerList.push(observer)
}
// 通知所有的观察者
notify() {
this.observerList.forEach(observer => {
observer.update()
})
}
}
const observerCallback = function() {
console.log('我被通知了')
}
const observer = new Observer(observerCallback)
const subject = new Subject();
subject.addObserver(observer);
subject.notify();
特点
- 角色很明确,没有事件调度中心作为中间者,目标对象
Subject
和观察者Observer
都要实现约定的成员方法。 - 双方联系更紧密,目标对象的主动性很强,自己收集和维护观察者,并在状态变化时主动通知观察者更新。
4.36 简单介绍以下Vue-router的原理【原理】
【网易传媒】
介绍
有空需要看下实现
4.37 Vue的插槽的实现原理是什么
【腾讯音乐】
不知道
4.38 Vue2的重复逻辑封装一般都有哪些方式
【腾讯音乐】
4.39 如果是需要通过调用API显示UI组件,这种需要如何实现(比如Toast、Dialog)
【腾讯音乐】
- this.$ref[‘myconponent’].show
4.40 useCallback和useMemo的区别和使用场景
【腾讯音乐】
5. vue3
5.1. vue3和vue2的区别【必背】
【易工品,腾讯,荔枝,360】
标题 | vue2 | vue3 |
---|---|---|
Template 是否支持多个根标签 | 不支持 | 支持 |
如何挂载实例 | new Vue({template, render}) | createApp(组件) |
v-model | v-model.sync | 直接写v-model |
emit使用 | this.$emit | context.emit |
具名插槽 | 子组件:<slot name="title"> ,父组件:template slot="title"></template> | 父组件:<template v-slot:title><h1>哈哈哈</h1></template> |
setup | 无 | setup 是 Vue3.x 新增的一个选项, 他是组件内使用 Composition API 的入口。,setup 执行时机是在 beforeCreate 之前执行 |
详情>
vue3 新属性
5.2 vue3 的组合式 API 有了解吗,它有哪些优势【必背】
【网易传媒】
link
优势:
vue3组合式 以业务为模块,结构更加明显,解决 数据和功能分离的问题,即方法和 data 里的数据隔了一层进行调用的问题
5.3 了解过 Vue3 么,为什么还没有上 Vue3,了解 Proxy 么,它和 defineProperty 的区别是什么,性能上有什么区别么【必背】
【佰锐科技】
自己有写过个人项目,项目上还没有用,主要是考虑vue3的成熟度,还有组里面成员的可接受程度,改造的工程量也大,学技术还可以,但是对象项目组来说,收益小。
Proxy: Vue2 的数据响应式有两个缺陷这一节有讲
性能:
proxy操作对象快,操作数组慢,V8 已经优化过一波了但还是很慢(只是优化了对象的访问,性能提升了 27% 到 438%,但是没有优化数组。)参考
5.4 为什么proxy比Object.defineProperty更快
【佰锐科技】
6. 手写
6.1 手写promise【必背,还要手写】
手写promise
实现 Promise.all
【美团买药,字节,奥凯信息】
//静态的all方法
static all(promiseArr) {
let index = 0
let result = []
return new MyPromise((resolve, reject) => {
promiseArr.forEach((p, i) => {
//Promise.resolve(p)用于处理传入值不为Promise的情况
MyPromise.resolve(p).then(
val => {
index++
result[i] = val
//所有then执行后, resolve结果
if(index === promiseArr.length) {
resolve(result)
}
},
err => {
//有一个Promise被reject时,MyPromise的状态变为reject
reject(err)
}
)
})
})
}
详情>
6.2 自己实现一个 Promise.all【必背,还要手写】
// 输入不仅仅只有Array
function promiseAll (args) {
return new Promise((resolve, reject) => {
const promiseResults = [];
let initIndex = 0;
// 用于迭代iterator数据
for (const item of args) {
// for of 遍历顺序,用于返回正确顺序的结果
// 因iterator用forEach遍历后的key和value一样,所以必须存一份for of的 initIndex
let resultIndex = initIndex;
initIndex += 1;
// 包一层,以兼容非promise的情况
Promise.resolve(item).then(res => {
promiseResults[resultIndex] = res;
if (initIndex === args.length) {
resolve(promiseResults)
}
}).catch(err => {
reject(err)
})
}
// 处理空 iterator 的情况
if(initIndex===0){
resolve(promiseResults)
}
}
)
}
if (!Promise.all) Promise.all = promiseAll;
6.2 懒加载
6.2.1 路由懒加载
const router = new VueRouter({
routes: [
{ path: '/home', component: () => import('@/views/home/home.vue') } // 匿名函数引入
]
})
link
6.2.2 组件懒加载
<template>
<div class="hello">
<One></One>
</div>
</template>
<script>
const One = ()=>import("./one" ); //组件懒加载
import Two from './Two' ; //非组件懒加载
export default {
components:{
One,
Two
},
data () {
return {
msg: 'Welcome to Your Vue.js App'
}
}
}
</script>
6.2.3 图片懒加载
link
6.4 继承【必背】
继承
6.5 模拟call【必背】
模拟call
6.6 new一个函数做了哪些事【必背】
【网易传媒,玄武科技】
// 新建构造函数--用来new的函数
// 构造函数是一种特殊的方法:用来在创建对象时为对象成员变量赋初始值
function Dog(name){
// 这里的this指向window,所以下面才需要重新将this指向自身,这样才能将属性挂载成功
this.name = name
this.say = function() {
console.log("my name is" + this.name);
}
}
// 手写new函数
function _new(fn,...arg){
const obj = {};
// 将构造函数的原型挂载到新对象上
Object.setPrototypeOf(obj, fn.prototype)
// 将this指向新对象,执行构造函数
let result = fn.apply(obj, arg);
//如果函数没有返回其他对象,那么 new 表达式中的函数调用会自动返回这个新对象
return result instanceof Object ? result : obj
}
// 验证
var dog = _new(Dog, "caigou")
dog.say();// my name is caigou
7. 安全
7.1鉴权有了解么,jwt 如何实现踢人,session 和 jwt 鉴权的区别【必背】
【aptap】【滴滴】
Json Web Token,jwt就是签发token和校验token的一种机制
jwt流程:
- 用户使用用户名密码来请求服务器
- 服务器进行验证用户信息
- 服务器通过验证发给用户一个token
- 客户端存储token,并在每次访问时加上这个token值
- 服务器验证token值 并返回数据
- 这个token必须在每次请求时传递给服务器,他应该保存在请求头里,另外服务端要支持CORS(跨来资源共享)的策略
JWT优点
- 因为json的通用性,所以JWT是可以进行跨语言支持的,像JAVA,JavaScript,NodeJS,PHP等很多语言都可以使用。
- 因为有了payload部分,所以JWT可以在自身存储一些其他业务逻辑所必要的非敏感信息。
- 便于传输,jwt的构成非常简单,字节占用很小,所以它是非常便于传输的。
- 它不需要在服务端保存会话信息, 所以它易于应用的扩展
传统的session认证
我们知道,http协议本身是一种无状态的协议,而这就意味着如果用户向我们的应用提供了用户名和密码来进行用户认证,那么下一次请求时,用户还要再一次进行用户认证才行,因为根据http协议,我们并不能知道是哪个用户发出的请求,所以为了让我们的应用能识别是哪个用户发出的请求,我们只能在服务器存储一份用户登录的信息,这份登录信息会在响应时传递给浏览器,告诉其保存为cookie,以便下次请求时发送给我们的应用,这样我们的应用就能识别请求来自哪个用户了,这就是传统的基于session认证。
但是这种基于session的认证使应用本身很难得到扩展,随着不同客户端用户的增加,独立的服务器已无法承载更多的用户,而这时候基于session认证应用的问题就会暴露出来.
session存在的问题
占内存
:通常而言session都是保存在内存中,而随着认证用户的增多,服务端的开销会明显增大。扩展能力弱
:用户认证之后,服务端做认证记录,如果认证的记录被保存在内存中的话,这意味着用户下次请求还必须要请求在这台服务器上,这样才能拿到授权的资源,这样在分布式的应用上,相应的限制了负载均衡器的能力。这也意味着限制了应用的扩展能力。
基于token的鉴权机制
基于token的鉴权机制类似于http协议也是无状态的,它不需要在服务端去保留用户的认证信息或者会话信息。这就意味着基于token认证机制的应用不需要去考虑用户在哪一台服务器登录了,这就为应用的扩展提供了便利。
JWT的构成
第一部分我们称它为头部(header),第二部分我们称其为载荷(payload, 类似于飞机上承载的物品),第三部分是签证(signature).
link
单点登录:
CAS分为两部分,CAS Server和CAS Client
-
CAS Server用来负责用户的认证工作,就像是把第一次登录用户的一个
标识
存在这里,以便此用户在其他系统登录时验证其需不需要再次登录。 -
CAS Client就是我们自己的应用,需要接入CAS Server端。当用户访问我们的应用时,首先需要重定向到CAS Server端进行验证,要是原来登陆过,就免去登录,重定向到下游系统,否则进行用户名密码登陆操作。
7.2 讲讲xss、csrf网络攻击【必背】
【滴滴,有米科技,字节,腾讯音乐】
1. XSS都有哪些方式,如果过滤都需要过滤哪些内容?
XSS:存储性
(存储在服务器端),反射型
(用户输入的数据"反射"给浏览器),基于DOM
(恶意脚本修改页面的DOM结构)
需要过滤script标签,eval函数等
流程:
跨站脚本攻击
:通过向某网站写入js脚本或插入恶意 html标签来实现攻击。
比如:攻击者在论坛中放一个看似安全的链接,骗取用户点击后,窃取cookie中的用户私密信息;
或者攻击者在论坛中加一个恶意表单
,当用户提交表单的时候,却把信息传送到攻击者的服务器
中,而不是用户原本以为的信任站点。
防范
- 1.开启内容安全策略 CSP
-
其实质就是白名单制度,开发者明确告诉客户端,哪些外部资源可以加载和执行,等同于提供白名单。它的实现和执行全部由浏览器完成,开发者只需提供配置。
-
两种方法可以启用 CSP:
- 设置 HTTP 的
Content-Security-Policy
头部字段 - 设置网页的标签,<meta http-equiv=“Content-Security-Policy” content=“script-src ‘self’; object-src ‘none’; style-src cdn.example third-party; child-src https:”>
- 设置 HTTP 的
-
-
- HttpOnly阻止Cookie劫持攻击(禁止js获取cookie)
2. CSRF原理,整体的攻击链路是怎样的,都有哪些解决方案?
流程:
跨站请求伪造
:CSRF 攻击是攻击者借助用户的 Cookie 骗取服务器的信任,以用户名义伪造请求发送给服务器。如:在请求的url后加入一些恶意的参数
换句话说,CSRF就是利用用户的登录态发起恶意请求。
假设某银行网站A以GET请求来发起转账操作,转账的地址为www.xxx/transfer.do?accountNum=l000l&money=10000
,参数accountNum表示转账的账户,参数money表示转账金额。
而某大型论坛B上,一个恶意用户上传了一张图片,而图片的地址栏中填的并不是图片的地址,而是前而所说的砖账地址:<img src="http://www.xxx/transfer.do?accountNum=l000l&money=10000">
当你登录网站A后,没有及时登出,这时你访问了论坛B,不幸的事情发生了,你会发现你的账号里面少了10000块…
防范
- 1.添加 token 验证
- 在 HTTP 请求中以参数的形式加入一个随机产生的 token,并在服务器端建立一个拦截器来验证这个 token,若请求无 token 或者 token 不正确,则认为可能是 CSRF 攻击而拒绝该请求。
- 2.Referer Check
- 在HTTP头中有一个字段叫做Referer,它记录了该HTTP请求的来源地址。通过Referer Check,可以检查是否来自合法的"源".
- 3.验证码
- 验证码会强制用户必须与应用进行交互,才能完成最终请求,但是也不能给网站所有的操作都加上验证码,所以只能作为防御 CSRF 的一种辅助手段,而不能作为最终的解决方案
XSS和CSRF区别:
- XSS是利用用户对指定网站的信任
- CSRF是利用网站对用户的信任
介绍和区别>
7.3 如果在js中执行location.href = url,这个行为有可能会有哪些安全问题
【腾讯音乐】
主要是怕跳转到不安全的网页
7.4 安全问题遇到过哪些,CSRF的加签名是如何做的
【腾讯广告】
详情
安全问题遇到过哪些?
7.5 线上问题一般如何处理
【腾讯广告】
- 如果有报错信息,先查看报错信息
- 如果没有的话,直接本地切master分支,查看本地代码是否有问题
- 如果本地没有问题,那可能不是代码的问题,需要分析是否是网络的问题,或者线上代码打包的问题
- 如果本地代码有问题的话,那么讨论是否是需要紧急修复,需要的话,并且修改时间短,那么及时发版,否则先回滚代码
- 然后新建分支,修改代码,然后合并
8 性能优化
8.1 请求20个接口都对第一屏内容很关键如何做优化
- 将请求分批放入不同的域名中,这样就可以实现并行加载
- 使用http2.0 可以通过流id方式同时加载多个请求,同时开启gzip压缩
link
8.2 项目优化的地方有哪些
1.抽离了公共的口岸列表
2.表格做了响应式处理
3.开发环境迁移到了vite
4.webpack优化了打包构建时间
8.3 性能优化webpack打包过程
【网易游戏,网易灵犀,大疆,字节】
详情->
speed-measure-webpack-plugin插件
webpack (多进程打包) thread-loader
8.4 有做过哪些首屏性能优化吗
【字节,360】
为什么要做首屏优化:
首屏时间的快与慢,直接影响到了用户对网站的认知度。
所以首屏时间的长短对于用户的滞留时间的长短、用户转化率都尤为重要。
懒加载(图片,路由,组件)
详情>
10.3 你为项目做了哪些优化?
【字节,映客直播,佰锐科技,cvte】
项目层面,业务层面,代码优化,webpack优化,重点是从自己出发,多说些webpack相关的
精简代码:(这些感觉不说好点)
1. 封装了一些列的全局弹窗,例如:导出excel,需要弹窗显示excel中那几行导出失败,并且可以复制失败的行id。之前是单独写在每个文件中。
2. 抽离了口岸列表,并且提供可配置的json,之前是每个菜单渲染自身的口岸数量,并且口岸数据写在当前页面,后面改成了封装了口岸按钮组组件,暴露给每个菜单使用。
可以在简历上体现
从体验,代码,打包,请求速度等多方面来谈
去除大量重复文件,提取可以复用文件,建立前端埋点日志,等等
详情>
10.4 做什么性能优化(图片做了什么优化)
【lineshop】
图片懒加载
10.5 如果页面中有大量的DOM更新,导致页面变卡,有哪些方案可以优化
【腾讯音乐】
先用一个变量保存所有更新操作,等所有操作完成后,再一次性修改dom。
版权声明:本文标题:前端知识体系1:【cssjsvuees6手写安全优化】 内容由热心网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:https://m.elefans.com/xitong/1729423593a1200712.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论