基本面试:
做过最满意的项目是什么?项目背景?为什么要做这件事情?最终达到什么效果?你处于什么样的角色,起到了什么方面的作用?在项目中遇到什么技术问题?具体是如何解决的? 如果再做这个项目,你会在哪些方面进行改善?
基础扎实:
IT行业,哪些发展好的同学都是具备扎实基础知识,如果理解计算机基础会更好,因为我们面临很多非前端计算问题的。
主动思考:
被动完成任务成长会很慢的,需要有自己的想法,而不是仅仅是完成任务的。
自动学习:
前端领域知识淘汰速度很快,需要经常学习新知识。
追溯深度:
遇到问题是多研究背后深层次的原因,而不是绕过去。如遇到一个bug,有时间一定要理解本质原因。
宽阔视野:
创新往往来自不同领域的交集,如果理解更多领域,就会有更多的想法。
学习总结:
谈到某个技术的时候,需扪心自问,学这个是为了做什么?如何学习的?可以从什么渠道了解最新的知识?
目录概览
- HTML
- CSS
- 1.css水平、垂直居中的写法,请至少写出4种?
- 2.文本超出省略号表示
- 3.让图文不可复制
- 4.行内元素有哪些?块级元素有哪些? 空(void)元素有那些?
- 5.画一条0.5px的直线
- 6.如何画一个三角形(边框的均分原理)
- 7.怎样处理 移动端 1px 被渲染成 2px 问题?
- 8.盒子模型
- 9.flex自适应布局
- 10.清除浮动的几种方式,及原理?
- 11.1rem、1em、1vh、1px各自代表的含义?
- 12.消除 transition 闪屏
- 13.css3 有哪些新的属性
- 14.什么是CSS 预处理器 ?大家为什么要使用他们?
- 15.CSS优化、提高性能的方法有哪些?
- 16.可能用到的meta标签
- 17.css英文不自动换行的问题
- 18.link 标签和 import 标签的区别
- JavaScript
- 推荐的JavaScript经典面试题
- 1.根据你的理解,请简述JavaScript脚本的执行原理?
- 2.JS代码规范
- 3.数据类型
- 4.用js递归的方式写1到100求和
- 5.数组去重
- 6.对Function与Object的理解
- 7.闭包
- 8.3==true 打印出什么
- 9.判断变量是不是数组的几个方法
- 10.this
- 11.拷贝
- 12.Promise
- 13.Var 、Let 和 const 的区别。
- 14.对js垃圾回收机制的理解
- 15.了解重绘和重排吗,知道怎么去减少重绘和重排吗,让文档脱离文档流有 哪些方法?
- 16.JS 的 new 操作符做了哪些事情
- 17.改变函数内部 this 指针的指向函数(bind,apply,call 的区别)
- 18.Ajax 解决浏览器缓存问题
- 19.JS 加载过程阻塞,解决方法。
- 20.js面向对象中继承实现
- 非技术型
- 框架通识(vue、uniapp)
- 网络相关
- 性能优化
- 前端工程化
- 真题汇聚
- 1、 说一说什么是闭包,应用场景是什么?使用闭包会产生什么问题?
- 2、 内存泄漏和内存溢出的区别
- 3、 怎么监控页面的内存有没有溢出
- 4、前端性能优化的方式
- 5、SSr如果使用懒加载会不会有什么问题
- 6、说说防抖和节流、重绘和重排
- 7、map和weakmap的区别,什么场景下使用weakmap
- 8、用过WeakMap那些插件
- 9、bable的原理是什么?
- 10、vue中路由懒加载的原理是什么?
- 11、es5怎么实现class静态属性、class的构造函数
- 12、怎么实现instanceof方法
- 13、使用递归时你觉得最重要的一点是什么?如果有一万条数据使用递归会有什么问题?如何避免?
- 14、快速排序的时间复杂度
- 15、如果有一个一万条数据的对象,每一个对象都有一个value值,如果让你取value值的最大三个,你会怎么做?时间复杂度多少?
- 16、js 处理十万条数据_前端如何处理十万的大量数据
- 前端面试中,回答不出的那些问题
- 前端面试官的套路,你懂吗?
- 前端面试常考的手写代码不是背出来的!
- 常规交谈
- VUE3相关面试
HTML
1.Doctype
告知浏览器解析器用什么文档标准解析这个文档
2.对语义化的理解
title,header,nav,main,article,h1~h6,ul,ol,address,canvas,dialog,aside,section,figure,details,mark
- 代码结构清晰,易于阅读,利于开发和维护
- 提高体验,在样式加载失败时,页面结构清晰
- 方便其他设备解析(如屏幕阅读器)根据语义渲染网页
- 有利于搜索引擎优化(SEO),搜索引擎爬虫会根据不同的标签来赋予不同的权重
3.link和@import引入css样式区别
1 属性差别。link属于XHTML标签,而@import完全是CSS提供的语法规则。
2 加载顺序的差别。当一个页面被加载的时候(就是被浏览者浏览的时候),link引用的CSS会同时被加载,而@import引用的CSS会等到页面全部被下载完再被加载。所以有时候浏览@import加载CSS的页面时开始会没有样式(就是闪烁)
3 兼容性的差别。由于@import是CSS2.1提出的所以老的浏览器不支持,@import只有在IE5以上的才能识别,而link标签无此问题。
4.HTML5新特性
4.1常见语义化标签
< header >定义了文档的头部区域
< footer >定义了文档的尾部区域
< nav >定义文档的导航
< section >定义文档中的节(section、区段)
< article >定义页面独立的内容区域
< aside >定义页面的侧边栏内容
< dialog >定义对话框,比如提示框
4.2新增表单元素
< datalist > 元素规定输入域的选项列表,使用 元素的 list 属性与 元素的 id 绑定
< keygen> 提供一种验证用户的可靠方法,标签规定用于表单的密钥对生成器字段。
< output > 用于不同类型的输出
4.2.1新增表单属性
- placehoder 属性,简短的提示在用户输入值前会显示在输入域上。即我们常见的输入框默认提示,在用户输入后消失。
- required 属性,是一个 boolean 属性。要求填写的输入域不能为空
- pattern 属性,描述了一个正则表达式用于验证 元素的值。
- min 和 max 属性,设置元素最小值与最大值。
- step 属性,为输入域规定合法的数字间隔。
- height 和 width 属性,用于 image 类型的 标签的图像高度和宽度。
- autofocus 属性,是一个 boolean 属性。规定在页面加载时,域自动地获得焦点。
- multiple 属性 ,是一个 boolean 属性。规定 元素中可选择多个值。
4.3新增视频 和音频 标签
- Canvas绘图
- SVG绘图
- 地理定位
- 拖放API
- Web Worker
- Web Storage
- WebSocket
5.click 在 ios 上有 300ms 延迟,原因及如何解决?
(1)粗暴型,禁用缩放
(2)利用 FastClick,其原理是: 检测到 touchend 事件后,立刻出发模拟 click 事件,并且把浏览器 300 毫秒之后真正出 发的事件给阻断掉
CSS
1.css水平、垂直居中的写法,请至少写出4种?
水平居中 (Flex布局下居中滚动溢出截断处理)
text-align: center; /* 行内元素 */
margin: 0 auto; /* 块级元素 */
position:absolute +left:50%+ transform:translateX(-50%)
display:flex + justify-content: center
垂直居中
设置line-height 等于height
position:absolute +top:50%+ transform:translateY(-50%)
display:flex + align-items: center
display:table+display:table-cell + vertical-align: middle;
注意点: 当translateX与translateY同时存在的话,后面的属性会盖住前面的属性,反正就只能有一个存在的!所以要改变一下写法:transform:translate(-50%,-50%);
2.文本超出省略号表示
单行文本
.xxx{
width: calc(100% - 60rpx);
overflow: hidden; /*超出部分隐藏*/
text-overflow: ellipsis; /*三点表示*/
white-space:nowrap; /*强制不换行*/
}
多行文本
.xxx{
display: -webkit-box; /*弹性伸缩盒子模型显示*/
word-break: break-all;
-webkit-box-orient: vertical; /*检索盒子模型的排列方式*/
-webkit-line-clamp: 4; /*需要显示的行数 */
overflow: hidden;
text-overflow: ellipsis;
}
3.让图文不可复制
-webkit-user-select: none;
-ms-user-select: none;
-moz-user-select: none;
-khtml-user-select: none;
user-select: none;
4.行内元素有哪些?块级元素有哪些? 空(void)元素有那些?
行内元素:a、b、span、img、input、strong、select、label、em、button、textarea
块级元素:div、ul、li、dl、dt、dd、p、h1-h6、blockquote
空元素:即系没有内容的HTML元素,例如:br、meta、hr、link、input、img
5.画一条0.5px的直线
/*css3的transform*/
height: 1px;
transform: scale(0.5);
6.如何画一个三角形(边框的均分原理)
div{
width:0px;
height:0px;
border-top:10px solid red;
border-right:10px solid transparent;
border-bottom:10px solid transparent;
border-left:10px solid transparent;
}
7.怎样处理 移动端 1px 被渲染成 2px 问题?
1、局部处理
- meta 标签中的 viewport 属性 ,initial-scale 设置为 1
- rem 按照设计稿标准走,外加利用 transfrome 的 scale(0.5) 缩小一倍即可;
2、全局处理
- meta 标签中的 viewport 属性 ,initial-scale 设置为 0.5
- rem 按照设计稿标准走即可
8.盒子模型
- box-sizing: content-box(W3C盒子模型):元素的宽高大小表现为内容的大小。
- box-sizing: border-box(IE盒子模型):元素的宽高表现为内容 + 内边距 + 边框的大小。背景会延伸到边框的外沿。
9.flex自适应布局
flex-direction: row; /*排列方向*/
flex-wrap: wrap; /*一条轴线排不下,如何换行*/
flex-flow: <flex-direction> || <flex-wrap>; /*flex-direction和flex-wrap的简写形式,默认值为row nowrap*/
justify-content: space-around; /*X轴上的对齐方式*/
align-items: center;/*Y轴上如何对齐*/
align-content: center; /*多根轴线的对齐方式一根轴线,该属性不起作用*/
align-self: center; /*允许单个项目有与其他项目不一样的对齐方式*/
10.清除浮动的几种方式,及原理?
浮动元素碰到包含它的边框或者浮动元素的边框停留。由于浮动元素不在文档流中,所以文档流的块框表现得就像浮动框不存在一样,浮动元素会漂浮在文档流的块框上.
(1)父级div定义height。
(2)结尾处加空div标签clear:both。
(3)父级div定义伪类:after和zoom。
(4)父级div定义overflow:hidden。
(5)父级div定义overflow:auto。
(6)父级div也浮动,需要定义宽度。
(7)父级div定义display:table。
(8)结尾处加br标签clear:both。
(Q2)比较好的是第3种方式,好多网站都这么用
父级添加overflow属性(父元素添加overflow:hidden)
.fahter{
width: 400px;
border: 1px solid deeppink;
overflow: hidden;
}
使用after伪元素清除浮动(推荐使用)
.clearfix:after{/*伪元素是行内元素 正常浏览器清除浮动方法*/
content: "";
display: block;
height: 0;
clear:both;
visibility: hidden;
}
.clearfix{
*zoom: 1;/*ie6清除浮动的方式 *号只有IE6-IE7执行,其他浏览器不执行*/
}
<body>
<div class="fahter clearfix">
<div class="big">big</div>
<div class="small">small</div>
<!--<div class="clear">额外标签法</div>-->
</div>
<div class="footer"></div>
</body>
优点:符合闭合浮动思想,结构语义化正确
缺点:ie6-7不支持伪元素:after,使用zoom:1触发hasLayout.
使用before和after双伪元素清除浮动
.clearfix:after,.clearfix:before{
content: "";
display: table;
}
.clearfix:after{
clear: both;
}
.clearfix{
*zoom: 1;
}
<div class="fahter clearfix">
<div class="big">big</div>
<div class="small">small</div>
</div>
<div class="footer"></div>
优点:代码更简洁
缺点:用zoom:1触发hasLayout.
注: haslayout是IE7-浏览器的特有属性。hasLayout是一种只读属性,有两种状态:true或false。当其为true时,代表该元素有自己的布局,否则代表该元素的布局继承于父元素。
11.1rem、1em、1vh、1px各自代表的含义?
- rem是全部的长度都相对于根元素元素。通常做法是给html元素设置一个字体大小,然后其他元素的长度单位就为rem。
- 子元素字体大小的em是相对于父元素字体大小,元素的width/height/padding/margin用em的话是相对于该元素的font-size。
- vw/vh视窗的宽度和高度,相当于 屏幕宽度和高度的 1%,不过,处理宽度的时候%单位更合适,处理高度的 话 vh 单位更好。
- px像素(Pixel)。相对长度单位。像素px是相对于显示器屏幕分辨率而言的。
- 一般电脑的分辨率有{19201024}等不同的分辨率,19201024 前者是屏幕宽度总共有1920个像素,后者则是高度为1024个像素
12.消除 transition 闪屏
.css {
-webkit-transform-style: preserve-3d;
-webkit-backface-visibility: hidden;
-webkit-perspective: 1000;
}
过渡动画(在没有启动硬件加速的情况下)会出现抖动的现象, 以上的解决方案只是改变视角来启动硬件加速的一种方式;启动硬件加速的另外一种方式:
.css {
-webkit-transform: translate3d(0,0,0);
-moz-transform: translate3d(0,0,0);
-ms-transform: translate3d(0,0,0);
transform: translate3d(0,0,0);
}
启动硬件加速
最常用的方式:translate3d、translateZ、transform
opacity 属性/过渡动画(需要动画执行的过程中才会创建合成层,动画没有开始或结束后元素还会回到之前的状态)
will-chang 属性(这个比较偏僻),一般配合opacity与translate使用(而且经测试,除了上述可以引发硬件加速的属性外,其它属性并不会变成复合层)。
弊端:硬件加速会导致 CPU 性能占用量过大,电池电量消耗加大 ;因此尽量避免泛滥使用硬件加速。
13.css3 有哪些新的属性
- 圆角–border-radius
- 阴影–box-shadow
- 文字特效–text-shadow
- 渐变–gradient
- 旋转–transform(rotate:旋转、scale:缩放、translate:定位、skew:倾斜)
- 多背景
- rgba
- 边框背景–border-image
- 服务器端字体:font-face
@font-face {
font-family: 'MyFont'; /* 表示为这种字体起一个名称,可以随意设置,这里用的是MyFont */
src: url('myfont.eot'); /* 这一行表示字体位置,由于ie只支持服务器端的eot字体,所以这一行是ie专用的 */
src: local('myfont.ttf'),
url('myfont.woff') format('woff'),
url('myfont.ttf') format('truetype'); /* local()表示在本机(客户端)查找该字体,如果本机已经安装了,就不用下载了。url()表示字体在服务器上的位置,format()用来说明字体格式。Firefox 3.5支持TrueType和OpenType字体,Firefox 3.6又增加了WOFF字体。其他基于Webkit引擎的浏览器(sarif,opera、chrome),目前好像只支持truetype */
}
h2{ font-family: "MyFont"; } /*使用*/
14.什么是CSS 预处理器 ?大家为什么要使用他们?
CSS 预处理器为 CSS 增加一些编程的特性,无需考虑浏览器的兼容性问题”,例如你可以在 CSS 中使用变量、简单的逻辑程序、函数(如右侧代码编辑器中就使用了变量$color)等等在编程语言中的一些基本特性,可以让你的 CSS 更加简洁、适应性更强、可读性更佳,更易于代码的维护等诸多好处。
结构清晰,便于扩展。可以方便地屏蔽浏览器私有语法差异。封装对浏览器语法差异的重复处理,减少无意义的机械劳动。可以轻松实现多重继承。完全兼容 CSS 代码,可以方便地应用到老项目中,LESS 只是在 CSS 语法上做了扩展,所以老的 CSS 代码也可以与 LESS 代码一同编译。
预处理器例如:LESS、Sass、Stylus,用来预编译Sass或less,增强了css代码的复用性,还有层级、mixin、变量、循环、函数等,具有很方便的UI组件模块化开发能力,极大的提高工作效率。
后处理器例如:PostCSS,通常被视为在完成的样式表中根据CSS规范处理CSS,让其更有效;目前最常做的是给CSS属性添加浏览器私有前缀,实现跨浏览器兼容性的问题。
15.CSS优化、提高性能的方法有哪些?
- 避免过度约束
- 避免后代选择符
- 避免链式选择符使用紧凑的语法
- 避免不必要的命名空间
- 避免不必要的重复,最好使用表示语义的名字。一个好的类名应该是描述他是什么而不是像什么
- 避免 !important,可选择其他选择器
- 尽可能的精简规则,你可以合并不同类里的重复规则
- 修复解析错误
- 避免使用多类选择符
- 移除空的css规则
- 正确使用display属性:由于display作用,某些样式组合会无效,徒增样式体积的同时也影响解析性能。
display:inline后不应该再使用width、height、margin、padding以及float。
display:inline-block后不应该再使用float。
display:block后不应该再使用vertical-align。
display:table-*后不应该再使用margin或者float。 - 不滥用浮动:虽然浮动不可避免,但不可否认很多css bug是由于浮动而引起。
- 不滥用web字体
对于中文网站来说Web Fonts可能很陌生,国外却很流行。web fonts通常体积庞大,而且一些浏览器在下载web fonts时会阻塞页面渲染损伤性能。 - 不声明过多的font-size:这是设计层面的问题,设计精良的页面不会有过多的font-size声明。
- 不在选择符中使用ID标识符,主要考虑到样式重用性以及与页面的耦合性。
- 不给h1~h6元素定义过多的样式
- 全站统一定义一遍heading元素即可,若需额外定制样式,可使用其他选择符作为代替。
- 值为0时不需要任何单位
- 使用CSS渐变等高级特性,需指定所有浏览器的前缀
- 避免让选择符看起来像正则表达式
- CSS3添加了一些类似~=等复杂属性,也不是所有浏览器都支持,需谨慎使用。
- 遵守盒模型规则
16.可能用到的meta标签
<!-- 设置缩放 -->
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no, minimal-ui" />
<!-- 可隐藏地址栏,仅针对IOS的Safari(注:IOS7.0版本以后,safari上已看不到效果) -->
<meta name="apple-mobile-web-app-capable" content="yes" />
<!-- 仅针对IOS的Safari顶端状态条的样式(可选default/black/black-translucent ) -->
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
<!-- IOS中禁用将数字识别为电话号码/忽略Android平台中对邮箱地址的识别 -->
<meta name="format-detection"content="telephone=no, email=no" />
<!-- 启用360浏览器的极速模式(webkit) -->
<meta name="renderer" content="webkit">
<!-- 避免IE使用兼容模式 -->
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<!-- 针对手持设备优化,主要是针对一些老的不识别viewport的浏览器,比如黑莓 -->
<meta name="HandheldFriendly" content="true">
<!-- 微软的老式浏览器 -->
<meta name="MobileOptimized" content="320">
<!-- uc强制竖屏 -->
<meta name="screen-orientation" content="portrait">
<!-- QQ强制竖屏 -->
<meta name="x5-orientation" content="portrait">
<!-- UC强制全屏 -->
<meta name="full-screen" content="yes">
<!-- QQ强制全屏 -->
<meta name="x5-fullscreen" content="true">
<!-- UC应用模式 -->
<meta name="browsermode" content="application">
<!-- QQ应用模式 -->
<meta name="x5-page-mode" content="app">
<!-- windows phone 点击无高光 -->
<meta name="msapplication-tap-highlight" content="no">
17.css英文不自动换行的问题
在css中非中文文本是不会自动换行的,可以使用word-wrap: break-word;或word-break:break-all;来让英文强制换行。
word-break: break-all; 设置文字的强制自动换行,但只对英文起作用,以字母作为换行依据。
18.link 标签和 import 标签的区别
- link 属于 html 标签,而@import 是 css 提供的
- 页面被加载时,link 会同时被加载,而@import 引用的 css 会等到页面加载结束后加载。
- link 是 html 标签,因此没有兼容性,而@import 只有 IE5 以上才能识别。
- link 方式样式的权重高于@import 的。
JavaScript
推荐的JavaScript经典面试题
数据类型到一些隐式转换这些基础知识,看代码说输出,v8底层执行机制、垃圾回收、闭包、作用域、作用域链,原型、原型链,手写代码,如:防抖、节流、bind、call、apply、深拷贝、浅拷贝、Promise、async、await、webpack、框架、http等。
1.根据你的理解,请简述JavaScript脚本的执行原理?
JavaScript是一种动态、弱类型、基于原型的语言,通过浏览器可以直接执行,当浏览器遇到< script > 标记的时候,浏览器会执行之间的javascript代码。嵌入的js代码是顺序执行的,每个脚本定义的全局变量和函数,都可以被后面执行的脚本所调用。 变量的调用,必须是前面已经声明,否则获取的变量值是undefined。
2.JS代码规范
- 变量名推荐使用驼峰法来命名
- 通常运算符 ( + - * / = ) 前后需要添加空格
- 通常使用 4 个空格符号来缩进代码块
- 一条语句通常以分号作为结束符
- 对象中将左花括号与类名放在同一行、冒号与属性值间有个空格、字符串使用双引号,数字不需要、最后一个属性-值对后面不要添加逗号、将右花括号独立放在一行,并以分号作为结束符号。
- 每行代码字符小于 80,如果一个 JavaScript 语句超过了 80 个字符,建议在 运算符或者逗号后换行。
- 产品线公用全局变量
- 全局变量使用"g_"打头,建议通过window.g_xxx定义
- 常量名全部大写,单词间用下划线分隔。
- 减少全局函数,尽量使用对象
- 建议使用严格的条件判断符。如:=== 、 !==
- if,else尽量使用 {} 括起来
3.数据类型
基本数据类型:string、number、null、undefined、boolean
引用数据类型:Object、Array、Date、RegExp
变量:var关键字来定义变量(ES5)、let命令来声明变量(ES6)、const命令声明一个只读的常量(ES6)
4.用js递归的方式写1到100求和
递归我们经常用到,vue在实现双向绑定进行数据检验的时候用的也是递归,但要我们面试的时候手写一个递归,如果对递归的概念理解不透彻,可能还是会有一些问题。
function add(num1,num2){
var num = num1+num2;
if(num2+1>100){
return num;
}else{
return add(num,num2+1)
}
}
var sum =add(1,2);
5.数组去重
此题看着简单,但要想面试官给你高分还是有难度的。至少也要写出几种方法
js:
var arr=['12','32','89','12','12','78','12','32'];
// 最简单数组去重法
function unique1(array){
var n = []; //一个新的临时数组
for(var i = 0; i < array.length; i++){ //遍历当前数组
if (n.indexOf(array[i]) == -1)
n.push(array[i]);
}
return n;
}
arr=unique1(arr);
// 速度最快, 占空间最多(空间换时间)
function unique2(array){
var n = {}, r = [], type;
for (var i = 0; i < array.length; i++) {
type = typeof array[i];
if (!n[array[i]]) {
n[array[i]] = [type];
r.push(array[i]);
} else if (n[array[i]].indexOf(type) < 0) {
n[array[i]].push(type);
r.push(array[i]);
}
}
return r;
}
//数组下标判断法
function unique3(array){
var n = [array[0]]; //结果数组
for(var i = 1; i < array.length; i++) { //从第二项开始遍历
if (array.indexOf(array[i]) == i)
n.push(array[i]);
}
return n;
}
es6:
//es6方法一数组去重
arr=[...new Set(arr)];
//es6方法二数组去重,
function dedupe(array) {
return Array.from(new Set(array)); //Array.from()能把set结构转换为数组
}
6.对Function与Object的理解
Function
函数就是对象,代表函数的对象就是函数对象。所有的函数对象是被Function这个函数对象构造出来的。Function是最顶层的构造器。它构造了系统中所有的对象,包括用户自定义对象,系统内置对象,甚至包括它自已。这也表明Function具有自举性(自已构造自己的能力)。这也间接决定了Function的call和constructor逻辑相同。每个对象都有一个constructor 属性,用于指向创建其的函数对象。
a、函数与对象具有相同的语言地位
b、没有类,只有对象
c、函数也是一种对象,所谓的函数对象
d、对象是按引用来传递的
Object
对于Object它是最顶层的对象,所有的对象都将继承Object的原型,但是你也要明确的知道Object也是一个函数对象,所以说Object是被Function构造出来的。
//定义角度,前者为定义一个js函数,后者为这个函数的名称
function Function(){}
//用法角度,a也是function
var a = new Object(function(){});
7.闭包
- 是指有权访问另外一个函数作用域中的变量的函数,是指有权访问另外一个函数作用域中的变量的函数
- 应用场景:访问函数内部的变量
- 优点:减少全局变量污染,希望一个变量长期存储在内存中(缓存变量)
- 缺点:影响脚本性能,常驻内存,增加内存使用量
8.3==true 打印出什么
会打印出false,这里会将true转变成1
9.判断变量是不是数组的几个方法
var a=[];
a.constructor===Array //true
a instanceof Array === true //true
⚠️ 注意:以上方法在跨frame时会有问题,跨frame实例化的对象不共享原型
var iframe = document.createElement('iframe'); //创建iframe
document.body.appendChild(iframe); //添加到body中
xArray = window.frames[window.frames.length-1].Array;
var arr = new xArray(1,2,3); // 声明数组[1,2,3]
alert(arr instanceof Array); // false
alert(arr.constructor === Array); // false
解决:
Object.prototype.toString.call(a) // "[object Array]"
Array.isArray(a) //true
10.this
function foo() {
console.log(this.a)
}
var a = 1
foo()
var obj = {
a: 2,
foo: foo
}
obj.foo()
// 以上两者情况 `this` 只依赖于调用函数前的对象,优先级是第二个情况大于第一个情况
// 以下情况是优先级最高的,`this` 只会绑定在 `c` 上,不会被任何方式修改 `this` 指向
var c = new foo()
c.a = 3
console.log(c.a)
// 还有种就是利用 call,apply,bind 改变 this,这个优先级仅次于 new
function a() {
return () => {
return () => {
console.log(this)
}
}
}
console.log(a()()())
箭头函数其实是没有 this 的,这个函数中的 this 只取决于他外面的第一个不是箭头函数的函数的 this。在这个例子中,因为调用 a 符合前面代码中的第一个情况,所以 this 是 window。并且 this 一旦绑定了上下文,就不会被任何代码改变。
11.拷贝
浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。
深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。
具体在业务中使用哪个,还得根据自己的业务需求。当然,深拷贝的方法也有很多个,这里只列出常用的一种方法,JSON.parse(JSON.stringify())
浅拷贝:定义了一个obj对象,和一个newObj对象,并让newObj的person等于指向obj对象(实则是将newObj的person属性指向obj对象的指针),所以改变newObj.person.name的值,实则是改变obj.name的值。
var obj = {
id: 123,
name: "小三",
address: "china"
}
var newObj = {}
newObj.person = obj; //浅拷贝
newObj.person.name = "小三"
console.log(obj.name); //打印小三
深拷贝 :定义obj对象和newObj对象,在给newObje对象的person属性赋值的时候,我们用JSON.parse(JSON.stringify(obj)) 方法将obj深拷贝了一份,也就是说重新申请了一个空间,与原对象不共享内存,属于完全独立的存在,所以在改变newObj.person.name属性之后,obj.name是不会跟着发生改变的。
var obj = {
id:123,
name:"小三",
address:"china"
}
var newObj = {}
newObj.person = JSON.parse(JSON.stringify(obj)); //深拷贝
newObj.person.name="小三"
console.log(obj.name);// 打印小三
12.Promise
Promise 是 ES6 新增的语法,解决了回调地狱的问题。
可以把 Promise 看成一个状态机。初始是 pending 状态,可以通过函数 resolve 和 reject ,将状态转变为 resolved 或者 rejected 状态,状态一旦改变就不能再次变化。
then 函数会返回一个 Promise 实例,并且该返回值是一个新的实例而不是之前的实例。因为 Promise 规范规定除了 pending 状态,其他状态是不可以改变的,如果返回的是一个相同实例的话,多个 then 调用就失去意义了。
对于 then 来说,本质上可以把它看成是 flatMap
// 三种状态
const PENDING = "pending";
const RESOLVED = "resolved";
const REJECTED = "rejected";
// promise 接收一个函数参数,该函数会立即执行
function MyPromise(fn) {
let _this = this;
_this.currentState = PENDING;
_this.value = undefined;
// 用于保存 then 中的回调,只有当 promise
// 状态为 pending 时才会缓存,并且每个实例至多缓存一个
_this.resolvedCallbacks = [];
_this.rejectedCallbacks = [];
_this.resolve = function (value) {
if (value instanceof MyPromise) {
// 如果 value 是个 Promise,递归执行
return value.then(_this.resolve, _this.reject)
}
setTimeout(() => { // 异步执行,保证执行顺序
if (_this.currentState === PENDING) {
_this.currentState = RESOLVED;
_this.value = value;
_this.resolvedCallbacks.forEach(cb => cb());
}
})
};
_this.reject = function (reason) {
setTimeout(() => { // 异步执行,保证执行顺序
if (_this.currentState === PENDING) {
_this.currentState = REJECTED;
_this.value = reason;
_this.rejectedCallbacks.forEach(cb => cb());
}
})
}
// 用于解决以下问题
// new Promise(() => throw Error('error))
try {
fn(_this.resolve, _this.reject);
} catch (e) {
_this.reject(e);
}
}
MyPromise.prototype.then = function (onResolved, onRejected) {
var self = this;
// 规范 2.2.7,then 必须返回一个新的 promise
var promise2;
// 规范 2.2.onResolved 和 onRejected 都为可选参数
// 如果类型不是函数需要忽略,同时也实现了透传
// Promise.resolve(4).then().then((value) => console.log(value))
onResolved = typeof onResolved === 'function' ? onResolved : v => v;
onRejected = typeof onRejected === 'function' ? onRejected : r => throw r;
if (self.currentState === RESOLVED) {
return (promise2 = new MyPromise(function (resolve, reject) {
// 规范 2.2.4,保证 onFulfilled,onRjected 异步执行
// 所以用了 setTimeout 包裹下
setTimeout(function () {
try {
var x = onResolved(self.value);
resolutionProcedure(promise2, x, resolve, reject);
} catch (reason) {
reject(reason);
}
});
}));
}
if (self.currentState === REJECTED) {
return (promise2 = new MyPromise(function (resolve, reject) {
setTimeout(function () {
// 异步执行onRejected
try {
var x = onRejected(self.value);
resolutionProcedure(promise2, x, resolve, reject);
} catch (reason) {
reject(reason);
}
});
}));
if (self.currentState === PENDING) {
return (promise2 = new MyPromise(function (resolve, reject) {
self.resolvedCallbacks.push(function () {
// 考虑到可能会有报错,所以使用 try/catch 包裹
try {
var x = onResolved(self.value);
resolutionProcedure(promise2, x, resolve, reject);
} catch (r) {
reject(r);
}
});
self.rejectedCallbacks.push(function () {
try {
var x = onRejected(self.value);
resolutionProcedure(promise2, x, resolve, reject);
} catch (r) {
reject(r);
}
});
}));
}
};
// 规范 2.3
function resolutionProcedure(promise2, x, resolve, reject) {
// 规范 2.3.1,x 不能和 promise2 相同,避免循环引用
if (promise2 === x) {
return reject(new TypeError("Error"));
}
// 规范 2.3.2
// 如果 x 为 Promise,状态为 pending 需要继续等待否则执行
if (x instanceof MyPromise) {
if (x.currentState === PENDING) {
x.then(function (value) {
// 再次调用该函数是为了确认 x resolve 的
// 参数是什么类型,如果是基本类型就再次 resolve
// 把值传给下个 then
resolutionProcedure(promise2, value, resolve, reject);
}, reject);
} else {
x.then(resolve, reject);
}
return;
}
// 规范 2.3.3.3.3
// reject 或者 resolve 其中一个执行过得话,忽略其他的
let called = false;
// 规范 2.3.3,判断 x 是否为对象或者函数
if (x !== null && (typeof x === "object" || typeof x === "function")) {
// 规范 2.3.3.2,如果不能取出 then,就 reject
try {
// 规范 2.3.3.1
let then = x.then;
// 如果 then 是函数,调用 x.then
if (typeof then === "function") {
// 规范 2.3.3.3
then.call(
x,
y => {
if (called) return;
called = true;
// 规范 2.3.3.3.1
resolutionProcedure(promise2, y, resolve, reject);
},
e => {
if (called) return;
called = true;
reject(e);
}
);
} else {
// 规范 2.3.3.4
resolve(x);
}
} catch (e) {
if (called) return;
called = true;
reject(e);
}
} else {
// 规范 2.3.4,x 为基本类型
resolve(x);
}
}
以上就是根据 Promise / A+ 规范来实现的代码,可以通过 promises-aplus-tests 的完整测试
13.Var 、Let 和 const 的区别。
var 声明的变量会挂载在 window 上,而 let 和 const 声明的变量不会:var 声明变量存在变量提升,let 和 const 不存在变量提升let 和 const 声明形成块作用域同一作用域下 let 和 const 不能声明同名变量,而 var 可以。
Const 1、一旦声明必须赋值,不能使用 null 占位。2、声明后不能再修改 3、如果声明的是复合类型数据,可以修改其属性
14.对js垃圾回收机制的理解
必要性: 由于字符串、对象和数组没有固定大小,所有当他们的大小已知时,才能对他们进行动态的存储分配。JavaScript 程序每次创建字符串、数组或对象时,解释器都必须分配内存来存储那个实体。只要像这样动态地分配了内存,最终都要释放这些内存以便他们能够被再用,否则,JavaScript 的解释器将会消耗完系统中所有可用的内存,造成系统崩溃。
这段话解释了为什么需要系统需要垃圾回收,JS 不像 C/C++,他有自己的一套垃圾回收机制(Garbage Collection),avaScript 的解释器可以检测到何时程序不再使用一个对象了,当他确定了一个对象是无用的时候,他就知道不再需要这个对象,可以把它所占用的内存释放掉了。例如:
var a="hello world";
var b="world";
var a=b;
//这时,会释放掉"hello world"
//释放内存以便再引用 垃圾回收的方法:标记清除、计数引用。
标记清除:
这是最常见的垃圾回收方式,当变量进入环境时,就标记这个变量为”进入环境“,从逻 辑上讲,永远不能释放进入环境的变量所占的内存,永远不能释放进入环境变量所占用 的内存,只要执行流程进入相应的环境,就可能用到他们。当离开环境时,就标记为离开环境。 垃圾回收器在运行的时候会给存储在内存中的变量都加上标记(所有都加),然后去掉 环境变量中的变量,以及被环境变量中的变量所引用的变量(条件性去除标记),删除 所有被标记的变量,删除的变量无法在环境变量中被访问所以会被删除,最后垃圾回收器,完成了内存的清除工作,并回收他们所占用的内存。
引用计数法:
另一种不太常见的方法就是引用计数法,引用计数法的意思就是每个值没引用的次数, 当声明了一个变量,并用一个引用类型的值赋值给改变量,则这个值的引用次数为 1; 相反的,如果包含了对这个值引用的变量又取得了另外一个值,则原先的引用值引用次 数就减 1,当这个值的引用次数为 0 的时候,说明没有办法再访问这个值了,因此就把 所占的内存给回收进来,这样垃圾收集器再次运行的时候,就会释放引用次数为 0 的这 些值。 用引用计数法会存在内存泄露,下面来看原因:
function problem() {
var objA = new Object();
var objB = new Object();
objA.someOtherObject = objB;
objB.anotherObject = objA;
}
在这个例子里面,objA 和 objB 通过各自的属性相互引用,这样的话,两个对象的引用 次数都为 2,在采用引用计数的策略中,由于函数执行之后,这两个对象都离开了作用 域,函数执行完成之后,因为计数不为 0,这样的相互引用如果大量存在就会导致内存 泄露。
特别是在 DOM 对象中,也容易存在这种问题:
DOM 的变化影响到了预算内宿的几何属性比如宽高,浏览器重新计算元素的几何属性, 其他元素的几何属性也会受到影响,浏览器需要重新构造渲染书,这个过程称之为重排,
浏览器将受到影响的部分重新绘制在屏幕上 的过程称为重绘,
var element=document.getElementById(’‘);
var myObj=new Object();
myObj.element=element;
element.someObject=myObj;
这样就不会有垃圾回收的过程。
15.了解重绘和重排吗,知道怎么去减少重绘和重排吗,让文档脱离文档流有 哪些方法?
引起重排重绘的原因有:
- 添加或者删除可见的 DOM 元素
- 元素尺寸位置的改变 浏览器页面初始化
- 浏览器窗口大小发生改变,重排一定导致重绘,重绘不一定导致重排
减少重绘重排的方法有:
-
不在布局信息改变时做 DOM 查询,
-
使用 csstext,className 一次性改变属性
-
使用 fragment
-
对于多次重排的元素,比如说动画。使用绝对定位脱离文档流,使其不影响其他元素
16.JS 的 new 操作符做了哪些事情
new 操作符新建了一个空对象,这个对象原型指向构造函数的 prototype,执行构造函数 后返回这个对象。
17.改变函数内部 this 指针的指向函数(bind,apply,call 的区别)
通过 apply 和 call 改变函数的 this 指向,他们两个函数的第一个参数都是一样的表示要 改变指向的那个对象,第二个参数,apply 是数组,而 call 则是 arg1,arg2…这种形式。通 过 bind 改变 this 作用域会返回一个新的函数,这个函数不会马上执行。
18.Ajax 解决浏览器缓存问题
-
在 ajax 发送请求前加上 anyAjaxObj.setRequestHeader(“If-Modified-Since”,“0”)。
-
在 ajax 发送请求前加上
anyAjaxObj.setRequestHeader(“Cache-Control”,“no-cache”)。 -
在 URL 后面加上一个随机数: “fresh=” + Math.random()。
-
在 URL 后面加上时间搓:“nowtime=” + new Date().getTime()。
-
如果是使用 jQuery,直接这样就可以了 $.ajaxSetup({cache:false})。这样页面的所有 ajax
都会执行这条语句就是不需要保存缓存记录。
19.JS 加载过程阻塞,解决方法。
指定 script 标签的 async 属性。 如果 async=“async”,脚本相对于页面的其余部分异步地执行(当页面继续进行解析时, 脚本将被执行) 如果不使用 async 且 defer=“defer”:脚本将在页面完成解析时执行
20.js面向对象中继承实现
面向对象的基本特征有:封闭、继承、多态。
Ⅰ.原型链(prototype chaining)
function car(price){
this.price = price;
}
car.prototype.sayPrice = function(){
console.log("Price is "+this.price);
}
var oCar = new car("100W");
oCar.sayPrice();
function toyCar(price){
this.price = price;
}
toyCar.prototype = new car()
var oCar2 = new toyCar("10CNY");
oCar2.sayPrice();
Ⅱ.call()/apply()
function useCall(a,b){
this.a = a;
this.b = b;
this.say = function(){
console.log("I'm "+this.a+" You're "+this.b);
}
}
function callThefunction (){
var args = arguments;
useCall.call(this,args[0],args[1]);
// useCall.apply(this,arguments);
}
var testCall1 = new useCall("Not YY","Not TT");
testCall1.say();
var testCall2 = new callThefunction("YY","TT");
testCall2.say();
Ⅲ.混合方式(prototype和call()/apply()结合)
function house(size,price){
this.size = size;
this.price = price;
}
house.prototype.showArea=function (){
console.log("面积为"+this.size);
}
house.prototype.sayPrice=function (){
console.log("价钱为"+this.price);
}
function maofan(size,price){
house.call(this,size,price);
}
maofan.prototype = new house();
var newmaofan = new maofan("20Square meters ","1000CNY");
newmaofan.showArea();
Ⅳ .对象冒充
function Person(name,age){
this.name = name;
this.age = age;
this.show = function(){
console.log(this.name+", "+this.age);
}
}
Person.prototype.sayHi = function(){
alert('hi');
}
function Student(name,age){
this.student = Person; //将Person类的构造函数赋值给this.student
this.student(name,age); //js中实际上是通过对象冒充来实现继承的
delete this.student; //移除对Person的引用
}
var s = new Student("小明",17);
s.show();
var p = new Person("小花",18);
p.show();
// 小明, 17
// 小花, 18
非技术型
1.get、post的区别
- get传参方式是通过地址栏URL传递,是可以直接看到get传递的参数,post传参方式参数URL不可见,get把请求的数据在URL后通过?连接,通过&进行参数分割。psot将参数存放在HTTP的包体内
- get传递数据是通过URL进行传递,对传递的数据长度是受到URL大小的限制,URL最大长度是2048个字符。post没有长度限制
- get后退不会有影响,post后退会重新进行提交
- get请求可以被缓存,post不可以被缓存
- get请求只URL编码,post支持多种编码方式
- get请求的记录会留在历史记录中,post请求不会留在历史记录
- get只支持ASCII字符,post没有字符类型限制
2.*如何中断ajax请求?
一种是设置超时时间让ajax自动断开,另一种是手动停止ajax请求,其核心是调用XML对象的abort方法,ajax.abort()
3.三次握手
- 客户端发syn包给服务端,等待服务器确认(syn:同步序列编号(Synchronize Sequence Numbers))
- 服务端发syn+ack包给客户端
- 客户端发确认包ack给服务端
4.四次挥手
中断连接端可以是Client端,也可以是Server端。
- 关闭主动方发送fin包
- 被动方发送ack包
- 被动方关闭连接,发送fin包
- 主动方发送ack包确认
5.从输入url到页面加载完成发生了什么?——前端角度
- 浏览器的地址栏输入URL并按下回车。
- 浏览器查找当前URL是否存在缓存,并比较缓存是否过期。
- DNS解析URL对应的IP。
- 根据IP建立TCP连接(三次握手)。
- HTTP发起请求。
- 服务器处理请求,浏览器接收HTTP响应。
- 渲染页面,构建DOM树。
- 关闭TCP连接(四次挥手)。
6.你所知道的http的响应码及含义?
-
1xx(临时响应)
100: 请求者应当继续提出请求。
101(切换协议) 请求者已要求服务器切换协议,服务器已确认并准备进行切换。 -
2xx(成功)
200:正确的请求返回正确的结果
201:表示资源被正确的创建。比如说,我们 POST 用户名、密码正确创建了一个用户就可以返回 201。
202:请求是正确的,但是结果正在处理中,这时候客户端可以通过轮询等机制继续请求。 -
3xx(已重定向)
300:请求成功,但结果有多种选择。
301:请求成功,但是资源被永久转移。
303:使用 GET 来访问新的地址来获取资源。
304:请求的资源并没有被修改过 -
4xx(请求错误)
400:请求出现错误,比如请求头不对等。
401:没有提供认证信息。请求的时候没有带上 Token 等。
402:为以后需要所保留的状态码。
403:请求的资源不允许访问。就是说没有权限。
404:请求的内容不存在。 -
5xx(服务器错误)
500:服务器错误。
501:请求还没有被实现。
7.描述一下cookies,sessionStorage和localStorage的区别?
相同点:都会在浏览器端保存,有大小和同源限制。
8.怎么让Chrome支持小于12px 的文字?
做移动端的时候,设计师图片上的文字假如是10px,我们实现在网页上之后。往往设计师回来找我们,这个字体能小一些吗?我设计的是10px?为啥是12px?其实我们都知道,谷歌Chrome最小字体是12px,不管你设置成8px还是10px,在浏览器中只会显示12px,那么如何解决这个坑爹的问题呢?
针对谷歌浏览器内核,加webkit前缀,用**transform:scale()**这个属性进行缩放!
<style>
p span{font-size:10px;-webkit-transform:scale(0.8);display:block;}
</style>
<p><span>小可爱10px</span></p>
9.浏览器在生成页面的时候,会生成那两颗树?
构造两棵树,DOM 树和 CSSOM 规则树,
当浏览器接收到服务器相应来的 HTML 文档后,会遍历文档节点,生成 DOM 树, CSSOM 规则树由浏览器解析 CSS 文件生成。
10.浏览器输入网址到页面渲染全过程
- DNS 解析
- TCP 连接
- 发送 HTTP 请求
- 服务器处理请求并返回 HTTP 报文
- 浏览器解析渲染页面
- 连接结束
框架通识(vue、uniapp)
1.MVVM
- View:界面
- Model:数据模型
- ViewModel:作为桥梁负责沟通 View 和 Model
MVVM模式有几大好处:
1、低耦合。View可以独立于Model变化和修改,一个ViewModel可以绑定到不同的View上,当View变化的时候Model可以不变,当Model变化的时候View也可以不变。
2、可重用性。可以把一些视图的逻辑放在ViewModel里面,让很多View重用这段视图逻辑。
3、独立开发。开发人员可以专注与业务逻辑和数据的开发(ViewModel)。设计人员可以专注于界面(View)的设计。
4、可测试性。可以针对ViewModel来对界面(View)进行测试
2.路由原理
前端路由实现起来其实很简单,本质就是监听 URL 的变化,然后匹配路由规则,显示相应的页面,并且无须刷新。目前单页面使用的路由就只有两种实现方式
- hash 模式
- history 模式
www.test.com/##/ 就是 Hash URL,当 ## 后面的哈希值发生变化时,不会向服务器请求数据,可以通过 hashchange 事件来监听到 URL 的变化,从而进行跳转页面。
History 模式是 HTML5 新推出的功能,比之 Hash URL 更加美观
3.脏数据检测
当触发了指定事件后会进入脏数据检测,这时会调用 $digest 循环遍历所有的数据观察者,判断当前值是否和先前的值有区别,如果检测到变化的话,会调用 $watch 函数,然后再次调用 $digest 循环直到发现没有变化。循环至少为二次 ,至多为十次。
脏数据检测虽然存在低效的问题,但是不关心数据是通过什么方式改变的,都可以完成任务,但是这在 Vue 中的双向绑定是存在问题的。并且脏数据检测可以实现批量检测出更新的值,再去统一更新 UI,大大减少了操作 DOM 的次数。所以低效也是相对的,这就仁者见仁智者见智了。
4.vue中的axios拦截器拦截302状态码
项目中前端需要统一处理后端返回的状态码并给出弹窗提示,需要在全局环境下对axios设置拦截器。
类似于401、403、500等状态码都可以在error回调中捕获到,但是302状态码是捕获不到的,因为当状态时302时,浏览器自行根据redirectUrl进行了跳转,所以无法在success回调中捕获弹窗,前端是无能为力的。
解决办法:
axios.interceptors.response.use((response) => {
return response;
}, function (error) {
if (401 === error.response.status) {
window.location = '/login';
} else {
return Promise.reject(error);
}
});
注意: axios请求头部不自带头部‘X-Requested-With’的,所以要加一句默认为异步请求:
axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
5.Vue3.0 是如何变得更快的?(底层,源码)
a. diff 方法优化
Vue2.x 中的虚拟 dom 是进行全量的对比。
Vue3.0 中新增了静态标记(PatchFlag):在与上次虚拟结点进行对比的时候,值对比 带有 patch flag 的节点,并且可以通过 flag 的信息得知当前节点要对比的具体内容化。
b. hoistStatic 静态提升:
Vue2.x : 无论元素是否参与更新,每次都会重新创建。
Vue3.0 : 对不参与更新的元素,只会被创建一次,之后会在每次渲染时候被不停的复用。
c. cacheHandlers 事件侦听器缓存:
默认情况下 onClick 会被视为动态绑定,所以每次都会去追踪它的变化但是因为是同一 个函数,所以没有追踪变化,直接缓存起来复用即可。
网络相关
1.AJAX的工作原理
ajax是一种通过后台与服务器进行少量的数据交换,使页面实现异步更新是一种创建交互式网页应用的网页开发技术。
1、创建ajax对象(XMLHttpRequest/ActiveXObject(Microsoft.XMLHttp))
2、判断数据传输方式(GET/POST)
3、打开链接 open()
4、发送 send()
5、当ajax对象完成第四步(onreadystatechange)数据接收完成,判断http响应状态(status)200-300之间或者304(缓存)执行回调函数
异步请求响应快,用户体验好;页面无刷新、数据局部更新;按需取数据,减少了冗余请求和服务器的负担。
异步回调问题、this指向问题、路由跳转back问题;对搜索引擎的支持比较弱,对于一些手机还不是很好的支持
DNS 解析也是需要时间的,可以通过预解析的方式来预先获得域名所对应的 IP。
<link rel="dns-prefetch" href="//yuchengkai.cn" />
缓存对于前端性能优化来说是个很重要的点,良好的缓存策略可以降低资源的重复加载提高网页的整体加载速度。
通常浏览器缓存策略分为两种:强缓存和协商缓存。
2.强缓存
实现强缓存可以通过两种响应头实现:Expires 和 Cache-Control 。强缓存表示在缓存期间不需要请求,state code 为 200。
Expires: Wed, 22 Oct 2018 08:41:00 GMT
Cache-control: max-age=30
3.协商缓存
如果缓存过期了,我们就可以使用协商缓存来解决问题。协商缓存需要请求,如果缓存有效会返回 304。
协商缓存需要客户端和服务端共同实现,和强缓存一样,也有两种实现方式。
Last-Modified 和 If-Modified-Since:
Last-Modified 表示本地文件最后修改日期,If-Modified-Since 会将 Last-Modified 的值发送给服务器,询问服务器在该日期后资源是否有更新,有更新的话就会将新的资源发送回来。
但是如果在本地打开缓存文件,就会造成 Last-Modified 被修改,所以在 HTTP / 1.1 出现了 ETag 。
ETag 和 If-None-Match:
ETag 类似于文件指纹,If-None-Match 会将当前 ETag 发送给服务器,询问该资源 ETag 是否变动,有变动的话就将新的资源发送回来。并且 ETag 优先级比 Last-Modified 高。
4.选择合适的缓存策略
- 对于大部分的场景都可以使用强缓存配合协商缓存解决,但是在一些特殊的地方可能需要选择特殊的缓存策略
- 对于某些不需要缓存的资源,可以使用 Cache-control: no-store ,表示该资源不需要缓存
- 对于频繁变动的资源,可以使用 Cache-Control: no-cache 并配合 ETag
使用,表示该资源已被缓存,但是每次都会发送请求询问资源是否更新。 - 对于代码文件来说,通常使用 Cache-Control: max-age=31536000, 并配合策略缓存使用,然后对文件进行指纹处理,一旦文件名变动就会立刻下载新的文件。
在开发中,可能会遇到这样的情况。有些资源不需要马上用到,但是希望尽早获取,这时候就可以使用预加载。
5.预加载
预加载其实是声明式的 fetch ,强制浏览器请求资源,并且不会阻塞 onload 事件,可以使用以下代码开启预加载.
<link rel="preload" href="http://example.com" />
预加载可以一定程度上降低首屏的加载时间,因为可以将一些不影响首屏但重要的文件延后加载,唯一缺点就是兼容性不好。
6.预渲染
<link rel="prerender" href="http://example.com" />
预渲染虽然可以提高页面的加载速度,但是要确保该页面百分百会被用户在之后打开,否则就白白浪费资源去渲染
性能优化
1.前端性能优化的七大手段
- 减少请求数量
- 减小资源大小
- 优化网络连接
- 优化资源加载
- 减少重绘回流
- 性能更好的API
- webpack优化
日常开发中遇到滚动事件中需要做个复杂计算或者实现一个按钮的防二次点击操作。防抖和节流的作用都是防止函数多次调用。区别在于,假设一个用户一直触发这个函数,且每次触发函数的间隔小于wait,防抖的情况下只会调用一次,而节流的 情况会每隔一定时间(参数wait)调用函数。
2.防抖、节流
// 这个是用来获取当前时间戳的
function now() {
return +new Date()
}
/**
* 防抖函数,返回函数连续调用时,空闲时间必须大于或等于 wait,func 才会执行
*
* @param {function} func 回调函数
* @param {number} wait 表示时间窗口的间隔
* @param {boolean} immediate 设置为ture时,是否立即调用函数
* @return {function} 返回客户调用函数
*/
function debounce (func, wait = 50, immediate = true) {
let timer, context, args
// 延迟执行函数
const later = () => setTimeout(() => {
// 延迟函数执行完毕,清空缓存的定时器序号
timer = null
// 延迟执行的情况下,函数会在延迟函数中执行
// 使用到之前缓存的参数和上下文
if (!immediate) {
func.apply(context, args)
context = args = null
}
}, wait)
// 这里返回的函数是每次实际调用的函数
return function(...params) {
// 如果没有创建延迟执行函数(later),就创建一个
if (!timer) {
timer = later()
// 如果是立即执行,调用函数
// 否则缓存参数和调用上下文
if (immediate) {
func.apply(this, params)
} else {
context = this
args = params
}
// 如果已有延迟执行函数(later),调用的时候清除原来的并重新设定一个
// 这样做延迟函数会重新计时
} else {
clearTimeout(timer)
timer = later()
}
}
}
对于按钮防点击来说的实现:如果函数是立即执行的,就立即调用,如果函数是延迟执行的,就缓存上下文和参数,放到延迟函数中去执行。一旦我开始一个定时器,只要我定时器还在,你每次点击我都重新计时。一旦你点累了,定时器时间到,定时器重置为 null,就可以再次点击了。对于延时执行函数来说的实现:清除定时器ID,如果是延迟调用就调用函数。
防抖动和节流本质是不一样的。防抖动是将多次执行变为最后一次执行,节流是将多次执行变成每隔一段时间执行。
/**
* underscore 节流函数,返回函数连续调用时,func 执行频率限定为 次 / wait
*
* @param {function} func 回调函数
* @param {number} wait 表示时间窗口的间隔
* @param {object} options 如果想忽略开始函数的的调用,传入{leading: false}。
* 如果想忽略结尾函数的调用,传入{trailing: false}
* 两者不能共存,否则函数不能执行
* @return {function} 返回客户调用函数
*/
_.throttle = function(func, wait, options) {
var context, args, result;
var timeout = null;
// 之前的时间戳
var previous = 0;
// 如果 options 没传则设为空对象
if (!options) options = {};
// 定时器回调函数
var later = function() {
// 如果设置了 leading,就将 previous 设为 0
// 用于下面函数的第一个 if 判断
previous = options.leading === false ? 0 : _.now();
// 置空一是为了防止内存泄漏,二是为了下面的定时器判断
timeout = null;
result = func.apply(context, args);
if (!timeout) context = args = null;
};
return function() {
// 获得当前时间戳
var now = _.now();
// 首次进入前者肯定为 true
// 如果需要第一次不执行函数
// 就将上次时间戳设为当前的
// 这样在接下来计算 remaining 的值时会大于0
if (!previous && options.leading === false) previous = now;
// 计算剩余时间
var remaining = wait - (now - previous);
context = this;
args = arguments;
// 如果当前调用已经大于上次调用时间 + wait
// 或者用户手动调了时间
// 如果设置了 trailing,只会进入这个条件
// 如果没有设置 leading,那么第一次会进入这个条件
// 还有一点,你可能会觉得开启了定时器那么应该不会进入这个 if 条件了
// 其实还是会进入的,因为定时器的延时
// 并不是准确的时间,很可能你设置了2秒
// 但是他需要2.2秒才触发,这时候就会进入这个条件
if (remaining <= 0 || remaining > wait) {
// 如果存在定时器就清理掉否则会调用二次回调
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
previous = now;
result = func.apply(context, args);
if (!timeout) context = args = null;
} else if (!timeout && options.trailing !== false) {
// 判断是否设置了定时器和 trailing
// 没有的话就开启一个定时器
// 并且不能不能同时设置 leading 和 trailing
timeout = setTimeout(later, remaining);
}
return result;
};
};
3.重绘、重排
每个页面至少在初始化的时候会有一次重排操作,任何对渲染树的修改,都可能引发重排或者重绘。
重绘(repaint):
当盒子的位置、大小以及其他属性,浏览器便把这些都按照各自的特性绘制一遍,将内容呈现在页面中。重绘是指一个元素外观的改变所触发的浏览器行为,浏览器会根据元素的新属性重新绘制,使元素呈现新的外观。
重排:
当渲染树中一部分,因为元素的规模尺寸,布局,隐藏等改变而需要重新构建,这就称为回流,每个页面至少需要一次回流,就是在页面第一次加载的时候。
重排和重绘的关系:在回流的时候,浏览器会使渲染树中受到影响的部分失效,并重新构造这部分渲染树,完成回流后,浏览器会重新绘制受影响的部分,该过程称为重绘,因此重排必定会引发重绘,但重绘不一定会引发重排。
如何减少重绘和重排
1、不要一个地单独修改属性,最好通过ClassName来定义这些修改
2、批量操作
(1)、把对节点的大量操作放在页面之外,用documentFragment来做修改。
(2)、clone节点,在clone之后的节点中做修改,然后直接替换掉以前的节点。
var fragment = document.createDocumentFragment();
var li = document.createElement('li');
li.innerHTML = 'apple';
fragment.appendChild(li);
var li = document.createElement('li');
li.innerHTML = 'watermelon';
fragment.appendChild(li);
document.getElementById('fruit').appendChild(fragment);
(3)、通过display:none来隐藏节点(直接导致一次重排和重绘),做大量的修改,然后显示节点(又一次重排和重绘),总共只有两次重排。
3、考虑到渲染树,一次修改会导致大量的绘制操作,比如绝对定位元素的动画就不会影响其他大部分元素。
4、浏览器优化: 浏览器自己会维护1个队列,把所有会引起回流、重绘的操作放入这个队列,等队列中的操作到了一定的数量或者到了一定的时间间隔,浏览器就会flush队列,进行一个批处理,这样就会让多次的回流、重绘变成一次回流重绘。
不要经常访问浏览器的flush队列属性;如果一定要访问,可以利用缓存,将访问的值存储起来,接下来使用就不会再引发回流。
// 例如myElement元素沿对角线移动,每次移动一个像素。到500*500像素的位置结束。timeout循环体中可以这么做
myElement.style.left = 1 + myElement.offsetLeft + 'px';
myElement.style.top = 1 + myElement.offsetTop + 'px';
if(myElement.offsetLeft >= 500){
stopAnimation();
}
// 显然这种方法低效,每次移动都要查询偏移量,导致浏览器刷新渲染队列而不利于优化。好的办法是获取一次起始位置的值,然后赋值给一个变量。如下
var current = myElement.offsetLeft;
current++;
myElement.style.left = current + 'px';
myElement.style.top = current + 'px';
if(myElement.offsetLeft >= 500){
stopAnimation();
}
4.优化渲染过程
4.1懒执行
懒执行就是将某些逻辑延迟到使用时再计算。该技术可以用于首屏优化,对于某些耗时逻辑并不需要在首屏就使用的,就可以使用懒执行。懒执行需要唤醒,一般可以通过定时器或者事件的调用来唤醒。
4.2懒加载
懒加载就是将不关键的资源延后加载。
懒加载的原理就是只加载自定义区域(通常是可视区域,但也可以是即将进入可视区域)内需要加载的东西。对于图片来说,先设置图片标签的 src 属性为一张占位图,将真实的图片资源放入一个自定义属性中,当进入自定义区域时,就将自定义属性替换为 src 属性,这样图片就会去下载资源,实现了图片懒加载。
懒加载不仅可以用于图片,也可以使用在别的资源上。比如进入可视区域才开始播放视频等等。
5.文件优化
5.1图片优化:
减少像素点、减少每个像素点能够显示的颜色
5.2图片加载优化:
- 不用图片。很多时候会使用到很多修饰类图片,其实这类修饰图片完全可以用 CSS 去代替。
- 对于移动端来说,屏幕宽度就那么点,完全没有必要去加载原图浪费带宽。一般图片都用 CDN
加载,可以计算出适配屏幕的宽度,然后去请求相应裁剪好的图片。 - 小图使用 base64 格式
- 将多个图标文件整合到一张图片中(雪碧图)
5.3选择正确的图片格式:
- 对于能够显示 WebP 格式的浏览器尽量使用 WebP 格式。因为 WebP
格式具有更好的图像数据压缩算法,能带来更小的图片体积,而且拥有肉眼识别无差异的图像质量,缺点就是兼容性并不好 - 小图使用 PNG,其实对于大部分图标这类图片,完全可以使用 SVG 代替
- 照片使用 JPEG
6.其他文件优化
- CSS 文件放在 head 中
- 服务端开启文件压缩功能
- 将 script 标签放在 body 底部,因为 JS 文件执行会阻塞渲染。当然也可以把 script 标签放在任意位置然后加上
defer ,表示该文件会并行下载,但是会放到 HTML 解析完成后顺序执行。对于没有任何依赖的 JS 文件可以加上 async
,表示加载和渲染后续文档元素的过程将和 JS 文件的加载与执行并行无序进行。 - 执行 JS 代码过长会卡住渲染,对于需要很多时间计算的代码可以考虑使用 Webworker。Webworker
可以让我们另开一个线程执行脚本而不影响渲染。
7.CDN
静态资源尽量使用 CDN 加载,由于浏览器对于单个域名有并发请求上限,可以考虑使用多个 CDN 域名。对于 CDN 加载静态资源需要注意 CDN 域名要与主站不同,否则每次请求都会带上主站的 Cookie。
8.使用 Webpack 优化项目
- 对于 Webpack4,打包项目使用 production 模式,这样会自动开启代码压缩
- 使用 ES6 模块来开启 tree shaking,这个技术可以移除没有使用的代码
- 优化图片,对于小图可以使用 base64 的方式写入文件中
- 按照路由拆分代码,实现按需加载
- 给打包出来的文件名添加哈希,实现浏览器缓存文件
9.监控
- 对于代码运行错误,通常的办法是使用 window.onerror 拦截报错。该方法能拦截到大部分的详细报错信息,但是也有例外
- 对于跨域的代码运行错误会显示 Script error. 对于这种情况我们需要给 script 标签添加 crossorigin 属性
- 对于某些浏览器可能不会显示调用栈信息,这种情况可以通过 arguments.callee.caller 来做栈递归
- 对于异步代码来说,可以使用 catch 的方式捕获错误。比如 Promise 可以直接使用 catch 函数,async await,可以使用 try catch,但是要注意线上运行的代码都是压缩过的,需要在打包时生成 sourceMap 文件便于 debug。
- 对于捕获的错误需要上传给服务器,通常可以通过 img 标签的 src 发起一个请求。
10.如何渲染几万条数据并不卡住界面
语法:
window.requestAnimationFrame(callback);
callback:
下一次重绘之前更新动画帧所调用的函数(即上面所说的回调函数)。该回调函数会被传入DOMHighResTimeStamp参数,该参数与performance.now()的返回值相同,它表示requestAnimationFrame() 开始去执行回调函数的时刻。
返回值:
一个 long 整数,请求 ID ,是回调列表中唯一的标识。是个非零值,没别的意义。你可以传这个值给 window.cancelAnimationFrame() 以取消回调函数。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Document</title>
</head>
<body>
<ul>
数据条渲染:
</ul>
<script>
setTimeout(() => {
// 插入千万条数据
const total = 10000000
// 一次插入 20 条,如果觉得性能不好就减少
const once = 20
// 渲染数据总共需要几次
const loopCount = total / once
var countOfRender = 0
var ul = document.querySelector('ul')
console.time('loopTime');
function add() {
// 优化性能,插入不会造成回流
const fragment = document.createDocumentFragment()
for (var i = 0; i < once; i++) {
const li = document.createElement('li')
li.innerText = Math.floor(Math.random() * total)
fragment.appendChild(li)
}
ul.appendChild(fragment)
countOfRender += 1
loop()
}
//获取代码的执行时间
console.timeEnd('loopTime');
function loop() {
if (countOfRender < loopCount) {
window.requestAnimationFrame(add)
}
}
loop()
}, 0)
</script>
</body>
</html>
11.xss攻击是什么?如何避免?
答:跨站脚本攻击就是xss攻击。有三种攻击方式:反射、注入、存储。 反射型:反射型XSS通常出现在网站的搜索栏,使用url地址挂载恶意的参数,一般主要是偷取cookie等。 注入:是在标签上使用属性添加的方式,恶意的破坏网站的数据。 存储:在接口请求中加入恶意代码,攻击或存储到数据库里。攻击,比如植入一段删除sql的语句;储存,把一段js脚本语法,早期很多网站有留言板,论坛,只要把内容中写入脚本,那么下一个来访问的人,他的信息就可能会被偷走。
标签攻击,尽量不要使用innerHTML这个语法。 url:需要在接口处做参数的判断和url校验 存储:转移,把他变成字符串,使用正则把内容种<>变成xx%字符
12.移动端兼容性
1.IOS 移动端 click 事件 300ms 的延迟相应 =》使用touchsyart、toychend、touchmove、tap(模拟事件)来取代cilidk事件!FastClick轻量级库
2.h5 底部输入框被键盘遮挡问题
3.CSS 动画页面闪白,动画卡顿
4.phone 及 ipad 下输入框默认内阴影
element{
-webkit-appearance:none;
}
5.防止手机中页面放大和缩小
<meta name="viewport" content="user-scalable=no">
<meta name="viewport" content="initial-scale=1,maximum-scale=1">
6.上下拉动滚动条时卡顿、慢
body{
-webkit-overflow-scrolling:touch;
overflow-scrolling:touch;
}
7.长时间按住页面出现闪退
element{
-webkit-touch-callout:none;
}
7.Android手机圆角失效
background-clip:padding-box;
13.如何在前端实现十万行数据的表格秒级响应?
- 分页:将数据分成多个页面,只在当前页面显示数据,从而减少浏览器的内存占用。
- 懒加载:使用懒加载技术,只在用户滚动到表格的底部时才加载更多数据。
- 虚拟列表:只渲染可见部分的数据,从而减少浏览器的渲染时间。
- 数据缓存:使用浏览器缓存或本地存储来缓存数据,减少请求次数,提高响应速度。
- 后端优化:使用数据库索引、分页等技术来优化数据查询和排序。
- Web Workers: 使用 web workers 来在后台线程上处理大量数据,减少主线程的负担。
- 前端框架优化:使用高性能的前端框架,如 React 和 Vue 等来优化表格渲染。
- 用 WebAssembly 来优化耗时的算法和计算,可以在前端使用汇编代码来提高性能。
- 使用跨域数据传输技术(CORS)来获取数据,减少网络请求延迟。
- 对于大量数据的表格,使用更高级的排序和筛选功能来减少需要渲染的数据量。
- 使用更高效的数据绑定方法,例如,使用Object.observe()或Proxy来替代使用setInterval()或setTimeout()来检查数据的变化。
- 使用 WebAssembly 和 WebGL 来加速图形渲染,如使用canvas 或 WebGL来渲染表格数据。
- 使用 WebSockets 或 Server-Sent Events 来实现实时数据更新,减少服务器的压力和提高响应速度。
- 使用高性能的 JavaScript 库,例如 lodash 和 Ramda 等来优化数据处理。
- 使用类型检查库,例如 Flow 或 TypeScript来减少类型错误导致的性能问题
- 使用单元测试和性能测试来确保代码的高性能和正确性。
- 减少 DOM 操作次数,优化渲染性能,可以使用 documentFragment 或其他方式来减少重绘。
- 使用工具,如 Lighthouse 和 DevTools 来优化性能,检测性能瓶颈并优化代码。
- 使用高性能的数据结构来存储数据,例如哈希表或二叉堆来提高查询和排序的性能。
前端工程化
前端工程化就是为了让前端开发能够“自成体系”,个人认为主要应该从模块化、组件化、规范化、自动化四个方面思考。
真题汇聚
汇聚有些黄金灿灿的车子,华丽朴素,就是四个轱辘,一身正气吹响面试造飞机号角,启动上班拧螺丝的征程!!!
携程一面题:
1、 说一说什么是闭包,应用场景是什么?使用闭包会产生什么问题?
请瞄一瞄javascript的第七点
2、 内存泄漏和内存溢出的区别
内存泄漏memoryleak:指程序在申请内存后,无法释放已申请的内存空间,一次内存泄漏似乎不会有大的影响,但内存泄漏堆积后的后果就是内存溢出。
内存溢出outofmemory:指程序申请内存时,没有足够的内存供申请者使用,或者说,给了你一块存储int类型数据的存储空间,但是你却存储long类型的数据,那么结果就是内存不够用,此时就会报错OOM,即内存溢出。
内存泄漏会导致内存溢出,内存泄漏是多个申请了内存空间的变量一直在占用,却无法被释放,也就是说内存泄漏是一个过程,进而导致内存溢出,内存溢出是结果,无法继续申请内存空间,内存占满了。
3、 怎么监控页面的内存有没有溢出
几种常见的js内存泄露:
1、意外的全局变量
JavaScript 处理未定义变量的方式比较宽松:未定义的变量会在全局对象创建一个新变量。在浏览器中,全局对象是 window ,
例如:
haorooms ="这是一个全局的haorooms"
实际上生成了一个全局的haorooms,虽然一个简单的字符串,无伤大雅,也泄露不了多少内存,但是我们在编程中尽量少的避免全局变量!
另外一种全局变量可能由this创建。例如:
function foo() {
this.variable = "potential accidental global";
}
// Foo 调用自己,this 指向了全局对象(window)
foo();
2、没有及时清理的计时器或回调函数
setInterval用多了,会占用大量的内存。因此setInterval我们必须及时清理!可以用如下方式清理setInterval。
function b() {
var a = setInterval(function() {
console.log("Hello");
clearInterval(a);
b();
}, 50);
}
b();
或者用2个函数:
function init()
{
window.ref = window.setInterval(function() { draw(); }, 50);
}
function draw()
{
console.log(\'Hello\');
clearInterval(window.ref);
init();
}
init();
或者我们用setTimeout:
function time(f, time) {
return function walk() {
clearTimeout(aeta);
var aeta =setTimeout(function () {
f();
walk();
}, time);
};
}
time(updateFormat, 1000)();
3、脱离 DOM 的引用
有时,保存 DOM 节点内部数据结构很有用。假如你想快速更新表格的几行内容,把每一行 DOM 存成字典(JSON 键值对)或者数组很有意义。此时,同样的 DOM 元素存在两个引用:一个在 DOM 树中,另一个在字典中。将来你决定删除这些行时,需要把两个引用都清除。
var elements = {
button: document.getElementById(\'button\'),
image: document.getElementById(\'image\'),
text: document.getElementById(\'text\')
};
function doStuff() {
image.src = \'http://some.url/image\';
button.click();
console.log(text.innerHTML);
// 更多逻辑
}
function removeButton() {
// 按钮是 body 的后代元素
document.body.removeChild(document.getElementById(\'button\'));
// 此时,仍旧存在一个全局的 #button 的引用
// elements 字典。button 元素仍旧在内存中,不能被 GC 回收。
}
4、闭包
function foo() {
var local = 'Hello';
return function() {
return local;
};
}
var bar = foo();
console.log(bar()); //=> Hello
/**这里所展示的让外层作用域访问内层作用域的技术便是闭包(Closure)。得益于高阶函数的应用,使foo()函数的作用域得到`延伸`。foo()函数返回了一个匿名函数,该函数存在于foo()函数的作用域内,所以可以访问到foo()函数作用域内的local变量,并保存其引用。而因这个函数直接返回了local变量,所以在外层作用域中便可直接执行bar()函数以获得local变量。**/
闭包是JAVASCRIPT的高级特性,因为把带有内部变量引用的函数带出了函数外部,所以该作用域内的变量在函数执行完毕后的并不一定会被销毁,直到内部变量的引用被全部解除。所以闭包的应用很容易造成内存无法释放的情况。
5、echart不停调用导致内存泄露
不停的用setInterval调用echart,更新echart表格及地图数据,及时清理了setInterval,也会导致内存泄露!
解决办法:
1、使用 Timeline 记录可视化内存泄漏
使用 Chrome DevTools 的 Timeline 面板可以记录和分析您的应用在运行时的所有活动。 这里是开始调查应用中可觉察性能问题的最佳位置。
2、。。。。。
4、前端性能优化的方式
请瞄一瞄前面的性能优化
5、SSr如果使用懒加载会不会有什么问题
SSr服务器端渲染(Server-Side Rendering)是指由服务侧完成页面的 HTML 结构拼接的页面处理技术,发送到浏览器,然后为其绑定状态与事件,成为完全可交互页面的过程。
例如Vue SSR 组件加载问题:
test.vue:
<template>
<div>
<h2>clientHeight: {{ clientHeight }} px </h2>
</div>
</template>
<script type="text/babel">
export default {
data(){
return {
}
},
computed :{
clientHeight() {
return document.body.clientHeight;
}
},
mounted(){
}
}
</script>
上面 test.vue 组件通过 Vue computed 属性 clientHeight 直接获取 document 的文档高度,这段代码在前端渲染是不会报错的,也能拿到正确的值。但如果把这个组件放到 SSR(Server Side Render) 模式下, 就会报如下错误:
ReferenceError: document is not defined
解决方案:
通过 typeof 判断是否是存在 document 对象, 如果存在则执行后面代码。 这种方式虽然能解决问题, 但在 Webpack 构建压缩时, 不会执行的代码不会被剔除,也会打包到 js 文件中去, 因为这个是在运行期才知道结果的, 所以在 Webpack 构建方案中,不建议使用 typeof 方式判断。而是使用 Webpack 提供的 webpack.DefinePlugin 插件定义常量解决。
clientHeight() {
return typeof document === 'object' ? document.body.clientHeight : '';
}
使用 Webpack 提供的 webpack.DefinePlugin 插件定义常量解决。 这里直接使用 easywebpack https:// github.com/hubcarl/easy webpack 内置的全局 Webpack 常量 EASY_ENV_IS_BROWSER http:// hubcarl.github.io/easyw ebpack/webpack/env 进行 判断。 这样在构建压缩期间, 如果是 Node 模式构建, EASY_ENV_IS_BROWSER 会被替换为 false,如果是 Browser 模式构建, EASY_ENV_IS_BROWSER 会被替换为 true,最后构建后代码也就是变成了 true 或者 false 的常量。 因为这个是构建期间执行的,压缩插件剔除永远不会被执行的代码, 也就是
dead_code
clientHeight() {
return EASY_ENV_IS_BROWSER ? document.body.clientHeight : '';
}
NPM Vue 组件 SSR 支持
针对上面这种自己写的代码,我们可以通过这种方式解决,因为可以直接修改。但如果我们引入的一个 npm Vue 插件想进行SSR渲染, 但这个插件里面使用了 window/docment 等浏览器对象, 并没有对 SSR 模式进行兼容,这个时候该如何解决呢?
一般我们通过 通过 v-if 来决定是否渲染该组件 和 Vue 只在前端挂载组件解决问题 可以解决。
通过 v-if 来决定是否渲染该组件:
<template>
<div v-if="isBrowser">
<Loading></Loading>
</div>
</template>
<script type="text/babel">
export default {
componets:{
Loading: () =>import('vue-loading');
}
data(){
return {
isBrowser: EASY_ENV_IS_BROWSER
}
},
mounted(){
}
}
</script>
Vue 只在前端挂载组件解决问题:
<template>
<div>
<Loading></Loading>
</div>
</template>
<script type="text/babel">
export default {
data(){
return {
}
},
beforeMount() {
// 只会在浏览器执行
this.$options.components.Loading = () =>import('vue-loading');
},
mounted(){
}
}
</script>
oading 组件因为没有注册, 在 SSR 模式, 会被原样输出到 HTML 中,不会报错且不能被浏览器识别, 在显示时不会有内容。当 SSR 直出 HTML 后,浏览器模式中执行 beforeMount 挂载组件, 从而达到解决服务端渲染报错的问题
6、说说防抖和节流、重绘和重排
请瞄一瞄前面的性能优化第2点和第3点
7、map和weakmap的区别,什么场景下使用weakmap
Map 和 WeakMap 是两种数据结构,可用于操纵键和值之间的关系。
区别
我们可以对 Map 的键和值使用对象或任何基本类型。但是,WeakMap 仅接受对象。这意味着我们不能将基本类型用作 WeakMap 的键。
const attrs = new WeakMap()
attrs.set('color', 'plum') // error
与 Map不同,WeakMap 不支持对键和值进行迭代。无法获取 WeakMap 的所有键或值。此外,也没有办法清除 WeakMap。
最重要的区别是,WeakMap 不会阻止在没有对键的引用时对键进行垃圾收集。
另一方面,Map 无限期地维护对键和值的引用。一旦创建了键和值,它们将占用内存,即使没有对它们的引用,也不会被垃圾收集。这可能会导致内存泄漏问题。
考虑下面的一个简单代码,我们将一个唯一的 ID 映射到特定的人的信息:
let id = { value: 1 }
const people = new Map()
people.set(id, {
name: 'Foo',
age: 20,
address: 'Bar'
})
// 移除 id
id = null
删除键对象 id 后,它仍然能够通过映射键访问其引用:
people.keys().next().value // { value: 1 }
由于这种差异,WeakMap(顾名思义)保存对键的弱引用。它解释了为什么它的键不可枚举,这在前面的区别中已经提到。
由于 WeakMap 保存对键的弱引用,且无法枚举,因此无法使用 keys()、values()、entries() 这些方法。
8、用过WeakMap那些插件
整理中。。。
http://www.javashuo.com/article/p-hihwtfzt-t.html
9、bable的原理是什么?
Babel 是 JavaScript 编译器:他能让开发者在开发过程中,直接使用各类方言(如 TS、Flow、JSX)或新的语法特性,而不需要考虑运行环境,因为 Babel 可以做到按需转换为低版本支持的代码;Babel 内部原理是将 JS 代码转换为 AST,对 AST 应用各种插件进行处理,最终输出编译后的 JS 代码。
10、vue中路由懒加载的原理是什么?
路由懒加载的主要原理就是原本的Vue模块是全部导入在一起的打包文件,运行后用户查看相关模块显示的内容时会将整个打包的文件引入而后在其中查找对应的模块然后才将其呈现给用户。这样会使得在打包文件中查找对应模块时,在浏览器中可能会出现短暂的空白页,从而降低用户体验。而路由懒加载是将各个模块分开打包,在用户查看下相关模块内容时就直接引入相关模块的打包文件然后进行显示,从而有效的解决了浏览器可能出现短暂时间空白页的情况。
11、es5怎么实现class静态属性、class的构造函数
原声js的类,静态方法继承
//es5中的类和静态方法
function Person(name,age) {
//构造函数里面的方法和属性
this.name=name;
this.age=age;
this.run=function(){
console.log(`${this.name}---${this.age}`)
}
}
//原型链上面的属性和方法可以被多个实例共享
Person.prototype.sex='男';
Person.prototype.work=function(){
console.log(`${this.name}---${this.age}---${this.sex}`);
}
//静态方法
Person.setName=function(){
console.log('静态方法');
}
var p=new Person('zhangsan','20'); /*实例方法是通过实例化来调用的,静态是通过类名直接调用*/
p.run();
p.work();
Person.setName(); /*执行静态方法*/
//es5继承
/*
原型链继承和对象冒充继承
对象冒充继承:没法继承原型链上面的属性和方法
原型链继承:可以继承构造函数里面以及原型链上面的属性和方法,实例化子类的时候没法给父类传参
* */
function Person(name,age) {
this.name=name;
this.age=age;
this.run=function(){
console.log(this.name+'---'+this.age);
}
}
Person.prototype.work=function(){
console.log('work');
}
function Web(name,age){
Person.call(this,name,age); /*对象冒充实现继承*/
}
Web.prototype=new Person();
var w=new Web('李四',20);
w.run();
w.work(); //w.work is not a function
es6中的类、静态方法 继承
//定义Person类
class Person{
constructor(name,age) { /*类的构造函数,实例化的时候执行,new的时候执行*/
this._name=name;
this._age=age;
}
getName(){
console.log(this._name);
}
setName(name){
this._name=name
}
}
var p=new Person('张三1','20');
p.getName();
p.setName('李四');
p.getName();
//es6里面的继承
class Person{
constructor(name,age){
this.name=name;
this.age=age;
}
getInfo(){
console.log(`姓名:${this.name} 年龄:${this.age}`);
}
run(){
console.log('run')
}
}
class Web extends Person{ //继承了Person extends super(name,age);
constructor(name,age,sex){
super(name,age); /*实例化子类的时候把子类的数据传给父类*/
this.sex=sex;
}
print(){
console.log(this.sex);
}
}
var w=new Web('张三','30','男');
w.getInfo();
//es6里面的静态方法
class Person{
constructor(name){
this._name=name; /*属性*/
}
run(){ /*实例方法*/
console.log(this._name);
}
static work(){ /*静态方法*/
console.log('这是es6里面的静态方法');
}
}
Person.instance='这是一个静态方法的属性';
var p=new Person('张三');
p.run();
Person.work(); /*es6里面的静态方法*/
console.log(Person.instance);
单例模式
//单例只执行一次构造函数 这样咱们以后在连接数据库的时候不会重复连接从而导致的资源浪费
class Db {
static getInstance(){ /*单例*/
if(!Db.instance){
Db.instance=new Db();
}
return Db.instance;
}
constructor(){
console.log('实例化会触发构造函数');
this.connect();
}
connect(){
console.log('连接数据库');
}
find(){
console.log('查询数据库');
}
}
var myDb=Db.getInstance();
var myDb2=Db.getInstance();
var myDb3=Db.getInstance();
var myDb4=Db.getInstance();
myDb3.find();
myDb4.find();
12、怎么实现instanceof方法
说instanceof 就想到typeof ,这里也介绍下typeof:,typeof是用来判断数据类型的,就一个参数 ,使用方式像这样: typeof num, 就是判断num是什么类型
typeof 一般只能返回如下几个结果**:“number”、“string”、“boolean”、“object”、“function” 和 “undefined”**; 除了"object" 其他都好说。
着重看这几个:
typeof 不存在的变量 = “undefined”
typeof 对象 = “object”
typeof null = “object”
typeof 数组 = “object”
typeod 方法的实例(比如 new Array()) =“object”
对象,数组 都是引用类型, 使用typeof 结果是 object类型,但是null 是基本数据类型,使用typeof结果也是 object,
可以这么理解:null 是 不指向任何对象 的 空指针, 因为它是指向对象的,所以typeof 就是 object, 但是它又是空的,所以就属于基本数据类型。但是要想判断一个变量是不是数组, 或者对象, 这时候就需要instanceof了(判断是不是null,直接用 变量 === null 就行, null===null 结果是 true)
现在说instanceof, 要想从根本上了解 instanceof 的奥秘,需要从两个方面着手:
1 语言规范中是如何定义这个运算符的。
2 JavaScript 原型继承机制。
JavaScript instanceof 语言规范 (简化版) 的运算代码如下:
function instance_of(L, R) {//L 表示左表达式,R 表示右表达式
var O = R.prototype;
L = L.__proto__;
while (true) {
if (L === null)
return false;
if (O === L) // 这里重点:当 O 严格等于 L 时,返回 true
return true;
L = L.__proto__;
}
}
规则简单来说就是 L的 proto 是不是强等于 R.prototype,不等于再找 L.proto .proto 直到 proto 为 null
模拟实现instanceof
对于用 typeof 就可以判断出来数据类型的这里就不处理,只处理 typeof 结果为 object ,并且不是 null 的。
方法一: 直接使用instanceof的规则
<script type="text/javascript">
function instance_of(L, R) {//L 表示左表达式,R 表示右表达式
var O = R.prototype;
L = L.__proto__;
while (true) {
if (L === null)
return false;
if (O === L) // 这里重点:当 O 严格等于 L 时,返回 true
return true;
L = L.__proto__;
}
}
// 开始测试
var a = []
var b = {}
function Foo(){}
var c = new Foo()
function child(){}
function father(){}
child.prototype = new father()
var d = new child()
console.log(instance_of(a, Array)) // true
console.log(instance_of(b, Object)) // true
console.log(instance_of(b, Array)) // false
console.log(instance_of(a, Object)) // true
console.log(instance_of(c, Foo)) // true
console.log(instance_of(d, child)) // true
console.log(instance_of(d, father)) // true
</script>
方法二:在方法一的基础上使用 constructor (此方法无法用于判断继承)
<script type="text/javascript">
function instance_of(L, R) {//L 表示左表达式,R 表示右表达式
var O = R;
L = L.__proto__;
while (true) {
if (L === null)
return false;
if (O === L.constructor) // 这里重点:当 O 严格等于 L 时,返回 true
return true;
L = L.__proto__;
}
}
// 开始测试
var a = []
var b = {}
function Foo(){}
var c = new Foo()
function child(){}
function father(){}
child.prototype = new father()
var d = new child()
console.log(instance_of(a, Array)) // true
console.log(instance_of(b, Object)) // true
console.log(instance_of(b, Array)) // false
console.log(instance_of(a, Object)) // true
console.log(instance_of(c, Foo)) // true
console.log(instance_of(d, child)) // false 这里就是无法用于判断继承的
console.log(instance_of(d, father)) // true
</script>
13、使用递归时你觉得最重要的一点是什么?如果有一万条数据使用递归会有什么问题?如何避免?
-
主要点:
1、明确你这个函数想要干什么
2、寻找递归结束条件
3、找出函数的等价关系式 -
问题:
function sum0(total, i, callback) {
if(i===0){
callback(total);
return;
}
sum0(total+i, i-1, callback)
}
sum0(0, 100, value=>{
console.log(value)
})
sum0(0, 100000, value=>{
console.log(value)
})
比如上面这个例子,递归次数在100的时候,还没有问题。一旦执行100000次的时候就会发生递归栈溢出的问题。
每次sum0都会执行sum0,导致调用栈中sum0过多溢出。
- 避免:
方法一:
可用setTimeout,解决调用栈过多的问题:
function sum0(total, i, callback) {
if(i===0){
callback(total);
return;
}
// sum0(total+i, i-1, callback)
setTimeout(sum0, 0, total+i, i-1, callback)
}
sum0(0, 100, value=>{
console.log(value)
})
sum0(0, 100000, value=>{
console.log(value)
})
但是问题是,要等很久才能出结果。这个原理是,当sum0运行到setTimeout处,任务被存至回调队列,然后sum0被返回,sum0执行完被弹出调用栈,任务队列的任务再进入调用栈中运行,所以每次调用栈中只能运行一次sum0。不过,任务加入任务队列再到调用栈有一定延迟,一般为几毫秒到十几毫秒不等,如果按照10毫秒计算,100000个任务就要10ms*100000=1000s了。所以会迟迟不出结果。
方法二:
function sum2(total, i, callback) {
if(i===0){
callback(total);
return;
}
let part = 1000;
if(i%part ===0 ){
setTimeout(sum2, 0, total+i, i-1, callback)
}else{
sum2(total+i, i-1, callback)
}
// sum0(total+i, i-1, callback)
}
sum2(0, 100000, value=>{
console.log(value)
})
原理是:每1000次调用sum2,才会执行一次setTimeout,sum2才会被安排到任务队列中,同时调用栈不会溢出。
14、快速排序的时间复杂度
快速排序的时间主要耗费在划分操作上,对长度为n的区间进行划分,共需n-1次关键字的比较,时间复杂度为O(n)。
对n个元素进行快速排序的过程构成一棵递归树,在这样的递归树中,每一层最多对n个元素进行划分,所花的时间为O(n)。当初始排序数据随机分布,使每次分成的两个子区间中的元素个数大致相等时,递归树高度为log2n,快速排序呈现最好情况,即最好情况下的时间复杂度为O(nlog2n)。快速排序算法的平均时间复杂度也是O(nlog2n)。所以快速排序是一种高效的算法。
15、如果有一个一万条数据的对象,每一个对象都有一个value值,如果让你取value值的最大三个,你会怎么做?时间复杂度多少?
。。。
16、js 处理十万条数据_前端如何处理十万的大量数据
后台角度:分页加载。。。
前端角度:worker来做子线程来实现
Worker 接口是Web Workers API的一部分,代表一个后台任务,它容易被创建并向创建者发回消息。创建一个运行者只要简单的调用Worker()构造函数,指定一个脚本,在工作线程中执行
通俗点讲就是:因为js是单线程运行的,在遇到一些需要处理大量数据的js时,可能会阻塞页面的加载,造成页面的假死。这时我们可以使用worker来开辟一个独立于主线程的子线程来进行哪些大量运算。这样就不会造成页面卡死。也说明 worker可以用来解决大量运算是造成页面卡死的问题。
语法规则:
const worker=new Worker(aURL, options)
//aURL(必须)是一个DOMString 表示worker 将执行的脚本的URL。它必须遵守同源策略。
//options (可选)它的一个作用就是指定 Worker 的名称,用来区分多个 Worker 线程
属性:
- Worker.onerror:指定 error 事件的监听函数
- Worker.onmessage:指定 message 事件的监听函数,发送过来的数据在Event.data属性中。
- Worker.onmessageerror:指定 messageerror
事件的监听函数。发送的数据无法序列化成字符串时,会触发这个事件。
方法:
- Worker.postMessage():向 Worker 线程发送消息。
- Worker.terminate():立即终止 Worker 线程。
例如:
求斐波纳茨数列的第38项
没有使用worker情况:
<div style="width:100px;height:100px;background-color:red;"></div>
document.querySelector('div').onclick=function(){
console.log('hello world');
}
function fibonacci(n){
return n<2?n:arguments.callee(n-1)+arguments.callee(n-2);
}
console.log(fibonacci(38));
使用了worker的情况
<div style="width:100px;height:100px;background-color:red;"></div>
var worker=new Worker('worker.js');
worker.postMessage(40);
worker.onmessage=function(event){
var data=event.data;
console.log(data)
};
worker.onerror=function(event){
console.log(event.fileName,event.lineo,event.message);
};
<!--worker.js-->
self.onmessage = function (event) {
var data = event.data;
var ans = fibonacci(data);
this.postMessage(ans);
};
function fibonacci(n) {
return n < 2 ? n : arguments.callee(n - 1) + arguments.callee(n - 2);
}
前端面试中,回答不出的那些问题
前端面试官的套路,你懂吗?
前端面试常考的手写代码不是背出来的!
常规交谈
VUE3相关面试
1. 什么是Vue3? Vue3有哪些新增特性?
Vue3是Vue.js框架的最新版本,它增加了很多新特性,包括Composition API、Teleport、Suspense
和Fragment
等。
2. Vue3 Composition API是什么?它的作用是什么?
Vue3 Composition API是Vue3中的一个新特性,它的作用是将组件中的逻辑分解成可复用的可组合函数。通过使用Composition API复合式,可以更好地组织代码和管理状态。
组合式 API 最基本的优势是它使我们能够通过组合函数来实现更加简洁高效的逻辑复用。在选项式 API 中我们主要的逻辑复用机制是 mixins,而组合式 API 解决了 mixins 的所有缺陷
3. Vue3中的Teleport是什么?它的作用是什么?
答:Vue3中的Teleport传送门
是控制渲染位置的新指令。它的作用是在DOM中移动一个组件的内容而不改变组件的父级。
<template>
<button @click="flag = true">打开弹框</button>
<!-- 注意这里,把刚才在 index.html 加入的节点,放到这里 ↓↓↓ -->
<!-- 然后被teleport包裹的内容,就会传送到#modal节点上(也就是成功跑到了#app节点之外) -->
<!-- 虽然跑到了#app节点之外,但依然可以享有vue带来的功能 -->
<teleport to="#modal">
<div v-if="flag" class="modal">
<div>
<h1>Hello</h1>
<button @click="flag = false">
关闭
</button>
</div>
</div>
</teleport>
</template>
<script>
export default {
data() {
return {
flag: false
}
}
}
</script>
既能享受到 Vue 带来的功能,又不受父组件影响
4. Vue3中的Suspense是什么?它的作用是什么?
Vue3中的Suspense是Vue3中新增的一个组件,它的作用是实现延迟加载和错误处理。在组件中加入Suspense
,可以让异步组件可以渲染出加载状态,并且如果异步组件加载时出现错误,也能够处理这些错误。
5. Vue3中的Fragment是什么?它的作用是什么?
Vue3中的Fragment是用来承载多个子元素的虚拟组件。它的作用是可以解决在Vue2中,使用v-for迭代元素时需要添加一个包装元素的问题。
6. 什么是响应式系统? Vue3中的响应式系统有哪些更新?
响应式系统是Vue中的核心概念之一,它允许在状态发生变化时更新视图。Vue3中的响应式系统更新包括Proxy、Reflect
和WeakMap
等。
7. Vue3中的事件修饰符有哪些?
Vue3中的事件修饰符与Vue2基本相同,包括stop、prevent、capture和self等。
8. Vue3中的指令有哪些?
Vue3中的指令包括v-if、v-for、v-bind、v-on、v-html、v-model、v-show、v-slot、v-text等。
9. Vue3中如何实现动态组件?
Vue3中使用 <component>
元素和 v-bind:is 属性来实现动态组件。例如, <component v-bind:is="currentComponent"></component>
。
10. Vue3如何实现异步组件加载?
Vue3中使用 import()
来异步加载组件。
11. Vue3如何实现插槽?
Vue3中使用 <slot name="slot-name"></slot>
来实现插槽。在父组件中使用 <template v-slot:slot-name></template>
来填充插槽。
12. Vue3如何实现自定义指令?
Vue3使用 app.directive()
方法来注册指令,例如 app.directive('focus', {mounted(el) {el.focus()}})
。
13. Vue3如何实现混入?
Vue3使用 app.mixin()
方法来注册混入,例如 app.mixin({created() {console.log('mixin created')}})
。
14. Vue3如何实现自定义渲染函数?
Vue3使用 h()
函数来创建虚拟节点,例如 h('div', {class: 'container'}, 'Hello, world')
。
15. Vue3中的响应式系统如何处理循环引用问题?
Vue3中使用WeakMap
来处理循环引用问题。
16. Vue3如何实现全局状态管理?
Vue3中使用 provide()
和 inject()
函数来实现全局状态管理。
// 父级/祖先 组件
<script lang="ts" setup>
import { provide,computed } from 'vue'
const message = ref('hello')
provide('message',message)
</script>
// 后代组件接收参数
<script lang="ts" setup>
import { inject,Ref,ref } from 'vue'
const r = inject<Ref<string>>('message',ref('defaultMessage'))
// r.value
</script>
17. Vue3中的ref指令有哪些用途?
Vue3中的ref指令可以用来在组件内部获取子组件的实例,也可以用来获取DOM元素或其他组件的实例。
18. Vue3中的setup()函数有什么用途?
Vue3中的setup()
函数是用来替代Vue2中的data、methods和computed等选项的。它可以用来创建响应式数据和添加需要在模板中使用的方法。
<script setup>
语法糖中的代码会在每次组件实例被创建的时候执行,顶层的绑定会被暴露给模板,
当使用 <script setup>
的时候,任何在<script setup>
声明的顶层的绑定 (包括变量,函数声明,以及 import 引入的内容) 都能在模板中直接使用。
//使用组件
<script setup>
import MyComponent from './MyComponent.vue'
</script>
<template>
<MyComponent />
</template>
//动态组件
<script setup>
import Foo from './Foo.vue'
import Bar from './Bar.vue'
</script>
<template>
<component :is="Foo" />
<component :is="someCondition ? Foo : Bar" />
</template>
19. Vue3如何使用provide和inject实现依赖注入?
答:在父组件中使用 provide()
,并在子组件中使用 inject()
来注入依赖项。
20. Vue3如何实现异步验证表单输入?
使用 watch()
函数,监听表单输入的变化,并使用异步函数处理验证逻辑。
21. Vue3中如何使用路由?
Vue3中使用Vue Router
来实现路由。首先需要安装Vue Router,然后使用 createRouter()
函数创建路由对象,然后在根Vue实例中使用 app.use()
方法注册Vue Router。
22. Vue3中的provide注入的依赖项如何在子组件中更新?
通过给provide
注入的对象添加响应式属性来让子组件能够更新依赖项。
23. Vue3中如何使用axios发送HTTP请求?
在Vue3中使用axios发送HTTP请求,需要先安装axios,并在组件中导入axios。然后可以使用axios的get、post、put、delete等方法来发送HTTP请求。
24. Vue3如何使用vuex进行状态管理?
Vue3中使用Vuex
进行状态管理,需要先安装Vuex,并在根Vue实例中使用 app.use() 方法注册Vuex。然后在组件中使用 store 选项来创建和访问Vuex的状态。
25. Vue3中如何使用emit事件来与父组件通信?
在子组件中使用 this.$emit()
方法触发 emit
事件,并将需要传递的数据作为参数传递给父组件。
26. Vue3中如何使用slot来构建可复用组件?
在组件中使用 <slot>
元素来定义插槽,在父组件中使用 <template v-slot:slot-name>
来填充插槽。
27. Vue3中如何处理条件渲染?
使用 v-if 指令来实现条件渲染。
28. Vue3中如何处理列表渲染?
使用 v-for 指令来实现列表渲染。
29. Vue3中如何处理动态绑定属性?
使用 v-bind 指令来实现动态绑定属性。
30. Vue3中如何处理事件绑定?
使用 v-on 指令来实现事件绑定。
31. Vue3.0 里为什么要用 Proxy API 替代 defineProperty API?—— 响应式优化(高频,重点!!!)
defineProperty API 的局限性最大原因是它只能针对单例属性做监听
a、Vue2.x中的响应式实现正是基于defineProperty中的descriptor,对 data 中的属性做了遍历 + 递归,为每个属性设置了 getter、setter。
b、这也就是为什么 Vue 只能对 data 中预定义过的属性做出响应的原因,在Vue中使用下标的方式直接修改属性的值或者添加一个预先不存在的对象属性是无法做到setter监听的,这是defineProperty的局限性。
Proxy API的监听是针对一个对象的,那么对这个对象的所有操作会进入监听操作, 这就完全可以代理所有属性,将会带来很大的性能提升和更优的代码。
Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。
响应式是惰性的
a、在 Vue.js 2.x 中,对于一个深层属性嵌套的对象,要劫持它内部深层次的变化,就需要递归遍历这个对象,执行 Object.defineProperty 把每一层对象数据都变成响应式的,这无疑会有很大的性能消耗。
b、在 Vue.js 3.0 中,使用 Proxy API 并不能监听到对象内部深层次的属性变化,因此它的处理方式是在 getter 中去递归响应式,这样的好处是真正访问到的内部属性才会变成响应式,简单的可以说是按需实现响应式,减少性能消耗。
基础用法:
let datas={
num:0
}
let proxy = new Proxy(datas,{
get(target,property){
return target[property];
},
set(target,property,value){
target[property] += value;
}
})
32. Vue3.0 编译做了哪些优化?(底层,源码)
- 静态模板标记:Vue3.0引入了静态模板标记,通过对模板进行静态分析和标记,可以更好地优化渲染性能。在编译阶段,Vue会自动将响应式数据与静态内容分离,只针对动态部分生成响应式的渲染函数,从而提高了组件初始化的速度。
- 优化的Slot生成:Vue3.0对Slot的编译进行了优化,只有当Slot内容发生变化时才会重新生成对应的渲染函数,避免了无意义的重复计算。这样可以减少渲染的开销,提高了渲染性能。
- 静态属性提升:Vue3.0对静态属性进行了提升,将静态属性放到组件实例的原型上,在渲染函数中直接访问,避免了对组件实例的额外访问,提高了渲染性能。
- 缓存事件处理程序:Vue3.0通过缓存事件处理程序的方式,减少了事件处理函数的创建和销毁开销。在组件初始化时,Vue会为每个事件处理函数生成一个缓存的包装器,并复用该包装器来绑定和解绑事件。这样可以避免多次创建和销毁事件处理函数,提高了性能。
综上所述,Vue3.0在编译方面进行了多项优化,旨在提高渲染性能和组件初始化速度,使应用更加高效、流畅。