前端八股文
H5
一、 html
1. 行内元素。块级元素。空元素?
- 行内元素一行可以放很多个,(设置不了宽高)
- span、img、input、..
- 块级元素一行只能放一个
- h1、h2、div、ul、li、footer、header、p..
- 空元素
- br、hr、
2. 元素之间转换?
使用CSS的display属性
1
2
3display:inline // 转为行内
display:inline-block // 行内块
display:block // 块级元素
3. link 与 @import的区别
- 先有的link,后有@import
- link是标签,@import是一个Css文件中的语法,代表着引入另一个css
- @import只能写在css文件最上面
- 先加载link,后加载import
4. title与h1、b与strong、i与em的区别
-
title是:网站的标题,可以告诉搜索引擎这个网站有什么内容,对Seo而言title比h1更加重要
h1是显示在网页内容上的
b:单纯的给字加粗,实体标签
strong:可以加粗文字,并且强调字符。利于SEO。
i:单纯的给字倾斜
em:标签内字符重要,用以强调
strong比em更加强调
5. img标签的title、alt
- title:当鼠标移上去一段时间就会出来一段描述(所有标签都有该属性)
- alt:当图片没有加载成功,就会显示一段文字来代替图片
- 在Seo层面上,蜘蛛抓不到图片的内容,所以需要加入alt属性来描述这张图是什么内容
6. png、jpg、gif是什么
- png:无损压缩,体积要比jpg大,适合做小图标
- jpg:有损压缩,适合做大背景图
- gif:动图
- webp:支持有损或无损压缩。用于更小的体积,兼容性不太好
7. 什么是语义化标签
去掉样式后页面呈现清晰的结构
盲人使用读屏器更好地阅读
对SEO更友好
易读性和维护性更高
ie8不兼容html5标签。可以通过html5shiv.js 去处理
有哪些新增的语义化标签
- h5新增的:
nav、header、footer、section、button
- h5新增的:
8. ios上有300ms延迟,如何解决
- 可以直接禁用缩放
<meta name="viewport" content="width=device-width, user-scalable=no">
- 可以使用FastClick
- 原理是检测到touchend事件后立即发出模拟click事件,并把300ms延迟取消掉
9. 如何优化性能?
减少http请求次数
请求的结果用变量保存,没有新数据时用本地数据
用innerHTML代替dom操作
需要设置很多样式时,应该用className而不是Style
少用全局变量,因为不会被垃圾回收
避免使用css表达式
尽量使用transform来代替js动画
10. 如何使用语义化标签
- 在设置头部,导航,底部,都有对应的harder,nav,footer。
- 少使用div和span
- 不使用样式标签,例如b标签
- css类名也要语义化等
11. 优化网站
优化网站首先在打包之后要确保包的体积更小,这样首次加载起来就会更快。这就涉及到了优化图片,优化和压缩css和js。优化的话体现在代码的复用和合并,比如公共的css和js可以抽离出来。然后还可以减少http请求。资源的懒加载。
如何对网站的文件和资源进行优化?
文件合并
文件压缩
使用 CDN 托管
减少页面加载时间的方法
- 优化图片,减少体积
- 优化css、压缩合并css
- 减少http请求
- 资源懒加载
12. 判断环境时Pc还是移动端
判断
navigator.userAgent,对于 Android/iPhone 可以匹配以下正则1
2
3
4
5const appleIphone = /iPhone/i;
const appleIpod = /iPod/i;
const appleTablet = /iPad/i;
const androidPhone = /\bAndroid(?:.+)Mobile\b/i; // Match 'Android' AND 'Mobile'
const androidTablet = /Android/i;可以用一个库来判断:
ismobilejs1
2
3import isMobile from "ismobilejs";
const mobile = isMobile();
二、CSS
0. 100px的盒子实际显示的就是100个像素吗?
在我们不同的显示器,可能分辨率不同,实际看到的大小也不同。在低分辨率的屏幕上看到的图像会更大。回到这个问题上,一般而言,显示的像素大小是不会有误的。
但微软想到了,可能在一些大屏显示器上看到的图像非常小,不利于用户观察,于是在设置里加入了 屏幕缩放 功能,正常而言是 100% 的缩放,如果你设置为了 125% 那就会导致 100 * 100 像素的盒子实际显示出 125 * 125 像素大小,从而导致误差
1. css盒子模型
- 标准盒子模型:content-box;大小 = 内容 + 边框 + 内边距 宽度
- 怪异盒子模型:border-box;大小 = (定义的宽度):(内容 + 边框 + 内边距)
- 如何转换:
- box-sizing: content-box / border-box
2. line-height 与height区别
- line-height:行高,文字每一行的高度,例如br换行或添加换行样式
word-wrap: break-word; - height:设置行内块或块级元素的高度
3. css选择器
| 选择器 | 语法 | 意义 |
|---|---|---|
| 通配符选择器 | * | 选择所有元素 |
| 标签选择器 | 标签名 | 选择所有相关的标签 |
| 类选择器 | .class | 选择所有相关的class |
| id选择器 | #id | 选择一个相关的id |
| 分组选择器 | element, element | 选择被定义的所有元素 |
| 后代选择器 | element element | 选择该元素下的所有相关元素 |
| 子代选择器 | element > element | 选择该元素下的第一层相关元素 |
| 相邻选择器 | element + element | 选择该元素的下一个元素 |
| 通用兄弟选择器 | element ~ element | 选择该元素之后的所有元素 |
| 属性选择器 | element[attribute=value] | 选择包含该属性的所有元素 |
| 伪类选择器 | element :nth-child(n) | 选择该元素的第n个元素 |
4. css优先级如何计算
| 类别 | 权重 |
|---|---|
| * 或 继承 | 1 |
| .class | 10 |
| id | 100 |
| 行内 | 1000 |
| !important | 无穷 |
注意:十个class,并不会覆盖一个id
5. css画三角形
1 | /* 用边框做 */ |
6. 不给宽高实现水平垂直居中
1 | <div class="container"> |
7. display的值
| 值 | 作用 |
|---|---|
| none | 不显示 |
| block | 转为块级元素 |
| inline-block | 转为行内块元素 |
| inline | 转为行内元素 |
| table | 作为块级表格显示 |
8. BFC是什么
- 块级格式化上下文(block formatting context),与之对应的还有IFC
- BFC是一种规则,这个规则规定了很多东西,就像你开车一样,必须遵循交通道路法的规则一样。所有块级元素都要遵循bfc的规则,例如块级元素一行只能放置一个、当给块级元素赋值宽度200后,默认是靠左显示的、默认是从上往下进行排布的、当上下外边距都有的时候会应用最大值。这些都是bfc的规则在生效。与之对应的还有IFC,他会规定行内元素的布局方式
- 说简单点就是在块级盒子内的元素会按照一定规则来进行排布,这就是BFC
误区:
是bfc环境里规定着块级元素的排布方式,而不是块级元素里有bfc环境。
而开启bfc环境需要一些特殊的属性才能开启
不同bfc环境的东西不会互相干扰,就像js中不同函数作用域一样
如何触发BFC?
- 根元素:
html - 浮动元素:float的值非none
- 绝对定位:overflow的值非visible
- display:非行内的属性
- overflow:非visible
- position的值为:absolute、flex
- 根元素:
如何触发IFC?
- 在块级元素中使用
display: inline-block;属性 - 父元素中使用
display: inline-block;或者display: inline-table; - 父元素中使用
display: flex;或者display: inline-flex;
- 在块级元素中使用
9. 清除浮动的方式
浮动是如何产生的?
- 父元素未设置高度
- 子元素使用了float
使用BFC可清除,但需要满足两个条件
- 浮动元素的父元素触发BFC
- 浮动元素父元素的
height为auto - 为什么添加BFC后可以有浮动呢?
- 因为在BFC的规则中,浮动的元素的高度是被计算在内的,因此可以清除浮动。而定位元素的高度会被忽略,所以定位的元素不会计算在内
.在子元素最后再加一个标签,给这个标签添加 clear:both
给外部容器添加伪元素,给伪元素添加 clear:both
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15<style>
ul::after,ul::before {
content:"";
display:block;
clear:both;
}
li {
float:left;
}
</style>
<ul>
<li>1</li>
<li>2</li>
</ul>
10. 网页中使用奇数还是偶数的字体?
- 使用偶数
- 在布局上:移动端开发要将宽度除以2,偶数就很方便计算
- 在显示上:在奇数的文字可能文本段落无法对齐
11. position的值有哪些
| 值 | 作用 |
|---|---|
| static | 无(默认) |
| sticky | 粘性定位,屏幕高度低于top值时,是absolute的效果,高于top值时,是距离浏览器顶部top距离(fixed效果) |
| relative | 正常位置的定位,生成相对的定位元素(不脱离文档流) |
| absolute | 相对于static定位以外的第一个父元素进行定位(脱离文档流) |
| fixed | 根据浏览器窗口定位(脱离文档流) |
12. 双飞翼布局
左中右布局占满屏幕,左右200px,中间自适应,要求先加载中间
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39<style>
.container div {
float: left;
}
.left {
margin-left: -100%;
width: 200px;
background-color: pink;
}
.right {
margin-left: -200px;
width: 200px;
background-color: pink;
}
.center {
width: 100%;
background-color: skyblue;
}
.main {
margin: 0 200px;
text-align: center;
}
</style>
<body>
<div class="container">
<div class="center">
<div class="main">
中
</div>
</div>
<div class="left">左</div>
<div class="right">右</div>
</div>
</body>
13. reset.css与normalize.css是什么
normalize.css (opens new window): 会保留有用的样式,比如 h1 的字体大小
reset.css (opens new window): 把所有样式都重置,比如 h1、h2、h3 的字体大小都进行了重置,保持了无样式
14. 雪碧图 / 精灵图 / sprite 是什么
- 将多个小图标,放在一个大图片中
- 有什么好处?
- 只需要请求一次,就可以拿到很多图标
- 缺点?
- 如果图片被改动了,可能图标会错位(维护较差)
- 需要对图标进行测量
15. display: none 与 visibility: hidden的区别
- display: none 不占位置
- visibility: hidden 占用位置
- 为什么占用位置和不占用位置?
- 因为display: none会产生一次回流:会重新计算页面位置,因此隐藏不占位置
- 而visibility: hidden只会产生重绘:设置隐藏,但不改变位置。(可以理解为透明度0的盒子)
16. opacity 与 rgba(x,x,x,0) 的区别
- opacity是将整个元素透明度降低,子元素也会受影响,取值范围 0~1
- rgba(0,0,0,0) 是将某个属性的透明度降低 ,取值范围 0~1
opacity 与 visibility: hidden 的区别
- opacity设置为0后,进行点击操作是可以响应的,并且可以用transition过渡
- visibility: hidden 不能进行点击事件,不能用transition过渡
17. 浏览器解析HTML过程
从浏览器接收Url到开启网络请求线程
收到html数据后开始解析
- 解析html,创建Dom树;同时解析Css,创建css树
- 合并DOM树和CSS规则树,生成Render树
- 布局Render树(layout重绘 / reflow回流),负责各元素的尺寸,位置计算。
- 绘制render树(paint)
- 将各层的信息发给GPU。GPU会将各层合成(composite),显示在屏幕上
18. 回流与重绘
回流(重排)是什么
- 引起Dom树结构变化、元素尺寸、位置、布局需要重新构建就叫回流
- 浏览器窗口大小发生改变、字体大小变化、增加或移除样式表、内容变化、激活伪类、增加或删除可见DOM元素、计算offsetWidth与offsetHeight、设置style属性。页面首次渲染、元素尺寸或位置改变等…都会产生回流
重绘是什么
- 单纯的改变样式,例如颜色、背景颜色。不影响布局的就是重绘
产生回流一定会造成重绘,但是重绘不一定会回流
重绘与回流会造成什么影响
- 会重新计算,影响性能
- 回流比重绘的代价更高
如何避免回流与重绘
- 避免使用table布局
- 尽可能在DOM树的最末端改变class
- 避免设置多层内联样式
- 将动画效果应用到position属性为absolute或fixed的元素上
- 避免使用CSS表达式(例如:calc())
- 避免频繁操作样式
- 避免频繁操作DOM
- 可以先为元素设置display: none,操作结束后再把它显示出来。因为在display属性为none的元素上进行的DOM操作不会引发回流和重绘
19. 伪元素的单冒号和双冒号有什么区别
单冒号是伪类
- :hover / :action
双冒号是伪元素
- ::before, ::after
虽然也可以直接写:before,但尽量写双冒号,因为这是规范
20. ::before / ::after 有什么作用
::before
- 在元素之前
::after
- 在元素之后
作用
- 一般用于放置共同图标,或清除浮动
21. 如何关闭ios键盘首字母自动大写
- 给input表单加一个 autocapitalize=’off’ 属性即可
22. 怎么让浏览器支持小于12px的文字
- 直接设置font-size是不能小于12px的。
- 可以设置transform: scale(0.5),让元素缩放
24. rem与em的区别
rem是依照html根节点的字体大小进行改变的
1
2
3
4
5
6html {
font-size:20px;
}
box {
width:2rem; /* 40px */
}em是根据自身父元素的字体大小进行改变的
1
2
3
4
5
6Father {
font-size:20px;
}
Son {
width:2em; /* 40px */
}
25. ios 元素被触摸时产生的半透明遮罩怎么去掉
- 添加 -webkit-tap-hightlight-color:rgba(0,0,0,0) CSS属性
1 | <style> |
26. 输入框的placeholder的颜色能变吗
可以通过input::placeholder 选择修改
1
2
3input::placeholder {
color:red;
}
27. 禁止触发系统菜单和长按选择
禁止触发系统菜单
touch-callout: none
1
2
3
4html,body {
touch-callout: none;
-webkit-touch-callout: none;
}
禁止选中
user-select: none
1
2
3
4html,body {
user-select: none;
-webkit-user-select: none;
}
28. 自适应布局(适配)
什么是自适应布局:
- 就是宽度自适应,在不同大小设备下网页以等比例缩放,呈现同样的主体和布局
有哪些方案
可以通过 rem+媒体查询
使用 淘宝的flexible.js
- 原理:根据页面大小,分成数份,取一个值赋值给html节点的font-size
vw / vh单位
29. 响应式布局
什么是响应式布局:
- 就是根据屏幕大小变化,内容排版会自动改变,呈现最好的用户体验
- 优点:一个url响应多端
- 缺点:内容庞大时会卡顿
- 就是根据屏幕大小变化,内容排版会自动改变,呈现最好的用户体验
有哪些方案
bootstrap框架
媒体查询
1
2
3
4
5
6
7
8
9
10
11
12/*
媒体查询语法
only: 用于排除不支持媒体查询的浏览器
screen: 设备类型
and (): 可以设置max-width / min-width / max-height / min-height
*/
@media only screen and (max-width: 1000px){
/* 处于小于等于1000px时做什么 */
}
@media only screen and (max-width: 700px){
/* 处于小于等于700px时做什么 */
}对不同设备来显示不同图片
1
2
3
4
5<picture>
<source srcset="3.jpg" media='(min-width:1000px)'> /* 小于1000加载这 */
<source srcset="2.jpg" media='(min-width:700px)'> /* 小于700加载这 */
<img srcset="1.jpg"> /* 默认情况下加载 */
</picture>
布局方案
什么情况下使用响应式布局?
- 数据不是特别多,用户量不是特别大,纯展示类的项目适合响应式
pc布局+移动端布局
- 数据特别多时就需要两套界面了,例如淘宝、京东
30. 垂直塌陷
什么是垂直塌陷?
- 上下两个div,给上div一个下外边距,给下div一个上外边距,总会应用一个最大值,而不会相加
- 解决办法:
- 尽量给只给一个盒子添加margin值,避免就好
- 给其中一个div外层再套一层div,给外层div启动BFC
1
2
3
4
5
6
7
8
9
10
11<style>
.box1 {
margin-bottom:200px;
}
.box2 {
margin-top:300px;
}
</style>
<div class='box1'></div>
<div class='box2'></div>
31. 包含塌陷
什么是包含塌陷
- 父子两个div,给子div一个上外边距,这时父跟着子一起向下移动了
出现原因
- 因为子元素的
margin-top值把父元素顶部的边界顶下去了,导致看起来像是父元素的高度变高了 - 具体来说,子元素的
margin-top值实际上是添加在父元素和子元素之间的外边距上的,而外边距会有折叠的现象,即取两个相邻外边距中的较大值。因此,父元素的高度会自动增加,以包含子元素的margin-top值,导致出现这种情况。
- 因为子元素的
解决办法:
- 给父元素添加一个边框。边框也会创建一个新的块级格式化上下文,从而避免外边距的折叠
- 使用
padding-top替代margin-top。因为内边距不会参与折叠 - 给父盒子设置 overflow: hidden,创建一个新的块级格式化上下文(单独计算子元素的边距),从而避免外边距的折叠
- 给父元素添加一个定位属性。比如可以设置
position: relative或position: absolute。定位元素也会创建一个新的块级格式化上下文,从而避免外边距的折叠
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16<style>
.box1 {
width: 400px;
height: 400px;
background-color: red;
}
.box2 {
width: 200px;
height: 200px;
background-color: pink;
margin: 100px;
}
</style>
<div class="box1">
<div class="box2"></div>
</div>
32. css伪类选择器有哪些
| 选择器 | 作用 |
|---|---|
| :active | 点击 |
| :hover | 鼠标悬停 |
| :first-letter | 所有文字当中的第一个 |
| :first-child | 第一个子元素 |
| :nth-child() | 获取子元素 |
| :nth-of-type | 获取指定类型的子元素 |
| … | … |
33. 元素不可见的方式
- opacity:0
- visiblety:0
- position:absolute;top:-10000px;left:-10000px;
- display:none
- transform: scale(0)
34. 如何避免样式冲突
- 可以根据不同的组件,使用对应的名字,名字可以起的细一点。例如header-title,header-img
- 例如vue可以使用scoped,只在当前组件生效
- React css module。他会对类名哈希化
35. 画一个 100x100 的方框,其中文字可以正常换行,文字过多超过方框显示滚动条
- 主要使用 word-wrap: break-word 或 break-all 和 overflow: scroll 实现
36. CSS 如何设置一行超出显示省略号
1 | overflow: hidden; |
37. CSS 如何设置多行超出显示省略号
1 | overflow: hidden; |
38. flex 布局中 order 有何作用
order属性定义 Flex 布局中子元素的排列顺序,数值越小,排列越靠前,默认为 0。
39. flex 布局中 align-content 与 align-items 有何区别
align-content作用于纵轴多行元素,一行元素不起作用align-items作用于纵轴单行元素
40. CSS 如何实现固定长宽比的元素
可用于flex布局是,使用flex时宽度需要自适应时,可以用该属性长宽比固定
aspect-ratio1
2
3div {
aspect-ratio: 16/9; /*16比9*/
}
41. 行内元素padding的问题
行内元素无法设置上下内边距(padding)。如果要设置上下内边距,可以将元素的 display 属性设置为 inline-block 或 block
这是因为ifc导致的,ifc规定着所有行内元素的格式化上下文。其中就有一条规定行内元素不能设置上下内边距。还有一些其他特性
- 行内元素默认沿着基线对齐(baseline alignment)。
- 行内元素的宽度只能受到内容和内边距的影响,无法设置外边距。
- 行内元素不能设置上下内边距,但可以设置左右内边距(这一点在早期版本的 CSS 中是不被允许的)。
除了这些规定外,IFC 还会影响一些其他的布局特性,例如文本流的方向、文本换行、文本溢出等等。因此,了解 IFC 对于正确理解行内元素的布局和渲染非常重要。
三、JavaScript
一、script
1. script 标签有三种执行机制
| 执行 | 作用 |
|---|---|
| <script> | 依照书写顺序来执行、加载时DOM会暂停 |
| <script defer> | 和DOM一起加载、但延迟到整个页面都解析完毕后再运行 |
| <script async> | 和DOM一起加载、但执行时DOM暂停(谁先加载完谁执行) |
2. script 缓存机制
- 浏览器会根据特定的设置缓存所有外部链接的 JavaScript 文件,这意味着如果两个页面都 用到同一个文件,则该文件只需下载一次。这最终意味着页面加载更快
3. noscript
早期浏览器不支持
javascript需要降级处理,最终出现了<noscript>,用于给不支持 JavaScript 的浏览器提供替代内容1
2
3
4
5<body>
<noscript>
<p>您的浏览器不支持javascript</p>
</noscript>
</body>
4. 数据类型有哪些
基本数据类型:
Number、String、Boolean、null、undefined、symbol、bigint引用数据类型:
Object1
2
3
4
5
6// 考题
alert( true + 1 ) // 2
alert( 'name' + true ) // nametrue
alert( undefined + 1 ) // NaN
alert( typeof null ) // object
alert( typeof function(){} ) // function
5. null 与 undefined 区别
null是一个表示空的对象,转为数值是0;undefined是一个空的初始值、转为数值是NAN- 在设置对象的初始值时需要设置为
null。在变量定义未赋值情况下默认是undefined
6. == 与 === 区别
==
会判断两端值是否一样
判断规则:
- 如果任一操作数是布尔值,则将其转换为数值再比较是否相等。false 转换为 0,true 转换 为 1。
- 如果一个操作数是字符串,另一个操作数是数值,则尝试将字符串转换为数值,再比较是否相等。
- 如果一个操作数是对象,另一个操作数不是,则调用对象的 valueOf() 方法取得其原始值,如果valueof得到的还不是原始类型,则调用
toString方法转换后,再根据前面的规则进行比较。
1
2
3
4
5
6
7
8console.log( 1 == '1' ) // true
console.log( true == 1 ) // true
console.log( null == undefined ) // true
console.log( [1,2] == 1,2 ) // true
console.log([] == '') // true
console.log([] == false) // true
console.log(42 == true) // false, 除了1之外都是false
typeof document.all // undefined
===
除了比较值,还比较数据类型
1
console.log( undefined === null ) // false
7. 宏任务与微任务
因为js是单线程语言,它按照事件循环的机制来执行,它会先把同步任务全部执行完毕,例如if语句,for循环,全部执行完毕后,就会去寻找异步任务的代码,异步任务又分为宏任务和微任务,事件循环会先去执行微任务任务的代码,执行完毕后会去寻找宏任务的代码。执行完一个宏任务后又会去清空所有微任务,就这样循环下去
js为什么是单线程?- 因为操作 Dom 不能有两个线程同时操作,不然就不知道听哪个线程为准了
事件循环与事件队列是什么
事件循环
- 事件循环中包含宏任务、微任务
- 同步任务 -> 事件循环 [先执行所有的微任务再执行一个宏任务] ->清空微任务 -> 一个宏任务 -> 清空微任务 -> 一个宏任务…
同步任务全部执行完毕后、才执行事件循环里面的代码
事件队列
- 即执行事件循环的场所
宏任务与微任务
- 常见的宏任务
- 定时器、事件绑定、AJAX/跨域(HTTP请求)
- 常见的微任务
- queueMicrotask() 直接往微任务队列插入一条任务
- Promise相关—— .then(),catch,await、Generator中的 yield等。
- 注意:Promise本身以及resolve、reject方法并不是异步的。
1
2
3
4
5
6
7
8
9
10
11
12
13// 考题 ---> 几秒打印几?
for(var i = 0; i<3; i++){
setTimeOut(()=>{
console.log(i)
},1000*i)
}
// 与var相关,var没有块级作用域,i是全局变量
// 先执行事件队列的所有任务,即for循环全部执行完毕,i变成了3,同时创建了三个定时器,分别是
// 1000 * 0的、1000 * 1的、和 1000 * 2
// 因此每隔一秒打印一次 3
// 如果把var改为let 那么就是每隔一秒分别打印 0 1 2- 常见的宏任务
8. 作用域
什么是作用域?
可以理解为一个独立隔离的空间,例如函数作用域,在函数外部通常访问不到函数内部的数据,而函数内部可以访问到函数外部的数据的
什么是作用域
- 作用域理解成一个单独独立的空间,在空间内部可以访问外部的变量,而外部不能访问内部的
除了函数外,
js没有块级作用域作用域链:
函数内部可以访问函数外部的变量
函数外部不可访问函数内部的变量
函数内部寻找变量时,会一层一层往外寻找,直到window
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69// 注意点
function fx(){
var fn = 1;
function fn(){}
console.log( fn ) // 1
// 普通声明和函数名冲突时,永远会打印变量。如果函数是变量声明的,则依照书写顺序覆盖
}
// 考题1
function fx(b) {
var b = 0
console.log(b); // 0,优先使用本地的,当使用let或const时将报错
}
fx(100)
// 考题2
function fx() {
var a = b = 10;
// 相当于var a = 10; window.b = 10;
// var a = 10, b = 10 这样才是都声明
}
fx()
console.log(b) // 10
// 考题3
function fx(){
var b = 1;
function fn(){
console.log( b ); // undefined 因为本作用域下的b变量提升
var b = 2;
console.log( b ); // 2
}
fn()
console.log( b ) // 1
}
fx()
// 考题4
var name = 'a'
(function(){
if( typeof name == 'undefined'){
var name = 'b';
console.log( '111' + name ); // 111b
// 在该作用域下,已经有var声明的name变量,在判断之前是undefined,因此会进入第一个语句
// 如果是let声明的关键字,具有块级作用域,name是String类型,会进入else语句打印222a
} else {
console.log( '222' + name );
}
})()
// 考题5
console.log(a); // undefined
if (false) {
var a = 10; // 没有进入判断也会执行变量提升
}
console.log(a); // undefined
// 考题6
function outFn() {
this.num = 10;
function inFn(){
var num = 100;
alert(this.num); // 10
}
alert(num); // 10
return inFn;
}
outFn()(); // 两次都是在全局调用的,找的是window下的10
9. 对象考题
对象是通过new操作符构造出来的,每一个对象都是一个新的对象,所以对象之间不相等
对象的key都是字符串类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18[1,2,3] === [1,2,3] // false
// 考题1
var a = { a: 1 }
var b = { b: 1 }
a[b] = '123';
console.log(a)
// a:1, [object Object]: "123"
// 以[] 给对象添加对象时,会直接变成字符串 Object
// 考题2
var a = { }
var b = { key: 1 }
var c = { key: 2 }
a[b] = '123'
a[c] = '456'
console.log( a[b] ) // 456
// 以[] 给对象添加对象时,会直接变成字符串 Object, 因此后者覆盖前者就是456
10. 原型与原型链
什么是原型
- 在js中,对象是通过对应的构造函数来创建的,构造函数和对象实例之间的关系就叫原型
什么是原型链
- 原型链就是链接构造函数和对象实例之间的链条。
- 实例对象本身是没有任何属性和方法的,需要我们自己定义。而构造函数上有一些js提供的方法,我们可以通过原型链查找到构造函数上的方法进行使用,如果方法找不到就会去原型对象中查找,一直找到顶层的null为止。这就是原型链
每一个函数都拥有
prototype(显式原型)每一个对象都拥有
__proto__(隐式原型)原型链是什么?
- 将实例对象和原型对象的链接链条,可以通过原型链往上级查找
原型链解决了什么问题
- 对象共享属性和共享方法
原型链的查找规则
先在对象本身找 -> 对象.prototype -> Object -> Object.prototype -> null
.png)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63function Fun(){ };
let f = new Fun();
console.log( Fun.prototype === f.__proto__ ) // true
// 考题1, 考点:如果函数不调用,是不会执行函数内部代码的
function Foo(){
getName = function(){ alert(1) }
return this;
}
Foo.getName = function(){ alert(2) }
Foo.prototype.getName = function(){ alert(3) }
var getName = function(){ alert(4) }
function getName(){ alert(5) }
Foo.getName() // 2,因为函数内部的没有使用关键字声明,因此使用的是全局的。
getName() // 4,执行外部定义的
Foo().getName() // 1,函数执行后,全局的getName被重新赋值了1,往外找就是1
getName() // 1,执行刚刚被覆盖了的函数1
new Foo().getName() // 3,找原型上的3
// 考题2
var o = {
a:10,
b:{
fn:function(){
console.log( this.a ); // undefined
console.log( this ) // b
}
}
}
o.b.fn()
// 考题3
window.name = 'name'
function A(){
this.name = 123;
}
A.prototype.getA = function(){
console.log( this )
return this.name + 1
}
let a = new A()
let funcA = a.getA // 此处没有执行,因此是把函数赋值过去
console.log( funcA() ) // 这里是window调用的,因此是name+1 ;name1
// 考题4
var length = 10;
function fn(){
return this.length + 1;
}
var obj = {
length:5,
test1(){
return fn()
}
}
obj.test2 = fn; // 此处obj.test2 的this指向了obj
console.log( obj.test1() ) // 11
console.log( fn() === obj.test2() ) // 11 === 6,false
console.log( obj.test1() == obj.test2() ) // 11 === 6,false
11. 判断数组的方式
isArray:Array.isArray(arr)
1
2Array.isArray([]) // true
Array.isArray({}) // falseinstanceof:arr instanceof Array
- instanceof也会将对象判断为数组
1
2
3{} instanceof Array // false
{} instanceof Object // true
[] instanceof Object // trueprototype:Object.prototype.toString.call( arr ).indexOf(‘Array’) != -1
1
2Object.prototype.toString.call([]).indexOf('Array') != -1
// true,数组打印是[Object Array]isPrototypeOf:Array.prototype.isPrototypeOf( arr )
1
Array.prototype.isPrototypeOf( [] ) // true
constructor:arr.constructor.toString().indexOf(‘Array’) != -1
1
[].constructor.toString().indexOf('Array') != -1 // true
12. slice是干嘛的,splice是否改变原数组
slice(截取,包括第一项,不包括最后一项),返回的是新数组,不影响原数组
1
2
3
4var arr = [1, 2, 3, 4, 5]
console.log(arr.slice(1, 2)) // [2]
console.log(arr.slice(-2)) // [4, 5]
console.log(arr.slice(2)) // [3, 4, 5]splice(添加或删除数组中指定的元素),返回的是被删除的元素,会影响原数组
1
2
3
4
5
6
7
8
9
10
11var arr = [1, 2, 3, 4, 5]
arr.splice(2, 2) // n,n 删除元素
console.log(arr); // [1, 2, 5]
var arr = [1, 2, 3, 4, 5]
arr.splice(1, 0, '500') // n,0,xxx 添加元素
console.log(arr); // [1, '500', 2, 3, 4, 5]
var arr = [1, 2, 3, 4, 5]
arr.splice(1, 1, '500') // n,1,xxx 修改元素
console.log(arr); // [1, '500', 3, 4, 5]
13. 找出多维数组的最大值
1 | // 1 |
14. 输入一串字、输出时拼接指定内容
1 | String.prototype.addS = function () { |
15. 找出字符串出现最多的字符以及字数
1 | // 统计 |
16. new操作符做了什么
- 创建了一个空对象
- 将空对象的 隐式原型__proto__ 指向构造函数的 显式原型prototype
- 将空对象的this指向构造函数
- 对构造函数返回值进行判断(如果返回的是对象就返回对象,不进行new操作,基本数据继续new)
1 | // 模拟 |
17. 闭包
闭包是什么
闭包是一个函数返回了另一个函数,并且返回的这个函数使用了外部函数的变量
1
2
3
4
5
6
7function fx(){
var a = 10;
return function fn(){ // fn 就是一个闭包函数
console.log(a)
}
}
fx()() // 10
闭包解决了什么问题(闭包的作用)
- 外部可以访问内部的变量
- 让这些变量不被销毁,始终保存在内存中
闭包的使用场景
- 一个Ajax请求的成功回调
- 给多个绑定事件回调的方法
- 一个setTimeout的延迟回调
1
2
3
4
5
6
7
8
9var lis = document.querySelectorAll('li')
for (var i = 0; i < lis.length; i++) {
(function (i) {
// 函数内部使用了外部的 i,并且是回调函数
lis[i].onclick = function () {
alert(i)
}
})(i)
}闭包的缺点
- 变量会驻留在内存中,造成内存损耗
- 解决:把闭包函数设置为 null
- 内存泄漏(只存在于ie老版本)
- 变量会驻留在内存中,造成内存损耗
18. 原型继承方式
ES6继承方式:extends xxx
1
2
3
4
5
6
7
8
9
10
11
12class P {
constructor (){
this.age = 18
}
}
class S extends P { // 使用 extends P
constructor (){
super()
this.name = '砂糖'
}
}
let o1 = new S()原型继承:s.prototype = new S()
1
2
3
4
5
6
7
8function P(){
this.age = 20
}
function S(){
this.name = '砂糖'
}
s.prototype = new S() // 将原型执行另一个实例,就可以访问另一个原型链的方法了
let o1 = new S()call:P.call(this)
1
2
3
4
5
6
7
8function P(){
this.age = 20
}
function S(){
this.name = '砂糖'
P.call(this) // 改变P的this指向S
}
let o1 = new S()组合式
1
2
3
4
5
6
7
8
9function P(){
this.age = 20
}
function S(){
P.call(this) // 2.
this.name = '砂糖'
}
s.prototype = new S() // 1.
let o1 = new S()
19. call、apply、bind 的区别
都可以改变this指向,区别在于使用场景不同
.call()
- 会立即执行
- 多个参数需要挨个写
- 日常情况下,需要直接修改this,参数也是一个个时使用
.apply()
会立即执行
第二个参数是数组
使用场景:数据是数组的时候
1
2
3
4// 例如需要借用数据方法时
let arr = [1,2,3,4,5,999]
Math.max( arr ) // NaN, max方法需要一个个值
Math.max.apply( null, arr ) // 999
.bind()
不会立即执行,返回的是一个函数,需要再次调用
多个参数需要挨个写
使用场景:使用回调函数时
1
2
3
4
5
6
7
8
9
10
11// 使用回调函数时
let btn = document.querySelector('botton')
let div = document.querySelector('div')
btn.onclick = function(){
console.log( this ) // btn
}
btn.onclick = function(){
console.log( this ) // div
}.bind(div)
20. sort 原理
默认根据Unicode字符码点进行排序,可以传递一个函数
原理:使用了InsertionSort(插入排序) 与 QuickSort(快速排序)排序
数量小于10的数组用InsertionSort、大于10的数组使用QuickSort
1
2
3
4
5
6
7
8
9
10
11
12let arr = [1,2,3,222,4,5,111]
let arr1 = arr.sort(function(a,b){
return a - b; // a-b升序,b-a降序
})
console.log(arr1) // [1,2,3,4,5,111,222]
let arr = [{age:1},{age:20},{age:5}]
let arr1 = arr.sort(function(a,b){
return a.age - b.age;
})
console.log(arr1) // [{age:1},{age:5},{age:20}]
21. 深拷贝与浅拷贝
浅拷贝是将地址值赋值过去
1
2
3let obj1 = {a:1}
let obj2 = obj1 // 这就是浅拷贝
Object.assign() // 也是浅拷贝深拷贝是将每个值都复制过去
- 使用JSON.parse(JSON.stringify())有一些问题
- 拷贝obj中的时间对象 - 时间对象会变成字符串
- 拷贝obj中的正则对象 / Error对象 - 序列化后会变成空对象
- 拷贝obj中的函数 / undefined - 序列化后会丢失
- 拷贝obj中的NaN、Infinity和-Infinity - 序列化的结果会变成null
- 如果对象中存在循环引用的情况也无法正确实现深拷贝
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28// 1.
let obj1 = {a:1}
let obj2 = JSON.parse(JSON.stringify( obj1 ))
console.log(obj1 === obj2) // false
// 2.
// 传递两个对象,一个新对象,一个旧对象
function deepCopy(newObj, oldObj) {
for (var k in oldObj) {
// 判断值是否是数组
if (oldObj[k] instanceof Array) {
newObj[k] = [];
deepCopy(newObj[k], oldObj[k])
// 判断是否是函数, 因为函数输入对象,因此需要在对象之前进行判断,否则会被放入对象中
} else if (oldObj[k] instanceof Function) {
newObj[k] = oldObj[k];
// 判断值是否是对象
} else if (oldObj[k] instanceof Object) {
newObj[k] = {};
deepCopy(newObj[k], oldObj[k])
// 属于简单数据类型
} else {
newObj[k] = oldObj[k];
}
}- 使用JSON.parse(JSON.stringify())有一些问题
深拷贝函数:
structuredClone()https://developer.mozilla.org/zh-CN/docs/Web/API/structuredClone- 该函数可以克隆绝大部分数据、如reg正则对象、date对象
- 但不能复制构造函数与类,如Object、Class
22. localStorage、sessionStorage、cookie的区别
| 分类 | 有效期 |
|---|---|
| localStorage | 存入的数据只能手动销毁,5M左右 |
| sessionStorage | 存入的数据关闭浏览器就销毁,5M左右 |
| cookie | 可以设置一个过期时间,在过期时间之前都不会销毁,4kb左右 |
1 | // 本地存储 |
23. 把多维数组变为一维数组
1 | //1. 递归 |
24. 写出冒泡排序
冒泡排序
1
2
3
4
5
6
7
8
9
10
11
12
13function bubbleSort(data) {
var temp = 0;
for (var i = data.length; i > 0; i--) {
for (var j = 0; j < i - 1; j++) {
if (data[j] > data[j + 1]) {
temp = data[j];
data[j] = data[j + 1];
data[j + 1] = temp;
}
}
}
return data;
}
25. 什么是BOM,有哪些常用属性
location
属性 作用 举例 location.href 返回或设置当前文档的URL 例如 http://www.baidu.com?xxx=xx location.search 返回URL中的查询字符串部分 例如 ?id=5&name=dreamdu location.hash 返回URL#后面的内容 例如 /#/xxx=xxx location.host 返回URL中的主域名部分 例如 http://www.baidu.com location.hostname 返回URL中的主域名部分 例如 dreamdu.com location.pathname 返回URL的域名后的部分 例如 /abc/ location.port 返回URL中的端口部分 例如 8080 location.protocol 返回URL中的协议部分 例如 http location.assign 设置当前文档的URL location.replace() 设置当前文档的URL,并移除历史记录 location.reload() 重载当前页面 history
属性 作用 history.go() 前进或后退指定的页面数 history.back() 后退一页 history.forward() 前进一页 Navigator
属性 作用 navigator.userAgent 返回用户浏览器版本信息等 navigator.cookieEnabled 返回浏览器是否支持(启用)cookie
26. 如何使用biglnt类型
- 只需要在数值后面加一个n即可,例如:
123456n
27. 如何判断数组包含某个值
indexOf
1
console.log([1,2,3,4,5].indexOf(1)) // 找到了就返回索引,没有找到就返回-1
include
1
console.log([1,2,3].include(2)) // 找到返回true
28. 0.1 + 0.2 != 0.3是为什么
- 因为计算机是二进制的,逢二进一
- 在表示
1/2,4/1,8/1,16/1是没有问题的 - 但在表示0.1时,是一个无限循环小数,在计算的时候,计算机会对其截取一定位小数,因此丢失了精度
29. promise API
- promise.all
- promise.any
- promise.rece
- promise.resolve
30. 类数组,如何转为数组
一个简单的定义,如果一个对象有
length属性值,则它就是类数组那常见的类数组有哪些呢?
- 如
document.getElementsByTagName,document.querySelectorAll等等。除了 DOM API 中,常见的function中的arguments也是类数组
- 如
类数组转为数组
1
2
3
4Array(...all)
Array.from(arrayLike);
Array.apply(null, arrayLike);
Array.prototype.concat.apply([], arrayLike);
31. 事件冒泡与事件捕获
- 事件冒泡
- 从目标元素向window一层一层获取,例如给目标元素和window都设置了click事件,点击了目标元素后会触发目标元素,同时也会触发window
- 事件捕获
- 从window一层一层向目标元素获取,先执行window,同时触发目标元素
- 默认是事件冒泡,当addEventListener第三个参数为true时,就是事件捕获
32. input中监听值的变化的事件
keypress:按下和抬起都会触发
keyup:键盘抬起
keydown:键盘按下
input:值发生变化时
change:失去焦点
33. load 事件与 DomContentLoaded 事件的先后顺序
- 当初始的
HTML文档被完全加载和解析完成之后,DOMContentLoaded事件被触发,而无需等待样式表、图像和子框架的完全加载. - 当整个页面及所有依赖资源如样式表和图片都已完成加载时,将触发
load事件
34. DOM 中如何阻止事件默认行为,如何判断事件否可阻止?
e.preventDefault(): 取消事件e.cancelable: 事件是否可取消
35. 事件委托(委派)是什么
如果有很多标签都要监听,将事件监听器绑定在父元素进行监听,此时数百个事件监听器变为了一个监听器,提升了网页性能。
1
2
3
4
5var ul = document.querySelector('ul')
ul.addEventListener('click', function (e) {/*
alert('成功') */
e.target.style.backgroundColor = 'pink'
})
36. 获取浏览器所有元素
document.querySelectorAll('*')document.getElementsByTagName('*')$$('*'),可在浏览器控制台使用document.all,已废弃,不建议使用
37. 监听 localStorage 的变动
- window.onstorage = function(){}
38. 浏览器中监听事件函数 addEventListener 第三个参数有那些值
- capture。监听器会在时间捕获阶段传播到 event.target 时触发。
- passive。监听器不会调用 preventDefault()。
- once。监听器只会执行一次,执行后移除。
- singal。调用 abort()移除监听器。
39. 防抖与节流
防抖
触发高频事件后 n 秒内函数只会执行一次,如果 n 秒内高频事件再次被触发,则重新计算时间
- 登录、发短信等按钮避免用户点击太快,以致于发送了多次请求,需要防抖
- 调整浏览器窗口大小时,resize 次数过于频繁,造成计算过多,此时需要一次到位,就用到了防抖
- 文本编辑器实时保存,当无任何更改操作一秒后进行保存
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18// 防抖函数
function debounce(fn, wait) {
let timer;
return function () {
let _this = this; // 保存当前调用者的this
let args = arguments;
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(function () {
fn.apply(_this, args);
}, wait);
};
}
// 使用
window.onresize = debounce(function () {
console.log("resize");
}, 500);
节流
高频事件触发,但在 n 秒内只会执行一次,所以节流会稀释函数的执行频率
scroll事件,每隔一秒计算一次位置信息等- 浏览器播放事件,每个一秒计算一次进度信息等
- input 框实时搜索并发送请求展示下拉列表,每隔一秒发送一次请求 (也可做防抖)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30// 方式1: 使用时间戳
function throttle1(fn, wait) {
let time = 0;
return function () {
let _this = this;
let args = arguments;
let now = Date.now();
if (now - time > wait) {
fn.apply(_this, args);
time = now;
}
};
}
// 方式2: 使用定时器
function thorttle2(fn, wait) {
let timer;
return function () {
let _this = this;
let args = arguments;
if (!timer) {
timer = setTimeout(function () {
timer = null;
fn.apply(_this, args);
}, wait);
}
};
}
40. 数组去重
Map
1
2
3
4function unique(arr) {
if (!Array.isArray(arr)) throw new TypeError();
return [...new Set(arr)];
}filter
1
2
3
4
5
6
7
8var arr = [1, 1, 8, 8, 12, 12, 15, 15, 16, 16];
function unlink(arr) {
return arr.filter(function (item, index, arr) {
// 检测该元素是否存在
return arr.indexOf(item, 0) === index;
});
}
console.log(unlink(arr));
41. 进程与线程
- 进程是程序的一次执行过程,是一个动态概念,是操作系统资源分配的基本单位
- 线程是任务调度和执行的基本单位,它可与同属一个进程的其他的线程共享进程所拥有的全部资源
- 一个程序至少有一个进程,一个进程至少有一个线程
- 线程的划分尺度小于进程,使得多线程程序的并发性高
- 另外,进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率
- 线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制
- 从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别
42. IIFE 立即执行函数
是什么
- 声明一个函数,并马上调用这个匿名函数就叫做立即执行函数;也可以说立即执行函数是一种语法,让你的函数在定义以后立即执行
作用
- 不必为函数命名,避免了污染全局变量
- 立即执行函数内部形成了一个单独的作用域,可以封装一些外部无法读取的私有变量
- 封装变量
使用场景
1
2
3
4
5
6
7
8
9var list = document.getElementById("list");
var li = list.children;
for(var i = 0 ;i<li.length;i++){
(function(j){
li[j].onclick = function(){
alert(j);
})(i); //把实参i赋值给形参j
}
}
43. 函数柯里化
什么是函数柯里化
- 说简单点就是把多参数函数,转化为单参数函数
1
2
3
4
5
6
7
8
9
10
11
12
13// 柯里化之前
function add (x, y) {
return x + y
}
add(1, 2) // 3
// 柯里化之后
function getAdd (y) {
return function (x) {
return x + y
}
}
getAdd(2)(1) // 3函数柯里化应用
- 参数复用(固定易变因素)
- 延迟执行
- 提前返回
- 利用柯里化制定约束条件,管控触发机制
- 处理浏览器兼容(参数复用实现一次性判断)
- 函数节流防抖(延迟执行)
- ES5前bind方法的实现
44. 垃圾回收机制
什么是垃圾
- 没有被引用的对象或变量
- 无法访问到的对象(几个对象引用形成一个环,互相引用)
可达性
- 可达性是指那些以某种方式可以访问到或可以用到的值,它们被保证存储在内存中。
- 有一组基本的固有可达值,由于显而易见而无法删除:
- 本地函数的局部变量和参数
- 嵌套调用链上的其他函数的变量与参数
- 全局变量
垃圾回收机制
- JS的垃圾回收机制是为了以防内存泄漏,内存泄漏的含义就是当已经不需要某块内存时这块内存还存在着,没有被释放,导致该内存无法被使用,垃圾回收机制就是间歇的不定期的寻找到不再使用的变量,并释放掉它们所指向的内存
垃圾回收方式
标记清除(mark and sweep)
- 有变量进入执行环境的时候,就标记这个值进入环境,离开的时候,标记为离开环境
- 离开环境之后还存在的变量则是需要被删除的变量
引用计数(reference counting)
机制就是跟踪一个值的引用次数,当声明一个变量并将一个引用类型赋值给该变量时该值引用次数加1,当这个变量指向其他一个时该值的引用次数便减一
当该值引用次数为0时,则说明没有办法再访问这个值了,被视为准备回收的对象,每当过一段时间开始垃圾回收的时候,就把被引用数为0的变量回收
用计数方法可能导致循环引用,类似死锁,导致内存泄露,例如
1
2
3
4
5
6
7function problem() {
var objA = new Object();
var objB = new Object();
objA.someOtherObject = objB;
objB.anotherObject = objA;
}
45. setImmediate
执行时机:
- 会在同步任务执行完毕之后、微任务执行之前执行,也就是说会比
queueMicrotask执行得更快
1
2
3
4
5
6
7
8
9
10/*
setImmediate
- 参数
- 必选:回调函数,用于执行
- 可选:...[] 用于传递参数给回调函数
- 在当前的任务队列执行完成后,在 I/O 事件的回调之前执行
*/
setImmediate((a, b,c) => {
console.log(a, b, c) // 'name' 123 undefined
}, 'name', 123, undefined)- 会在同步任务执行完毕之后、微任务执行之前执行,也就是说会比
46. 标签函数
和普通函数类似,但是调用方式是以 `` 来进行调用的
1 | const arr = ['f', 'o', 'r', '(', ';', ';', ')', '{', '}'] |
47. promise终极面试题
1 | Promise.resolve().then(() => { |
问题一:执行顺序能详细说说吗
第一个promise的
resolve()执行后,立即调用了.then(),因此调用栈里会出现第一个微任务[p0],这个微任务里输出了0。然后这个then()返回了一个新的promise.resolve()接着第一个promise最后又调用了一次
.then(),此时微任务队列有两个任务[p0, pres]然后就是第二个promise,它也调用了
.then()方法,此时微任务队列有两个任务[p0, pres, p1]第一个promise和第二个promise的
Promise.resolve()会立即执行这是没疑问的然后第一个
promise.then会被执行,输出第一个promise的0和第二个promise的1。- 但第一个promise中
return返回的是一个新的立即完成的promise,这个时候promiseA+规范规定:如果返回的是一个新promise,那么需要再次调用该新promise.then方法,去实现外面的promise,在浏览器中这步操作仍然是微任务的。这步操作完成不会打印任何东西,只会往微任务里添加一个.then() - 此时任务队列里只有一个
promise.then()
- 但第一个promise中
接着执行第二个promise中的
() => console.log(2)- 此时任务队列里只有一个
promise.then(),() => console.log(2)
- 此时任务队列里只有一个
核心:当return的是一个新promise时,会自动调用这个新promise的
then()方法,去实现外面的promise。这一步任是微任务的,意味着会在任务队列中新增一个任务。因此当 0和1执行完成后,实际上会执行程序员写的then()并放入微队列中。接着执行2,然后完成前面的then(),但这个then返回的是一个新的promise,内部会进行then()一次,目的是为了实现Promise.resolve()的微任务。于是又被放入了微任务队列后。然后执行3,这时候才真正执行了4,最后输出5和6
问题二:将 return Promise.resolve(4) 换为 return 4 结果会发生变化吗?为什么
- 会,如果换为
return 4,将会作为结果直接返回给下一个then(),因此结果是0142356
48. JS引起内存泄漏的情况
内存泄露是什么
内存泄漏(Memory leak)是在计算机科学中,由于疏忽或错误造成程序未能释放已经不再使用的内存。导致一致占用计算机内存
意外的全局变量
1
2
3
4
5
6
7
8
9
10
11// 情况1.
function foo(arg) {
bar = "this is a hidden global variable";
}
// 情况2.
function foo() {
this.variable = "potential accidental global";
}
// foo 调用自己,this 指向了全局对象(window)
foo();定时器造成内存泄露
1
2
3
4
5
6
7
8var someResource = getData();
setInterval(function() {
var node = document.getElementById('Node');
if(node) {
// 处理 node 和 someResource
node.innerHTML = JSON.stringify(someResource));
}
}, 1000);闭包,维持函数内局部变量,使其得不到释放
1
2
3
4
5
6
7function bindEvent() {
var obj = document.createElement('XXX');
var unused = function () {
console.log(obj, '闭包内引用obj obj不会被释放');
};
obj = null; // 解决方法
}没有清理对DOM元素的引用同样造成内存泄露
1
2
3
4
5
6let refA = document.getElementById('refA');
document.body.removeChild(refA); // dom删除了
console.log(refA, 'refA'); // 但是还存在引用能console出整个div 没有被回收
refA = null;
console.log(refA, 'refA'); // 解除引用
49. 如何执行字符串代码?
有几种方法
eval 函数
1
const data = eval('for(;;)')
Function构造函数
1
const data = Function('for(;;)')()
Array构造函数
1
const data = Array(() => 'for(;;)')[0]()
Object构造函数
1
const data = Object(() => 'for(;;)')()
二、ES6
1. var、let、const 的区别
var 具有变量提升机制(变量悬挂声明)。而let、const没有
var可以重复声明同一个变量,后者覆盖前者。而let、const不能
var、let 声明的变量可以被修改,而const不行
var没有自身作用域。let、const有块级作用域
1
2
3
4
5
6
7
8
9
10
11
12
13// 考题1
function demo(){
var a = 1;
if(true){
var a = 2
}
console.log(a) // 2、因为var没有块级作用域、如果换成let则是1
}
// 考题二
const obj = {a:1}
obj.a = 2
consolt.log(a) // 2、const在基本数据类型是不可变的,在引用类型不修改内存地址情况下是可以修改obj内部的变量
2. 合并对象
通过 Object.assign 方法(有共同属性时,后者会覆盖前者)
1
2
3
4const a = {a:1,b:4}
const b = {b:2,c:3}
let obj1 = Object.assign(a,b)
console.log( obj1 ) // { a:1, b:2, c:3 }通过ES6 扩展运算符
1
2let obj2 = {...a, ...b}
console.log( obj2 ) // { a:1, b:2, c:3 }自己 封装函数
1
2
3
4
5
6
7
8function extend(target, source) {
for (const k in source) {
target[k] = source[k] // 把每一项赋值给第一个对象
}
return target
}
console.log( extend({ a: 1, b: 4 }, { b: 2, c:3 }) );
// { a:1, b:2, c:3 }
4. 箭头函数与普通函数的区别
- this 指向问题
- 普通函数:指向调用者,可修改
- 箭头函数:指向外层第一个普通函数的this,而且不可修改
- 箭头函数不能 new(不能当做构造函数)
- 箭头函数没有 prototype(原型)
- 箭头函数没有 arguments,如果需要arguments可以在箭头函数外部包一层普通函数
- 不可以使用
yield命令,因此箭头函数不能用作 Generator 函数。
5. promise问题
什么是promise
promise是ES6中新增的异步编程解决方案, 在代码中的表现是一个对象
开发中为了保存异步代码的执行顺序, 那么就会出现回调函数层层嵌套
如果回调函数嵌套的层数太多, 就会导致代码的阅读性, 可维护性大大降低
promise对象可以将异步操作以同步流程来表示, 避免了回调函数层层嵌套问题,避免了回调地狱问题
三种状态,一旦决定就不可改变
pending(进行中)reslove(已成功)rejected(已失败)
promise 解决了什么问题?
解决了回调地狱的问题,比如在请求里需要等待一个请求里面的参数,再通过这个参数发送请求,就会出现回调套娃的问题。通过promise就可以使用then方法来实现一步一步类似同步的写法,方便维护
1
2
3
4
5
6
7
8
9Promise.resolve('foo').then(res => {
console.log(res);
}).then(() => {
console.log(1);
}).then(() => {
console.log(2);
}).then(() => {
console.log(3);
}) // foo /1 /2 /3但一个promise里写了太多内容的话也会不好维护,于是出现了generator
6. generator
用于优化promise写法
它会等待上一个结果返回后再下一个
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15// 定义函数,在前边加*号就是generator函数. 使用yield关键字来定义事件
function* fx() {
yield 请求1;
yield 请求2;
},
// 使用函数
let res = fx()
console.log(res.next().value.then(res=>{console.log(res)})) // 结果1
console.log(res.next().value.then(res=>{console.log(res)})) // 结果2
// 也可以和async/await搭配使用
let a = this.fx();
console.log(await a.next().value); // 结果1
console.log(await a.next().value); // 结果2
7. async / await
该关键字会将等到的结果直接拿到。
被async定义的函数会默认返回一个Promise对象resolve的值,因此对async函数可以直接then
await 放置在Promise调用之前,await 强制后面的代码等待,直到Promise对象resolve,得到resolve的值作为await表达式的运算结果
await 修饰的如果是Promise对象:可以获取Promise中返回的内容(resolve或reject的参数),且取到值后 语句才会往下执行;如果不是Promise对象:把这个非promise的东西当做await表达式的结果。
1
2
3
4async function del() {
let { data } = await '请求'
// data中就是返回的数据了
},
8. find 与 filter的区别
filter(过滤)
返回的是数组,不会改变原数组。他返回所有符合条件的值
1
2
3
4
5let arr = [1,2,3,4,5,6]
let newArr = arr.filter(item=>{
return item > 2
})
console.log(newArr) // [3,4,5,6]
find(寻找)
返回查找符合条件的第一个结果,不会改变原数组。
1
2
3
4
5let arr = [1,2,3,4,5,6]
let newArr = arr.find(item=>{
return item > 2
})
console.log(newArr) // 3
9. some 与 every 的区别
some
只需要数组中一个值符合条件 就返回true
1
2
3
4
5let arr = [1,2,3,4,5,6]
let newArr = arr.some(item=>{
return item > 2
})
console.log(newArr) // true
every
需要数组中所有值都符合,才为true
1
2
3
4
5let arr = [3, 4, 5, 6]
let newArr = arr.every(item => {
return item > 2
})
console.log(newArr) // true
10. forEach的性能是否比for差
- 一般来说for会更快
11. forEach中可以用async / await吗
可以,给回调函数加上async,内部的代码是可以使用await的
1
2
3
4
5
6
7
8
9
10
11
12arr.forEach(async (item) => {
console.log(1);
const data = await new Promise((res, rej) => {
setTimeout(() => {
res(2)
}, 1000);
})
console.log(data);
await queueMicrotask(() => {
console.log(3);
})
})
12. ES6中新增了什么?
- let、const
- 箭头函数
- promise
- 解构赋值
- async / await
- proxy
- class
- 新增的API
14. 包装对象
- 什么是包装对象
- 指的是与数值、字符串、布尔值分别相对应的Number、String、Boolean三个原生对象。
- 这三个原生对象可以把原始类型的值变成(包装成)对象。
15. 把对象转为key,value的二维数组
使用:Object.entries(obj)
1
Object.entries({0:1,1:2}) // [[0,1],[1,2]]
也可以遍历循环push
1
2
3
4
5
6
7
8
9let o = {
a: 1,
b: 2
}
let arr = []
for (const k in o) {
arr.push([k, o[k]])
}
console.log(arr);
16. 解构赋值
ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构
如果有默认值的情况下,值是undefind默认值才有效,其余的都会覆盖默认值
undefined和null不可解构,会报错
数组的结构赋值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15let [a, b, c] = [1, 2, 3];
// a=1,b=2,c=3
let [ , , third] = ["foo", "bar", "baz"];
third // "baz"
let [x, y, ...z] = ['a'];
// x: "a" ,y: undefined ,z: []
// 默认值
let [x = 1] = [undefined];
x // 1
let [x = 1] = [null];
x // null对象的解构赋值
- 对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。真正被赋值的是后者,而不是前者
- 对象的解构赋值可以取到继承的属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15let { foo: baz } = { foo: 'aaa', bar: 'bbb' }
// baz: "aaa",foo:'报错'
上面代码中,foo是匹配的模式,baz才是变量。真正被赋值的是变量baz,而不是模式foo
const obj1 = {};
const obj2 = { foo: 'bar' };
Object.setPrototypeOf(obj1, obj2);
const { foo } = obj1;
foo // "bar"
var {x = 3} = {x: undefined};
x // 3
var {x = 3} = {x: null};
x // null
17. Set数据结构
它类似于数组,但是成员的值都是唯一的,没有重复的值。
Set本身是一个构造函数,用来生成 Set 数据结构。Set函数可以接受一个数组(或者具有 iterable 接口的其他数据结构)作为参数,用来初始化。1
2
3
4
5
6
7
8const s = new Set();
[2, 3, 5, 4, 5, 2, 2].forEach(x => s.add(x));
for (let i of s) {
console.log(i);
}
// 2 3 5 4
Set 实例的属性和方法
Set.prototype.constructor:构造函数,默认就是Set函数。Set.prototype.size:返回Set实例的成员总数。
Set 操作
Set.prototype.add(value):添加某个值,返回 Set 结构本身。Set.prototype.delete(value):删除某个值,返回一个布尔值,表示删除是否成功。Set.prototype.has(value):返回一个布尔值,表示该值是否为Set的成员。Set.prototype.clear():清除所有成员,没有返回值。
遍历Set
Set的遍历顺序就是插入顺序由于 Set 结构没有键名,只有键值(或者说键名和键值是同一个值),所以
keys方法和values方法的行为完全一致Set.prototype.keys():返回键名的遍历器Set.prototype.values():返回键值的遍历器Set.prototype.entries():返回键值对的遍历器Set.prototype.forEach():使用回调函数遍历每个成员
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22let set = new Set(['red', 'green', 'blue']);
for (let item of set.keys()) {
console.log(item);
}
// red
// green
// blue
for (let item of set.values()) {
console.log(item);
}
// red
// green
// blue
for (let item of set.entries()) {
console.log(item);
}
// ["red", "red"]
// ["green", "green"]
// ["blue", "blue"]
18. WeakSet
WeakSet 结构与 Set 类似,也是不重复的值的集合。但是,它与 Set 有两个区别。
首先,WeakSet 的成员只能是对象,而不能是其他类型的值。
其次,WeakSet 中的对象都是弱引用,即垃圾回收机制不考虑 WeakSet 对该对象的引用,也就是说,如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象还存在于 WeakSet 之中。
语法
WeakSet 是一个构造函数,可以使用
new命令,创建 WeakSet 数据结构。1
2
3
4
5const ws = new WeakSet();
const a = [[1, 2], [3, 4]];
const ws = new WeakSet(a);
// WeakSet {[1, 2], [3, 4]}三个方法:
- WeakSet.prototype.add(value):向 WeakSet 实例添加一个新成员。
- WeakSet.prototype.delete(value):清除 WeakSet 实例的指定成员。
- WeakSet.prototype.has(value):返回一个布尔值,表示某个值是否在 WeakSet 实例之中。
1
2
3
4
5
6
7
8
9
10
11
12const ws = new WeakSet();
const obj = {};
const foo = {};
ws.add(window);
ws.add(obj);
ws.has(window); // true
ws.has(foo); // false
ws.delete(window);
ws.has(window); // false
WeakSet 没有
size属性,没有办法遍历它的成员。WeakSet 不能遍历,是因为成员都是弱引用,随时可能消失,遍历机制无法保证成员的存在,很可能刚刚遍历结束,成员就取不到了。
- WeakSet 的一个用处,是储存 DOM 节点,而不用担心这些节点从文档移除时,会引发内存泄漏
19. Map数据结构
JavaScript 的对象(Object),本质上是键值对的集合(Hash 结构),但是传统上只能用字符串当作键。这给它的使用带来了很大的限制。
为了解决这个问题,ES6提供了Map结构,他类似对象,也是键值对的集合。但键不仅限字符串,各种类型的值(对象),也可以当做键
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22const m = new Map();
const o = {p: 'Hello World'};
m.set(o, 'content')
m.get(o) // "content"
m.has(o) // true
m.delete(o) // true
m.has(o) // false
// --------------------------
const map = new Map([
['name', '张三'],
['title', 'Author']
]);
map.size // 2
map.has('name') // true
map.get('name') // "张三"
map.has('title') // true
map.get('title') // "Author"如果对同一个键多次赋值,后面的值将覆盖前面的值
1
2
3
4
5
6
7const map = new Map();
map
.set(1, 'aaa')
.set(1, 'bbb');
map.get(1) // "bbb"如果读取一个未知的键,则返回
undefined注意,只有对同一个对象的引用,Map 结构才将其视为同一个键。这一点要非常小心。
1
2
3
4const map = new Map();
map.set(['a'], 555);
map.get(['a']) // undefined,不是同一个数组 内存地址不一样1
2
3
4
5
6
7
8
9
10
11
12
13
14
15let map = new Map();
map.set(-0, 123);
map.get(+0) // 123
map.set(true, 1);
map.set('true', 2);
map.get(true) // 1
map.set(undefined, 3);
map.set(null, 4);
map.get(undefined) // 3
map.set(NaN, 123);
map.get(NaN) // 123Map具有size属性,代表成员总数
操作
Map.prototype.set(key, value)
set方法设置键名key对应的键值为value,然后返回整个 Map 结构。如果key已经有值,则键值会被更新,否则就新生成该键
Map.prototype.get(key)
get方法读取key对应的键值,如果找不到key,返回undefined。
Map.prototype.has(key)
has方法返回一个布尔值,表示某个键是否在当前 Map 对象之中。
Map.prototype.delete(key)
delete方法删除某个键,返回true。如果删除失败,返回false。
Map.prototype.clear()
clear方法清除所有成员,没有返回值。
Map 结构原生提供三个遍历器生成函数和一个遍历方法。
Map.prototype.keys():返回键名的遍历器。Map.prototype.values():返回键值的遍历器。Map.prototype.entries():返回所有成员的遍历器。Map.prototype.forEach():遍历 Map 的所有成员。
需要特别注意的是,Map 的遍历顺序就是插入顺序。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36const map = new Map([
['F', 'no'],
['T', 'yes'],
]);
for (let key of map.keys()) {
console.log(key);
}
// "F"
// "T"
for (let value of map.values()) {
console.log(value);
}
// "no"
// "yes"
for (let item of map.entries()) {
console.log(item[0], item[1]);
}
// "F" "no"
// "T" "yes"
// 或者
for (let [key, value] of map.entries()) {
console.log(key, value);
}
// "F" "no"
// "T" "yes"
// 等同于使用map.entries()
for (let [key, value] of map) {
console.log(key, value);
}
// "F" "no"
// "T" "yes"
20. weakMap
WeakMap结构与Map结构类似,也是用于生成键值对的集合。WeakMap与Map的区别有两点。WeakMap只接受对象作为键名(null除外),不接受其他类型的值作为键名。WeakMap的键名所指向的对象,不计入垃圾回收机制
WeakMap的设计目的在于,有时我们想在某个对象上面存放一些数据,但是这会形成对于这个对象的引用。就不会被垃圾回收- WeakMap 就是为了解决这个问题而诞生的,它的键名所引用的对象都是弱引用,即垃圾回收机制不将该引用考虑在内。因此,只要所引用的对象的其他引用都被清除,垃圾回收机制就会释放该对象所占用的内存。也就是说,一旦不再需要,WeakMap 里面的键名对象和所对应的键值对会自动消失,不用手动删除引用
- weakMap语法
- WeakMap 与 Map 在 API 上的区别主要是两个,一是没有遍历操作(即没有
keys()、values()和entries()方法),也没有size属性。 WeakMap只有四个方法可用:get()、set()、has()、delete()
- WeakMap 与 Map 在 API 上的区别主要是两个,一是没有遍历操作(即没有
21. Proxy
Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写,意为代理拦截
1
2
3
4
5
6
7
8
9
10var proxy = new Proxy(target, handler);
// target参数表示所要拦截的目标对象,handler参数也是一个对象,用来定制拦截行为。
let obj = { a: 1, b: 2 };
var proxy = new Proxy(obj, {
get: function (target, propKey) {
return 35;
},
});
proxy.a // 35get:访问时执行
set:修改时执行
22. 创建一百个元素为0的数组
Array.fill()
1
Array(100).fill(0)
Array.from()
1
Array.from({length: 100}, () => 0);
23. forof 与 forin的区别
- for…in
- 循环对象的话出来的是key,数组的话 是数组的下标
- 还会遍历原型上的值
- for…of
- for…of不能循环普通的对象,需要通过和OBject.Keys()搭配使用 大多数用来遍历数组
24. ?? 运算符
?? 类似于 ||
- 区别:
- ?? 在判断0时,会返回0
- ?? 在判断false时,会返回false
1
2
3
4
5false || [] // []
0 || [] // []
false ?? [] // false
0 ?? [] // 0- 区别:
24. 创建一个从0-100的数组
1 | let x = 0; |
1 | Array.from(Array(100).keys(), n => n + 1); |
三、HTTP
1. ajax是什么
ajax(Asynchronous JavaScript and XML:异步的javascript和xml)
- 一种不用刷新整个页面便可与服务器通信的办法(通过ajax与服务器进行数据交换,使网页实现局部更新)
2. http响应的结构是怎么样的
状态码:
- 描述了响应的状态。可以用来检查是否成功的完成了请求。请求失败的情况下,状态码可用来找出失败的原因
HTTP头部
- 包含了更多关于响应的信息。比如:头部可以指定认为响应过期的过期日期,或者是指定用来给用户安全的传输实体内容的编码格式
主体Body
- 它包含了响应的内容。它可以包含HTML代码,图片,等等。主体是由传输在HTTP消息中紧跟在头部后面的数据字节组成的。
3. http与https区别
- https协议需要到ca申请证书,一般免费证书较少,因而需要一定费用。
- http是超文本传输协议,信息是明文传输,https则是具有安全性的ssl加密传输协议
- http和https使用的是完全不同的连接方式,用的端口也不一样,http是80,https是443。
- http的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全
4. get和post的区别
一般get主要是从服务器中获取数据,post主要是向服务器发送数据
get会把请求内容放在url中,而post放在 Message Body 中传送更安全
- 一般请求可以用get,而一些敏感信息,例如密码,需要用post
get因为请求参数放置在url上,因此长度会比post小。
- 不同浏览器所支持的长度不一致,比如ie是2083比特,谷歌最大是8182比特字符
get执行效率要比post高,因为get是直接发送数据。而post会先发送请求头和服务器验证,再发送真正数据
GET在浏览器回退时是无害的,而POST会再次提交请求。
5. http的优点
- http使用的是可靠的数据传输协议
- 底层是tcp协议,确保顺序内容的正确,确保没有丢包,因此能够保证传输过程不会损坏
- 简单快速
- 客户向服务器发送请求时,只需要输入请求方法与路径,常用的有
- Get、head、put、delete、post
- 客户向服务器发送请求时,只需要输入请求方法与路径,常用的有
- 灵活
- http允许传输任意类型的数据对象
- http协议的无状态性
- 是一种不保证状态,即无状态协议。http协议自身不对请求和响应之间的通讯状态进行保存,不对发送过的请求和响应做持久化处理
6. cookie 与 session
cookie
http是无状态的,是怎么分辨用户的呢?
每次http请求时,客户端都会在请求头中发送相应的cookie信息到服务端,只需要记录下cookie就知道你是谁了
如果客户端禁用cookie怎么办
- 一般会在url后面附加一个参数来识别用户
session
- 是另一种记录客户端状态的机制
- 不同的是Cookie保存在客户端浏览器中,而Session保存在服务器上。
- 客户端浏览器访问服务器的时候,服务器把客户端信息以某种形式记录在服务器上。这就是Session。客户端浏览器再次访问时只需要从该Session中查找该客户的状态就可以了,session是一种特殊的cookie。cookie是保存在客户端的,而session是保存在服务端的
- session原理
- 当客户端第一次请求服务器的时候,服务器生成一份session保存在服务端,将该数据(session)的id以cookie的形式传递给客户端;以后的每次请求,浏览器都会自动的携带cookie来访问服务器(session数据id)。
为什么要用session
- 由于cookie 是存在用户端,而且它本身存储的尺寸大小也有限,最关键是用户可以是可见的,并可以随意的修改,很不安全
区别
- cookie是保存在客户端的
- cookie有大小限制
- cookie可以设置过期时间
- session是保存在服务器端
- session会比较占用服务器性能,当访问增多时应用cookie
- session的过期时间依赖于服务器
- session更加安全
7. 常见状态码
- 200
- 200:请求成功
- 201:请求成功并且创建了资源
- 300
- 301:临时重定向
- 302:永久重定向
- 304:没有命中强缓存并走协商缓存时就把这些值跟资源文件的信息进行比对,如果资源没更改,返回304,浏览器读取本地缓存。 如果资源有更改,返回200,返回最新的资源。
- 400
- 401:请求要求身份验证
- 403:服务器拒绝请求
- 404:没有找到资源
- 500
- 服务器内部错误
1 | // 请求码可以由后端指定,一般遵循以下规范 |
8. 报文(Message)
- 报文由一行行简单的字符串组成
- 客户端发给服务器的叫请求报文(request)
- 服务器返回给客户端的叫响应报文
- 而HTTP协议就是对这个报文的格式进行规定
- 报文的组成
请求报文
打开浏览器开发者工具,发送请求后查看 请求标头,整个请求标头 就是一个请求报文
| 组成部分 | 作用 |
|---|---|
| 请求首行 | 第一行就是起始行,包含请求类型,url,协议版本。以及响应状态 |
| 请求头 | 以key:val保存了类型 |
| 空行(body) | 作为分割,将整体分为上下两部分(告知请求头结束) |
| 请求体 | 只有post请求才有请求体,通过请求体携带数据 |
1 | // 请求首行, 包含了请求方式, 请求地址, 协议版本 |
响应报文
格式与请求报文类似
1 | // 响应首行、 协议版本, 响应状态码, 响应的描述 |
9. 请求头常见的内容
| 请求类型 | 作用 |
|---|---|
| Accept | 浏览器可接受的MIME类型 |
| Accept-Charset | 浏览器可接受的字符集 |
| Accept-Encoding | 浏览器可接受的数据解码方式 |
| Connection | 是否需要持久链接 |
| Content-Length | 正文长度 |
| Cookie | |
| Host | 主机端口 |
| User-Agent | 浏览器类型 |
| 响应类型 | 作用 |
|---|---|
| Allow | 服务器支持哪些请求方法 |
| Content-Encoding | 文档的编码 |
| Content-Length | 内容长度 |
| Content-Type | MIME类型 |
| Date | 当前GMT时间 |
| Expires | 设置文档过期时间,从而不再缓存 |
| Lest-Modified | 文档最后改动时间 |
| Refresh | 表示多久后刷新文档,单位:秒 |
10. TCP模型概念是怎么样的?
| OSI七层网络模型 | TCP/Ip四层模型概念 | 对应网络协议 |
|---|---|---|
| 应用层(AppLication) | 应用层 | HTTP、TFTP、FTP、NFS、WAIS、SMTP |
| 表示层(Presentation) | TelInet、Rlogin、SNMP、Gopher | |
| 会话层(Session) | SMTP、DNS | |
| 传输层(Transport) | 传输层 | TCP、UDP |
| 网络层(NetWork) | 网络层 | IP、ICMP、ARP、RARP、AKP、UUCP |
| 数据链路层(DataLink) | 数据链路层 | FDDI、Ethernet、Arpanet、PDN、SLIP、PPP |
| 物理层(Physical) | IEEE 802.1A、IEEE 802.2到IEEE 802.11 |
11.一个完整的http请求会经历哪些步骤?
- 输入url后,将地址发给dns,域名解析(DNS服务),找到服务器ip与端口号
- 浏览器先查看浏览器缓存-系统缓存-路由器缓存,如果缓存中有,会直接在屏幕中显示页面内容。没有继续走下面
- 发起TCP三次握手,建立TCP链接
- 发起Http请求,发送Header、body等信息
- 服务端响应http,将资源封装成响应包返回,关闭链接。浏览器得到html代码
- 浏览器解析html代码,遇到图片,js,css等会再次发送请求,拿到数据,最终显示内容
- 浏览器渲染页面呈现给用户
11-1 三次握手是什么?
第一次:客户端发送消息给服务器,服务器接收到了
- 客户端给服务器端发送一个报文(SYN:申请建立链接)
- 这样服务端就能得出结论:客户端的发送能力、服务端的接收能力是正常的
第二次:服务器返回一个响应给客户端,说ok。我同意你的链接
- 服务器收到报文之后,会应答一个报文给客户端(SYN:申请和客户端链接,ACK:同意链接)
- 这样客户端就能得出结论:服务端的接收、发送能力,客户端的接收、发送能力是正常的。不过此时服务器并不能确认客户端的接收能力是否正常
第三次:客户端再发送请求,正文
- 客户端收到报文后再给服务器发送一个报文(ACK:同意链接)
- 这样服务端就能得出结论:客户端的接收、发送能力正常,服务器自己的发送、接收能力也正常。
因此,需要三次握手才能确认双方的接收与发送能力是否正常
11-2 四次挥手是什么?
https://blog.csdn.net/weixin_44865458/article/details/117234974
断开链接需要经历四次挥手,发起者可能是客户端,也有可能是服务端。以下以客户端发起为例
第一次:客服端发送一个消息给服务器,示意结束
- 会发送一个FIN报文,指定一个序列号
第二次:服务端返回一个消息给客户端,示意知道了。但不会立即断开,因为数据可能没有接收完毕
- 会返回一个ACK报文,把客户端的序列号值 +1 作为 ACK 报文的序列号值
第三次:当服务端接受完数据后,会再返回一个信息。示意数据接收完毕,可以断开连接
- 会再返回一个FIN报文,告诉客户端
第四次:客户端返回一个消息告诉服务器,示意我知道了
- 客户端收到 FIN 之后,发送一个 ACK 报文作为应答,断开链接
11-3 TCP/IP 协议族
TCP/IP 协议族中包含了一组协议
- 这组协议中规定了互联网中所有的通讯细节
网络通讯的过程由四层组成
- 应用层:软件所在的层,如浏览器,服务器
- 传输层:将一整个大数据拆分为一个个小的包。在此阶段还没有进行传输(TCP协议处于该位置)
- 网络层:网络层负责给每一个包添加信息(如地址等信息)(IP协议处于该位置)
- 数据链路层(物理层):负责传输数据
客户端与服务器是通过数据链路层链接的
发数据是从上往下执行的,收数据是从下往上执行的(解包的信息、合并包)
12. 什么是套接字(socket)
- 用于描述ip地址和端口,是一种通讯机制
- 套接字包含一个节点地址和一个端口号,用来标识这一服务
- 像websocket
- WebSocket(网络套接字)是一种用于在单个TCP连接上进行全双工通信的协议。它使用HTTP协议来建立连接,然后使用独立的通信协议传输数据。WebSocket通信协议为客户端和服务器之间的数据传输提供了一种双向通道,使得客户端无需轮询服务器,即可接收实时数据更新。
13. 代理是什么
- 代理位于客户端和服务器之间,接收所有客户端的HTTP请求,并将这些请求转发给服务器
- 代理的方式
- 正向代理:走一个中介,代理请求者的网络内部,访问外部网络
- 反向代理:请求到服务器,转发到内部其他服务器
14. 浏览器缓存
https://blog.csdn.net/qq_46658751/article/details/123433335
- 为什么要缓存
- 缓存可以减少网络 IO 消耗,提高访问速度。浏览器缓存是一种操作简单、效果显著的前端性能优化手段
- 缓存的机制
- Memory Cache 内存中的缓存
- 主要包含的是当前页面中已经抓取到的资源,例如页面上已经下载的样式、脚本、图片等
- Service Worker Cache 服务缓存
- Disk Cache 硬盘中的缓存
- Push Cache 推送缓存
- 当以上三种缓存都没有命中时,它才会被使用。
- Memory Cache 内存中的缓存
- 缓存它又分为强缓存和协商缓存。优先级较高的是强缓存,在命中强缓存失败的情况下,才会走协商缓存
- 在 Response Headers 中将过期时间写入 expires 字段,现在一般使用Cache-Control 两者同时出现使用Cache-Control
- 协商缓存,Last-Modified 是一个时间戳,如果我们启用了协商缓存,它会在首次请求时随着 Response Headers 返回:每次请求去判断这个时间戳是否发生变化。从而去决定你是304读取缓存还是给你返回最新的数据
强缓存
强制缓存就是向浏览器缓存查找请求结果,并根据该结果的缓存规则来决定是否使用该缓存结果的过程。
不会向服务器发送请求,直接从缓存中读取资源,在chrome控制台的Network选项中可以看到该请求返回200的状态码,并且Size显示from disk cache或from memory cache。
强制缓存的情况主要有三种
- 不存在该缓存结果和缓存标识,强制缓存失效
- 存在该缓存结果和缓存标识,但是结果已经失效,强制缓存失效,则使用协商缓存
- 存在该缓存结果和缓存标识,且该结果没有还没有失效,强制缓存生效,直接返回该结果
当浏览器向服务器发送请求的时候,服务器会将缓存规则放入HTTP响应报文的HTTP头中和请求结果一起返回给浏览器,控制强制缓存的字段分别是Expires和Cache-Control,其中Cache-Conctrol的优先级比Expires高
协商缓存
- 协商缓存就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程,主要有以下两种情况
- 协商缓存生效,返回304和Not Modified
- 协商缓存失效,返回200和请求结果
15. 跨域是什么,如何解决跨域
- 协议,域名,端口,三者有一不一样,就是跨域
- 案例:
www.baidu.com与zhidao.baidu.com是跨域
- 案例:
- 如何解决跨域
- CORS:在服务器设置响应头,允许跨域。
'Access-Control-Allow-Origin: '*' - 反向代理:使用nginx配置一个反向代理的地址,前端将请求发给这个地址即可
- JSONP:对于资源,用script标签请求,因为改标签不受同源政策限制
- jsonp原理
- 动态创建
script,使用script.src加载请求跨过跨域
- document.domain
- 该方式只能用于二级域名相同的情况下,比如 a.test.com 和 b.test.com 适用于该方式。
- 只需要给页面添加 document.domain = ‘test.com’ 表示二级域名都相同就可以实现跨域
- CORS:在服务器设置响应头,允许跨域。
基础类问题
项目开发流程
项目调研
首先是公司高层对项目进行分析,讨论为这个项目投入的时间,资金和资源
需求分析
- 需求文档
- 原型图设计
- UI设计
需求审评
然后由项目经理会对这个项目准备一个设计稿或者原型图,召集大家来对这个产品需求进行分析,没问题确认下来后就进入开发阶段
Q:在需求分析的时候,你作为一个前端 你应该去做些什么?
我们应该去考虑页面上某些功能的细节,比如设计稿上有一个input框,作为一个前端我需要考虑的就是input框的一些功能,例如最大能输入多少个字?有没有表单的验证功能?
开发阶段
分析完毕后,内容一旦确认下来,就会对原型图上的功能进行分解,一般情况下会把功能拆成一个个的任务
前端开发页面,后端设计数据库,设计接口。测试去准备一些测试用例。我们各干各的。
但前端对接口是有一些要求的,有时候就要去和后端沟通。
开发完毕后就需要进入测试阶段
Q:你觉得你的开发效率怎么样?
我觉得开发效率还不错,主要能够快速理解需求,和同事沟通,共同完成任务
测试
开发完毕后,就会会有一个提交送测的操作,把项目打包成 dist
我们的项目肯定是多人协同开发,会用git来提交代码,然后通过一些方式告诉项目经理和测试人员。我们是发送邮件来通知的。然后测试就会对我们的项目的功能进行验证。我们是有一个bug的列表网页,如果出现了问题,测试就会把出现的问题和相关人员列出来让我们去修改
而我们首先需要确认这个bug是哪里导致的,可能是后端的问题,还是需求的问题。如果是后端的问题,就要找测试,把bug传给后端进行解决
最后产品通过了测试,没问题后就会进行上线。
上线
- 上线申请
- 提交上线分支
- 最终上线到生产环境
迭代总结
Q:你们的上线流程(上线迭代周期)是怎样的
我们在完成一些页面时,可能就要提交到主干,然后会进行代码审查和测试。这就是一个迭代,我们一般会每2周发布一个版本,发布没问题就会进行回顾和评估,确定下一个版本的工作重点
HTML 基础相关
h5的新特性
| html5 | 备注 |
|---|---|
| 只有一种 DOCTYPE ⽂件类型声明(统 一标准) | |
| 增加了一些新的标签元素(功能, 语义化) | section, video, progress, nav, meter, time, aside, canvas, command, datalist, details, embed, figcaption, figure, footer, header, hgroup… |
| input 支持了几个新的类型值 | date, email, url 等等 |
| 新增了一些标签属性 | charset(⽤于 meta 标签);async(⽤于 script 标 签) |
| 新增的全域属性 | contenteditable, draggable… hidden… |
| 新增API | 本地存储, 地理定位, Canvas绘图, 拖拽API, 即时通信 WebSocket |
css3新增的特性
说出下方常用的即可, 无需全部背出
1、伪元素
2、弹性布局flex
3、媒体查询
4、圆角
5、渐变
6、阴影
7、平面转换
8、3D转换
9、动画
盒子/图片水平垂直居中
- 要使一个盒子水平和垂直居中,最简单的方法是使用 CSS 的 flex 布局。
首先,让父元素使用 display: flex 并设置 align-items: center 和 justify-content: center,这样父元素就会在水平和垂直方向上居中。然后,将子元素设置为盒子即可。
下面是一个示例:
1 | .parent { |
如果你希望盒子在垂直方向上居中,但在水平方向上靠左或靠右,则可以使用 justify-content: flex-start 或 justify-content: flex-end。
如果你想使用其他布局方式来实现居中,也可以使用 position: absolute 和 transform: translate(-50%, -50%)。
1 | .parent { |
还有其他一些方法可以实现居中,但上述方法是最常用的。
css盒模型
CSS盒模型是用于描述HTML元素的布局的一种方法。它用于在浏览器中渲染HTML元素的边框和内容。
每个HTML元素都是一个盒子,其中包含元素的内容,内边距(padding),边框和外边距(margin)。
在CSS中,你可以使用多种属性来控制盒模型的外观和布局,包括:
width和height属性用于控制元素的内容区域的大小。
padding属性用于控制元素的内边距。
border属性用于控制元素的边框。
margin属性用于控制元素的外边距。
你还可以使用box-sizing属性来控制元素的盒模型类型。默认情况下,HTML元素使用的是“标准盒模型”,在这种情况下,元素的宽度和高度仅指内容区域的宽度和高度。但是,你也可以将box-sizing属性设置为“边距盒模型”,在这种情况下,元素的宽度和高度将包括内边距和边框。
块级元素和行内元素
在HTML中,元素可以分为两种类型:块级元素和行内元素。
块级元素
块级元素在文档流中占据一整行。
块级元素可以设置宽度和高度。
块级元素可以设置内外边距和边框。
常见的块级元素包括:div,h1,p,form,header,footer,section等。
行内元素
行内元素在文档流中只占据所需的空间。
行内元素不能设置宽度和高度。
行内元素只能设置内边距。
常见的行内元素包括:a,span,button,input,label,select等。
注意:元素的类型并不是固定的,你可以使用CSS的display属性来改变元素的类型。例如,你可以将一个行内元素转换为块级元素,或者将一个块级元素转换为行内元素。
css选择器权重值
在CSS中,选择器的权重值(也称为优先级)是用于确定哪些样式规则应该应用到HTML元素的一种机制。
选择器的权重值由两部分组成:选择器的特殊性和样式定义的位置。
选择器的特殊性是根据选择器中使用的各种类型的选择器来计算的。每种类型的选择器都有一个固定的权重值,如下表所示:
| 选择器类型 | 权重值 |
|---|---|
| 通配符(*) | 0 |
| 元素选择器(例如p) | 1 |
| 类选择器(例如.class) | 10 |
| ID选择器(例如#id) | 100 |
| 属性选择器(例如[type]) | 10 |
| 伪类选择器(例如:hover) | 10 |
| 伪元素选择器(例如::before) | 10 |
为了计算选择器的特殊性,需要对选择器中使用的每种类型的选择器的权重值进行加权。
- 例如,如果选择器为
#id .class p,则其特殊性为(100 * 1) + (10 * 1) + (1 * 1) = 111。
样式定义的位置也会影响选择器的权重值。在所有其他因素相同的情况下,样式定义出现的位置越靠后,其权重值就越大。这意味着如果有多个样式规则适用于同一个HTML元素,那么最后出现的样式规则会覆盖先前出现的样式规则。
为了确定哪些样式规则应用到HTML元素,浏览器会比较所有适用于该元素的样式规则的权重值,并应用权重值最大的样式规则。
你可以使用 !important 来强制应用样式规则,即使其权重值低于其他样式规则。例如,如果你想要强制应用某个样式规则,你可以在样式值后面添加 !important,例如:
这将强制将所有段落的文本颜色设置为红色,即使有其他样式规则为段落设置了不同的颜色。注意,使用!important会使样式规则的权重值变得非常高,因此应该谨慎使用。
H5事件
- onblur失焦事件
- onfocus聚焦事件
- onchange改变事件
- onclick点击事件
- onerror错误事件
- oninput输入事件
- onkeydown键盘按下事件
- onkeyup键盘抬起事件
- onmousemove鼠标移动事件
- onmouseover鼠标进入事件
- onmouseout鼠标移出事件等
H5中input元素的type属性值
color颜色
password密码
text文本
CheckBox复选框
radio单选框
date日期
button按钮
submit提交按钮等
事件对象和事件委托
事件对象
一个函数或者方法都会带有一个事件对象参数
事件对象.target是获取最先触发的元素
事件对象有两种公共的方法:
.preventDefault() 阻止默认行为
.stopPropagation()阻止冒泡
事件委托:
可以把事件处理器添加到一个上级元素上,避免把事件处理器添加到多个子元素上,提高性能
动态添加的元素仍然可以触发该事件
主要依靠的就是事件冒泡,也就是当一个元素接收到事件的时候,会把他接收到的事件传给自己的父级,一直到window
优点:
- 无须监听每个元素的事件,减少事件处理函数。
- 元素增减,不会影响监听事件。
- JavaScript和DOM节点之间的关联减少,避免因循环引用引起的内存泄漏。
场景:
- 存在大量相同事件,子节点频繁添加或移除。
- 🔔可以举例React中的合成事件。
sessionStorage和localStorage的区别
sessionStorage 和 localStorage 是两种存储数据的方式,都可以用来保存在浏览器中的数据。但是,它们之间有一些区别
存储范围不同:
sessionStorage只在浏览器的当前会话中有效,当会话结束(浏览器关闭)时,数据就会被清除。而localStorage在浏览器中是持久的,除非用户手动清除数据或者浏览器清除缓存,否则数据不会丢失。存储容量不同:一般来说,
sessionStorage和localStorage的存储容量都是有限的。但是,localStorage的容量通常比sessionStorage大一些,具体数值取决于浏览器的具体实现。使用方法不同:
sessionStorage和localStorage的使用方法基本相同,都可以使用setItem() 和 getItem()方法来存储和获取数据。但是,在使用时,需要注意区分两者的作用域。
总的来说,sessionStorage 和 localStorage 都是用于存储浏览器中的数据的方式,但是它们在存储范围、容量和使用方法等方面有一些区别。在选择使用哪种存储方式时,应该根据自己的需要进行选择。
总结
localStorage: 页面关闭数据不丢失,具有永久性质。除非主动删除数据。sessionStorage:页面关闭,数据会自动清除
rem和em
rem 和 em 都是用于设置字体大小的单位,它们的区别在于计算方式不同。
rem(Root em)是相对于根元素的字体大小的单位。也就是说,如果你在根元素(一般是 html 元素)中设置了字体大小,那么所有使用 rem 单位的字体大小都会相对于根元素的字体大小进行计算。
em(em)是相对于父元素的字体大小的单位。也就是说,如果你在父元素中设置了字体大小,那么所有使用 em 单位的字体大小都会相对于父元素的字体大小进行计算。
例如,如果你在根元素中设置了字体大小为 16px,并在一个子元素中使用了 rem 单位设置字体大小为 2rem,那么子元素的字体大小就会是 32px(2 * 16px)。如果你在子元素中使用了 em 单位设置字体大小为 2em,那么子元素的字体大小就会是 32px(2 * 16px)。
一般来说,rem 单位更常用于设置整个网站的字体大小,因为它是相对于根元素计算的,可以方便地在整个网站中控制字体大小。而 em 单位更常用于设置单个元素的字体大小
响应式布局
响应式布局是指网站的布局能够根据用户设备的不同尺寸和分辨率进行自适应。这种布局方式可以让网站在不同的设备上都能够很好的呈现,包括电脑、平板电脑、手机等。
下面是一些常用的响应式布局方法:
- 使用流式布局(Fluid Layout):流式布局是指使用百分比来设置元素的宽度和高度,而不是固定的像素值。这种布局方式可以让元素在不同的设备上自动调整大小,适应屏幕尺寸的变化。
- 使用媒体查询(Media Queries):媒体查询是一种 CSS 技术,可以根据用户设备的屏幕尺寸和分辨率动态地应用不同的样式。这种方式可以让你为不同的设备设置不同的布局和样式。
- 使用自适应网格布局(Adaptive Grid Layout):自适应网格布局是指使用网格布局(Grid Layout)技术,在不同的屏幕尺寸下自动调整网格的列数和行数,使得布局看起来更合理。
- 使用弹性盒布局(Flexbox Layout):弹性盒布局是一种 CSS 技术,可以让元素在一个容器中自动地按照一定的规则进行布局。它可以让元素在不同的屏幕尺寸下自动调整大小,使得布局看起来更合理
有没有用过视频组件
是的,我曾使用过视频组件。视频组件是一种用于在网页或应用中播放视频的组件,通常使用 HTML5 中的 video 元素来实现。
在使用视频组件时,通常可以指定视频的源文件地址、视频的宽度和高度、是否自动播放等参数。视频组件还可以支持一些常用的控制功能,如播放、暂停、快进、倒退等。
例如,可以使用以下代码来创建一个视频组件:
1 | <video src="video.mp4" width="480" height="360" autoplay></video> |
这样,当网页加载时,就会在网页中自动播放 video.mp4 这个视频文件,但是各个浏览器会有所限制。据我所知,谷歌浏览器是禁用自动播放的。QQ浏览器没有禁用。
但还是有些网站也可以实现自动播放,比如B站。我之间了解过,是浏览器中维护了一个名单,这些名单里有常见的域名权重。当权重到达一定时,就不会禁用这些域名的自动播放。
vue中可以使用类似:vue-core-video-player 的组件
也可以回答,业务中没接触过此业务,但看过vue生态中有对应的组件库,如果需要可以很快学会。视频播放是前端的一个细分领域,有兴趣的了解下名称,开阔视野即可
为什么会跨域 ?
本质:跨域是浏览器基于同源策略的一种安全手段。
同源策略:
本质:是一种约定, 浏览器 的一种⽤于隔离潜在恶意⽂件的重要安全保护机制。
含义:即指在同一个域
三个相同点:
协议相同(protocol)
主机相同(host)
端口相同(port)
反之非同源请求,也就是协议、端口、主机其中一项不相同的时候,这时候就会产生跨域
不受同源策略影响的有哪些
除了以下三个资源获取类型的标签,在浏览器中,⼤部分内容都受同源策略限制。
url从输入地址到访问的过程
参考:https://blog.csdn.net/ky1in93/article/details/122380722
JS 基础相关
1、js数据类型
JavaScript有七种数据类型
字符串(String):用于表示文本数据。例如:”hello world”。
数字(Number):用于表示数值数据。例如:42。
布尔(Boolean):用于表示真假值。例如:true或false。
undefined:用于表示未定义的值。
null:用于表示空值。
对象(Object):用于表示各种类型的数据的集合。例如:{name: “John”, age: 30}。
数组(Array):用于表示一组有序的值的集合。例如:[1, 2, 3]。
JavaScript还有一种数据类型叫做函数(Function),它是一个可执行的代码块,可以被当作数据来使用。
你可以使用typeof运算符来检查一个变量的数据类型。
例如:
1 | let num = 42; |
注意,使用typeof运算符检查数组和null的数据类型时会返回”object”。因此,如果你想要确定一个变量是否是数组或null,你可以使用其他方法,例如使用Array.isArray()方法来检查一个变量是否是数组,或者使用双等号(==)进行比较来检查一个变量是否是null。
例如:
1 | let arr = [1, 2, 3]; |
此外,JavaScript还有一种特殊的数据类型叫做符号(Symbol),它用于创建唯一的值。你可以使用Symbol()函数来创建一个符号
例如:
1 | let sym = Symbol(); |
符号是JavaScript的一种新的数据类型,可以用来创建唯一的标识符,例如在对象的属性名称中使用。因为符号是唯一的,所以你可以使用它来防止属性名称冲突。
例如:
1 | let sym1 = Symbol(); |
注意,使用符号作为对象属性名称时,需要在属性名称前后添加方括号,这样才能正确地将属性名称解析为符号。
2、回流和重绘
回流(reflow)是指当浏览器渲染HTML页面时,对页面布局和几何的计算过程。当页面中的元素的大小、位置或布局方式改变时,浏览器就需要进行回流来调整布局。
重绘(repaint)是指当浏览器渲染HTML页面时,对页面中元素的外观进行更新的过程。当页面中元素的颜色、字体或其他外观属性改变时,浏览器就需要进行重绘来更新外观。
回流和重绘都是浏览器在渲染HTML页面时执行的必要过程,但是它们都有一定的开销,因此应该尽量避免不必要的回流和重绘。例如,你可以使用优化的CSS选择器和结构来减少回流的次数,或者使用缓存来避免重复的重绘。
注意,在JavaScript中有一些操作会触发回流和重绘
例如:
- 改变HTML元素的尺寸或位置,例如使用offsetWidth、offsetHeight、offsetLeft、offsetTop等属性。
- 改变HTML元素的布局方式,例如使用display、position、float等属性。
- 改变HTML元素的外观,例如使用color、background-color、font-size等属性。
你可以使用浏览器的开发工具来查看页面的回流和重绘次数,以及每次回流和重绘的原因。这有助于你发现并优化页面的性能问题。
此外,我们可以使用 requestAnimationFrame() 函数来让浏览器在下一次重绘之前执行特定的代码。这可以让你在更新页面元素的外观时,将多个操作合并成一次重绘,从而减少重绘的次数。例如:
1 | function updatePage() { |
注意,
requestAnimationFrame()函数是异步执行的,因此你不能依赖它来控制代码的执行顺序。
3、闭包
闭包是一种特殊的对象,它包含了一个函数和与该函数相关的引用环境。
闭包的函数可以访问闭包创建时的环境,即使在该函数被调用时,外部的环境已经发生了变化。这使得闭包很适合用来做回调函数或者保存状态。
举个例子,假设你有一个函数,它接受一个数字作为参数,并返回一个函数。该返回的函数可以对传入的数字进行累加,每次调用都会将累加器加1
1 | function createAdder(x) { |
在上面的例子中,createAdder 函数创建了一个闭包,该闭包包含了一个函数和与该函数相关的环境。这个环境包含了变量 x 的值。当我们调用 createAdder(5) 时,它返回了一个新函数,这个函数可以对传入的参数 y 进行累加。在这个函数中,变量 x 的值是 5,所以调用 add5(2) 和 add5(10) 时,都会将它们的参数与 5 相加。
闭包是一种非常有用的技术,它可以帮助你保存状态、创建封装的函数等
4、原型和原型链
在JavaScript中,每个对象都有一个原型(prototype),它是另一个对象。原型对象可以包含属性和方法,这些属性和方法可以被对象继承。这种继承机制被称为原型链(prototype chain)。
例如,你可以定义一个Person构造函数来表示人,并在Person.prototype中定义sayHello方法,表示人会说话:
1 | function Person(name) { |
在上面的例子中,john是Person类型的对象,它继承了Person.prototype中的sayHello方法。你可以使用instanceof运算符来检查一个对象是否是某个类型的实例
例如:
1 | console.log(john instanceof Person); // true |
注意,每个对象都有一个constructor属性,表示该对象的构造函数。你可以使用constructor属性来检查一个对象的类型,
例如:
1 | console.log(john.constructor === Person); // true |
此外,每个对象还有一个__proto__属性,表示该对象的原型。你可以使用__proto__属性来检查一个对象的原型对象,
例如:
1 | console.log(john.__proto__ === Person.prototype); // true |
5、this指向问题
在 JavaScript 中,this 关键字的指向可能有以下几种情况:
在全局作用域中,this 指向全局对象。在浏览器中,全局对象是 window,在 Node.js 中,全局对象是 global。
在函数中,默认情况下,this 指向全局对象。但是,如果将函数作为对象的方法调用,则 this 指向调用方法的对象。
在箭头函数中,this 的指向是定义时所在的作用域的 this。箭头函数没有自己的 this,因此它永远不会改变指向。
可以使用 call、apply 或 bind 方法显式地指定 this 的指向。
例如:
1 | const obj = { |
6、数组中forEach和map
forEach 和 map 是 JavaScript 中常用的数组方法,都可以用来遍历数组中的元素,但是它们之间有一些区别:
功能不同:forEach 方法是遍历数组的方法,它提供了一种快捷的方式来遍历数组的所有元素。而 map 方法是映射数组的方法,它可以根据指定的规则对数组的每个元素进行映射,并返回一个新的数组。
返回值不同:forEach 方法不会返回任何值,只会对数组的每个元素执行回调函数。而 map 方法会返回一个新的数组,该数组的元素是对原数组中每个元素执行回调函数之后的返回值。
处理流程不同:forEach 方法是按顺序依次对数组的每个元素执行回调函数。而 map 方法是同时对数组的每个元素执行回调函数,并将结果放入新的数组中。
总的来说,forEach 和 map 是两种常用的数组方法,但是它们的功能、返回值和处理流程都有所不同。根据自己的需要,可以选择适合自己的方法进行使用。
7、 call、bind、apply ( 函数上下文调用模式 )
JavaScript 提供了三种方法来改变函数的调用上下文:call、bind 和 apply。
call方法允许您将函数的调用上下文指定为某个对象,并传递一些参数给函数。这样可以在不同的上下文中调用同一函数。例如:1
2
3
4
5
6function greet(greeting) {
return `${greeting}, ${this.name}!`;
}
const person = { name: 'John' };
console.log(greet.call(person, 'Hello')); // "Hello, John!"
在上面的示例中,我们将函数 greet 的调用上下文指定为 person 对象,并传递了一个参数 ‘Hello’。这样,函数中的 this 将指向 person 对象,并返回 “Hello, John!”。
bind方法与call类似,但它并不立即调用函数,而是返回一个新函数,该函数的调用上下文已被指定为您提供的对象。例如:1
2const greetJohn = greet.bind(person);
console.log(greetJohn('Hello')); // "Hello, John!"
在上面的示例中,我们使用 bind 方法将函数 greet 的调用上下文指定为 person 对象,并返回了一个新函数 greetJohn。当我们调用 greetJohn 时,它的调用上下文已被指定为 person,并返回 “Hello, John!”。
apply方法与call类似,但它允许您使用数组来传递参数,而不是使用单独的参数列表
另外,还有几点需要注意:
在使用 call 或 apply 时,您必须立即调用函数,而 bind 方法则返回一个新函数。
在使用 bind 方法时,您可以通过在绑定时提供额外的参数来为新函数指定参数。例如:
1
2const greetJohnHello = greet.bind(person, 'Hello');
console.log(greetJohnHello()); // "Hello, John!"在这种情况下,新函数 greetJohnHello 将固定使用参数 ‘Hello’,无论您在调用时提供了什么参数。\
8、new操作符具体过程:
使用 new 操作符创建一个新对象的过程如下:
- 创建一个新对象。
- 将这个新对象的原型设置为构造函数的原型。
- 将这个新对象的 this 设置为这个新对象。
- 如果构造函数返回了对象,则返回这个对象;否则,返回这个新对象。
例如,下面是使用 new 操作符创建一个新对象的示例:
1 | function Person(name) { |
在这个例子中,我们定义了一个构造函数 Person,然后使用 new 操作符创建了一个新的 Person 对象,并将其赋值给变量 john。这个新对象继承了 Person.prototype 上的属性和方法,并且其 this 被设置为这个新对象。
注意,使用 new 操作符创建对象时,构造函数中的代码会在新对象上执行,并且新对象会继承构造函数的原型。因此,在上面的示例中,新对象 john 具有名为 name 的属性,并且可以调用 Person.prototype 上的 greet 方法。
在使用 new 操作符时,还有几点需要注意:
如果构造函数返回了对象,则返回这个对象,而不是这个新对象。这意味着,如果构造函数中的代码显式地返回了一个对象,则这个对象将作为最终结果返回,而新创建的对象将被忽略。例如:
1
2
3
4
5
6
7function Person(name) {
this.name = name;
return { greeting: 'Hello' };
}
const john = new Person('John');
console.log(john); // { greeting: 'Hello' }
在这个例子中,构造函数返回了一个对象,因此最终结果是这个对象,而不是新创建的对象。
如果构造函数返回了原始值(例如数字、字符串或布尔值),则忽略该值并返回新对象。例如:
1
2
3
4
5
6
7function Person(name) {
this.name = name;
return 42;
}
const john = new Person('John');
console.log(john); // Person { name: 'John' }在这个例子中,构造函数返回了数字 42,但最终结果仍然是新创建的对象。
如果构造函数没有返回值,则默认返回新对象。例如:
1
2
3
4
5
6function Person(name) {
this.name = name;
}
const john = new Person('John');
console.log(john); // Person { name: 'John' }在这个例子中,构造函数没有返回值,因此最终结果是新创建的对象。
总之,new 操作符创建一个新对象并使用构造函数初始化它。它还会将新对象的原型设置为构造函数的原型,并且在构造函数执行时将 this 设置为新对象。如果构造函数返回了对象,则返回这个对象;否则,返回新对象。
使用 new 操作符是 JavaScript 中常见的模式,可以使用它来创建具有共同属性和方法的对象,并使用构造函数的原型链来为这些对象提供共享的功能。
9、浅拷贝和深拷贝
深拷贝和浅拷贝是指在复制对象时,复制的方式不同。
浅拷贝只是复制对象的引用,并不会复制对象本身。如果对象是基本类型,则复制的是值本身;如果对象是引用类型,则复制的是指向对象内存地址的指针。因此,如果对象是引用类型,则拷贝后的对象和原对象指向的是同一块内存,任意一方的修改都会影响另一方。
深拷贝则是完全复制了对象本身,包括对象内部的所有属性和方法。深拷贝后的对象和原对象没有任何关联,对其中一方的修改不会影响另一方。
在 JavaScript 中,可以使用以下方法实现深拷贝:
使用 JSON 序列化和反序列化。这种方法适用于简单对象和数组,但不能复制函数、正则表达式、日期等对象。
1
2
3const deepCopy(obj) {
return JSON.parse(JSON.stringify(obj))
}使用递归算法。这种方法可以复制任意类型的对象
1
...
另外,JavaScript 中还有一种浅拷贝的方法:使用Object.assign()函数。这个函数可以把一个或多个源对象的所有可枚举属性复制到目标对象中。
1 | let obj = { a: 1, b: 2 } |
这种方法只能复制对象的属性,对于对象的方法和属性值为对象的属性,不会进行复制。
10、js事件循环
JavaScript 是一种单线程语言,这意味着只能执行一个任务。但是,JavaScript 引擎会在后台持续执行一些任务,例如响应用户的输入、下载网络资源等。这些任务是由事件循环管理的。
事件循环是一种机制,用于在执行主线程的同时处理异步任务。它按照以下步骤工作:
- 主线程执行同步任务。
- 当遇到异步任务时,将其排入队列中。
- 当主线程空闲时,从队列中取出第一个任务并执行。
- 当任务完成时,如果有回调函数,则将其排入队列中。
- 重复步骤 1-4,直到主线程的任务完成。
例如,下面是一个使用事件循环的示例:
1 | console.log('Start'); |
在这个例子中,事件循环会执行以下步骤:
执行 console.log(‘Start’)。
遇到 setTimeout 函数,将其排入队列中。
执行 console.log(‘End’)。
一秒钟后,从队列中取出第一个任务并执行 console.log(‘Timeout’)。
11、防抖和节流
防抖和节流是两种常见的控制函数执行频率的方法,常用于限制用户的输入频率或频繁触发的事件。
防抖(debouncing)是指在一定时间内只执行最后一次触发事件,在这个时间内如果有新的触发事件,会取消之前的触发事件并重新计时。防抖通常用于限制输入频率,比如在输入框中输入文本,或者在搜索框中输入关键字。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20function debounce(func, delay) {
let timeout;
return function() {
const context = this;
const args = arguments;
clearTimeout(timeout);
timeout = setTimeout(() => {
func.apply(context, args);
}, delay);
}
}
// 使用
const input = document.querySelector('input');
input.addEventListener('input', debounce(() => {
console.log('Debounced input event');
}, 500));节流(throttling)是指在一定时间内只执行一次事件,在这个时间内如果有新的触发事件,会忽略这个事件,等到这个时间结束后才会执行。节流通常用于限制事件触发的频率,比如在滚动条事件中执行实时网络请求,调整窗口大小事件等。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20function throttle(func, delay) {
let lastCall = 0;
return function() {
const context = this;
const args = arguments;
const currentTime = Date.now();
if (lastCall + delay < currentTime) {
lastCall = currentTime;
func.apply(context, args);
}
}
}
// 使用
const input = document.querySelector('input');
input.addEventListener('input', throttle(() => {
console.log('Throttled input event');
}, 500));
需要注意的是,防抖和节流的效果是有区别的。防抖会延迟执行函数,直到输入停止一段时间后才执行;而节流会保证函数在一定时间内只执行一次,如果在这段时间内有多次触发事件,只有第一次会被执行。根据实际需求来选择使用防抖还是节流。
12、ES6新增方法
ECMAScript 6(也称为 ECMAScript 2015)是 JavaScript 的下一代标准,提供了很多新的特性。下面是一些常见的新增特性
let 和 const 关键字:这两个关键字用于声明变量,其中 let 声明的变量可以被修改,而 const 声明的变量是常量,不能被修改。
箭头函数(Arrow functions):这是一种简化函数的语法,它比传统的函数定义更短、更简洁
1
const greet = name => `Hello, ${name}!`;
块级作用域:在 ES6 中,你可以使用 let 和 const 在代码块(如 for 循环或 if 语句)内声明变量。这意味着这些变量只在声明它们的代码块内可用,而不是整个函数内可用。
类(Classes):ES6 中引入了类的概念,使得创建和维护对象变得更加容易。你可以使用类来声明新的对象类型,并使用类的构造函数来创建新的对象实例。
模板字符串(Template literals):模板字符串是一种新的字符串表示方式,使用反引号(`)包围,允许模板字符串内插变量。例如,你可以使用如下的模板字符串来创建带有变量的字符串
1
const name = 'John'; console.log(`Hello, ${name}!`); // 输出 "Hello, John!"
解构赋值(Destructuring assignment):解构赋值是一种方便的方法,可以在一个表达式中提取数组或对象的多个值。例如,你可以使用解构赋值将对象的属性赋值给变量
1
2
3
4const user = { name: 'John', age: 30 };
const { name, age } = user;
console.log(name);
// 输出 "John" console.log(age); // 输出 30
这些只是 ES6 中的一些新增特性,还有许多其他的特性,例如 Promises、Iterators、Generators、Modules 等。
13、set和map的区别
JavaScript 中的 Set 和 Map 是两种常用的数据结构,它们都是用于存储数据的容器,但是它们有一些区别。
Set
Set 是一种无序的数据集合,其中的元素都是唯一的。Set 中的元素可以是任意类型的值。
Set 支持的操作包括添加、删除、查找和枚举,可以使用 for-of 循环来遍历 Set 中的所有元素。
Set 是一种新的数据结构,它在 ECMAScript 2015 中引入。
Map
Map 是一种无序的键值对集合,其中的元素都是以键值对的形式存储的。Map 中的键和值可以是任意类型的值。
Map 支持的操作包括添加、删除、查找和枚举,可以使用 for-of 循环来遍历 Map 中的所有元素。
Map 也是一种新的数据结构,它在 ECMAScript 2015 中引入。
在使用时,应根据实际需求来选择使用 Set 或 Map。
如果你需要存储一组唯一的数据,可以使用 Set;
如果你需要存储一组键值对数据,可以使用 Map。
以下是一个简单的 Set 的使用示例:
1 | const set = new Set([1, 2, 3, 4, 5]); |
以下是一个简单的 Map 的使用示例:
1 | const map = new Map([ |
14、点击穿透现象及解决办法
点击穿透是指在使用移动设备浏览网页时,当用户点击屏幕上的某一元素时,点击事件会同时触发下方的元素。这种现象通常发生在设计中使用了透明或半透明背景的元素上。
要解决这个问题,你可以采取以下几种方法:
使用不透明的背景:为触发点击穿透的元素使用不透明的背景,这样就不会出现点击穿透的现象了。
给元素添加边框:为触发点击穿透的元素添加边框,这样就可以使用边框来阻止点击事件穿透到下方的元素。
使用 JavaScript 来阻止点击穿透:可以使用 JavaScript 代码来阻止点击事件穿透到下方的元素。
- 你可以在元素上添加一个点击事件监听器,并在事件处理函数中调用
event.preventDefault()方法来阻止默认的点击行为。
- 你可以在元素上添加一个点击事件监听器,并在事件处理函数中调用
使用 CSS 属性
pointer-events: none:你可以使用这个 CSS 属性来禁用元素的点击事件。这样就可以阻止点击穿透的现象了。使用 CSS 伪类 :
active:你可以使用 :active 伪类来模拟点击效果,从而避免点击穿透的现象。你可以在元素的样式中使用 :active 伪类来设置点击时的样式,例如:1
2
3
4button:active {
background-color: blue;
}
/* 这样,当用户点击按钮时,按钮的背景颜色会变成蓝色。这种方法可以在不使用 JavaScript 的情况下解决点击穿透的问题。*/
总之,点击穿透是一个常见的问题,但是有很多方法可以解决这个问题。你可以根据自己的需求来选择最合适的方法来解决点击穿透的问题。
15、js的继承
原型链继承:让新实例的原型等于父实例
可继承:实例的构造函数的属性,父类构造函数属性,以及父类原型的属性
不可继承:父类实例的属性
借用构造函数继承:使用 apply()和 call()方法将父类构造函数引入子类函数
可继承:父类构造函数的属性
不可继承:父类原型的属性
组合继承:将原型链和借用构造函数的技术组合在一块,从而发挥两者之长的一种继承模式
原型式继承:借助原型可以基于已有的对象创建新对象,类似复制了一个对象
寄生式继承:就是给原型式继承外面套个壳子,没用到原型
寄生组合式继承:通过借用函数来继承属性,通过原型链的混成形式来继承方法(常用)
16、如何理解promise
Promise 是一种用于异步编程的解决方案,它可以让你以同步的方式编写异步代码。
在 JavaScript 中,很多操作都是异步的,例如网络请求、读取文件或等待用户输入。这些操作会在后台运行,因此你不能直接等待它们完成,而是需要提供一个回调函数,在操作完成时调用。
Promise 可以解决这种回调地狱的问题,它使用一种称为链式调用的方法,让你能够在 then 方法中连续调用多个异步操作。
例如,你可以使用 Promise 发起一个网络请求,然后在请求完成后调用回调函数。你可以使用 then 方法来处理请求结果,如下所示:
1 | axios.get('http://example.com/movies.json') |
在这个例子中,axios.get 方法会发起一个网络请求,然后返回一个 Promise。你可以在 then 方法中提供一个回调函数来处理响应数据,然后再次使用 then 方法来处理解析后的数据。如果在任何阶段发生错误,你可以使用 catch 方法来处理错误。
这种方法使得异步编程变得更加简单,因为你不再需要写很多嵌套的回调函数。你可以使用 then 方法链式调用多个异步操作,而不用担心回调地狱的问题。
Promise 还有一些其他的特性,例如你可以使用 Promise.all 方法来并行执行多个 Promise,或者使用 Promise.race 方法来执行多个 Promise 中最先完成的那个。你还可以使用 Promise.resolve 和 Promise.reject 方法来创建已完成或已拒绝的 Promise。
总之,Promise 是一种有用的工具,可以让你在异步编程中更加简单、灵活地处理异步操作。
17、 async/await的理解
async/await 是 JavaScript 中的一种语法,用于处理异步任务。
async 关键字用于修饰函数,表示该函数是一个异步函数。异步函数返回一个 Promise 对象,表示异步操作的结果。
await 关键字用于在异步函数内部等待一个 Promise 对象的完成。当使用 await 关键字时,JavaScript 会暂停执行异步函数,等待 Promise 对象完成,然后再继续执行异步函数。
例如,我们可以使用 async/await 来等待一个异步函数的执行结果:
1 | async function getData() { |
在这个例子中,getData 函数是一个异步函数,它会发送一个 HTTP 请求获取数据,然后使用 await 关键字等待 HTTP 请求的结果。
async/await 语法的优点在于可以让异步代码看起来像同步代码一样,更容易阅读和维护。你可以在 await 后面跟任何返回 Promise 对象的函数或表达式,例如 fetch 或者自定义的异步函数。
需要注意的是,async/await 语法只能在 async 函数内部使用,如果在其他地方使用会导致语法错误。另外,使用 await 关键字时,必须确保在 await 后面跟的是一个返回 Promise 对象的函数或表达式,否则会抛出错误。
使用 async/await 语法时,你还可以使用 try/catch 语句来捕获异步函数中可能出现的错误。例如:
1
2
3
4
5
6
7
8
9async function getData() {
try {
const result = await fetch('https://example.com/data');
const data = await result.json();
return data;
} catch (error) {
console.error(error);
}
}在这个例子中,如果在发送 HTTP 请求或者解析响应数据时出现错误,就会执行 catch 块中的代码来处理错误。
总之,async/await 语法是一种方便的方法来处理异步任务,可以让异步代码看起来更像同步代码,使得代码更容易阅读和维护。
18、作用域
在 JavaScript 中,作用域是指程序中定义变量和函数的区域。一般来说,变量和函数在它们被声明的地方可用,并且其他地方不可用。
JavaScript 中有两种类型的作用域:全局作用域和局部作用域。
全局作用域:全局作用域是整个程序的范围。在全局作用域中声明的变量和函数可在整个程序的任何地方使用。如果在函数内部声明的变量没有使用 var 关键字,则该变量也是全局变量。
局部作用域:局部作用域是在函数内部定义的作用域。在局部作用域中声明的变量和函数仅在函数内部可用,在函数外部不可用。使用 var 关键字声明的变量是局部变量,如果没有使用 var 关键字,则该变量是全局变量。
19、跨域怎么处理 ?
跨域是指浏览器访问的网站与请求资源所在的服务器之间存在跨域的情况。在跨域的情况下,浏览器会拒绝访问资源,以保证安全。
对于跨域的情况,通常有以下几种解决方案:
使用代理服务器:可以在本地搭建一个代理服务器,通过代理服务器转发请求,从而解决跨域问题。
使用 JSONP:JSONP 是一种跨域数据传输方式,可以通过动态插入 script 标签的方式实现跨域访问,JSONP是比较老的一种方案,只支持get请求,不支持post请求
使用 CORS:CORS 是一种跨域资源共享的方式,可以通过在服务器端配置 HTTP 头信息,让浏览器允许跨域访问。
使用 WebSocket:WebSocket 是一种基于 TCP 的协议,可以实现双向通信,不存在跨域的限制。
根据具体情况,可以选择适合自己的跨域解决方案。
20、forEach与map的区别
forEach 和 map 是 JavaScript 中常用的数组方法,都可以用来遍历数组中的元素,但是它们之间有一些区别:
功能不同:forEach 方法是遍历数组的方法,它提供了一种快捷的方式来遍历数组的所有元素。而 map 方法是映射数组的方法,它可以根据指定的规则对数组的每个元素进行映射,并返回一个新的数组。
返回值不同:forEach 方法不会返回任何值,只会对数组的每个元素执行回调函数。而 map 方法会返回一个新的数组,该数组的元素是对原数组中每个元素执行回调函数之后的返回值。
处理流程不同:forEach 方法是按顺序依次对数组的每个元素执行回调函数。而 map 方法是同时对数组的每个元素执行回调函数,并将结果放入新的数组中。
总的来说,forEach 和 map 是两种常用的数组方法,但是它们的功能、返回值和处理流程都有所不同。根据自己的需要,可以选择适合自己的方法进行使用。
21、宏任务和微任务
- Js 中,有两类任务队列:宏任务队列(macro tasks)和微任务队列(microtasks)。宏任务队列可以有多个,微任务队列只有一个
宏任务:(task)
- setTimeout, setInterval
微任务:(jobs)
- Promise
22、ES6中var let const的区别
JavaScript 中有三种声明变量的方法:var、let 和 const。它们有如下区别:
- var:这是 JavaScript 中最常用的声明变量的方法,可以声明全局变量或函数内的局部变量。var 声明的变量可以被修改,并且存在变量提升的问题。
- let:let 是 ES6 中新增的声明变量的方法,它的作用和 var 类似,但是 let 声明的变量只在声明它的代码块内有效,并且不存在变量提升的问题。
- const:const 也是 ES6 中新增的声明变量的方法,它声明的是常量,常量的值不能被修改。和 let 一样,const 声明的变量只在声明它的代码块内有效,并且不存在变量提升的问题。
总之,你应该尽量使用 let 和 const 来声明变量,因为它们具有更好的作用域控制和不存在变量提升的问题。只有在必要的时候才使用 var。
例如,你应该使用如下的方式声明变量:
1 | // 声明局部变量 let count = 0; |
22、JS的数据类型,储存上的差距
JavaScript 中有几种不同的数据类型,这些数据类型在储存上的差距是有区别的。
原始数据类型:原始数据类型包括布尔值(boolean)、数字(number)、字符串(string)和空值(null)、未定义(undefined)五种。这些数据类型在储存上占用的空间很小,通常只需要几个字节。
对象类型:对象类型包括数组(array)、函数(function)、对象(object)等。这些数据类型在储存上占用的空间相对较大,因为它们可能包含多个属性或方法。
总之,不同的数据类型在储存上的差距是有区别的,你应该根据使用场景来选择合适的数据类型。
23、构造函数在被实例化的时候内部发生了什么
在内存中新建一个空对象;
this指向这个内存中的空对象;
根据定义的键值和传入的参数,依次给这个空对象添加上键值对;
在构造函数语句末尾添加return this,也就是把这个指向内存中刚刚创建的新对象的指针return出去,传址赋值给变量。
24、for of 和 for in的区别
for…of 和 for…in 是 JavaScript 中的两种循环语句。
for…of 循环遍历可迭代对象(例如数组)的值。
for…in 循环遍历对象的属性。
在使用 for…of 和 for…in 循环时,可以使用 break 和 continue 语句来控制循环的流程。
在使用 for…in 循环时,应注意遍历的是对象的所有可枚举属性,包括它的继承链中的属性。因此,如果对象继承了一些不希望遍历的属性,可以使用 hasOwnProperty 方法来检查该属性是否为对象本身的属性:
for…in 循环的迭代顺序是不确定的,因此如果希望按照特定顺序遍历对象的属性,可以使用 Object.keys 方法将对象的属性名组成的数组传递给 for…of 循环。
25、什么是同步异步
在 JavaScript 中,同步和异步指的是程序的执行方式。
JavaScript 是单线程语言,意味着它只能在一个时间点上执行一个任务。如果遇到耗时较长或涉及网络通信的任务,如果使用同步方式执行,程序就会停止执行,直到任务完成,这显然不是理想的方案。
因此,JavaScript 中的异步编程就应运而生。异步编程的核心思想是使用回调函数或事件机制,将耗时较长的任务交给浏览器或系统执行,JavaScript 程序在等待任务完成时继续执行,在任务完成时调用回调函数或触发事件通知程序进行下一步操作。
例如,在 JavaScript 中使用 XMLHttpRequest 对象发送网络请求时,可以设置回调函数或事件处理器来处理服务器响应,这样 JavaScript 程序就可以在等待服务器响应时继续执行,在服务器响应到达时再调用回调函数或触发事件进行处理。
常见的异步方式
定时器,setTimeout,setInterval
接口调用,axios.get() , axios.post(), axios({}),node中的 readFile(),writeFile()
事件函数, dom.addEventLister(“click”,function(){})
26、深拷贝 为什么他能做到互不影响
深拷贝是指拷贝对象的具体内容,而内存地址是自主分配的,拷贝结束之后,两个对象虽然存的值是相同的,但是内存地址不一样,两个对象也互不影响,互不干涉。深拷贝就是拷贝出和原来仅仅是值一样,但是内存地址完全不一样的新的对象,创建后和原对象没有任何关系。
参考:https://www.jianshu.com/p/21403175f922
27、记得Object.defineProperty有几个参数吗?知道每个参数做了什么?
- Object.defineProperty 方法有三个参数:
- obj: 要在其上定义属性的对象。
- prop: 要定义或修改的属性的名称。
- descriptor: 将被定义或修改的属性描述符。可以是以下属性的对象
- value: 属性的值。可以是任何有效的 JavaScript 值(数字,对象,函数等)。默认值为 undefined。
- writable: 如果为 true,则可以通过赋值运算符更改属性的值。默认值为 false。
- enumerable: 如果为 true,则可以枚举该属性。默认值为 false。
- configurable: 如果为 true,则可以通过 delete 运算符删除该属性,以及修改属性的特性。默认值为 false。
例如,下面的代码使用 Object.defineProperty 方法在对象 obj 上定义了一个名为 foo 的属性,该属性的值为 ‘bar’,可写,可枚举,可配置:
1 | let obj = {}; |
除了上述属性,descriptor 对象还可以包含两个函数:
get: 当读取属性值时调用的函数。这个函数没有参数,并返回属性值。
set: 当写入属性值时调用的函数。这个函数有一个参数,表示要写入的新值。
例如,下面的代码使用 Object.defineProperty 方法在对象 obj 上定义了一个名为 foo 的属性,该属性的值由 get 和 set 函数控制:
1 | let obj = {}; |
请注意,如果同时定义了 value、writable 和 get、set 函数,则会抛出错误。你只能选择定义属性的值或访问器函数。
28、纯函数是什么
纯函数就是一个函数,只不过具有一些特点,你可能平时开发中都有用到,只是没有意识到这是一个纯函数。
纯函数(Prue function)具有以下特点:
纯函数每一次调用时传入同样的参数,返回的都是同样的结果;它不会改变参数的值,也不会改变外部变量的值;它不会依赖于外部的变量,仅依赖于你传入的参数;
纯函数没有其他副作用(side effect)
如果你每次传入的参数一样,但是返回的结果不一样,则不是一个纯函数
这是一个纯函数
1 | /* |
29、怎么创建一个文档碎片
【必答】在JS中我们主要是通过 document.createDocumentFragment() 方法来创建一个文档碎片
【选答】文档碎片有一个很重要的特点:当需要添加多个dom元素时,如果先将这些元素添加到DocumentFragment(文档碎片)中,再统一将DocumentFragment(文档碎片)添加到页面,会减少页面渲染dom的次数,效率会明显提升。Vue的底层就充分使用了文档碎片来提升页面渲染性能。
30、递归
递归是一种编程技术,允许函数调用自身。这种技术可用于解决一些复杂的问题,并且通常比循环更简洁易懂。
举个例子,假设你想要计算某个数的阶乘。阶乘是一个数的所有自然数的乘积,如5的阶乘为5 * 4 * 3 * 2 * 1。
你可以使用递归来实现这个函数,具体方法是:
如果这个数小于等于1,则返回1。
否则,返回这个数乘上它的(n-1)的阶乘。
这样,每次调用函数时,它就会调用自身,直到数字小于等于1,然后开始返回结果。
递归还有很多其他应用场景,例如:
计算斐波那契数列的某一项。
在数据结构中,例如二叉树或链表,递归可以用来遍历整个数据结构。
在排序算法中,递归可以用来实现快速排序或归并排序。
总的来说,递归是一种非常有用的编程技术,能帮助你解决一些复杂的问题。但同时,也要注意递归程序可能会占用大量的内存,因此要谨慎使用。
31、js中怎么判断类型
判断JS类型,有以下几种方法:
typeof :可以判断基本类型,如:number,string,boolen、symbol、undefined、null等
instance of:只能用来判断复杂数据类型,如:数组,方法,对象等
object.property.toString.call:这个方法兼容基本类型和复杂类型,兼容性最好,很多框架内部都使用它来进行类型判断
参考地址:https://blog.csdn.net/weixin_43758377/article/details/117333465
32、数组去重
https://blog.csdn.net/Shivy_/article/details/122595663
工具类(git&webpack)
git命令、git的工作区有哪些
Git 工作区包括以下几个部分:
本地仓库:本地仓库是 Git 管理的文件的集合,它存储在本地磁盘上。
工作目录:工作目录是用来修改和查看文件的地方。它包含了本地仓库中的文件的实际内容,也就是我们平常使用的文件。
暂存区(Staging Area):暂存区是用来准备下一次提交的地方。当我们在工作目录中修改了文件,如果想要把这些修改提交到本地仓库,就需要先把修改的文件添加到暂存区。
版本库(Repository):版本库是用来存储所有提交的地方。它包含了所有的提交历史,以及每次提交时本地仓库的快照。
在 Git 中,我们通常会在工作目录中修改文件,然后使用 git add 命令将修改的文件添加到暂存区,再使用 git commit 命令将暂存区中的修改提交到本地仓库。本地仓库中的文件就会更新为最新的版本,同时也会把提交记录存储到版本库中。
webpack版本号
2015,webpack1支持CMD和AMD,同时拥有丰富的plugin和loader,webpack逐渐得到广泛应用。
2016,webpack2相对于webpack1最大的改进就是支持ES Module,可以直接分析ES Module之间的依赖关系
2017,webpack3相对于webpack2,过渡相对平稳,但是新的特性大都围绕ES Module提出
2018年,webpack4,可以零配置运行,打包速度比之前提高了90%
2020年,webpack5,对构建速度做了突破性的改进,开启文件缓存之后,再次构建,速度提升明显
网络类问题
浏览器缓存?
当我们在浏览器中请求网页资源时,浏览器会将这些资源缓存到本地。这样,当我们再次请求同一资源时,浏览器可以从本地缓存中获取资源,而不需要再次从服务器请求资源。这样可以提高网页的加载速度和用户的体验。
浏览器缓存机制分为两种:强缓存和协商缓存。
强缓存
浏览器直接从本地缓存中获取资源的机制。浏览器在请求资源时,会在本地缓存中查找是否存在该资源的缓存。如果存在,并且缓存未过期,则直接从本地获取资源,不会向服务器发送请求。常用的控制强缓存的响应头有Expires和Cache-Control。
协商缓存
是浏览器通过向服务器发送请求来确认资源是否更新的机制。当强缓存失效时,浏览器会向服务器发送请求,服务器会返回资源的标识,如Etag或Last-Modified。浏览器会将这些标识存储在缓存中,并在下次请求资源时将这些标识发送给服务器,询问资源是否有更新。如果服务器返回304状态码,表示资源未更新,则浏览器可以从本地缓存中获取资源。常用的控制协商缓存的响应头有ETag和Last-Modified。
http有了解过么 展开说说?
HTTP(Hypertext Transfer Protocol)是一种基于请求/响应模型的、无状态的协议,用于在网络中传输超文本文档。
HTTP通常用于在万维网上传输超文本文档(HTML),但是它也可以用于传输其他类型的文档,例如XML和JSON。
HTTP协议使用端口号80和443。端口号80是HTTP协议的默认端口,端口号443是HTTPS协议的默认端口。
HTTP协议包括五个方法,分别是GET、POST、PUT、DELETE和HEAD。每个方法都有其特定用途。
GET方法用于获取指定资源的信息。
POST方法用于向指定资源提交数据进行处理(例如提交表单或上传文件)。
PUT方法用于更新指定资源的信息。
DELETE方法用于删除指定资源。
HEAD方法用于获取指定资源的信息,但是不返回资源的实际内容。
HTTP协议使用状态码表示请求的结果。常见的状态码包括200 OK(请求成功)、301 Moved Permanently(永久性重定向)、404 Not Found(未找到)和500 Internal Server Error(服务器内部错误)。
http握手总共有几次 分别是什么
HTTP是一种应用层协议,用于在计算机之间传输数据。它使用 TCP 协议来保证数据的可靠传输。
TCP 协议使用了三次握手来建立连接。三次握手过程如下:
客户端发送一个连接请求给服务器,包含了一个随机的序列号。
服务器收到连接请求后,会返回一个应答报文,包含了随机的序列号以及一个确认号。
客户端收到服务器的应答后,会再次发送一个应答报文给服务器,其中包含服务器发送的随机序列号和确认号。
这样,服务器和客户端就完成了三次握手,建立了一条可信的连接。在这条连接中,客户端和服务器就可以交换数据。
三次握手的目的是为了保证数据的可靠传输。在建立连接之前,客户端和服务器都会发送一些控制报文来确认对方的存在,并且确定序列号和确认号,以便在之后的数据传输过程中使用。这样可以避免数据包丢失或重复出现,保证了数据的可靠传输。
一个页面从输入 URL 到页面加载显示完成的过程
当用户在浏览器中输入 URL 并按下回车时,浏览器会向服务器发送 HTTP 请求,请求指定的资源。服务器收到请求后,会根据请求的 URL 返回对应的资源,这通常是一个 HTML 文件。浏览器收到服务器返回的 HTML 文件后,会开始解析 HTML 代码,并在浏览器中构建 DOM (Document Object Model) 树。
在解析过程中,浏览器会根据 HTML 中的标签引用的外部资源(如 CSS、JavaScript 文件)发送请求,加载这些资源。浏览器会等待所有这些资源都加载完成,然后再渲染页面。
在渲染过程中,浏览器会使用构建的 DOM 树和加载的 CSS 文件来计算每个元素的布局,并将这些元素绘制到浏览器窗口中。如果 HTML 中还包含 JavaScript 代码,浏览器会执行这些代码,可能会修改 DOM 结构或者添加新的内容。
当所有的 HTML 代码解析完成,所有的资源加载完成,所有的 JavaScript 代码执行完成后,页面就会显示完成。
常见的 HTTP 状态码:
HTTP 状态码是 HTTP 协议的一部分,用于表示 HTTP 请求的结果。它们通常是服务器向浏览器发送的 3 位数字代码。下面是一些常见的 HTTP 状态码:
200 OK:服务器成功处理了请求。这是最常见的 HTTP 状态码,表示请求已成功。
301 Moved Permanently:永久性重定向。请求的资源已被永久地移动到新位置,并且将来的所有请求都应该使用新的 URI。
302 Found:临时性重定向。请求的资源临时从不同的 URI 响应请求。注意,在 HTTP/1.0 中,这个状态码也被用于重定向。
400 Bad Request:服务器无法理解请求。这通常是因为客户端请求中的语法错误。
401 Unauthorized:请求要求身份验证。客户端必须先使用授权标头发送身份验证信息。
403 Forbidden:服务器拒绝了请求。这通常是因为服务器上的文件或目录的权限设置导致的。
404 Not Found:服务器找不到请求的资源。这是最常见的 HTTP 错误代码,表示服务器无法找到请求的网页。
500 Internal Server Error:服务器内部错误。这表示服务器遇到了意料不到的情况,导致了它无法处理请求。这可能是由于服务器上的代码错误或者其他原因造成的。
以上这些只是一些常见的 HTTP 状态码,实际上 HTTP 协议中定义了许多其他状态码。例如,HTTP 状态码 100 表示继续(Continue),表示客户端应该继续其请求;HTTP 状态码 204 表示无内容(No Content),表示服务器已成功处理请求,但是没有返回任何内容。
三次握手和四次挥手
三次握手是用于在两台计算机之间建立网络连接。它包括以下三个步骤:
客户端向服务器发送连接请求。
服务器向客户端发送确认消息,表明服务器已准备好接受连接。
客户端向服务器发送确认消息,表明客户端已收到服务器的确认消息,并准备好开始数据传输。
四次挥手是用于在两台计算机之间终止网络连接。它包括以下四个步骤:
- 客户端向服务器发送断开连接请求。
- 服务器向客户端发送确认消息,表明服务器已收到断开连接请求。
- 服务器向客户端发送断开连接请求。
- 客户端向服务器发送确认消息,表明客户端已收到服务器的断开连接请求。
HTTP和HTTPS区别
HTTP (HyperText Transfer Protocol) 和 HTTPS (HTTP Secure) 都是用于在计算机之间传输数据的协议。但是,HTTPS在传输数据时使用了安全套接层 (SSL) 或者传输层安全 (TLS) 协议来加密数据,以防止第三方拦截或窃取数据。
- 主要区别如下:
安全性:HTTPS 在传输过程中使用了加密技术,而 HTTP 没有。这意味着,使用 HTTPS 可以保护数据不被窃取或篡改。
证书:使用 HTTPS 需要在服务器端安装 SSL 证书。这个证书可以用来验证服务器的身份,确保用户访问的是真实的网站。
端口号:HTTP 使用的是 80 端口,而 HTTPS 使用的是 443 端口。
URL:HTTP 的 URL 以 “
http://“ 开头,而 HTTPS 的 URL 以 “https://“ 开头。
总的来说,使用 HTTPS 可以提供更好的安全性,特别是在处理敏感信息或者在公共网络上使用时。
websockt的使用
我们都知道前端主要是通过ajax请求一个后台提供的url地址来交互数据,而后台提供的通常是http或者https协议的url,Http或者Https协议是每次请求结束后会断开的,那么这就导致了如果服务器想给客户端主动推送数据变得不可能。所以websocket的出现就是为了解决这个问题的。
【这个一定要说】websocket的使用是要有后端支持的,也就是java或者nodejs工程师开发一个配套websocket的接口,前端通过websocket去链接这个接口后就可以实现前后端数据交互了。
有没有用过websocket
什么是websocket?
本质: 一种双向通信协议,
作用:常用来做即时通讯。
场景:聊天会话、股票交易等。
Web的前端,实现即时通讯的方式:
短轮询 (历史方案)开个定时器, 每隔一段时间发请求 (实时性不强)
Comet - ajax长轮询(历史方案)发送一个请求, 服务器只要数据不更新, 就一直阻塞 (服务器压力过大)SSE(利用了http协议, 流数据的传输, 并不是严格意义的双向通信, 无法复用连接)
Vue框架
MVVM的理解
MVVM是一种设计模式,用于将用户界面(UI)与业务逻辑分离。这样可以使 UI 的开发与业务逻辑的开发相互独立,更容易维护和扩展。
MVVM 的名称来自于三个部分:
Model(模型):表示应用程序中的数据模型。它代表着应用程序中的业务逻辑和状态。
View(视图):表示应用程序的用户界面。它是用户与应用程序交互的方式。
ViewModel(视图模型):是一个桥梁,将模型与视图连接在一起。它提供了视图所需的数据和命令,并将用户的输入转换为模型的操作。
通常,ViewModel 通过数据绑定将数据提供给视图,并使用命令处理视图中的用户输入。这样,视图可以直接与 ViewModel 交互,而无需与模型直接交互。这使得视图的开发和模型的开发相互独立,并且可以更轻松地测试和维护应用程序。
MVVM 模式的优点包括:
- 将 UI 和业务逻辑分离,使得 UI 和业务逻辑的开发相互独立。
- 提供了更好的测试支持,因为视图和业务逻辑是分离的,所以可以更轻松地对它们进行单元测试。
- 易于维护和扩展,因为业务逻辑和 UI 分离,所以可以更轻松地更改一个而不会影响另一个。
MVVM 模式也有一些缺点,包括:
可能会使代码变得较复杂,因为需要编写数据绑定和命令的代码。
可能需要使用特定的框架才能实现 MVVM 模式,例如 Vue 或 Angular。
总的来说,MVVM 是一种有用的设计模式,可以帮助你将用户界面与业务逻辑分离,从而使应用程序更易于维护和扩展。
对组件的二次封装?
我二次封装过一些表单组件,分页组件
- 表单组件:在项目中,我们经常需要使用表单来收集用户的输入信息,而表单的实现过程往往比较繁琐,涉及到表单元素的校验、表单数据的处理等。因此我封装了一个表单组件,通过对 ElementUI 表单组件进行二次封装,使得表单的实现更加简单和高效。
- 分页组件:在项目中有个小需求就是,页数很多的时候,中间会有三个点。我们希望点击这三个点能弹出一个输入框,能够跳转到对应的页数。就二次封装了分页组件,把三个点的逻辑放在了插槽中进行实现
首屏优化
首屏加载时间过长,首先要检查包的体积是不是太大了,一般大部分的库,cssjs文件都被合并到 vendor.js 文件里了,就会导致他变得很大,于是用户加载时间就会比较长。我们可以通过
webpack-bundle-analyzer库来分析用到的库占用的内存,去进行拆分,或者异步加载。第三方库的话可以通过CDN的方式来引入
对路由进行懒加载
使用异步组件,他会在加载组件的时候展示一个默认组件,然后等组件加载完毕后再展示组件(分包),来减少首屏加载的时间
图片压缩,或者可以先加载一个压缩后的图片,等加载完毕后再替换成清晰的图片
开启 gzip压缩
代码层面
指令合理使用,例如vif和vshow
watch和computed合理使用
定时器的销毁
注意意外的闭包
vfor指定唯一的key
css合并,公共代码合并
组件的$refs
可以通过给组件绑定 ref 来获取组件的属性和方法,优点是方便操作子组件。缺点是子组件容易产生依赖,因为如果要修改子组件里的数据,那大部分是父组件传入的,如果子组件的业务发生了调整,就可能导致错误,不利于后期的维护。所以更推荐使用props和emit来进行通讯
vue2常见修饰符
事件修饰符
- .stop
阻止单击事件继续传播 - .prevent
提交事件不再重载页面 - .capture
添加事件监听器时使用事件捕获模式 - .self
只当在 event.target 是当前元素自身时触发处理函数 - .once
点击事件将只会触发一次 - .passive
滚动事件的默认行为 (即滚动行为) 将会立即触发
- .stop
按键修饰符
- .enter
回车调用
- .enter
表单修饰符
.lazy
在“change”时而非“input”时更新.trim
去除两侧空格.number
将字符串转为number
组件修饰符
- .sync
双向修改
- .sync
.sync修饰符
他可以理解为 emit 的语法糖,当我们给数据加上 .sync 后,子组件就可以直接修改这个数据,并且父组件的数据也会跟着变化。
很多ui组件库都是来这么交互的
v-model和.sync的区别
一个组件上只能有一个v-model,.sync修饰符可以有多个。
一般 v-model 用在表单上
而 .sync 在组件身上使用
v-model 是value和input事件的语法糖
.sync 是 emit 的语法糖
插槽
在 Vue.js 中,插槽(slot)是一种机制,可以在父组件的模板中定义一个占位符,然后在子组件中插入内容。这样,子组件就可以在父组件的模板中渲染内容,使得子组件可以更灵活地与父组件进行交互。
插槽有两种类型:
具名插槽:具名插槽使用特定的名称来标识,子组件可以插入到特定的插槽中。
默认插槽:默认插槽没有名称,子组件中的内容会插入到默认插槽中。
在父组件的模板中,可以使用
1 | <template> |
在上面的例子中,父组件有三个插槽:一个不具名插槽和两个具名插槽。在子组件中,可以使用 元素来定义要插入的内容。例如:
1 | <template> |
vue组件中data为什么必须是一个函数:
在 Vue.js 中,组件的 data 选项必须是一个函数,这是因为每个组件实例都应该有自己的状态,如果 data 不是一个函数,那么所有实例将共享同一个数据对象,这会导致组件之间的状态混乱。
举个例子,假设你有一个组件 A 和组件 B,如果你把 data 写成这样:
1 | data: { |
那么组件 A 和组件 B 中的 message 都是同一个值,如果在组件 A 中修改了 message,那么组件 B 中的 message 也会改变。这显然不是你想要的结果。
因此,Vue.js 要求 data 选项必须是一个函数,这样每个组件实例都可以有自己的 data 对象。你可以这样写:
1 | data: function () { |
这样就能保证每个组件实例都有自己的 data 对象,组件之间的状态就不会混乱了。
Vue 数据双向绑定的原理
在 Vue.js 中,数据双向绑定是通过使用观察者模式来实现的。观察者模式是软件设计模式的一种,它允许对象之间的一对多关系。在这种关系中,一个对象(称为发布者)维护一组依赖于它的对象(称为订阅者),并在其状态更改时通知所有订阅者。
在 Vue.js 中,发布者是 Vue 实例,订阅者是观察者。当 Vue 实例的数据发生变化时,Vue 会通知观察者,观察者会更新视图。这样,当用户在视图中进行操作时,Vue 会更新数据,并通知观察者更新视图。
为了实现数据双向绑定,Vue 使用了一种叫做劫持的技术。劫持就是在数据变化时捕获数据并通知观察者。Vue 实现劫持的方法是使用 Object.defineProperty() 函数,该函数允许在对象的属性被访问或修改时进行拦截。
当 Vue 实例被创建时,它会在内部遍历所有数据并使用 Object.defineProperty() 函数为每个属性添加 getter 和 setter。这样,当数据被访问或修改时,Vue 就可以捕获数据并通知观察者。
例如,当你在 Vue 实例中声明一个属性 message:
1
2
3
4
5data: function () {
return {
message: 'Hello'
}
}Vue 会使用 Object.defineProperty() 函数为 message 属性添加 getter 和 setter,这样在访问或修改 message 属性时,Vue 就可以捕获数据并通知观察者。
当视图中的数据改变时,Vue 会使用 setter 通知观察者。观察者会更新视图,而用户在视图中的操作会使用 getter 更新数据。这样,数据和视图之间就形成了双向绑定。
自己举一个使用vuex的完整过程
使用 Vuex 的过程大致如下:
安装 Vuex:使用 npm 或 yarn 安装 Vuex。
创建 store:创建一个 store.js 文件,并在文件中定义 store 的状态、getters、mutations 和 actions。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
// 定义状态
},
getters: {
// 定义 getters
},
mutations: {
// 定义 mutations
},
actions: {
// 定义 actions
}
})在入口文件中加载 store:在应用的入口文件中引入 store.js 文件,并将 store 实例作为参数传递给 new Vue()。
1
2
3
4
5
6
7
8import Vue from 'vue'
import App from './App.vue'
import store from './store'
new Vue({
store,
render: h => h(App)
}).$mount('#app')在组件中使用 Vuex:在组件中使用 $store 访问 store 中的状态、getters、mutations 和 actions。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18export default {
computed: {
// 使用 getters
count() {
return this.$store.getters.count
}
},
methods: {
// 使用 mutations
increment() {
this.$store.commit('increment')
},
// 使用 actions
asyncIncrement() {
this.$store.dispatch('asyncIncrement')
}
}
}以上就是使用 Vuex 的完整过程。
v-model实现的原理
在 Vue.js 中,v-model 指令是一种语法糖,它的作用是为表单元素绑定双向数据绑定。这意味着当用户输入表单元素时,它会更新 Vue 实例的数据,同时也会将 Vue 实例中的数据反映到表单元素上。
例如,如果你想要在 Vue 实例中绑定一个输入框的值,你可以使用 v-model 指令:
1 | <template> |
实现原理
v-model 指令的实现原理是基于计算属性和事件绑定。它会将表单元素的 value 属性绑定到一个计算属性上,并且在计算属性的 setter 中触发 input 事件来更新 Vue 实例中的数据。
例如,上面的代码实际上等价于:
1 | <template> |
这样,当用户在输入框中输入文本时,输入框的 value 属性会被更新
单页面应用
单页面应用(Single-Page Application,简称 SPA)是一种架构方式,它在一个单独的网页中加载所有的资源,并使用 JavaScript 在客户端渲染用户界面,而不是通过服务器进行渲染。这意味着,当用户在 SPA 应用中导航到不同的页面时,不会发生实际的页面跳转,而是在当前页面内使用 JavaScript 更新内容。这使得 SPA 应用的体验非常流畅,因为不会有页面加载的延迟。
与传统的多页面应用相比,单页面应用具有许多优势,包括:
快速响应:由于不需要重新加载页面,因此 SPA 应用可以快速响应用户的操作。
减少服务器负载:由于大部分的处理都是在客户端进行的,因此 SPA 应用可以减少服务器的负载。
更好的用户体验:由于 SPA 应用的页面切换非常流畅,因此它可以提供更好的用户体验。
然而,单页面应用也有一些缺点,包括:
较难调试:由于 SPA 应用的代码都在客户端运行,因此调试可能会更加困难。
SEO 难度较大:对于单页面应用,搜索引擎爬虫可能无法正常抓取页面内容,因此 SPA 应用的 SEO 较为困难。为了解决这个问题,可以使用服务端渲染(Server-Side Rendering,简称 SSR)技术,在服务器端渲染 SPA 应用的内容,使得爬虫可以正常抓取页面内容。
总的来说,单页面应用是一种有效的架构方式,它可以提供流畅的用户体验,但在 SEO 和离线使用方面存在一定的困难。
vue的理解?
Vue.js 是一个渐进式的 JavaScript 框架,旨在通过尽可能简单的 API 实现响应式数据绑定和组合的视图组件。
Vue.js 的核心是一个叫做 Vue 实例的 JavaScript 对象,该对象用于控制一个 HTML 页面中的一个区域,称为“视图”。Vue 实例可以通过声明式模板或使用 JavaScript 代码动态渲染视图。
Vue.js 的响应式系统使得在模型数据变化时,视图会自动更新。这意味着,你可以在不手动操作 DOM 的情况下改变视图。Vue.js 还提供了许多其他功能,如组件系统、路由、状态管理等,可以帮助你构建复杂的单页应用。
总的来说,Vue.js 是一个轻量级的、易于使用的前端框架,可以帮助你快速构建响应式的 Web 应用。
vue.js中如何监听路由信息的改变
通过watch监听
1 | // 监听,当路由发生变化的时候执行 |
通过路由钩子函数监听
1 | export default { |
nexttick的理解
nextTick 是 Vue.js 中一个用来在下一个事件循环中调用回调函数的方法。这个方法通常用在等待数据或 DOM 元素更新之后执行一些代码的情况下。
举个例子,假设你有一个组件,它的模板中使用了一个计算属性 fullName,该计算属性由组件中的 firstName 和 lastName 两个数据项拼接而成:
1 | <template> |
假设你想要在更新 firstName 和 lastName 后执行一些代码,但你发现 Vue 的计算属性是同步的,所以你无法在计算属性更新之后立即执行代码。这时你就可以使用 nextTick 方法:
1 | this.firstName = 'Jane' |
这样你就可以在计算属性更新之后执行代码了。
你可能会在组件的生命周期钩子函数 created 中使用 nextTick,因为在该钩子函数中组件的模板还没有被渲染,但是你想要在模板渲染之后执行一些代码:
vue给对象添加新属性界面不刷新?
原因:Vue 不允许在已经创建的实例上,动态添加新的响应式属性;
三种解决办法:
使用
Vue.set( target , key , value)使用
$fourceUpdate强制刷新克隆新对象,如
this.persons ={...this.persons}, this.persons = Object.assign({}, this.persons)
之前有用到Vue 2.几的版本
在命令面板中输入:npm view vue versions 就可以查看到所有可用的vue版本
vue中key的原理
在 Vue.js 中,当使用 v-for 指令渲染一个列表的时候,Vue.js 强制要求使用 key 属性来提高性能。这个 key 属性是 Vue.js 内部使用的,它并不会对你的应用造成任何影响。
在 Vue.js 内部,使用 key 属性可以帮助 Vue.js 识别列表中的每个元素。这样 Vue.js 就可以更快地更新列表中的元素,因为它可以直接找到要更新的元素,而不是遍历整个列表来寻找需要更新的元素。
使用 key 属性的方法是在每个 v-for 指令的模板中给每个元素添加一个唯一的 key 属性,如下所示:
1
<div v-for="item in items" :key="item.id">{{ item.text }}</div>
在上面的例子中,Vue.js 会使用每个元素的 id 属性作为 key 属性。你可以使用任何唯一的值作为 key 属性,只要它能唯一标识列表中的每个元素即可。
总的来说,使用 key 属性可以帮助 Vue.js 更快地更新列表中的元素,提高应用的性能。
Vue 组件通讯
父传子:子组件定义
props属性接收子传父:子组件中使用
this.$emit方法兄弟传值:
事件总线,$on方法- 使用eventBus,跨组件通信
- 使用状态提升(将数据放在父组件身上)
- 使用Vuex
父传孙:
provide和inject方式
自定义指令的使用以及生命周期/钩子函数
分类:全局自定义指令,局部自定义指令
全局自定义指令
1 | Vue.directive('指令名', { |
局部自定义指令
1 | directives: { |
生命周期/钩子函数
- inserted:被绑定元素插入父节点时调用
- bind:只调用一次,指令第一次被绑定到元素时
- update:元素本身更新时触发
- componentUpdate:组件和子组件更新时触发
- unbind:指令被移除时触发
每个钩子函数中都有el和binding参数,而componentUpdate钩子中还暴露一个oldVal,用以区分旧值和新值
el:绑定元素
binding:是一个参数对象,一般我们会用到其中的value值,用于src的属性值
说5个vue的指令
v-bind:绑定属性
v-if 、v-show:条件渲染
v-for: 列表渲染
v-model:双向绑定
v-html:解析html字符串
v-on:绑定事件
路由懒加载
在 Vue.js 中,懒加载可以帮助你减小应用的初始加载大小,并在路由被访问时再加载对应的组件。这可以通过使用 Vue 的异步组件和 Webpack 的代码分割功能来实现。
首先,你需要将你的组件定义为异步组件,这意味着它不是立即加载的,而是在被访问时才加载。你可以使用 Vue 的 component 属性提供一个工厂函数来实现这一点,例如:
1 | const SomeComponent = () => ({ |
接下来,你可以在路由配置中使用这个异步组件。例如:
1 | const router = new VueRouter({ |
这样,当你访问 /some-route 时,组件 some-component.vue 就会被异步加载,而不是在应用初始加载时一并加载。
注意,上面的例子假设你已经使用了 Webpack 和 vue-loader,并且在 Webpack 配置中启用了代码分割功能。这样,Webpack 就会将组件分割成独立的包,并在被访问时动态加载。
Vuex核心属性
state:定义需要管理的数据
getters:state派生出来的数据,相当于state的计算属性
mutation:里面定义的是同步的更新数据方法,每个方法里都有两个参数,一个是state,一个是payload,通过store.commit调用
action:里面定义的是异步的方法,每个方法里面有两个参数,一个是store,一个是payload,通过store.dispatch调用,在actions里也可以提交mutation,通过store.commit
module:将vuex模块化,可以让每一个模块拥有自己的state、mutation、action、getters,结构清晰,方便管理
路由模式
hash模式:
浏览器中符号是“#”,#以及#后面的字符称之为 hash,又叫前端路由
用 window.location.hash 读取
hash 虽然在 URL 中,但不被包括在 HTTP 请求中
hash 改变会触发 hashchange 事件
hash发生变化的url都会被浏览器记录下来,从而浏览器的前进后退都可以用
history模式:
history 采用 HTML5 的新特性
history 模式不仅可以在url里放参数,还可以将数据存放在一个特定的对象中
它也有个问题:不怕前进,不怕后退,就怕刷新(如果后端没有准备的话,会分分钟刷出一个404来),因为刷新是实实在在地去请求服务器的
路由之间跳转方式:
四种方式:
router-link搭配to属性,在模板中使用
push()跳转到指定页面
replace()跳转到指定页面,但是没有历史记录跳不回去
go(N) N可以为正数也可以为负数,正数是向前跳转,负数是向后跳转
如何封装组件
原因:封装组件可以提升项目开发效率,把页面抽象成多个相对独立的模块,复用性高
步骤:
创建一个组件
Vue.component注册组件
子组件需要数据,可以在props中接受定义
而子组件修改好数据后,想把数据传递给父组件,可以采用emit方法
v-show和v-if的区别
作用:添加渲染,切换显示与隐藏
不同:v-if会移除dom或组件树,v-show则只是通过样式隐藏
场景:
v-if:切换不频繁、敏感数据的隐藏(如权限按钮)
v-show:切换频繁的场景
v-for和v-if为什么不能一起使用
【回答此句-良好】在vue2中v-for优先级高于v-if,如果二者放在同一级标签里面,每次都要先循环,再判断,消耗很多性能。
【回答此句-优秀】对于同一组数据来说,如果我们要先判断再渲染,可以在外层包装一个div,使用v-if做一次判断即可。Vue3解决了这个问题,将v-if的优先级调整为高于v-for了。
说说vue的生命周期
好的,这个问题其实是这样的:
【必须回答】Vue中的生命周期本质上就是按顺序固定执行一个个的钩子函数,我们开发者可以在每个函数中写入特定代码来实现我们需要的功能
例如我们常用的ajax请求通常就放在created或者mounted中
vue2的生命周期从分类上来看有如下几种情况:
组件创建和挂载相关的钩子函数有
beforeCreate
created
beforeMount
mounted
组件更新相关的钩子函数有
beforeUpdate
updated
组件销毁相关的钩子函数有
beforeDestroy
destroyed
【以下如果能回答出来是超过面试官预期的,更加优秀】
还有一个组件缓存激活相关的钩子函数是:activated和deactivated,这两个要配合keep-alive 缓存的组件一起使用
vue3总体来说什么周期执行顺序是一样的,不同点在于beforeCreate和created都被setup函数替代了
参考链接:https://cn.vuejs.org/v2/api/#activated
父子组件生命周期顺序
挂载阶段
执行顺序为:
父beforeCreate -> 父created -> 父beforeMount -> 子beforeCreate -> 子created -> 子beforeMount -> 子mounted -> 父mounted
更新阶段
执行顺序为:
父beforeUpdate -> 子beforeUpdate -> 子updated -> 父updated
销毁阶段
执行顺序为:
父beforeDestroy -> 子beforeDestroy -> 子destroyed -> 父destroyed
规律就是:父组件先开始执行,然后等到子组件执行完,父组件收尾。
Vue组件之间的传递方式
父组件向子组件传递数据,使用props属性;子组件向父组件中传递数据,在子组件中使用$emit派发事件,父组件中使用v-on监听事件。缺点:组件嵌套层次多的话,传递数据比较麻烦。
通过Vuex,实现多个组件进行数据共享,推荐使用这种方式进行项目中各组件间的数据传递。
[如果还能将第3条回答出-良好]
- 通过事件总线(eventbus)的方式,可以实现任意两个组件间进行数据传递;缺点:不支持响应式,这个概念是vue1.0版本中的,现在已经废弃
[如果还能将第4,5条回答出-优秀]
祖先组件通过依赖注入(inject/provide)的方式,向其所有子孙后代传递数据;缺点:无法监听数据修改的来源,不支持响应式。
通过属性parent/$children/ref,访问根组件、父级组件、子组件中的数据;缺点:要求组件之间要有传递性。
vue-router的钩子函数
钩子函数有三种:
【必答-合格】全局守卫:beforeEach(全局前置守卫),beforeResolve(全局解析守卫) ,afterEach(全局后置钩子)
【选答-良好】路由独享守卫:可以直接在路由配置上定义 beforeEnter 守卫
1
2
3
4
5
6
7
8
9
10const routes = [
{
path: '/users/:id',
component: UserDetails,
beforeEnter: (to, from) => {
// reject the navigation
return false
},
},
]【选答-优秀】组件内的守卫
beforeRouteEnter
beforeRouteUpdate
beforeRouteLeave
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18const UserDetails = {
template: `...`,
beforeRouteEnter(to, from) {
// 在渲染该组件的对应路由被验证前调用
// 不能获取组件实例 `this` !
// 因为当守卫执行时,组件实例还没被创建!
},
beforeRouteUpdate(to, from) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 `/users/:id`,在 `/users/1` 和 `/users/2` 之间跳转的时候,
// 由于会渲染同样的 `UserDetails` 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 因为在这种情况发生的时候,组件已经挂载好了,导航守卫可以访问组件实例 `this`
},
beforeRouteLeave(to, from) {
// 在导航离开渲染该组件的对应路由时调用
// 与 `beforeRouteUpdate` 一样,它可以访问组件实例 `this`
},
}
Pinia和vuex的区别,vuex的不足
Vuex和Pinia都是vue.js的状态管理工具,Vuex是vue2使用,而在vue3推荐了Pinia,主要有以下几点区别
Pinia没有mutation,他只有state,getters,action【同步、异步】使用它来修改state数据
Pinia语法上比vuex更容易理解和使用,灵活。
Pinia没有modules配置,每一个独立的仓库都是definStore生成出来的、
Pinia的state是一个在函数中返回的对象,和vue组件中的data编写方式差不多
vuex的不足 :
- Pinia和Vuex都是非常好用的数据管理工具,在某些情况下,使用Pinia的web应用程序会比使用Vuex更快,这种性能的提升可以归因于Pinia的极轻的重量,Pinia体积约1KB。
vite和wabpack的区别
【必答】 webpack会先打包,然后启动开发服务器,请求服务器时直接给予打包结果,当项目文件大的时候会出现打包时间长的问题启动服务器缓慢的问题;vite是直接启动开发服务器,请求哪个模块再对该模块进行实时编译,所以启动速度快,感受好,启动服务器时的优势相对明显
【必答】在热更新方面,当改动了一个模块后,仅需让浏览器重新请求该模块即可,不像webpack那样需要把该模块的相关依赖模块全部编译一次,效率更高。
【选答】当需要打包到生产环境时,vite使用传统的rollup进行打包,因此,vite的主要优势在开发阶段。另外,由于vite利用的是ES Module,因此在代码中不可以使用CommonJS
TS的作用、TS代码可以在浏览器解析吗
【必答】ts相对于js而言主要增加了一个类型检测系统,我们大多数也是用它来帮助我们更好的开发项目,它多了如下作用:
可以轻松避免在编写代码过程中的类型错误,例如: let num = 10; num =”20” ts会给你实施抛出类型错误,这样能够让我们的代码变得更加规范,减少系统在运行时的错误
使大型、复杂的应用程序源码更易阅读。
【必答】TS代码本身不能在浏览器中解析,它需要基于node的一个typescript编译器来将ts代码编译成js代码,才能在浏览器上运行
vue3和vue2区别
答题技巧:以下4部分内容,挑选出你能够hold得住的去回答即可
vscode插件和调试工具方面
代码高亮,语法提示方面:vue2项目主要用Vetur插件,vue3中主要用Volar
语法片段方面:在vue2中我们一直使用Vue 2 Snippets,在vue3我们推荐使用Vue 3 Snippets,因为它支持vue3的同时完全向前兼容vue2
在浏览器调试工具方面:vue2版本的chrome devtools不再支持vue3,vue3我们需要单独下载Vue.js devtools beta
兼容性方面:
vue2 不支持 IE8 及以下版本,因为 Vue2 使用了 IE8 无法模拟的 ECMAScript 5 特性,例如:Object.defineProperty()
vue3 不支持 IE11 及以下版本。
语法层面
在vue2中,我们只要定义在data()方法中的数据就是响应式数据,在vue3中我们可以使用ref和reactive定义的响应式数据
组合式api:为了让相关代码更紧凑vue3提出了组合式api,组合式api能将同一个逻辑关注点相关代码收集在一起。 组合式api的入口就是setup方法。
在 vue2 中template不支持多根节点组件,vue3支持了多根节点的组件
底层实现方面
- vue2的响应式使用的是Object.defineProperty()实现的,Vue3使用的Proxy实现的
webpack有了解过么?
webpack主要是我们做工程化开发的打包工具,它会让你指定一个入口,然后通过这个入口分析出此项目的所有依赖,最终将它们打包合并成一个或者多个js文件。
同时他还有很多插件和loader能帮我们提升开发效率。
服务端渲染和客户端渲染分别是什么?
服务器渲染
页面渲染的工作都是由服务端来完成的,数据也是由服务端提供的,浏览器只负责展示页面内容
容易被爬虫爬取数据,同时能被搜索引擎搜索到,能在搜索引擎中向用户展示数据
客户端渲染
页面的渲染工作都是由浏览器来完成的,服务器只是负责提供数据。
客户端渲染能尽早的把页面展示给用户,用户体验好
不容易被爬虫爬取数据,同时也无法被搜索引擎搜索到
Vue3中 setup的作用是什么?为什么Vue3比Vue2在script中多了一个setup这个语法糖?
setup的设计是为了在Vue3中使用组合式api。
在Vue2中data、computed、methods、watch 组织逻辑在大多数情况下都有效。然而,当我们的组件变得更大时,我们同一个功能的代码会分散在data、computed、methods、watch,这会导致组件难以阅读和理解而通过setup可以将该部分抽离成函数,让其他开发者就不用关心该部分逻辑了。
script中多了一个setup,主要是为了让我们更加方便的编写代码,在setup函数中编写的属性和方法都需要return,如果在
