前端八股文
一、 html
1. 行内元素。块级元素。空元素?
- 行内元素一行可以放很多个,(设置不了宽高)
- span、img、input、..
- 块级元素一行只能放一个
- h1、h2、div、ul、li、footer、header、p..
- 空元素
- br、hr、
2. 元素之间转换?
使用CSS的display属性
display: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 可以匹配以下正则const 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;可以用一个库来判断:
ismobilejsimport 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画三角形
/* 用边框做 */
div {
width: 0;
height: 0;
border: 100px solid transparent;
border-top: 100px solid #ccc;
}6. 不给宽高实现水平垂直居中
<div class="container">
<div class="main"></div>
</div>
/* 方式一:position + transifrom */
<style>
.container {
position:relative;
width:500px;
height:500px;
}
.main {
position:absolute;
top:50%;
left:50%;
transifrom:translate(-50%,-50%)
}
</style>
/* 方式二:flex */
<style>
.container {
display:flex;
justify-content:center;
align-items:center;
width:500px;
height:500px;
}
.main {
background:red;
}
</style>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
<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,中间自适应,要求先加载中间
<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根节点的字体大小进行改变的
html { font-size:20px; } box { width:2rem; /* 40px */ }em是根据自身父元素的字体大小进行改变的
Father { font-size:20px; } Son { width:2em; /* 40px */ }
25. ios 元素被触摸时产生的半透明遮罩怎么去掉
- 添加 -webkit-tap-hightlight-color:rgba(0,0,0,0) CSS属性
<style>
button,a,input {
-webkit-tap-hightlight-color:rgba(0,0,0,0)
}
</style>
<button>1</button>
<a>1</a>
<input>1</input>26. 输入框的placeholder的颜色能变吗
可以通过input::placeholder 选择修改
input::placeholder { color:red; }
27. 禁止触发系统菜单和长按选择
禁止触发系统菜单
touch-callout: none
html,body { touch-callout: none; -webkit-touch-callout: none; }
禁止选中
user-select: none
html,body { user-select: none; -webkit-user-select: none; }
28. 自适应布局(适配)
什么是自适应布局:
- 就是宽度自适应,在不同大小设备下网页以等比例缩放,呈现同样的主体和布局
有哪些方案
可以通过 rem+媒体查询
使用 淘宝的flexible.js
- 原理:根据页面大小,分成数份,取一个值赋值给html节点的font-size
vw / vh单位
29. 响应式布局
什么是响应式布局:
- 就是根据屏幕大小变化,内容排版会自动改变,呈现最好的用户体验
- 优点:一个url响应多端
- 缺点:内容庞大时会卡顿
- 就是根据屏幕大小变化,内容排版会自动改变,呈现最好的用户体验
有哪些方案
bootstrap框架
媒体查询
/* 媒体查询语法 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时做什么 */ }对不同设备来显示不同图片
<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
<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。定位元素也会创建一个新的块级格式化上下文,从而避免外边距的折叠
<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 如何设置一行超出显示省略号
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;37. CSS 如何设置多行超出显示省略号
overflow: hidden;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;38. flex 布局中 order 有何作用
order属性定义 Flex 布局中子元素的排列顺序,数值越小,排列越靠前,默认为 0。
39. flex 布局中 align-content 与 align-items 有何区别
align-content作用于纵轴多行元素,一行元素不起作用align-items作用于纵轴单行元素
40. CSS 如何实现固定长宽比的元素
可用于flex布局是,使用flex时宽度需要自适应时,可以用该属性长宽比固定
aspect-ratiodiv { 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 的浏览器提供替代内容<body> <noscript> <p>您的浏览器不支持javascript</p> </noscript> </body>
4. 数据类型有哪些
基本数据类型:
Number、String、Boolean、null、undefined、symbol、bigint引用数据类型:
Object// 考题 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方法转换后,再根据前面的规则进行比较。
console.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
===
除了比较值,还比较数据类型
console.log( undefined === null ) // false
7. 宏任务与微任务
因为js是单线程语言,它按照事件循环的机制来执行,它会先把同步任务全部执行完毕,例如if语句,for循环,全部执行完毕后,就会去寻找异步任务的代码,异步任务又分为宏任务和微任务,事件循环会先去执行微任务任务的代码,执行完毕后会去寻找宏任务的代码。执行完一个宏任务后又会去清空所有微任务,就这样循环下去
js为什么是单线程?- 因为操作 Dom 不能有两个线程同时操作,不然就不知道听哪个线程为准了
事件循环与事件队列是什么
事件循环
- 事件循环中包含宏任务、微任务
- 同步任务 -> 事件循环 [先执行所有的微任务再执行一个宏任务] ->清空微任务 -> 一个宏任务 -> 清空微任务 -> 一个宏任务...
同步任务全部执行完毕后、才执行事件循环里面的代码
事件队列
- 即执行事件循环的场所
宏任务与微任务
- 常见的宏任务
- 定时器、事件绑定、AJAX/跨域(HTTP请求)
- 常见的微任务
- queueMicrotask() 直接往微任务队列插入一条任务
- Promise相关—— .then(),catch,await、Generator中的 yield等。
- 注意:Promise本身以及resolve、reject方法并不是异步的。
// 考题 ---> 几秒打印几? 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
// 注意点 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] === [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)
function 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)
Array.isArray([]) // true Array.isArray({}) // falseinstanceof:arr instanceof Array
- instanceof也会将对象判断为数组
{} instanceof Array // false {} instanceof Object // true [] instanceof Object // trueprototype:Object.prototype.toString.call( arr ).indexOf('Array') != -1
Object.prototype.toString.call([]).indexOf('Array') != -1 // true,数组打印是[Object Array]isPrototypeOf:Array.prototype.isPrototypeOf( arr )
Array.prototype.isPrototypeOf( [] ) // trueconstructor:arr.constructor.toString().indexOf('Array') != -1
[].constructor.toString().indexOf('Array') != -1 // true
12. slice是干嘛的,splice是否改变原数组
slice(截取,包括第一项,不包括最后一项),返回的是新数组,不影响原数组
var 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(添加或删除数组中指定的元素),返回的是被删除的元素,会影响原数组
var 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
let arr = [[1, 2, 3, 4], [11, 22, 33, 44], [111, 222, 333, 444]]
arr.forEach(item => {
console.log(Math.max(...item)); // 4、44、444
})
// 2
Math.max(...[1,2,3,[2,34,6]].flat()) // 3414. 输入一串字、输出时拼接指定内容
String.prototype.addS = function () {
return '---' + this + '---'; // 谁调用了谁就是this,因此this是123
}
console.log('123'.addS()); // ---123---15. 找出字符串出现最多的字符以及字数
// 统计
let str = '11aaaaaaaaaaaaaaadddaabbbcccdddcdddddd'
let obj = {}
for (let i = 0; i < str.length; i++) {
let At = str[i]
if (obj[At]) {
obj[At]++;
} else {
obj[At] = 1
}
}
console.log(obj);
// 最大值
let max = 0;
for (let k in obj) {
if (obj[k] > max) {
max = obj[k]
}
}
// 最多的字符
for (let k in obj) {
if (obj[k] == max) {
console.log("最多的字符是" + k);
console.log("出现的次数是" + max);
}
}16. new操作符做了什么
- 创建了一个空对象
- 将空对象的 隐式原型__proto__ 指向构造函数的 显式原型prototype
- 将空对象的this指向构造函数
- 对构造函数返回值进行判断(如果返回的是对象就返回对象,不进行new操作,基本数据继续new)
// 模拟
function Create(fn, ...args) {
// 1. 创建空对象
var obj = {}
// 2. 将空对象的隐式原型 指向构造函数的显式原型
Object.setPrototypeOf(obj, fn.prototype)
// 3. 将实例的this指向构造函数
var result = fn.apply(obj, args)
// 4. 对返回值进行判断处理
return result instanceof Object ? result : obj
}
function Fun(age, name) {
this.age = age;
this.name = name;
}
console.log(Create(Fun, 18, '张三'));17. 闭包
闭包是什么
闭包是一个函数返回了另一个函数,并且返回的这个函数使用了外部函数的变量
function fx(){ var a = 10; return function fn(){ // fn 就是一个闭包函数 console.log(a) } } fx()() // 10
闭包解决了什么问题(闭包的作用)
- 外部可以访问内部的变量
- 让这些变量不被销毁,始终保存在内存中
闭包的使用场景
- 一个Ajax请求的成功回调
- 给多个绑定事件回调的方法
- 一个setTimeout的延迟回调
var 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
class P { constructor (){ this.age = 18 } } class S extends P { // 使用 extends P constructor (){ super() this.name = '砂糖' } } let o1 = new S()原型继承:s.prototype = new S()
function P(){ this.age = 20 } function S(){ this.name = '砂糖' } s.prototype = new S() // 将原型执行另一个实例,就可以访问另一个原型链的方法了 let o1 = new S()call:P.call(this)
function P(){ this.age = 20 } function S(){ this.name = '砂糖' P.call(this) // 改变P的this指向S } let o1 = new S()组合式
function 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()
会立即执行
第二个参数是数组
使用场景:数据是数组的时候
// 例如需要借用数据方法时 let arr = [1,2,3,4,5,999] Math.max( arr ) // NaN, max方法需要一个个值 Math.max.apply( null, arr ) // 999
.bind()
不会立即执行,返回的是一个函数,需要再次调用
多个参数需要挨个写
使用场景:使用回调函数时
// 使用回调函数时 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
let 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. 深拷贝与浅拷贝
浅拷贝是将地址值赋值过去
let obj1 = {a:1} let obj2 = obj1 // 这就是浅拷贝 Object.assign() // 也是浅拷贝深拷贝是将每个值都复制过去
- 使用JSON.parse(JSON.stringify())有一些问题
- 拷贝obj中的时间对象 - 时间对象会变成字符串
- 拷贝obj中的正则对象 / Error对象 - 序列化后会变成空对象
- 拷贝obj中的函数 / undefined - 序列化后会丢失
- 拷贝obj中的NaN、Infinity和-Infinity - 序列化的结果会变成null
- 如果对象中存在循环引用的情况也无法正确实现深拷贝
- 使用JSON.parse(JSON.stringify())有一些问题
// 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];
}
}
- 深拷贝函数:`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左右 |
```js:v-pre
// 本地存储
localStorage.set(key,value) // 存储
localStorage.get(key) // 查询
localStorage.removeItem(key) // 删除
localStorage.clear() // 清空
// cookie,需要在线上环境中使用
document.cookie // 获取cookie
document.cookie = 'name=123;age=456' // 设置cookie
var date = new Date()
var time = 5000;
time = date.getTime() + time
date.setTime(time)
document.cookie = 'name=123;age=123;Expires=' + date.toUTCString() + '' // 设置过期时间
// 只需要将过期时间设置为过去,即可删除cookie23. 把多维数组变为一维数组
//1. 递归
let arr = [1, 2, 3, [4, 5, [6, 7]], [8, 9, [0]]]
function flatten(arr) {
var result = [];
for (var i = 0, len = arr.length; i < len; i++) {
if (Array.isArray(arr[i])) {
result = result.concat(flatten(arr[i]))
}
else {
result.push(arr[i])
}
}
return result;
}
console.log(flatten(arr));
// 2. toString
let arr = [1, 2, 3, [4, 5, [6, 7]], [8, 9, [0]]]
function flatten(arr) {
return arr.toString().split(',').map(function (item) {
return +item
})
}
console.log(flatten(arr));
// 3. 扩展运算符
let arr = [1, 2, 3, [4, 5, [6, 7]], [8, 9, [0]]]
function flatten(arr) {
while (arr.some(item => Array.isArray(item))) {
arr = [].concat(...arr);
}
return arr;
}
console.log(flatten(arr));
// 4. flat(数组重塑),里面可以传值,代表平铺几维数组
let arr = [1, 2, 3, [4, 5, [6, 7]], [8, 9, [0]]]
console.log(arr.flat(n)) // [1,2,3,4,5,6,7,8,9,0]24. 写出冒泡排序
冒泡排序
function 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
console.log([1,2,3,4,5].indexOf(1)) // 找到了就返回索引,没有找到就返回-1include
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也是类数组
- 如
类数组转为数组
Array(...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. 事件委托(委派)是什么
如果有很多标签都要监听,将事件监听器绑定在父元素进行监听,此时数百个事件监听器变为了一个监听器,提升了网页性能。
var 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 次数过于频繁,造成计算过多,此时需要一次到位,就用到了防抖
- 文本编辑器实时保存,当无任何更改操作一秒后进行保存
// 防抖函数 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: 使用时间戳 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
function unique(arr) { if (!Array.isArray(arr)) throw new TypeError(); return [...new Set(arr)]; }filter
var 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 立即执行函数
是什么
- 声明一个函数,并马上调用这个匿名函数就叫做立即执行函数;也可以说立即执行函数是一种语法,让你的函数在定义以后立即执行
作用
- 不必为函数命名,避免了污染全局变量
- 立即执行函数内部形成了一个单独的作用域,可以封装一些外部无法读取的私有变量
- 封装变量
使用场景
var 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. 函数柯里化
什么是函数柯里化
- 说简单点就是把多参数函数,转化为单参数函数
// 柯里化之前 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的变量回收
用计数方法可能导致循环引用,类似死锁,导致内存泄露,例如
function problem() { var objA = new Object(); var objB = new Object(); objA.someOtherObject = objB; objB.anotherObject = objA; }
45. setImmediate
执行时机:
- 会在同步任务执行完毕之后、微任务执行之前执行,也就是说会比
queueMicrotask执行得更快
/* setImmediate - 参数 - 必选:回调函数,用于执行 - 可选:...[] 用于传递参数给回调函数 - 在当前的任务队列执行完成后,在 I/O 事件的回调之前执行 */ setImmediate((a, b,c) => { console.log(a, b, c) // 'name' 123 undefined }, 'name', 123, undefined)- 会在同步任务执行完毕之后、微任务执行之前执行,也就是说会比
46. 标签函数
和普通函数类似,但是调用方式是以 `` 来进行调用的
const arr = ['f', 'o', 'r', '(', ';', ';', ')', '{', '}']
function tagFn(...arg) {
// return Function(arr.join(''))
console.log(arg)
return arg.join('')
}
console.log(tagFn`12${arr}34${arr[0]}`);47. promise终极面试题
Promise.resolve().then(() => {
console.log(0);
return Promise.resolve(4);
}).then((res) => {
console.log(res)
})
Promise.resolve().then(() => {
console.log(1)
}).then(() => {
console.log(2);
}).then(() => {
console.log(3);
}).then(() => {
console.log(5);
}).then(() =>{
console.log(6);
})问题一:执行顺序能详细说说吗
第一个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. function foo(arg) { bar = "this is a hidden global variable"; } // 情况2. function foo() { this.variable = "potential accidental global"; } // foo 调用自己,this 指向了全局对象(window) foo();定时器造成内存泄露
var someResource = getData(); setInterval(function() { var node = document.getElementById('Node'); if(node) { // 处理 node 和 someResource node.innerHTML = JSON.stringify(someResource)); } }, 1000);闭包,维持函数内局部变量,使其得不到释放
function bindEvent() { var obj = document.createElement('XXX'); var unused = function () { console.log(obj, '闭包内引用obj obj不会被释放'); }; obj = null; // 解决方法 }没有清理对DOM元素的引用同样造成内存泄露
let refA = document.getElementById('refA'); document.body.removeChild(refA); // dom删除了 console.log(refA, 'refA'); // 但是还存在引用能console出整个div 没有被回收 refA = null; console.log(refA, 'refA'); // 解除引用
49. 如何执行字符串代码?
有几种方法
eval 函数
const data = eval('for(;;)')Function构造函数
const data = Function('for(;;)')()Array构造函数
const data = Array(() => 'for(;;)')[0]()Object构造函数
const data = Object(() => 'for(;;)')()
二、ES6
1. var、let、const 的区别
var 具有变量提升机制(变量悬挂声明)。而let、const没有
var可以重复声明同一个变量,后者覆盖前者。而let、const不能
var、let 声明的变量可以被修改,而const不行
var没有自身作用域。let、const有块级作用域
// 考题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 方法(有共同属性时,后者会覆盖前者)
const 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 扩展运算符
let obj2 = {...a, ...b} console.log( obj2 ) // { a:1, b:2, c:3 }自己 封装函数
function 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方法来实现一步一步类似同步的写法,方便维护
Promise.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写法
它会等待上一个结果返回后再下一个
// 定义函数,在前边加*号就是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表达式的结果。
async function del() { let { data } = await '请求' // data中就是返回的数据了 },
8. find 与 filter的区别
filter(过滤)
返回的是数组,不会改变原数组。他返回所有符合条件的值
let arr = [1,2,3,4,5,6] let newArr = arr.filter(item=>{ return item > 2 }) console.log(newArr) // [3,4,5,6]
find(寻找)
返回查找符合条件的第一个结果,不会改变原数组。
let arr = [1,2,3,4,5,6] let newArr = arr.find(item=>{ return item > 2 }) console.log(newArr) // 3
9. some 与 every 的区别
some
只需要数组中一个值符合条件 就返回true
let arr = [1,2,3,4,5,6] let newArr = arr.some(item=>{ return item > 2 }) console.log(newArr) // true
every
需要数组中所有值都符合,才为true
let 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的
arr.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)
Object.entries({0:1,1:2}) // [[0,1],[1,2]]也可以遍历循环push
let 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不可解构,会报错
数组的结构赋值
let [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对象的解构赋值
- 对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。真正被赋值的是后者,而不是前者
- 对象的解构赋值可以取到继承的属性
let { 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 接口的其他数据结构)作为参数,用来初始化。const 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():使用回调函数遍历每个成员
let 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 数据结构。const 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 实例之中。
const 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结构,他类似对象,也是键值对的集合。但键不仅限字符串,各种类型的值(对象),也可以当做键
const 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"如果对同一个键多次赋值,后面的值将覆盖前面的值
const map = new Map(); map .set(1, 'aaa') .set(1, 'bbb'); map.get(1) // "bbb"如果读取一个未知的键,则返回
undefined注意,只有对同一个对象的引用,Map 结构才将其视为同一个键。这一点要非常小心。
const map = new Map(); map.set(['a'], 555); map.get(['a']) // undefined,不是同一个数组 内存地址不一样let 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 的遍历顺序就是插入顺序。
const 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 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写,意为代理拦截
var 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()
Array(100).fill(0)Array.from()
Array.from({length: 100}, () => 0);
23. forof 与 forin的区别
- for...in
- 循环对象的话出来的是key,数组的话 是数组的下标
- 还会遍历原型上的值
- for...of
- for…of不能循环普通的对象,需要通过和OBject.Keys()搭配使用 大多数用来遍历数组
24. ?? 运算符
?? 类似于 ||
- 区别:
- ?? 在判断0时,会返回0
- ?? 在判断false时,会返回false
false || [] // [] 0 || [] // [] false ?? [] // false 0 ?? [] // 0- 区别:
24. 创建一个从0-100的数组
let x = 0;
Array.from({length: 100}, ()=> ++x)
// [0, 1, 2, 3, ..., 100]Array.from(Array(100).keys(), n => n + 1);
// [0, 1, 2, 3, ..., 100]三、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
- 服务器内部错误
// 请求码可以由后端指定,一般遵循以下规范
/*
- 1xx 请求处理中
- 2xx 请求成功
- 3xx 请求的重定向
- 4xx 客户端错误
- 5xx 服务器错误
*/8. 报文(Message)
- 报文由一行行简单的字符串组成
- 客户端发给服务器的叫请求报文(request)
- 服务器返回给客户端的叫响应报文
- 而HTTP协议就是对这个报文的格式进行规定
- 报文的组成
请求报文
打开浏览器开发者工具,发送请求后查看 请求标头,整个请求标头 就是一个请求报文
| 组成部分 | 作用 |
|---|---|
| 请求首行 | 第一行就是起始行,包含请求类型,url,协议版本。以及响应状态 |
| 请求头 | 以key:val保存了类型 |
| 空行(body) | 作为分割,将整体分为上下两部分(告知请求头结束) |
| 请求体 | 只有post请求才有请求体,通过请求体携带数据 |
// 请求首行, 包含了请求方式, 请求地址, 协议版本
GET /s?name=zhangsan HTTP/1.1
// 以下都是请求头
/*
Accept: 允许浏览器接受的文件类型
Accept-Encoding: 浏览器允许的压缩编码
Accept-Language: 浏览器允许的语言
User-Agent: 用户代理, 他是一段用来描述浏览器信息的字符串
*/
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Cache-Control: no-cache
Connection: keep-alive
Cookie: ""
Host: www.baidu.com
Pragma: no-cache
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: none
Sec-Fetch-User: ?1
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36
sec-ch-ua: "Not?A_Brand";v="8", "Chromium";v="108", "Google Chrome";v="108"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"响应报文
格式与请求报文类似
// 响应首行、 协议版本, 响应状态码, 响应的描述
HTTP/1.1 200 OK
// 响应头
/*
- Date 响应时间
- Content-Type 描述响应体的类型
- Content-Length 描述响应体的大小
*/
Date: Sat, 10 Dec 2022 11:35:19 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 1072
Transfer-Encoding: chunked
Connection: keep-alive
Server: nginx
Vary: Accept-Encoding
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
X-UA-Compatible: chrome=1
Expires: Sun, 1 Jan 2000 01:00:00 GMT
Pragma: must-revalidate, no-cache, private
Cache-Control: no-cache
Set-Cookie: oschina_new_user=false; path=/; expires=Wed, 10 Dec 2042 11:35:18 -0000
Set-Cookie: gitee-session-n=OGIyT0NodGl0SHowdHlmemdGMHpmbGVyVFFMZ3MzRWNYRzdoMHpFY25wVVI2MUtWd3I2M3BnaWRQM1JIZHJjNFlxOWMxN2Zrb0cxd2NPRGtVa282NkJ5M2IrQWI3KzBpY3BGMTBWL1Z2UlZ5czBiZUtMbTZ5OWJaVlJpakJTZWt5TU9XdGFHK0RVT0hZeHlaOExUUXRjc0EraXlEZy8xT0pTYkF0SW9hOTd0MlRMalBvZWVLaW9Hc2E1cVN2RVhyY285ZEp6OUlmQWJ4NHNuOUw5YUhnOFgzeHJYSTU1NUZldURaQkFyR0J4UExMVml5bS9UbitEUUhmSDRCYjduaTUra2dEeE9yaFBuTHBsV1paN1RrSjNIZ0QzRUpYWmpodGdjQXVVdjh0Z2tlelpodDFLdHNpQzNJdS94L2xJd0luU2UyRVFEcC9nWGpGSmtqZDQ2TlBRMmhTQ0tyQ3Y3cE44M3BDMmVJTm5ER3RWdTZuQkxXM0dCdFZ5WWRsaGxYbEVHM2licE0wVlV0TWZ6dlZQZzhCZDZFRHhCOVI4WmdNRy9aaEk3eUFKOWV2b29rY0gvcmVLTGpzNTJGN3RyV09mcHRZL25xdmlEM05BN1Yvc2VpV0RTd2xsU1RhZWxmajVVQ0J0dmJ2cE9YSTNPUjhKbnByc2tGMC9uazl6Rlk5VnJMVU5YQktzTTc0RWpteDhmejlaelhmUCtKSXJSa3M4aUtrZklFaTJxZmU2NjNpQllkMkI5Nk13SkFacHllTms0NFVLUGJDMEJHWDVmcTFlR3IvZ2RDZUVnamc2TU1MbGhpNU80dFA4Zz0tLVhGT21NMkVkaEJsSS9sU0F1Wnp2a1E9PQ%3D%3D--474a140d89de63b99e2d38cee50252b30c9c59d1; domain=.gitee.com; path=/; HttpOnly
X-Request-Id: d6203f25d6be563483f41e6866e7c038
X-Runtime: 0.573366
X-Frame-Options: SAMEORIGIN
Content-Security-Policy: frame-ancestors 'self' https://*.gitee.com
Content-Encoding: gzip9. 请求头常见的内容
| 请求类型 | 作用 |
|---|---|
| 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,这样父元素就会在水平和垂直方向上居中。然后,将子元素设置为盒子即可。
下面是一个示例:
.parent {
display: flex;
align-items: center;
justify-content: center;
}
.child {
width: 100px;
height: 100px;
}
<div class="parent">
<div class="child">I am centered</div>
</div>如果你希望盒子在垂直方向上居中,但在水平方向上靠左或靠右,则可以使用 justify-content: flex-start 或 justify-content: flex-end。
如果你想使用其他布局方式来实现居中,也可以使用 position: absolute 和 transform: translate(-50%, -50%)。
.parent {
position: relative;
}
.child {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}还有其他一些方法可以实现居中,但上述方法是最常用的。
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 元素来实现。
在使用视频组件时,通常可以指定视频的源文件地址、视频的宽度和高度、是否自动播放等参数。视频组件还可以支持一些常用的控制功能,如播放、暂停、快进、倒退等。
例如,可以使用以下代码来创建一个视频组件:
<video src="video.mp4" width="480" height="360" autoplay></video> 这样,当网页加载时,就会在网页中自动播放 video.mp4 这个视频文件,但是各个浏览器会有所限制。据我所知,谷歌浏览器是禁用自动播放的。QQ浏览器没有禁用。
但还是有些网站也可以实现自动播放,比如B站。我之间了解过,是浏览器中维护了一个名单,这些名单里有常见的域名权重。当权重到达一定时,就不会禁用这些域名的自动播放。
vue中可以使用类似:vue-core-video-player 的组件
也可以回答,业务中没接触过此业务,但看过vue生态中有对应的组件库,如果需要可以很快学会。视频播放是前端的一个细分领域,有兴趣的了解下名称,开阔视野即可
为什么会跨域 ?
本质:跨域是浏览器基于同源策略的一种安全手段。
同源策略:
本质:是一种约定, 浏览器 的一种⽤于隔离潜在恶意⽂件的重要安全保护机制。
含义:即指在同一个域
三个相同点:
协议相同(protocol)
主机相同(host)
端口相同(port)
反之非同源请求,也就是协议、端口、主机其中一项不相同的时候,这时候就会产生跨域
不受同源策略影响的有哪些
除了以下三个资源获取类型的标签,在浏览器中,⼤部分内容都受同源策略限制。
<img><link><script>
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运算符来检查一个变量的数据类型。
例如:
let num = 42;
console.log(typeof num); // "number"
let str = "hello world";
console.log(typeof str); // "string"
let bool = true;
console.log(typeof bool); // "boolean"
let obj = {name: "John", age: 30};
console.log(typeof obj); // "object"
let arr = [1, 2, 3];
console.log(typeof arr); // "object"
let func = function() {};
console.log(typeof func); // "function"
let undef;
console.log(typeof undef); // "undefined"
let nul = null;
console.log(typeof nul); // "object"注意,使用typeof运算符检查数组和null的数据类型时会返回"object"。因此,如果你想要确定一个变量是否是数组或null,你可以使用其他方法,例如使用Array.isArray()方法来检查一个变量是否是数组,或者使用双等号(==)进行比较来检查一个变量是否是null。
例如:
let arr = [1, 2, 3];
console.log(Array.isArray(arr)); // true
let nul = null;
console.log(nul == null); // true此外,JavaScript还有一种特殊的数据类型叫做符号(Symbol),它用于创建唯一的值。你可以使用Symbol()函数来创建一个符号
例如:
let sym = Symbol();
console.log(typeof sym); // "symbol"符号是JavaScript的一种新的数据类型,可以用来创建唯一的标识符,例如在对象的属性名称中使用。因为符号是唯一的,所以你可以使用它来防止属性名称冲突。
例如:
let sym1 = Symbol();
let sym2 = Symbol();
let obj = {
[sym1]: "hello",
[sym2]: "world"
};
console.log(obj[sym1]); // "hello"
console.log(obj[sym2]); // "world"注意,使用符号作为对象属性名称时,需要在属性名称前后添加方括号,这样才能正确地将属性名称解析为符号。
2、回流和重绘
回流(reflow)是指当浏览器渲染HTML页面时,对页面布局和几何的计算过程。当页面中的元素的大小、位置或布局方式改变时,浏览器就需要进行回流来调整布局。
重绘(repaint)是指当浏览器渲染HTML页面时,对页面中元素的外观进行更新的过程。当页面中元素的颜色、字体或其他外观属性改变时,浏览器就需要进行重绘来更新外观。
回流和重绘都是浏览器在渲染HTML页面时执行的必要过程,但是它们都有一定的开销,因此应该尽量避免不必要的回流和重绘。例如,你可以使用优化的CSS选择器和结构来减少回流的次数,或者使用缓存来避免重复的重绘。
注意,在JavaScript中有一些操作会触发回流和重绘
例如:
- 改变HTML元素的尺寸或位置,例如使用offsetWidth、offsetHeight、offsetLeft、offsetTop等属性。
- 改变HTML元素的布局方式,例如使用display、position、float等属性。
- 改变HTML元素的外观,例如使用color、background-color、font-size等属性。
你可以使用浏览器的开发工具来查看页面的回流和重绘次数,以及每次回流和重绘的原因。这有助于你发现并优化页面的性能问题。
此外,我们可以使用 requestAnimationFrame() 函数来让浏览器在下一次重绘之前执行特定的代码。这可以让你在更新页面元素的外观时,将多个操作合并成一次重绘,从而减少重绘的次数。例如:
function updatePage() {
// 更新页面元素的外观
...;
requestAnimationFrame(updatePage);
}
updatePage();注意,
requestAnimationFrame()函数是异步执行的,因此你不能依赖它来控制代码的执行顺序。
3、闭包
闭包是一种特殊的对象,它包含了一个函数和与该函数相关的引用环境。
闭包的函数可以访问闭包创建时的环境,即使在该函数被调用时,外部的环境已经发生了变化。这使得闭包很适合用来做回调函数或者保存状态。
举个例子,假设你有一个函数,它接受一个数字作为参数,并返回一个函数。该返回的函数可以对传入的数字进行累加,每次调用都会将累加器加1
function createAdder(x) {
return function(y) {
return x + y;
}
}
const add = createAdder(5);
console.log(add(2)); // 7
console.log(add(5)); // 10 在上面的例子中,createAdder 函数创建了一个闭包,该闭包包含了一个函数和与该函数相关的环境。这个环境包含了变量 x 的值。当我们调用 createAdder(5) 时,它返回了一个新函数,这个函数可以对传入的参数 y 进行累加。在这个函数中,变量 x 的值是 5,所以调用 add5(2) 和 add5(10) 时,都会将它们的参数与 5 相加。
闭包是一种非常有用的技术,它可以帮助你保存状态、创建封装的函数等
4、原型和原型链
在JavaScript中,每个对象都有一个原型(prototype),它是另一个对象。原型对象可以包含属性和方法,这些属性和方法可以被对象继承。这种继承机制被称为原型链(prototype chain)。
例如,你可以定义一个Person构造函数来表示人,并在Person.prototype中定义sayHello方法,表示人会说话:
function Person(name) {
this.name = name;
}
Person.prototype.sayHello = function() {
console.log(`Hello, my name is ${this.name}`);
};
let john = new Person("John");
john.sayHello(); // "Hello, my name is John"在上面的例子中,john是Person类型的对象,它继承了Person.prototype中的sayHello方法。你可以使用instanceof运算符来检查一个对象是否是某个类型的实例
例如:
console.log(john instanceof Person); // true注意,每个对象都有一个constructor属性,表示该对象的构造函数。你可以使用constructor属性来检查一个对象的类型,
例如:
console.log(john.constructor === Person); // true此外,每个对象还有一个__proto__属性,表示该对象的原型。你可以使用__proto__属性来检查一个对象的原型对象,
例如:
console.log(john.__proto__ === Person.prototype); // true5、this指向问题
在 JavaScript 中,this 关键字的指向可能有以下几种情况:
在全局作用域中,this 指向全局对象。在浏览器中,全局对象是 window,在 Node.js 中,全局对象是 global。
在函数中,默认情况下,this 指向全局对象。但是,如果将函数作为对象的方法调用,则 this 指向调用方法的对象。
在箭头函数中,this 的指向是定义时所在的作用域的 this。箭头函数没有自己的 this,因此它永远不会改变指向。
可以使用 call、apply 或 bind 方法显式地指定 this 的指向。
例如:
const obj = {
name: 'John',
greet: function() {
console.log(`Hello, ${this.name}!`);
}
};
obj.greet(); // Hello, John! 因为是obj调用的greet方法,所以greet方法内部的this指向的是obj
const greet = obj.greet;
greet(); // Hello, undefined!,因为greet()是通过window调用的,所以this指向window
const arrowGreet = () => {
console.log(`Hello, ${this.name}!`);
};
arrowGreet(); // Hello, undefined! this指向window
const boundGreet = obj.greet.bind(obj);
boundGreet(); // Hello, John! // this被bind修改为了obj,所以this指向了obj6、数组中forEach和map
forEach 和 map 是 JavaScript 中常用的数组方法,都可以用来遍历数组中的元素,但是它们之间有一些区别:
功能不同:forEach 方法是遍历数组的方法,它提供了一种快捷的方式来遍历数组的所有元素。而 map 方法是映射数组的方法,它可以根据指定的规则对数组的每个元素进行映射,并返回一个新的数组。
返回值不同:forEach 方法不会返回任何值,只会对数组的每个元素执行回调函数。而 map 方法会返回一个新的数组,该数组的元素是对原数组中每个元素执行回调函数之后的返回值。
处理流程不同:forEach 方法是按顺序依次对数组的每个元素执行回调函数。而 map 方法是同时对数组的每个元素执行回调函数,并将结果放入新的数组中。
总的来说,forEach 和 map 是两种常用的数组方法,但是它们的功能、返回值和处理流程都有所不同。根据自己的需要,可以选择适合自己的方法进行使用。
7、 call、bind、apply ( 函数上下文调用模式 )
JavaScript 提供了三种方法来改变函数的调用上下文:call、bind 和 apply。
call方法允许您将函数的调用上下文指定为某个对象,并传递一些参数给函数。这样可以在不同的上下文中调用同一函数。例如:function 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类似,但它并不立即调用函数,而是返回一个新函数,该函数的调用上下文已被指定为您提供的对象。例如:const greetJohn = greet.bind(person); console.log(greetJohn('Hello')); // "Hello, John!"
在上面的示例中,我们使用 bind 方法将函数 greet 的调用上下文指定为 person 对象,并返回了一个新函数 greetJohn。当我们调用 greetJohn 时,它的调用上下文已被指定为 person,并返回 "Hello, John!"。
apply方法与call类似,但它允许您使用数组来传递参数,而不是使用单独的参数列表
另外,还有几点需要注意:
在使用 call 或 apply 时,您必须立即调用函数,而 bind 方法则返回一个新函数。
在使用 bind 方法时,您可以通过在绑定时提供额外的参数来为新函数指定参数。例如:
const greetJohnHello = greet.bind(person, 'Hello'); console.log(greetJohnHello()); // "Hello, John!"在这种情况下,新函数 greetJohnHello 将固定使用参数 'Hello',无论您在调用时提供了什么参数。\
8、new操作符具体过程:
使用 new 操作符创建一个新对象的过程如下:
- 创建一个新对象。
- 将这个新对象的原型设置为构造函数的原型。
- 将这个新对象的 this 设置为这个新对象。
- 如果构造函数返回了对象,则返回这个对象;否则,返回这个新对象。
例如,下面是使用 new 操作符创建一个新对象的示例:
function Person(name) {
this.name = name;
}
Person.prototype.greet = function() {
console.log(`Hello, my name is ${this.name}`);
};
const john = new Person('John');
john.greet(); // "Hello, my name is John" 在这个例子中,我们定义了一个构造函数 Person,然后使用 new 操作符创建了一个新的 Person 对象,并将其赋值给变量 john。这个新对象继承了 Person.prototype 上的属性和方法,并且其 this 被设置为这个新对象。
注意,使用 new 操作符创建对象时,构造函数中的代码会在新对象上执行,并且新对象会继承构造函数的原型。因此,在上面的示例中,新对象 john 具有名为 name 的属性,并且可以调用 Person.prototype 上的 greet 方法。
在使用 new 操作符时,还有几点需要注意:
如果构造函数返回了对象,则返回这个对象,而不是这个新对象。这意味着,如果构造函数中的代码显式地返回了一个对象,则这个对象将作为最终结果返回,而新创建的对象将被忽略。例如:
function Person(name) { this.name = name; return { greeting: 'Hello' }; } const john = new Person('John'); console.log(john); // { greeting: 'Hello' }
在这个例子中,构造函数返回了一个对象,因此最终结果是这个对象,而不是新创建的对象。
如果构造函数返回了原始值(例如数字、字符串或布尔值),则忽略该值并返回新对象。例如:
function Person(name) { this.name = name; return 42; } const john = new Person('John'); console.log(john); // Person { name: 'John' }在这个例子中,构造函数返回了数字 42,但最终结果仍然是新创建的对象。
如果构造函数没有返回值,则默认返回新对象。例如:
function Person(name) { this.name = name; } const john = new Person('John'); console.log(john); // Person { name: 'John' }在这个例子中,构造函数没有返回值,因此最终结果是新创建的对象。
总之,new 操作符创建一个新对象并使用构造函数初始化它。它还会将新对象的原型设置为构造函数的原型,并且在构造函数执行时将 this 设置为新对象。如果构造函数返回了对象,则返回这个对象;否则,返回新对象。
使用 new 操作符是 JavaScript 中常见的模式,可以使用它来创建具有共同属性和方法的对象,并使用构造函数的原型链来为这些对象提供共享的功能。
9、浅拷贝和深拷贝
深拷贝和浅拷贝是指在复制对象时,复制的方式不同。
浅拷贝只是复制对象的引用,并不会复制对象本身。如果对象是基本类型,则复制的是值本身;如果对象是引用类型,则复制的是指向对象内存地址的指针。因此,如果对象是引用类型,则拷贝后的对象和原对象指向的是同一块内存,任意一方的修改都会影响另一方。
深拷贝则是完全复制了对象本身,包括对象内部的所有属性和方法。深拷贝后的对象和原对象没有任何关联,对其中一方的修改不会影响另一方。
在 JavaScript 中,可以使用以下方法实现深拷贝:
使用 JSON 序列化和反序列化。这种方法适用于简单对象和数组,但不能复制函数、正则表达式、日期等对象。
const deepCopy(obj) { return JSON.parse(JSON.stringify(obj)) }使用递归算法。这种方法可以复制任意类型的对象
...
另外,JavaScript 中还有一种浅拷贝的方法:使用Object.assign()函数。这个函数可以把一个或多个源对象的所有可枚举属性复制到目标对象中。
let obj = { a: 1, b: 2 }
let copy = Object.assign({}, obj)这种方法只能复制对象的属性,对于对象的方法和属性值为对象的属性,不会进行复制。
10、js事件循环
JavaScript 是一种单线程语言,这意味着只能执行一个任务。但是,JavaScript 引擎会在后台持续执行一些任务,例如响应用户的输入、下载网络资源等。这些任务是由事件循环管理的。
事件循环是一种机制,用于在执行主线程的同时处理异步任务。它按照以下步骤工作:
- 主线程执行同步任务。
- 当遇到异步任务时,将其排入队列中。
- 当主线程空闲时,从队列中取出第一个任务并执行。
- 当任务完成时,如果有回调函数,则将其排入队列中。
- 重复步骤 1-4,直到主线程的任务完成。
例如,下面是一个使用事件循环的示例:
console.log('Start');
setTimeout(function() {
console.log('Timeout');
}, 1000);
console.log('End');在这个例子中,事件循环会执行以下步骤:
执行 console.log('Start')。
遇到 setTimeout 函数,将其排入队列中。
执行 console.log('End')。
一秒钟后,从队列中取出第一个任务并执行 console.log('Timeout')。
11、防抖和节流
防抖和节流是两种常见的控制函数执行频率的方法,常用于限制用户的输入频率或频繁触发的事件。
防抖(debouncing)是指在一定时间内只执行最后一次触发事件,在这个时间内如果有新的触发事件,会取消之前的触发事件并重新计时。防抖通常用于限制输入频率,比如在输入框中输入文本,或者在搜索框中输入关键字。
function 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)是指在一定时间内只执行一次事件,在这个时间内如果有新的触发事件,会忽略这个事件,等到这个时间结束后才会执行。节流通常用于限制事件触发的频率,比如在滚动条事件中执行实时网络请求,调整窗口大小事件等。
function 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):这是一种简化函数的语法,它比传统的函数定义更短、更简洁
const greet = name => `Hello, ${name}!`;块级作用域:在 ES6 中,你可以使用 let 和 const 在代码块(如 for 循环或 if 语句)内声明变量。这意味着这些变量只在声明它们的代码块内可用,而不是整个函数内可用。
类(Classes):ES6 中引入了类的概念,使得创建和维护对象变得更加容易。你可以使用类来声明新的对象类型,并使用类的构造函数来创建新的对象实例。
模板字符串(Template literals):模板字符串是一种新的字符串表示方式,使用反引号(`)包围,允许模板字符串内插变量。例如,你可以使用如下的模板字符串来创建带有变量的字符串
const name = 'John'; console.log(`Hello, ${name}!`); // 输出 "Hello, John!"解构赋值(Destructuring assignment):解构赋值是一种方便的方法,可以在一个表达式中提取数组或对象的多个值。例如,你可以使用解构赋值将对象的属性赋值给变量
const 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 的使用示例:
const set = new Set([1, 2, 3, 4, 5]);
console.log(set.has(3)); // true
console.log(set.has(6)); // false
set.add(6);
set.delete(5);
for (const value of set) {
console.log(value);
}
/*
在这个示例中,我们创建了一个 Set 对象,并通过构造函数传入了一个数组 [1, 2, 3, 4, 5]。
然后我们使用 Set 的 has() 方法来检查 Set 中是否存在某个元素。
接下来我们使用 add() 方法向 Set 中添加一个新元素 6,
并使用 delete() 方法删除 Set 中的元素 5。
最后我们使用 for-of 循环遍历 Set 中的所有元素。
*/
以下是一个简单的 Map 的使用示例:
const map = new Map([
['name', 'John'],
['age', 30]
]);
console.log(map.get('name')); // John
console.log(map.get('age')); // 30
map.set('country', 'USA');
map.delete('age');
for (const [key, value] of map) {
console.log(`${key}: ${value}`);
}
/*
在这个示例中,我们创建了一个 Map 对象,并通过构造函数传入了一个数组 [ ['name', 'John'], ['age', 30] ]。
然后我们使用 Map 的 get() 方法来获取 Map 中的元素值。
接下来我们使用 set() 方法向 Map 中添加一个新的键值对 ['country', 'USA'],
并使用 delete() 方法删除 Map 中的键值对 ['age', 30]。
最后我们使用 for-of 循环遍历 Map 中的所有元素。
*/14、点击穿透现象及解决办法
点击穿透是指在使用移动设备浏览网页时,当用户点击屏幕上的某一元素时,点击事件会同时触发下方的元素。这种现象通常发生在设计中使用了透明或半透明背景的元素上。
要解决这个问题,你可以采取以下几种方法:
使用不透明的背景:为触发点击穿透的元素使用不透明的背景,这样就不会出现点击穿透的现象了。
给元素添加边框:为触发点击穿透的元素添加边框,这样就可以使用边框来阻止点击事件穿透到下方的元素。
使用 JavaScript 来阻止点击穿透:可以使用 JavaScript 代码来阻止点击事件穿透到下方的元素。
- 你可以在元素上添加一个点击事件监听器,并在事件处理函数中调用
event.preventDefault()方法来阻止默认的点击行为。
- 你可以在元素上添加一个点击事件监听器,并在事件处理函数中调用
使用 CSS 属性
pointer-events: none:你可以使用这个 CSS 属性来禁用元素的点击事件。这样就可以阻止点击穿透的现象了。使用 CSS 伪类 :
active:你可以使用 :active 伪类来模拟点击效果,从而避免点击穿透的现象。你可以在元素的样式中使用 :active 伪类来设置点击时的样式,例如:button:active { background-color: blue; } /* 这样,当用户点击按钮时,按钮的背景颜色会变成蓝色。这种方法可以在不使用 JavaScript 的情况下解决点击穿透的问题。*/
总之,点击穿透是一个常见的问题,但是有很多方法可以解决这个问题。你可以根据自己的需求来选择最合适的方法来解决点击穿透的问题。
15、js的继承
原型链继承:让新实例的原型等于父实例
可继承:实例的构造函数的属性,父类构造函数属性,以及父类原型的属性
不可继承:父类实例的属性
借用构造函数继承:使用 apply()和 call()方法将父类构造函数引入子类函数
可继承:父类构造函数的属性
不可继承:父类原型的属性
组合继承:将原型链和借用构造函数的技术组合在一块,从而发挥两者之长的一种继承模式
原型式继承:借助原型可以基于已有的对象创建新对象,类似复制了一个对象
寄生式继承:就是给原型式继承外面套个壳子,没用到原型
寄生组合式继承:通过借用函数来继承属性,通过原型链的混成形式来继承方法(常用)
16、如何理解promise
Promise 是一种用于异步编程的解决方案,它可以让你以同步的方式编写异步代码。
在 JavaScript 中,很多操作都是异步的,例如网络请求、读取文件或等待用户输入。这些操作会在后台运行,因此你不能直接等待它们完成,而是需要提供一个回调函数,在操作完成时调用。
Promise 可以解决这种回调地狱的问题,它使用一种称为链式调用的方法,让你能够在 then 方法中连续调用多个异步操作。
例如,你可以使用 Promise 发起一个网络请求,然后在请求完成后调用回调函数。你可以使用 then 方法来处理请求结果,如下所示:
axios.get('http://example.com/movies.json')
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error(error)); 在这个例子中,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 来等待一个异步函数的执行结果:
async function getData() {
const result = await fetch('https://example.com/data');
const data = await result.json();
return data;
}在这个例子中,getData 函数是一个异步函数,它会发送一个 HTTP 请求获取数据,然后使用 await 关键字等待 HTTP 请求的结果。
async/await 语法的优点在于可以让异步代码看起来像同步代码一样,更容易阅读和维护。你可以在 await 后面跟任何返回 Promise 对象的函数或表达式,例如 fetch 或者自定义的异步函数。
需要注意的是,async/await 语法只能在 async 函数内部使用,如果在其他地方使用会导致语法错误。另外,使用 await 关键字时,必须确保在 await 后面跟的是一个返回 Promise 对象的函数或表达式,否则会抛出错误。
使用 async/await 语法时,你还可以使用 try/catch 语句来捕获异步函数中可能出现的错误。例如:
async 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。
例如,你应该使用如下的方式声明变量:
// 声明局部变量 let count = 0;
// 声明常量 const PI = 3.14;
// 声明全局变量 let globalVar;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',可写,可枚举,可配置:
let obj = {};
Object.defineProperty(obj, 'foo', {
value: 'bar',
writable: true,
enumerable: true,
configurable: true
});除了上述属性,descriptor 对象还可以包含两个函数:
get: 当读取属性值时调用的函数。这个函数没有参数,并返回属性值。
set: 当写入属性值时调用的函数。这个函数有一个参数,表示要写入的新值。
例如,下面的代码使用 Object.defineProperty 方法在对象 obj 上定义了一个名为 foo 的属性,该属性的值由 get 和 set 函数控制:
let obj = {};
Object.defineProperty(obj, 'foo', {
get: function() {
return this._foo;
},
set: function(value) {
this._foo = value;
}
});请注意,如果同时定义了 value、writable 和 get、set 函数,则会抛出错误。你只能选择定义属性的值或访问器函数。
28、纯函数是什么
纯函数就是一个函数,只不过具有一些特点,你可能平时开发中都有用到,只是没有意识到这是一个纯函数。
纯函数(Prue function)具有以下特点:
纯函数每一次调用时传入同样的参数,返回的都是同样的结果;它不会改变参数的值,也不会改变外部变量的值;它不会依赖于外部的变量,仅依赖于你传入的参数;
纯函数没有其他副作用(side effect)
如果你每次传入的参数一样,但是返回的结果不一样,则不是一个纯函数
这是一个纯函数
/*
它没有改变外部变量的值
每次调用时如果传递的值相同,那每次返回的结果也相同
*/
let a = 1;
let b = 2;
function add(x, y) {
return x + y;
}
add(a, b);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的前端,实现即时通讯的方式:
webSocket (主流):性能和效率都高!参考:https://blog.csdn.net/m0_57712926/article/details/120690725websoket具体用法参考:https://blog.csdn.net/lingshengxueyuan/article/details/107382030?spm=1001.2101.3001.6661.1&utm_medium=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-1-107382030-blog-123863493.pc_relevant_aa_2&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-1-107382030-blog-123863493.pc_relevant_aa_2&utm_relevant_index=1
短轮询 (历史方案)开个定时器, 每隔一段时间发请求 (实时性不强)
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)是一种机制,可以在父组件的模板中定义一个占位符,然后在子组件中插入内容。这样,子组件就可以在父组件的模板中渲染内容,使得子组件可以更灵活地与父组件进行交互。
插槽有两种类型:
具名插槽:具名插槽使用特定的名称来标识,子组件可以插入到特定的插槽中。
默认插槽:默认插槽没有名称,子组件中的内容会插入到默认插槽中。
在父组件的模板中,可以使用 <slot> 元素来定义插槽。例如:
<template>
<div class="parent-component">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
</template>在上面的例子中,父组件有三个插槽:一个不具名插槽和两个具名插槽。在子组件中,可以使用 <template> 元素来定义要插入的内容。例如:
<template>
<div class="child-component">
<template #header>
<h1>这是子组件的头部</h1>
</template>
<p>这是子组件的主体内容</p>
<template #footer>
<p>这是子组件的底部</p>
</template>
</div>
</template>vue组件中data为什么必须是一个函数:
在 Vue.js 中,组件的 data 选项必须是一个函数,这是因为每个组件实例都应该有自己的状态,如果 data 不是一个函数,那么所有实例将共享同一个数据对象,这会导致组件之间的状态混乱。
举个例子,假设你有一个组件 A 和组件 B,如果你把 data 写成这样:
data: {
message: 'Hello'
}那么组件 A 和组件 B 中的 message 都是同一个值,如果在组件 A 中修改了 message,那么组件 B 中的 message 也会改变。这显然不是你想要的结果。
因此,Vue.js 要求 data 选项必须是一个函数,这样每个组件实例都可以有自己的 data 对象。你可以这样写:
data: function () {
return {
message: 'Hello'
}
}这样就能保证每个组件实例都有自己的 data 对象,组件之间的状态就不会混乱了。
Vue 数据双向绑定的原理
在 Vue.js 中,数据双向绑定是通过使用观察者模式来实现的。观察者模式是软件设计模式的一种,它允许对象之间的一对多关系。在这种关系中,一个对象(称为发布者)维护一组依赖于它的对象(称为订阅者),并在其状态更改时通知所有订阅者。
在 Vue.js 中,发布者是 Vue 实例,订阅者是观察者。当 Vue 实例的数据发生变化时,Vue 会通知观察者,观察者会更新视图。这样,当用户在视图中进行操作时,Vue 会更新数据,并通知观察者更新视图。
为了实现数据双向绑定,Vue 使用了一种叫做劫持的技术。劫持就是在数据变化时捕获数据并通知观察者。Vue 实现劫持的方法是使用 Object.defineProperty() 函数,该函数允许在对象的属性被访问或修改时进行拦截。
当 Vue 实例被创建时,它会在内部遍历所有数据并使用 Object.defineProperty() 函数为每个属性添加 getter 和 setter。这样,当数据被访问或修改时,Vue 就可以捕获数据并通知观察者。
例如,当你在 Vue 实例中声明一个属性 message:
data: 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。
import 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()。
import 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。
export 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 指令:
<template>
<input v-model="message">
</template>
<script>
export default {
data() {
return {
message: 'Hello, world!'
}
}
}
</script>实现原理
v-model 指令的实现原理是基于计算属性和事件绑定。它会将表单元素的 value 属性绑定到一个计算属性上,并且在计算属性的 setter 中触发 input 事件来更新 Vue 实例中的数据。
例如,上面的代码实际上等价于:
<template>
<input :value="message" @input="message = $event.target.value">
</template>
<script>
export default {
data() {
return {
message: 'Hello, world!'
}
}
}
</script>这样,当用户在输入框中输入文本时,输入框的 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监听
// 监听,当路由发生变化的时候执行
watch:{
$route(to,from){
console.log(to.path);
}
},
// 或者
// 监听,当路由发生变化的时候执行
watch: {
$route: {
handler: function(val, oldVal){
console.log(val);
},
// 深度观察监听
deep: true
}
},
// 或者
// 监听,当路由发生变化的时候执行
watch: {
'$route':'getPath'
},
methods: {
getPath(){
console.log(this.$route.path);
}
}通过路由钩子函数监听
export default {
name: 'app',
// 监听,当路由发生变化的时候执行
beforeRouteEnter (to, from, next) {
// 在渲染该组件的对应路由被 confirm 前调用
// 不!能!获取组件实例 `this`
// 因为当钩子执行前,组件实例还没被创建
},
beforeRouteUpdate (to, from, next) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
// 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 可以访问组件实例 `this`
},
beforeRouteLeave (to, from, next) {
// 导航离开该组件的对应路由时调用
// 可以访问组件实例 `this`
}nexttick的理解
nextTick 是 Vue.js 中一个用来在下一个事件循环中调用回调函数的方法。这个方法通常用在等待数据或 DOM 元素更新之后执行一些代码的情况下。
举个例子,假设你有一个组件,它的模板中使用了一个计算属性 fullName,该计算属性由组件中的 firstName 和 lastName 两个数据项拼接而成:
<template>
<div>{{ fullName }}</div>
</template>
<script>
export default {
data() {
return {
firstName: 'John',
lastName: 'Doe'
}
},
computed: {
fullName() {
return `${this.firstName} ${this.lastName}`
}
}
}
</script> 假设你想要在更新 firstName 和 lastName 后执行一些代码,但你发现 Vue 的计算属性是同步的,所以你无法在计算属性更新之后立即执行代码。这时你就可以使用 nextTick 方法:
this.firstName = 'Jane'
this.lastName = 'Doe'
this.$nextTick(() => {
// 在这里执行代码
console.log(this.fullName)
})这样你就可以在计算属性更新之后执行代码了。
你可能会在组件的生命周期钩子函数 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 属性,如下所示:
<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方式
自定义指令的使用以及生命周期/钩子函数
分类:全局自定义指令,局部自定义指令
全局自定义指令
Vue.directive('指令名', {
// 当被绑定的元素插入到 DOM 中时……
inserted: function (el) {
// 操作
}
})局部自定义指令
directives: {
指令名: {
// 指令的定义
inserted: function (el) {
// 操作
}
}
}生命周期/钩子函数
- 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 属性提供一个工厂函数来实现这一点,例如:
const SomeComponent = () => ({
// 这个组件会在被访问时异步加载
component: import('./some-component.vue'),
loading: SomeLoadingComponent,
error: SomeErrorComponent,
delay: 200,
timeout: 3000
})接下来,你可以在路由配置中使用这个异步组件。例如:
const router = new VueRouter({
routes: [
{
path: '/some-route',
component: SomeComponent
}
]
})这样,当你访问 /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 守卫
const routes = [ { path: '/users/:id', component: UserDetails, beforeEnter: (to, from) => { // reject the navigation return false }, }, ]【选答-优秀】组件内的守卫
beforeRouteEnter
beforeRouteUpdate
beforeRouteLeave
const 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实现的
参考:https://juejin.cn/post/7098575243240800286
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,如果在
<script setup>中编写可以直接使用而无需return在 script setup 语法糖中,引入的组件可以自动注册,不需要再通过 components 进行注册,而且无法指定当前组件的名字,会自动以文件名为主,省去了 name 属性。如果需要写name,可以直接在script标签上添加name属性
vue-router传参方式有哪些?
编程式路由导航
动态路由-路径拼接传参
传:this.router.push({path: `/xxx/{id}`)
接:this.$route.params.id
动态路由-params属性传参
传:this.$router.push({name: 'xxx', params: {id: 值})
接:this.$route.params.id
query属性传参
传:this.$router.push({ name: 'xxx', query: { id: 值 } })
接:this.$route.query.id
声明式路由导航
- 声明式的导航:
- 跳转:
<router-link to="news"></router-link> - params:
<router-link :to="{name:'news',params:{userid:1111}}"></route-link> - query:
<router-link :to="{path:'/news',query:{userId:1111}}"></router-link>
- 跳转:
vuex第一次刷新导致首页空白
【回答此句-良好】我举个例子吧,在我们做后台管理系统时,通常会采用动态添加路由的方法来实现,此方法会在登陆时利用vuex将用户信息以及菜单栏需要展示的菜单路由保存,当刷新页面的时候,vuex数据会丢失,所以动态添加路由这一步也就失效了,就会出现白屏现象。
【回答此句-优秀】只需要添加路由守卫,在每次页面刷新的时候都来判断是否添加的动态路由,如果需要即再次执行一遍添加动态路由的过程即可。
前置路由守卫
路由跳转之前, 会触发的一个函数 叫前置路由守卫
语法:
router.beforeEach((to, from, next) => {这里可以写路径的跳转判断/有无token值的情况分析})- to : 到哪里去
- from:从哪里来的
- next:放行函数 next():放行 , next(false):不放行
作用 : 防止别人猜到网址的hash值后直接跳过登录就可以查看数据
参考:https://blog.csdn.net/m0_65132206/article/details/125023965
计算属性与watch的区别
computed是用来计算出来一个值的,这个值调用的时候不需要加括号,会根据依赖进行缓存,依赖不变,computed的值不会重新计算
watch是来监听的,有2个选项
immediate:表示是否要在第一次渲染的时候执行这个函数
deep:如果我们监听一个对象,那么我们是否要看这个对象里面属性的变化
如果某个属性变化了,就去执行一个函数
watch的深度监听在哪种场景下使用
使用deep: true解决监听不到对象属性变化的问题
data () {
return {
firstName: '张',
lastName: '三'
}
},
watch: {
// 监听obj对象里所有所有属性的变化 -- 首次加载/刷新/`obj`里的任一属性发生改变,这个函数里的handler方法就会执行
obj: {
handler (newName, oldName) {
console.log('obj changed')
},
immediate: true,
deep: true
},
// 监听对象里的某个属性 -- 监听obj对象里所有属性的变化开销较大,若只监听某一属性的变化,可优化性能
'obj.a': {
handler(newName, oldName) {
console.log('obj.a changed')
},
immediate: true,
deep: true
}
}keep-alive使用时从这个页面跳转到另外一个页面返回后这个数据还存在吗?为什么?
数据还存在,keep-alive 可以缓存页面的数据,在页面回跳的时候不会更新数据;
因为activated 在开启keep-alive时页面不会重新渲染 也不会进入生命周期
计算属性绑定数据后,跳转到另一个页面再返回,数据是否还在?为什么?
当计算结果不变时,该函数仅会调用一次,这是computed的缓存功能,合理使用会大大提高代码的运行速度。
页面优化
v-if和v-for不能连用
更多的情况下,使用v-if代替v-show
要保证key值的唯一
使用组件懒加载或者图片懒加载
防抖和节流的使用
模块按需导入
打包优化
使用cdn加载第三方模块
缓存常用信息
精灵图,base64
动态组件
定义:多个组件使用同一个挂载点,并动态切换
使用:,当控制 componentName 改变时就可以动态切换选择组件
Axios拦截器
作用
Axios 是一个基于 promise 的 HTTP 库,支持promise所有的API
可以拦截请求和响应
可以转换请求数据和响应数据,并对响应回来的内容自动转换成 JSON类型的数据
安全性更高
相关配置
url:请求的服务器地址
method:请求方法
baseURL:基准路径
headers:请求头
parmas:路径参数
做了什么
请求拦截器:
- 在请求发送前进行的操作,如:每个请求体里加上token
响应拦截器:
- 接收到响应后进行的操作,如:服务器返回的登录状态失效,就跳转到登录页
webpacks
定义:是一个打包模块化的工具,在webpack中一切文件皆为模块,通过loader转换文件,通过plugin注入钩子,最后输出由多个模块组合成的文件
作用:由于浏览器对于js中的很多代码不可以直接进行解析读取,这个时候需要先通过 wabpack 把资源进行打包,解析成浏览器可以识别的代码
配置:
入口:指示webpack使用哪个模块作为构建其内部依赖图的开始,默认值是:‘./src/index.js’
出口:告诉webpack在哪里输出它所创建的bundle,以及如何命名这些文件,主要输出文件的默认值是‘./dist/main.js’
mode:配置模式,development(开发环境)、production(生产环境)、none(不使用任何默认优化选项)
loader:自带能力,用于转换某些类型的模块
plugin:打包优化,资源管理,注入环境变量等
流程:
初始化参数
开始编译
确定入口
编译
完成模块编译
输出资源
输出完成
深度侦听在什么情况下使用?立即侦听什么情况下使用?
deep(深度侦听):默认情况下,侦听器无法侦听对象的属性值的变化,如果想实现这个效果,则需要添加deep配置为true
handler(固定方法触发):因为你要添加deep的配置,所以,侦听器的形式要变更为对象形式,只有对象才能添加其它的配置, 同时侦听函数必须为handler
immediate(立即侦听):如果需要默认一进页面就触发一次,添加immediate配置选项为true
参考:https://blog.csdn.net/GZZ__z/article/details/120852612
token失效处理
第一种方案是:服务器端保存token状态,用户每次操作都会自动推迟token的过期时间,session就是采用这种策略保持token的有效期,但是当前后端分离,单页面的时候,每秒钟的请求发起多次,每次都去刷新一下过期时间会非常消耗性能的;
第二种方案:使用refresh token,避免频繁的刷新token,此时服务端只要在token过期的时候反馈给前端,前端使用refresh token申请一个全新的token继续使用即可
vue3双向绑定怎么实现
vue3 是通过Proxy实现的数据双向绑定,采用的proxy劫持的是整个对象,相比vue2.0defineProperty,能够监听动态新增的属性,可以监听数组的索引和length属性。
参考:https://blog.csdn.net/m0_49471668/article/details/125180606
微信小程序
微信小程序已上线要怎么重构,比如原生做的小程序升级为vue+uinapp框架
使用官方推荐的HBuilderX, github地址:https://github.com/zhangdaren/miniprogram-to-uniapp。该插件十分强大,通过简单的脚本命令,直接将我本地的小程序原生项目,clone生成了一份vue.js的项目。
参考:https://ask.dcloud.net.cn/article/38791
做uniapp商城小程序遇到了什么坑?
参考:https://blog.csdn.net/qq_42625428/article/details/107060379
之前做uniapp开发的时候有做过多端吗?最多做过几端?
uni-app 是一个使用 Vue.js 开发所有前端应用的开源框架,开发者编写一套代码,可发布到的端有:
iOS
Android
Web(响应式)
以及各种小程序(微信/支付宝/百度/头条/飞书/QQ/快手/钉钉/淘宝)等多个平台。
根据你了解的情况去回答,我们微信小程序是学过的,如何发布成H5页讲过,这两个端至少可以回答
参考:https://blog.csdn.net/qq_15041931/article/details/121711320
扫码进来,有一条链接,但是我要获取链接里面带的参数,怎么获取?
直接在对应的页面中的onLoad生命周期方法中通过options即可获取到,二维码中url传入的参数
const url = new URL('https://example.com?foo=1&bar=2'); const params = new URLSearchParams(url.search); params.get("foo") //1 params.get("bar") //2得到url字符串
通过字符串的substring(1)方法,截取查询字符串
通过字符串的split("&"),获取参数数组
遍历参数数组,组装成参数对象
通过对象.xxx属性名,获取参数
uniapp 项目要在不同多端发布那么在开发的时候要考虑哪方面的问题呢?
多端兼容性问题,
微信开发文档中的配置问题
uniApp开发一般适合什么项目呢?开发中的项目要怎么能达到uniapp的专业规范呢?
为了实现多端兼容,综合考虑编译速度,运行性能等因素,uni-app约定了如下开发规范
页面文件遵循Vue单文件组件(SFC)规范
组件标签靠近小程序规范,详见uni-app组件规范
接口能力(JS API)靠近微信小程序规范,但需将前缀wx替换为uni,详见uni-app接口规范
数据绑定及事件处理同Vue.js规范,同时补充了App及页面的生命周期
为兼容多端运行,建议使用flex布局进行开发
小程序的生命周期
应用级别:Page()中触发
onLaunch:小程序启启动时
onShow:小程序前台运行时
onHide:小程序后台运行时
onError:执行错误时
onPageNotFount:冷启动(如扫码)打开小程序的页面不存在时
页面级别:
onLoad:页面加载时触发。一个页面只会调用一次,可以在onLoad的参数中获取打开当前页面路径的参数
onShow:页面显示/切入前台时触发(返回、tabBar切换、前台运行)
onReady:页面初次渲染完毕,相当于vue的mounted。一个页面只会调用一次,代表页面已经准备妥当,可以可视图层进行交互
onHide:页面隐藏/切入后台时触发(跳转、tabBar切换、后台运行)
onUnload:页面卸载时触发。如redirectTo 或 navigateBack 到其他页面时
小程序的登陆流程
【阐述具体流程-合格】
调用 wx.login() 获取 临时登录凭证 code
将临时 code 传到我们的后端,后端调用换取用户唯一标识 OpenID 和 会话密钥 session_key
后端自定义新的密钥并关联返回的 session_key 和 openid,将新的密钥返给前端,前端将其存储在 storage 中。
【登录特点-优秀】小程序登录流程主要是要与微信服务器进行通信验证
参考:https://blog.csdn.net/allen_he_123/article/details/121111061
小程序的跳转方式
[回答第1,2,3条-合格,因为是常用的]
wx.navigateTo() : 保留当前页面,跳转到应用内的某个页面。但是不能跳到 tabbar 页面
wx.switchTab() : 跳转到 TabBar 页面,并关闭其他所有非 tabBar 页面
wx.navigateBack() : 关闭当前页面,返回上一页面或多级页面。可通过getCurrentPages() 获取当前的页面栈,决定需要返回几层
[回答第4条-良好]
- wx.redirectTo() : 关闭当前页面,跳转到应用内的某个页面。但是不允许跳转到 tabbar 页面
[回答第5条-优秀]
- wx.reLaunch() : 关闭所有页面,打开到应用的某个页面
参考:https://www.jianshu.com/p/5d4c9fff5b3c
小程序的数据绑定和vue有什么区别
参考:https://blog.csdn.net/weixin_41277748/article/details/117047654
小程序支付流程
参考:https://blog.csdn.net/weixin_46419373/article/details/108718652
微信小程序监听数据变化
和vue一样,但使用的是 observers 选项
observer: {
'字段a, 字段b': ('字段a新值', '字段b新值') => {
// ...
}
}全局状态管理
在main.js里配置global,把属性写进去
好像也可以使用 redux 库来实现,但没有用过
场景
如何处理静态文件加载失败?
一般来说最常见的资源是图片,css,js加载失败,还有的会有一些设计类网站会加载字体文件,这些文件体积比较大也可能会加载失败,
无论是img 还是 link 还是script都有onerror事件可以绑定,我们可以在onerror里尝试重新加载资源,或者加载备用资源。图片的话最好添加alt属性,在onerror中可以设置为一张失败提示的图片。
除此之外还可以再onerror里进行错误上报,然后去分析这些资源是因为什么问题导致错误,是跨域还是资源过大这些问题。
请说说你对函数式编程思想的理解
对于函数式编程他其实就是把一个大问题拆分成很多小问题,然后依次去解决这些小问题。最终组合起来去解决大问题,在这些过程中我们会声明很多的函数,这些函数可以被复用,可以被继承。这其实就是函数式编程的思想了。 就像面向对象一样,面向对象是以对象为中心,去研究他的属性和方法来实现编程,通过继承,封装等方式来实现复用。
纯函数
同样的输入得到同样的输出,且函数内部不会改变外部环境
高阶函数
一个函数的参数是函数,那他就是一个高阶函数,比如最常见的节流和防抖。还有想让某个函数具有缓存作用的函数,前提是这个函数最好是纯函数。
比如传递一个函数后,拿到他的所有args,然后代理这个函数,直到外界调用了这个函数,能从代理中拿到结果。然后将函数名、参数、结果缓存,下次遇到同样的函数时就可以直接返回缓存。
还有就是函数式编程可以很好的进行单元测试
缓存函数
function cacheFunction(fn) {
const cache = new Map();
return function(...args) {
const key = JSON.stringify(args);
if (cache.has(key)) {
return cache.get(key);
} else {
const result = fn(...args);
cache.set(key, result);
return result;
}
}
}
const add = (a, b) => {
return a + b;
}
const sAdd = cacheFunction(add);
console.log(sAdd(1, 2));图片性能优化
懒加载
<img src="" loading="lazy" />- intersection Observer API
- 监听Scroll,判断视口位置来渲染图片
如何进行结算
- 添加到购物车,点击下单后会进入结算页面,计算商品总价,然后将用户token和商品集合发送给后端,后端计算后会返回一个session_id,并记录当前的时间戳来倒计时
- 点击付款后,带着session_id给后端,后端来调用对应的SDK接口,然后把链接返回给前端
- 情况一:该链接是个二维码,用qrcode来生成二维码,进行轮询验证订单状态
- 情况二:该链接是第三方支付,支付完成后会自动重定向到付款的页面
你在项目中遇到的问题
我们之前需要写一个悬浮窗的广告,使用的 position: sticky ,但他在 Safari 浏览器中只渲染出了部分内容,并没有全部展示出来,后来查资料发现position: sticky 样式在父元素具有 transform 样式时 会导致悬浮元素渲染不完整。在谷歌浏览器没问题。
最终我们使用 transform: translateZ(0) 来开启GPU加速,解决了这个问题
无感刷新?
我们在做商城的时候,用户最烦的其实是需要重新登录。
在前端登录后,后端会返回token和token有效时间,我们会把token和过期时间存在本地存储,当token过期的时候要用旧token去获取新的token
我们是做了两步操作
第一步是用定时器,当用户初次加载页面时就去开启定时器,然后快过期的时候发送请求刷新token的请求,但这有个问题就是用户 如果停留在页面太久了,又做了一些高性能的操作,会导致定时器被延迟,就导致了token过期之后才发送刷新token的操作。如果用户在token过期的这段时间进行了操作,就需要重新登录了,所以有了第二步
第二步是在 axios 的响应拦截器中做了一次判断,就是当用户发起请求后,如果token过期了,后端返回一个token过期的状态,这时候我们会再发一次刷新 token 的操作,然后再重新发送一次用户点击的请求。如果刷新 token 失败了,我们就给用户跳转到登录页
有个问题就是,如果同时有多个请求发送,但token过期了,就会导致所有的请求都会触发一次获取token的操作。解决这个问题,我们是记录了一个flag变量,还维护了一个数组列表。当第一次去刷新token的时候,把flag改为true,给后续的请求都封装成了promise放进数组中,这样等token回来后,全部执行,遍历数组重新发送请求 就解决这个问题了
在请求回来的数据保存至localstorage里面页面没有刷新,怎么获取数据
我们知道获取数据通常会通过ajax请求后台接口来获取的。如果这个请求回来的数据保存到了localstorage中,而又不在页面刷新的情况下想要获取到数据的话
- 【必答】我们可以通过setInterval开启一个定时器,每隔5秒钟去访问一下localstorage中的数据,这样就可以获取到数据了。
在使用vuex时怎么实现数据的持久化
- 我们通常是将数据保存到本地存储中,当重新刷新页面后再从本地存储中将之前的数据加载回来保存到vuex中的state中。这样就能实现vuex的数据持久化了。
全局前置守卫的应用,自己举个小例子说出来怎么使用
【此条如果有说出-优秀】路由中的前置守卫可以监听到所有的路由跳转,并且只有在前置守卫中放行后才能真正去加载路由对应的组件并渲染出来。
【此条如果有说出-合格】所以我们可以使用前置守卫来做一些全局的判断工作,例如:我们可以使用前置守卫来做全局的登录判断,如果在前置守卫中判断没有token或者token失效了则表示没有登录,否则就是有登录,放行本地请求
有一个数组,数组都是数字怎么把里面的奇数偶数分开
【先说基本原理】因为偶数可以被2整除,奇数不能被2整除,所以可以使用 %2余数得0位偶数,否则为奇数来进行判断
【常规做法】首先通过forEach遍历数组,然后使用遍历到的数字%2如果余数为0则是偶数,否则是奇数,最后分别用两个新数组存储偶数和奇数
【良好做法】通过filter或map方法结合 求余 来分开
后端是使用什么来实现接口的
给我们前端ajax提供的后端数据接口通常的开发语言有
Java语言:现在最流行的,基本上占有90%以上的接口都是用java开发的,也是我们接触最多的
Node.js: 一些小型项目或者不是很复杂的接口可能是前端自己开发
Python语言、PHP语言、.Net语言,语言也都可以开发接口
项目中运用哪些框架,项目是否上线
项目框架组合有很多,可以自行挑选:
【这个必答-可以自行挑选你熟悉的讲】2016年以后
Vue2+ elementUI(UI组件库)、Vue2 + iView(UI组件库)开发PC端网站
Vue-element-Admin(花裤衩)开发后台管理系统
Vue2+Vant(移动端组件库)开发移动端网站
React + antd(蚂蚁金服UI组件库) 开发PC端网站
React + antd-mobile(移动端UI组件库) 开发移动端网站
【这个能体现出你的经验】2016年以前,最常见前端框架组合【
jQuery + BootStrap 开发网站前台和网站后台管理系统
jQuery+layui.js 、jQuery+easyui、jQuery+extjs 开发后台管理系统
2013年开始到2016年左右使用angularjs开发
【这个能体现出你的经验】打包工具,2016年之前使用 gulp和grunt打包项目居多,2016年以后webpack居多
vue用了这么久 你对他有什么看法 比如语法 或者其他一些东西
可以从Vue2优点和缺点方面去讲解
优点
轻量级的框架
双向数据绑定
组件化开发
单页面路由
学习成本低
虚拟dom
渐进式框架
数据和结构的分离
运行速度快
插件化
缺点
不支持IE8以下
社区可能没有Angular和React那么丰富
Vue 不缺入门教程,可是很缺乏高阶教程与文档。同样的还有书籍
因为是单页面应用,不利于seo优化
初次加载时耗时多
服务端渲染和客户端渲染分别是什么?
服务器渲染:
页面渲染的工作都是由服务端来完成的,数据也是由服务端提供的,浏览器只负责展示页面内容
容易被爬虫爬取数据,同时能被搜索引擎搜索到,能在搜索引擎中向用户展示数据
客户端渲染:
页面的渲染工作都是由浏览器来完成的,服务器只是负责提供数据。
客户端渲染能尽早的把页面展示给用户,用户体验好
不容易被爬虫爬取数据,同时也无法被搜索引擎搜索到
移动端兼容(适配)、浏览器兼容、不同的分辨率兼容
移动端兼容(适配)参考:https://zhuanlan.zhihu.com/p/36021907
浏览器兼容参考:https://juejin.cn/post/6972937716660961317
不同的分辨率兼容 参考:https://juejin.cn/post/684490388191525274
echars有没有用在移动端?
参考:https://www.cnblogs.com/leoxuan/p/6544351.html
自己有封装过axios吗?就是封装一个request.js
不知道
和其他部门的人沟通如何处理?
和产品经理(自研公司称呼)或者需求人员(项目外包公司称呼)确认产品设计交互流程
和UI设计师确认UI设计稿
和后端确认接口地址,入参,返回结果,以及结果中的字段确认,如果碰到了接口问题,找对应开发人员解决
和测试人员确认bug出现的步骤,交流bug问题所在,如果是需求问题重新确认需求
低代码
低代码(Low Code)是一种可视化的应用开发方法,用较少的代码、以较快的速度来交付应用程序,将程序员不想开发的代码做到自动化,称之为低代码。
低代码是一组数字技术工具平台,基于图形化拖拽、参数化配置等更为高效的方式,实现快速构建、数据编排、连接生态、中台服务。通过少量代码或不用代码实现数字化转型中的场景应用创新。
参考:https://baike.baidu.com/item/%E4%BD%8E%E4%BB%A3%E7%A0%81/60863339?fr=aladdin
什么样的数据能放cdn
js、css、图片、音频、视频等静态资源
cdn的作用:
一. 静态资源加速
二. 动态资源加速
三. 音频/视频加速
四. 图片加速
五. 下载加速
六. 海外加速
七. 安全加速
具体参考:https://zhidao.baidu.com/question/927324190036530939.html
有接触即时通讯吗
做过几次聊天室,基于Web的前端,存在以下几种可实现即时通讯的方式:
短轮询 (历史方案)
- 开个定时器, 每隔一段时间发请求,如果消息频繁会忽略掉部分信息 (实时性不强)
Comet - ajax长轮询(历史方案)
发送一个请求, 服务器只要数据不更新, 就一直阻塞 (服务器压力过大)
SSE(利用了http协议, 流数据的传输, 并不是严格意义的双向通信, 无法复用连接)
WebSocket (主流):
- 性能和效率都高,服务端可以主动发送消息给客户端,也可以广播,很方便
参考:https://blog.csdn.net/m0_57712926/article/details/120690725
怎么学习前端的
- 睡觉
商城购物车是怎么做的 ?在购物车订单页面怎么去更新数据?
用户已登录:
每次用户添加商品,向后台发送用户添加的商品。
切换到购物车页面,从后台查询用户的购物车商品列表
更新界面。
用户未登录
每次用户添加商品,在本地缓存中,存储添加的商品信息。
每次切换到购物车页面,从本地缓存中,查询用户的购物车商品列表。
更新页面。
后台管理的权限如何实现
现在权限相关管理系统用的框架都是element提供的vue-element-admin模板框架比较常见。
权限控制常见分为三大块
菜单权限控制
按钮权限控制
请求url权限控制
项目优化打包
- 一。减少请求数量
- 二、减小资源大小
- 三、优化网络连接
- 四、优化资源加载
- 五、减少重绘回流
- 六、性能更好的API
- 七、webpack优化
具体参考:[https://blog.csdn.net/weixin_44485276/article/details/119975366]
从上家公司出来有什么感悟
技能方面提升:
- 如:JS、项目、沟通等方面的提升。
心态方面:
- 如:学习能力、抗压能力方面的提升
本地开发怎么测试,怎么到线上去测
本地开发:连接开发环境的数据库,正常测试即可。
线上测试:
JS报错问题: 💥注意:线上发布的版本通常是压缩、混淆后的代码,控制台往往不易测试。
可以考虑通过proxy将请求转发到生产环境的服务器
在本地复现线上报错问题调试。
资源加载不到问题:
- 登录线上服务器,确认静态资源路径、是否存在等。
有没有做过一些小游戏
直接答没做过即可,表示可以学习。
之前前端有多少人
自己想一个数字就好,通常按照, 前端:后端 = 1:2的比例去回答
上个公司经常加班吗?
有时加有时不加,项目比较赶的时候会经常加班。
不断的加载数据,不断的生成控件,怎么优化性能
解决方案:使用虚拟列表、
工作中:使用组件库的无限滚动组件
之前的开发流程
我们公司开发流程没有那么规范,由于系统基本上是一些常规的功能开发,所以我们也没有多少的系统分析和设计
- 【必答】组长和后端进行接口定好以后,我们内部有一个接口文档,已经定制好了接口地址,参数,响应回来的数据格式,然后组长搭建好基本框架和封装好通用功能后(例如,统一的ajax请求文件,路由设定等功能),我们在搭建好的框架上进行自己模块功能的开发。开发完功能就会提测,然后就是修复测试人员提交过来的bug。就这样一个个功能开发,直到完成。
切图用什么软件
现在都是使用蓝湖在线查看,或者Skech在前查看,早年使用Photo Shop软件来切图
原理
事件线程的理解
线程是进程内的一个独立执行单元,是程序执行的一个完整流程,是CPU的最小的调度单元,应用程序必须运行在某个进程的某个线程上;
一个进程中至少有一个运行的线程:主线程,进程启动后自动创建
一个进程中也可以同时运行多个线程,我们说程序是多线程运行的
一个进程内的数据可以供其中的多个线程直接共享
参考:https://blog.csdn.net/m0_59897687/article/details/123130937
JS的运行机制
答题技巧:从单线程->任务队列->EventLoop(事件循环) 宏任务和微任务依次讲解,其中如果能把EventLoop讲清楚那么是非常优秀的,如果实在不行,可以只讲 单线程->任务队列,提一下EventLoop
参考:https://zhuanlan.zhihu.com/p/88510041
图片懒加载底层原理
首先将页面上的图片的 src 属性设为空字符串,而图片的真实路径则设置在 data-original 属性中,当页面滚动的时候需要去监听 scroll 事件,在 scroll 事件的回调中,判断我们的懒加载的图片是否进入可视区域,如果图片在可视区内则将图片的 src 属性设置为 data-original 的值,这样就可以实现延迟加载。
参考:https://blog.csdn.net/weixin_49733248/article/details/119305813
说一下vue2底层原理 ?vue2的特点 ?
vue 作为一种MVVM模式的框架, 其数据绑定的底层原理为:数据劫持 + 发布订阅者模式。
其中主要有这么四种“角色”:
Observer :主要负责 数据劫持, 核心是通过Obeject.defineProperty()来监听数据的变动,这个函数内部可以定义setter和getter。每当数据发生变化,就会触发setter()。这时候 Observer 就要通知给Dep 说有数据发生了变化。
Dep数据收集: Dep 收到来自 Observer 的数据变化通知时,会调用 notice() 方法把发生变化的依赖告诉 Watcher。
Watcer订阅者:是连接 Observer 和 Compile 之间通信的桥梁,当它收到来自 Dep 的数据变化通知后,会调用自身的 update() 方法,并触发Compile中绑定的回调。
Compiler 模板编译器:主要做的事情是解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦接收到数据有变动,收到通知,更新视图。
项目逐字稿
人资分析
基于vue-element-admin搭建项目环境;使用 Element-ui 里的 form 组件实现表单校验;利用 axios 拦截器,统一设置基路径和请求头 token 及优化代码;使用 vue-router 进行页面跳转,展示对应的视图内容,并使用路由的前置守卫进行登录拦截的控制;利用 vuex 来管理共享的用户信息数据,并利用 js-cookie 将仓库数据持久化到本地存储中;使用 NProgress 插件实现页面跳转时出现在浏览器顶部的进度条;使用el-table实现表格的渲染,并使用作用域插槽对复杂数据进行回显;使用el-pagination实现分页效果,并使用size-change和current-change事件来处理当前的表格总条数和页码变化;注册全局过滤器,处理文本数据和时间的格式化;注册全局的处理图片加载失败的自定义指令;使用xlsx插件,完成excel的导入导出;使用qrcode插件,完成二维码的展示;使用vue-print-nb插件完成页面的表格的打印;使用vue-i18n插件,完成页面的中英文配置和切换;利用递归算法封装一个专门将列表型的数据,转换成树形结构的方法,并结合 Tree 树形控件行组织架构 的模块渲染;基于 RBAC 权限设计思想,控制登录用户的菜单权限和按钮权限;使用路由懒加载优化打包问题以及后期维护与更新;开发环境用 webpack 配置反向代理。【生产环境在 node 服务器配置代理 解决跨域问题】可以采用CDN的方式,在页面模板中预先引入,将项目依赖包挂载到cdn,达到优化前端包总体的体积的效果使用环境变量配置前端的不同环境下的基地址;
人资项目口语化
本人参与的一个项目是人力资源管理后台系统,该系统所拥有的功能包括权限管理、员工管理、部门管理、权限管理等,可以通过该系统进行更好的员工信息管理以及维护,本人负责的模块包括权限路由的分配,部门管理,权限管理,员工管理。静态结构主要用了 Element-ui 组件进行编写,通过 Vue-router 进行页面之间的相互跳转,在各个管理的页面引入相对应的接口函数,请求并保存数据,然后进行渲染。
人资功能逐字稿
一、登录
我们做的人资系统是个后台管理系统, 不是人人都能访问, 于是需要登录系统.
整个登录流程大概分为三个部分
首先是使用 ElementUI 组件进行登录表单的布局
接着在这个表单当中, 用组件自带的校验功能配置好用户输入的格式校验, 包括手机号格式, 密码长度
一旦用户输入完毕, 点击登录, 其实就开始发送请求, 这里咱们将登录逻辑封装在了 vuex 里面, 不是直接调用接口, 反而是调用 vuex 的actions, 拿到token 存在 vuex 的 state 里面, 然后进行页面跳转即可
一些细节是, 刷新数据会丢失, 所以咱们用了本地储存进行数据持久化
二、角色管理页-公司设置
我们的后台管理系统中有个公司设置页面里面显示公司信息和角色管理表格
其中公司信息比较简单, 只是进入页面发送请求, 绑定到 表单中就可以, 不用做任何修改
但是角色管理就复杂一些, 是后台系统中常见的增删查改业务, 首先是进入页面获取数据并且用 element ui 的表格和分页组件实现渲染的功能
新增用的是 elment ui 的 dialog 弹窗, 每当点击新增按钮的时候弹出, 里面有个表单, 带有数据的绑定和表单验证, 当用户输入完毕所有数据, 点击确定的时候, 发送请求即可, 当然为了用户的体验, 在请求前后, 做了表单校验, 用户提醒, 页面的数据更新等等优化, 如果用户点击了取消, 咱们会清理表单数据和校验再关闭, 并且这个操作不知取消按钮需要, 表单 close 事件也需要, 为的是照顾点击右上方 X 按钮操作.
编辑时其实是复用了新增弹窗, 区别在于, 会在点击时带上被点击角色 id, 向后端获取详情回填到表单上再弹起弹窗,还有就是会在用户点击确定时, 根据表单有没有 id 存在决定是新增还是编辑请求.
删除是最简单的功能, 点击时带上id, 发送请求即可, 当然为了用户体验, 删除前后, 加上了二次询问, 提示用户和更新页面的操作.
三、组织架构(部门页)
我们在人力资源管理系统当中, 有一个部门管理页面, 主要是用来添加删除编辑部门的数据 这个页面比较复杂 进入页面时是普通的数据查询获取到后台部门列表, 进行渲染即可 这里有两个问题, 第一是树形组件需要递归数据, 第二树形渲染需要自定义样式, 我们封装了一个函数, 对后台传出来的数据进行了转换, 把原本通过 pid 进行上下级关联的数据转成 elment ui 要求的 children 嵌套, 另外使用作用域插槽自定义了树形组件渲染, 这个逻辑比较复杂, 我们封装在了一个树形组件 tree-tools 当中
查询完成之后, 咱们做了新增和编辑删除的功能, 因为新增和编辑功能比较复杂, 也是封装了一个组件作为弹窗, 里面有一个表单, 供用户输入部门数据, 点击确定后就能发送请求进行数据处理
这个页面最难的点在于, 树形子组件+父页面+弹窗子组件之间的交互, 因为点击树形子组件时需要将弹窗弹起来, 并且弹窗也需要知道到底是哪个部门触发了新增和编辑, 解决方案是, 通过父页面作为桥梁, 间接实现树形和弹窗通讯, 逻辑是,每当树形被点击, 将事件和被点击的id往父页面传, 父页面存储到 data 以后再传给 弹窗组件即可
这里面还做了一个附加的校验功能, 部门名称在同一个父部门下不能重名, 部门编码, 在整个公司都不能重复, 这里使用到了element ui 表单中的自定义校验函数, 每当输入框失去焦点就会触发校验函数, 在里面我们拿到整个公司的部门列表, 根据需要跟用户, 如果有重复就报错, 最终实现发送请求之前完成重名校验
删除时最简单的, 就是带上 id 发个请求即可, 后续有提醒用户, 更新页面数据等体验上的操作
四、员工管理
员工管理页面,主要是实现员工的增删改查,批量导入导出员工功能。
首页进入页面是需要接口获取到员工列表数据,然后配置elementUI组件进行渲染。
新增员工主要用到dialog组件和表单组件配置新增接口实现新增功能。
编辑员工,主要是员工详情信息比较多,所以是配置了一个独立的路由页面来实现功能的,首先是获取到员工id,然后根据员工id进行数据回显,然后再调用编辑接口实现员工信息的修改,这里涉及到腾讯云cos(对象存储)的使用,因为所有的员工头像都是上传到腾讯会cos中的,腾讯云cos使用的注意点就是需要添加允许跨域的配置,其他只需要按照文档实现即可,这里我们也专门封装了一个功能上传图片的组件出来。
删除员工,只需要点击删除按钮的时候获取到该员工的id,然后调用删除接口实现删除功能,这里我们也考虑到了误删的情况,因此点击删除按钮的时候会先显示“是否确认删除”的询问框,当用户再次确认的时候才实现真正的删除。
批量导入导出功能,这个功能主要是利用xlsx插件实现导入导出功能,这里的难点是导出的时候,需要把请求到的数据转换为xlsx要求的格式才能导出成功;实现批量导入功能的时候,也需要把通过xlsx插件解析好的Excel表格数据转换为接口需要的数据格式。
比如:批量导入数据时,因为获取到的Excel表格数据字段都是中文名的,但是后端接口需要的是英文名,因此我们把数据通过接口传递给后端之前需要先把数据转换为后端规定的格式,我们的做法是先准备好一个中英文字段对照字典表,然后再根据字典表把Excel表格的中文字段名替换成英文字段名,从而实现数据转换。(注意:这一段如果面试官 没有问题具体数据是怎么转换的,不需要跟面试官讲,如果问到了可以这样回答)
五、权限设置
人资项目中有一个权限设置页面,这个页面主要是用来实现权限数据的增删改查。
首先进入页面通过接口获取到全选数据,然后配置elementUI的表格组件进行渲染。因为权限分为页面访问权限和按钮操作权限因此渲染的时候需需要把数据渲染成一个表格的树形结构,因此我们获取到数据后首先通过一个递归函数把数据转为树形结构,然后再配合表格组件的用法渲染成一个表格树形结构的。
然后是新增权限功能,新增权限功能要注意的是需要区分新增的是按钮操作权限还是页面访问权限,如果是页面访问权限的话必须要传递固定的2个参数给后端,pid为0,type为1,以此表示此刻用户添加的是页面访问权限,当添加的是按钮操作权限的时候,传递pid为页面访问权限的id,type为固定值2,以此表示用户此刻添加的是按钮操作权限。
编辑权限功能直接复用新增功能的结构样式,编辑权限的时候首先要获取到点击的那个权限的id并通过该id获取权限详情数据最后进行数据回显,然后调用编辑权限接口,传入新的数据从而实现编辑功能。
删除功能相对比较简单,只需要点击删除按钮的时候获取到该权限的id,然后调用删除接口实现删除功能,这里我们也考虑到了误删的情况,因此点击删除按钮的时候会先显示“是否确认删除”的询问框,当用户再次确认的时候才实现真正的删除。
六、RBAC权限设计
人资项目权限设计用到的是现在比较流行的一种设计模,叫RBAC的权限设计模式,这个模式主要有3个部分构成,员工,角色以及权限,我们要做的就是先完成员工,角色,以及权限的增删改查,然后给员工添加角色,给角色添加权限,这样员工就具有对应的权限了,我们这里的权限主要是两个方面的权限,一个页面访问权限,一个是按钮操作权限。
当我们在系统中完成了权限配置之后,员工登录系统可以通过一个接口获取到该员工的“页面访问权限点”和“按钮操作权限点”。那么我们可以根据获取到的这些权限点来分别动态设置页面的访问权限和按钮操作权限。
页面访问权限,先通过获取到的“页面访问权限点”筛选出来具有权限的路由对象,然后是通过路由的一个addRoutes方法实现动态路由权限的添加。
按钮操作权限主要是通过mixin混入一个全局函数,在函数中通过“按钮操作权限点”查找某个按钮是否具有操作权限,有权限则返回true,否则返回false,最后配置v-if指令实现按钮显示隐藏,从而实现设置按钮操作权限。
七、主页
主页部分主要实现展示日历,流程申请,公告,等信息。
其中日历我们是利用elementUI的组件进行了二次封装,并且对日历的内容进行自定义的显示,比如周末可以在日历中显示一个“休”字(自定义内容主要是因为日历组件提供了作用域插槽给我们使用,才能够实现自定义内容的功能)。
流程申请主要是可以进行“加班离职”,“请假调休”等等的申请,我们直接使用elementUI的dialog组件配合表单组件来实现申请的布局,然后通过调用相应的接口实现申请功能。
公告主要是通过接口获取公司发布的一些公告信息进行展示。
项目优化&代码编写过程
增加员工、修改员工信息弹窗还有图片上传的组件封装, ESlint 的代码格式化,编写代码时更加严谨,运用 Git 管理 多人共同开发
黑马u购逐字稿
https://www.yuque.com/docs/share/674f21ae-e6bd-487d-9711-b53043c7b77a?# 《黑马优购 - 微信小程序商城项目逐字稿》
小兔鲜儿逐字稿
项目搭建
我们做的小兔鲜儿项目是个电商网站,使用到了 Vue3 技术。
项目都是我们从0到1搭建的,都是按 Vue3 的最新标准操作。
脚手架用的是 Vite,技术栈是 Vue3 + TS + Pinia,使用组合式API的 setup 语法糖开发。
脚手架 Vite 配置了:路径别名,服务器代理,setup语法糖拓展插件,less 变量自动导入等。
风格管理我还配置了 ESlint + Prettier + EditorConfig 做统一格式化,按保存就能自动根据配置格式化,防
止提交的时候,为了方便其他同事开发的时候保持一样风格,我还做了一些工作区配置和插件推荐集成
到项目中 setting.json 和 extensions.json。
axios 请求库针对 TS 类型进行了封装一层,让接口的返回值有更好的TS类型提示,项目中的接口类型声
明文件也做了统一的规划管理。
首页模块
首页和很多页面的头尾都是一样的,做了一些路由的划分,封装了公共的头部和尾部组件。
由于封装抽离了,就要考虑到状态管理的问题,整个首页的数据都通过Pinia做全局状态管理。
首页的为了增强用户体验,也增加了一些交互特效如导航滚动吸顶,返回顶部等,首页楼层的一些重复
的部分都抽离成组件方便复用。
因为PC端没有合适的组件库,首页的轮播图组件,骨架加载组件都是自己封装的,其实整个项目的组件
库都是自己用 Vue3 + TS 封装的,使用时还支持类型提示和校验。
这里封装组件库的时候遇到了一个难点,就是把自己封装的组件库作为全局组件之后,就没有了TS的类
型检查和提示了,这里我是借鉴了 Element-Plus 的源码最后找到了解决方案,自己为组件库写了个类型
文件之后,项目中全局组件也能有类型提示了。
首页还做了一个懒加载的优化和数据缓存优化。懒加载的思路是模块进入可视区后,再发送请求获取数
据,最终渲染组件。数据缓存通过Pinia的插件实现打开首页速度更快。
懒加载的功能比较常用,所以我用组合式API直接封装成了一个 hooks 钩子函数方便复用。
分类模块
分类模块整体比较简单一些,主要是分类和商品的列表渲染。
但是在处理的过程中也遇到了两个路由的问题。第一个是路由的缓存,点击顶部切换分类的时候,不会根据新的分类id请求新的分类数据,解决方案是
给二级路由的 RouterView 加 Key 值为路由的 fullPath 就能解决,其实还有一个解决方案,可以通过
watch 侦听路由的 fullPath ,如果变化就重新发送请求渲染新的分类数据。
第二个问题是滚动行为,切换分类的时候需要返回顶部,处理起来比较简单,踩坑的原因是主要是新版
VueRouter 的API变了,查最新官方文档后解决了,后来再系统看了一遍官方文档就没踩什么新的坑了。
详情模块
详情模块以内容展示为主。
难点是在这个页面需要自己封装一些组件,如商品切换效果组件,地址选择组件,商品数量组件,加载
中,消息提示组件,按钮组件,商品规格选择组件。
其中按钮组件虽然简单,为了更好的TS类型提示,也遇到了TS类型 props 默认值的问题,最后查阅最新
官方文档其实有三种解决方案,最简单的是基于 JS 的基础上添加类型断言,还可以通过 withDefaults 和
最新的响应性语法糖解决。
商品数量组件需要通过 v-model 实现双向绑定,Vue2 和 Vue3 组件的 v-model 有些差异,Vue2 的 :value
改名为了 modelValue,@input 事件改名为了 @update:modelValue,这些都可以在 Vue3 的官方文档查
阅。Vue3 现在最新的官方文档真的好用,不过中文版还没正式发布,我都是通过查 GitHub 开源仓库的
时候找到的这个未发布的中文官网并分享给了同事。
登录模块
项目支持多种登录方式:有账号密码登录,手机号+验证码登录和第三方授权登录。
登录逻辑我都是封装到了 Pinia 中,添加一种新的登录方式只需要添加一个新的 actions 就可以了,并且
方便进行全局状态管理,登录成功后返回的 token 也是通过 Pinia 自动同步到本地防止页面刷新后数据
丢失。
第三方QQ授权登录,前端需要 appid 和登录成功后的回调地址。
回调地址需要配置环境才可以实现,如登录回调地址需要修改电脑的 hosts 文件和项目 vite 脚手架的配
置。
在QQ登录成功后,可以通过QQ互联的API获取QQ用户信息,OpenId 做登录绑定,如果已经绑定账号过
的用户都是可以一键授权登录的。
这里有个小插曲,QQ互联的API源码是用JS写的,我项目是TS开发并且还配置了Eslint,直接引入 JS 代
码会报类型错误和全局变量错误,这里我是通过手写 TS 类型声明文件 + eslint 配置全局变量解决的。
购物车模块
我们的项目是分为两种状态的购物车。一种是用户已登录,还有一种就是用户未登录,这里也拆出来一
个 Pinia 模块管理购物车。
已登录版的购物车相对比较简单,只需封装一个个的 actions ,如加入购物车,删除商品,修改数量和
选中状态,在用户操作的时候调用对应接口就可以了。未登录的版本就麻烦一些,加入购物车,删除商品,修改数量和选中状态这些逻辑都由前端完成,为了
防止刷新数据丢失,还需要自动同步到本地存储。
购物车如何区分已登录和未登录两种状态,我是先把用户登录信息缓存到购物车模块的 getters 中,在
每一个 actions 内部进行登录状态判断,用户已登录那就调接口,未登录前端完成增删改查逻辑,这样
就能实现在组件中调用 actions 的时候更方便,也方便后期维护,用户登录和退出与购物车的逻辑也是
做了同步处理的,登录合并购物车,退出清空购物车。
这里还有个小细节就是,未登录状态如何保持本地商品的信息是最新的,这里我封装了一个 action ,在
本地版购物车进行增删改查的时候,主动调用接口查询最新商品信息库存和价格。当然在用户首次进入
网站的时候,也会同步购物车列表所有商品的库存和价格。
订单与支付模块
下单要求用户登录,不是人人都能访问,这里我通过设计路由导航守卫进行判断,如果用户未登录就跳
转到登录页,并且完成了登录成功后的页面回跳到原来的页面,提升用户体验。
下单需要用户选择收货地址,收货地址的弹出的对话框也是自己封装到组件库中,这里还用到了 Vue3
新增的 Teleport 传送门组件防止出现对话框定位的 Bug 。
用户提交订单后就会生成一个订单号,并且跳转到支付页面,下单页面的倒计时我也封装了一个 hooks
函数,方便在项目中复用。
下单后可以用支付宝完成支付,支付成功后就会回跳到支付结果页,用户也可以在个人中心查询到支付
或未支付的订单。微信支付由于还没接口所以没有实现,不过流程也都差不多。
会员中心
会员中心主要是用户信息的展示和订单信息的展示,这里就不过多阐述了。
常见人事问题
自我介绍
面试官好,我叫XX,来自XXX,毕业于XXXXXXX,上一家公司是XXXXXXXXX,因为之前的公司是外包公司,所以接触的项目比较多,最熟悉的是vue,经常用vue写各个端口的项目,比如在pc端,经常用的第三方组件是element-ui,比如在移动端,一般都是搭配vant来完成页面,布局一般都是用vw、vh和flex布局来完成页面的布局,平常的管理项目一般所以git,然后也写过微信小程序,使用的是uni-app,因为这个框架与vue类似一点。平常用的都是less预处理器,在写微信小程序的时候也用过scss。平常开发的时候都是前后端分开,一般都是后端提供api然后我们前端直接调用,当然如果后台还没写好就先自己造假数据来完成。平常都是用webpack来构建项目。
公司介绍
我们公司主要是做项目外包服务,从一些中小型公司手中承接一些项目外包,公司人员不多,属于小型公司
第一家公司
名字:广州市悦智计算机有限公司 2018.06-2019.03
地址:广州市番禺区市桥街光明南路163号置业华逸大厦三楼329号
人数:40人左右
老板:杨山河
成立时间:2007年
第二家公司
名字:广州维狐网络科技有限公司 2019.04-2021.08
地址:广州市天河区东圃东泷创意园H栋(车陂地铁站D口)
人数:40人左右
老板:李冰
成立时间:2014年
离职原因
- 深圳的IT行业发展的更好,而且在一家公司也两年了,也想换个环境,学到更多的东西
公司架构
研发部
产品部
财务部
行政部
人员架构
- 我们公司有40多人,技术部有23人,分为三个项目组,我自己所在的项目组有3个前端4个后端,公司一共有7个前端,10个后端
开发流程
项目经理拿到项目需求,进行开发时间评估
UI根据需求出设计稿
前后端商量API接口,前端开始静态页面的开发,后端也开始API接口的开发
等接口完成后,前端再进行API的调用,如果还没完成接口,前端就先模拟一些数据测试
如果接口调用有问题的时候前后端进行协商更改
自己边写边测试,如果觉得差不多没什么问题的时候就交给测试人员进行测试
然后就开始测bug,改bug的重复操作(TAPD提交bug)
运维人员发布上线
优缺点
- 优点:待人真诚,做事负责,学习能力也还可以,性格随和、心态还不错,讲究效率,今日事今日毕
- 缺点:不喜欢运动,贪睡
薪资架构
- 4000 基本工资(12000) + 绩效工资(2000) , 社保只有五险 , 扣税扣社保到手工资大概13000多点,自己交社保不到700
面试的反问
- 咱们公司目前团队人数多少
- 咱们公司目前在做什么项目
- 如果我能加入咱们公司,可能会接触那块的业务,我提前准备一下
- 技术这块之后会往那些方向拓展
你觉得你个性上最大的优点是什么?
- 回答提示:沉着冷静、条理清楚、立场坚定、顽强向上、乐于助人和关心他人、适应能力和幽默感、乐观和友爱。
说你最大的缺点?
- 回答提示:这个问题企业问的概率很大,通常不希望听到直接回答的缺点是什么等,如果求职者说自己小心眼、爱 忌妒人、非常懒、脾气大、工作效率低,企业肯定不会录用你。绝对不要自作聪明地回答“我最大的缺点是过于追 求完美”,有的人以为这样回答会显得自己比较出色,但事实上,他已经岌岌可危了。也喜欢求职者从自己的优点 说起,中间加一些小缺点,最后再把问题转回到优点上,突出优点的部分,企业喜欢聪明的求职者。
你对加班的看法?
- 回答提示:实际上好多公司问这个问题,并不证明一定要加班,只是想测试你是否愿意为公司奉献。
- 回答样本:如果是工作需要我会义不容辞加班,我现在单身,没有任何家庭负担,可以全身心的投入工作。但同时, 我也会提高工作效率,减少不必要的加班。
你对薪资的要求?
- 回答提示:如果你对薪酬的要求太低,那显然贬低自己的能力;如果你对薪酬的要求太高,那又会显得你分量过重, 公司受用不起。一些雇主通常都事先对求聘的职位定下开支预算,因而他们第一次提出的价钱往往是他们所能给予的最高价钱,他们问你只不过想证实一下这笔钱是否足以引起你对该工作的兴趣。如果你自己必须说出具体数目, 请不要说一个宽泛的范围,那样你将只能得到最低限度的数字。最好给出一个具体的数字,这样表明你已经对当今的人才市场作了调查,知道像自己这样学历的雇员有什么样的价值。
- 回答样本一:我对工资没有硬性要求,我相信贵公司在处理我的问题上会友善合理。我注重的是找对工作机会,所以只要条件公平,我则不会计较太多。
- 回答样本二:我受过系统的软件编程的训练,不需要进行大量的培训,而且我本人也对编程特别感兴趣。因此,我希望公司能根据我的情况和市场标准的水平,给我合理的薪水。
在五年的时间内,你的职业规划?
- 回答提示:这是每一个应聘者都不希望被问到的问题,但是几乎每个人都会被问到,比较多的答案是“管理者”。但是近几年来,许多公司都已经建立了专门的技术途径。这些工作地位往往被称作“顾问”、“参议技师”或“高级软件工程师”等等。当然,说出其他一些你感兴趣的职位也是可以的,比如产品销售部经理,生产部经理等一些与你的专业有相关背景的工作。要知道,考官总是喜欢有进取心的应聘者,此时如果说“不知道”,或许就会使你丧失一个好机会。最普通的回答应该是“我准备在技术领域有所作为”或“我希望能按照公司的管理思路发展”。
你朋友对你的评价?
回答提示: 想从侧面了解一下你的性格及与人相处的问题。
- 回答样本一:我的朋友都说我是一个可以信赖的人。因为,我一旦答应别人的事情,就一定会做到。如果我做不到, 我就不会轻易许诺。
- 回答样本二:我觉的我是一个比较随和的人,与不同的人都可以友好相处。在我与人相处时,我总是能站在别人的 角度考虑问题。
还有什么问题要问吗?
- 回答提示:企业的这个问题看上去可有可无,其实很关键,企业不喜欢说“没问题”的人,因为其很注重员工的个性和创新能力。企业不喜欢求职者问个人福利之类的问题如果有人这样问:贵公司对新入公司的员工有没有什么 培训项目,我可以参加吗?或者说贵公司的晋升机制是什么样的?企业将很欢迎,因为体现出你对学习的热情和对 公司的忠诚度以及你的上进心。企业文化,项目的方向。公司发展方向!!!
如果通过这次面试我们单位录用了你,但工作一段时间却发现你根本不适合这个职位,你怎么办?
回答提示:一段时间发现工作不适合我,有两种情况:
- ①、如果你确实热爱这个职业,那你就要不断学习,虚心向领 导和同事学习业务知识和处事经验,了解这个职业的精神内涵和职业要求,力争减少差距;
- ②、你觉得这个职业可有 可无,那还是趁早换个职业,去发现适合你的,你热爱的职业,那样你的发展前途也会大点,对单位和个人都有好处。
在完成某项工作时,你认为领导要求的方式不是最好的,自己还有更好的方法,你应该怎么做?
回答提示:
- ①、原则上我会尊重和服从领导的工作安排,同时私底下找机会以请教的口吻,婉转地表达自己的想法,看看领导是否能改变想法。
- ②、如果领导没有采纳我的建议,我也同样会按领导的要求认真地去完成这项工作。
- ③、还有一种情况,假如领导要求的方式违背原则,我会坚决提出反对意见,如领导仍固执己见,我会毫不犹豫地再向上级领导反映。
谈谈你对跳槽的看法?
回答提示:
- ①、正常的“跳槽”能促进人才合理流动,应该支持。
- ②、频繁的跳槽对单位和个人双方都不利,应该反对。
你对于我们公司了解多少?
- 回答提示:在去公司面试前上网查一下该公司主营业务。
- 如回答:贵公司有意改变策略,加强与国外大厂的 OEM合作,自有品牌的部分则透过海外经销商。的新客户,同时,对老客户做更全面周到的服务,开发老客户的新需求和消费。”等等。
最能概括你自己的三个词是什么?
- 回答提示:我经常用的三个词是:适应能力强,有责任心和做事有始终,结合具体例子向主考官解释,
你的业余爱好是什么?
- 回答提示:找一些富于团体合作精神的,这里有一个真实的故事:有人被否决掉,因为他的爱好是深海潜水。主考官说:因为这是一项单人活动,我不敢肯定他能否适应团体工作。
为什么要离职?
- 回答提示:回答这个问题时一定要小心,就算在前一个工作受到再大的委屈,对公司有多少的怨言,都千万不要表 现出来,尤其要避免对公司本身主管的批评,避免面试官的负面情绪及印象。建议此时最好的回答方式是将问题归咎在自己身上,例如觉得工作没有学习发展的空间,自己想在面试工作的相关产业中多加学习,或是前一份工作与 自己的生涯规划不合等等,回答的答案最好是积极正面的。
- 回答样本:我希望能获得一份更好的工作,如果机会来临,我会抓住。我觉得目前的工作,已经达到顶峰,即沒有 升迁机会。
说说你对行业、技术发展趋势的看法?
- 回答提示:企业对这个问题很感兴趣,只有有备而来的求职者能够过关。求职者可以直接在网上查找对你所申请的行业部门的信息,只有深入了解才能产生独特的见解。企业认为最聪明的求职者是对所面试的公司预先了解很多,包括公司各个部门,发展情况,在面试回答问题的时候可以提到所了解的情况,企业欢迎进入企业的人是“知己”, 而不是“盲人”。
你欣赏哪种性格的人?
- 回答提示:诚实、不死板而且容易相处的人、有“实际行动”的人。
你通常如何处理別人的批评?
回答提示:
- ①、沈默是金,不必说什么,否则情况更糟,不过我会接受建设性的批评。
- ②、我会等大家冷靜下来再讨论。
怎样对待自己的失敗?
- 回答提示:我们大家生来都不是十全十美的,我相信我有第二个机会改正我的错误。
什么会让你有成就感?
- 回答提示:为贵公司竭力效劳,尽我所能,完成一个项目。
你和别人发生过争执吗?你是怎样解决的?
- 回答提示:这是面试中最险恶的问题,其实是考官布下的一个陷阱,千万不要说任何人的过错,应知成功解决矛盾 是一个协作团体中成员所必备的能力。假如你工作在一个服务行业,这个问题简直成了最重要的一个环节。你是否 能获得这份工作,将取决于这个问题的回答。考官希望看到你是成熟且乐于奉献的。他们通过这个问题了解你的成 熟度和处世能力。在没有外界干涉的情况下,通过妥协的方式来解决才是正确答案。
你做过的哪件事最令自己感到骄傲?
回答提示:这是考官给你的一个机会,让你展示自己把握命运的能力。这会体现你潜在的领导能力以及你被提升的 可能性。假如你应聘于一个服务性质的单位,你很可能会被邀请去午餐。
记住:你的前途取决于你的知识、你的社 交能力和综合表现。
您在前一家公司的离职原因是什么?
- 回答提示:
- ①、最重要的是:应聘者要使找招聘单位相信,应聘者在过往的单位的“离职原因”在此家招聘单位里不存在。
- ②、避免把“离职原因”说得太详细、太具体。
- ③、不能掺杂主观的负面感受,如“太辛苦”、“人际关系复杂”、“管理太混乱”、“公司不重视人才”、“公司排斥我们某某的员工”等。
- ④、但也不能躲闪、回避,如“想换换环 境”、“个人原因”等。
- ⑤、不能涉及自己负面的人格特征,如不诚实、懒惰、缺乏责任感、不随和等。
- ⑥、尽量使解释的理由为应聘者个人形象添彩。
- ⑦、相关例子:如“我离职是因为这家公司倒闭;我在公司工作了三年多,有较深的感情;从去年始,由于市场形势突变,公司的局面急转直下;到眼下这一步我觉得很遗憾,但还要面对显示,重新寻找能发挥我能力的舞台。”同一个面试问题并非只有一个答案,而同一个答案并不是在任何面试场合都有效, 关键在应聘者掌握了规律后,对面试的具体情况进行把握,有意识地揣摩面试官提出问题的心理背景,然后投其所好。分析:除非是薪资太低,或者是最初的工作,否则不要用薪资作为理由。“求发展”也被考官听得太多,离职理由 要根据每个人的真实离职理由来设计,但是在回答时一定要表现得真诚。实在想不出来的时候,家在外地可以说是 因为家中有事,须请假几个月,公司又不可能准假,所以辞职,这个答案一般面试官还能接受。
谈谈你过去做过的成功案例?
- 回答提示:举一个你最有把握的例子,把来龙去脉说清楚,而不要说了很多却没有重点。切忌夸大其词,把别人的功劳到说成自己的,很多主管为了确保要用的人是最适合的,会打电话向你的前一个主管征询对你的看法及意见, 所以如果说谎,是很容易穿梆的。
如何安排自己的时间?会不会排斥加班?
- 回答提示:基本上,如果上班工作有效率,工作量合理的话,应该不太需要加班。可是我也知道有时候很难避免加 班,加上现在工作都采用责任制,所以我会调配自己的时间,全力配合。分析:虽然不会有人心甘情愿的加班,但依旧要表现出高配合度的诚意。
工作中学习到了些什么?
- 回答提示:这是针对转职者提出的问题,建议此时可以配合面试工作的特点作为主要依据来回答,如业务工作需要 与人沟通,便可举出之前工作与人沟通的例子,经历了哪些困难,学习到哪些经验,把握这些要点做陈述,就可以 轻易过关了。
除了本公司外,还应聘了哪些公司?
- 回答提示:很奇怪,这是相当多公司会问的问题,其用意是要概略知道应徵者的求职志向,所以这并非绝对是负面答案,就算不便说出公司名称,也应回答“销售同种产品的公司”,如果应聘的其他公司是不同业界,容易让人产 生无法信任的感觉。
何时可以到职?
- 回答提示:大多数企业会关心就职时间,最好是回答“如果被录用的话,到职日可按公司规定上班”,但如果还未辞去上一个工作、上班时间又太近,似乎有些强人所难,因为交接至少要一个月的时间,应进一步说明原因,录取 公司应该会通融的。
微信小程序
零、简介
小程序与普通网页的区别
- 运行环境不同
- 网页运行在浏览器中
- 小程序运行在微信中
- API不同
- 小程序没有DOM、BOM,因此不能调用浏览器对象模型
- 但可以调用微信提供的API、例如地理定位、支付等
- 开发模式不同
- 小程序需要申请小程序
- 安装小程序开发者工具
- 创建和配置小程序项目
申请小程序
- 注册:https://mp.weixin.qq.com/
- 进入开发管理,获取开发者ID
- 下载开发者工具:https://developers.weixin.qq.com/miniprogram/dev/devtools/download.html
- 打开开发者工具,扫码登录
创建小程序项目
- 选择页面的小程序,点击+号添加
- 按照需求选择创建即可
- 视图大小建议iPhone6/7/8
文件目录结构
- pages:用来存放所有小程序的页面
- utils:用来存放工具性质的模块
- app.js:小程序项目的入口文件
- app.json:小程序项目全局文件
- app.wxss:小程序项目全局样式文件
- project.config.json:项目的配置文件
- sitemap.json:配置小程序以及页面是否允许被微信索引
JSON文件说明
app.json
- 是当前小程序的全局配置
- 包含了小程序的所有页面、窗口外观、界面表现、底部tab等
- 基础配置:
- pages:用于记录当前小程序的所有页面
- window:用于定义小程序所有页面的背景色、颜色等
- style:用于定义小程序使用的样式版本
- sitemapLocation:用于指明sitemap.json的文件位置
project.config.json
- 是用来记录我们对小程序开发工具的个性化配置
- 基础配置:
- setting:保存了编译相关的配置
- projectname:保存了项目名称
- appid:保存了小程序id
sitemap.json
- 微信开放了小程序搜索,类似于网页的SEO,该文件用于配置小程序是否被微信索引
页面.json
- 每一个页面的json文件,来操作本页面的配置,相同配置会覆盖window中的配置项
一、基本构建
新建小程序页面
- 只需要在
app.json -> pages配置中新增页面的存放路径,小程序会自动帮我们创建对应的文件
修改项目首页
- 只需要调整
app.json -> pages数组中页面路径的前后顺序,即可修改项目首页。 - 小程序会把排在第一的页面当做首页渲染
WXML模板
- 类似html,是微信小程序的一套标签语言
- 区别:
- 标签名不同
- 属性节点不同
- 提供了类似Vue的模板语法
VWSS
- 类似css,是微信小程序的一套样式语言
- 区别:
- 新增了rpx尺寸单位
- 提供了全部的样式和局部样式
- 局部的wxss会覆盖全局的
- wxss只支持部分的css选择器
- .calss、id、element
- 并集选择器、后代选择器
- 伪类选择器
Js
app.js
- 是整个小程序的入口,通过调用App()函数来启动小程序
页面.js
- 是页面的入口文件,通过调用Page()函数来创建并运行页面
普通.js
- 用于封装公共的函数或属性
二、通讯模型
通讯的主体
- 小程序中的通讯主体是渲染层和逻辑层
- VXML和WXSS工作在渲染层
- JS脚本工作在逻辑层
小程序的通讯模型
- 渲染层与逻辑层之间的通讯:由微信客户端进行转发
- 逻辑层与服务器之前的通讯:由微信客户端进行转发
小程序的运行机制
- 执行过程:
- 把小程序的代码包下载到本地
- 解析app.json全局配置文件
- 执行app.js小程序入口文件,调用App()创建小程序实例
- 渲染小程序首页
- 小程序启动完成
页面的渲染过程
- 执行过程:
- 加载解析页面的.json文件
- 加载页面的wxml与wxss样式
- 执行页面的js文件,调用Page()创建页面实例
- 页面渲染完毕
三、组件
视图容器
view
普通的视图区域
类似于HTML中的div,是一个块级元素
常用来实现页面的布局效果
基本使用:
实现flex的横向布局效果
<view class="container1"> <view>A</view> <view>B</view> <view>C</view> </view>.container1 { display: flex; justify-content: space-around; } .container1 view { width: 100px; height: 100px; text-align: center; line-height: 100px; color: #ffffff; font-weight: 200; background-color: lightcoral; }
scroll-view
可滚动的视图区域
常用与可滚动的列表效果
使用:给父盒子限定高度后再加上
scroll-y属性即可也可以给普通view加高度再加
overflow: scroll<scroll-view class="container2" scroll-y> <view>A</view> <view>B</view> <view>C</view> </scroll-view>
swiper与swiper-item
轮播图容器与轮播图item组件
<!-- indicator-dots 显示指示点 --> <!-- circular 衔接滑动 --> <!-- 更多属性访问:https://developers.weixin.qq.com/miniprogram/dev/component/swiper.html --> <swiper circular="true" indicator-dots="true" indicator-color="#000" indicator-active-color="#fff"> <swiper-item style="background-color:cadetblue;"> <view>A</view> </swiper-item> <swiper-item style="background-color: coral;"> <view>B</view> </swiper-item> <swiper-item style="background-color: cornflowerblue;"> <view>C</view> </swiper-item> </swiper>
基础内容
text
- 通过text组件的user-select="true"属性实现长按选择文本内容的效果
rich-text
通过该节点的nodes属性,来将内容替换为HTML的元素结构
会覆盖原标签的内容,类似v-html或innerHTML
使用场景:服务器返回了html标签,就需要通过该标签进行渲染
<rich-text nodes="<h1 style='color: blue'>标题</h1>">原来的内容会被覆盖</rich-text>
button
- 按钮组件
- 通过type属性来决定不同的按钮样式
- 通过size属性来决定按钮尺寸
- 通过plain属性来决定按钮是否镂空
- 通过open-type属性可以调用微信提供的各种功能
image
图片组件
src属性用于加载资源的路径
mode属性用于决定图片的裁剪与缩放模式
- 具体值参考:https://developers.weixin.qq.com/miniprogram/dev/component/image.html
navigator
- 页面导航组件,类似于html中的a标签
表单组件
导航组件
媒体组件
map地图组件
canvas画布组件
开放能力
无障碍访问
四、API
事件监听API
- 特点:以on开头,用来监听某些事件的触发
- 例如:wx.onWindowResize(function callback):监听尺寸的变化
同步API
- 特点:
- 都是以Sync结尾的
- 同步API执行的结果,可以通过函数返回值直接获取,如果出错会抛出异常
- 例如:wx.setStorageSync(key, val):向本地存储写内容
异步API
- 类似于axios的发送请求
- 例如:wx.request()发送网络请求,通过sucess接收数据
五、协同工作
在中大型公司中,人员分工很仔细,会有不同员工同时参与设计与开发
了解权限管理需求
- 我们需要对不同岗位的员工进行边界的划分,例如开发人员只有开发的权限,没有测试和发布的权限
了解项目成员的组织结构
- 项目管理者
- 产品组:提需求
- 设计组:出设计方案
- 开发组:代码开发
- 测试组:项目测试
小程序开发流程
提出需求 -> 设计 -> 开发 -> 体验 -> 测试 -> 发布
小程序成员管理
登录微信小程序 -> 成员管理添加即可
小程序的版本
| 版本阶段 | 说明 |
|---|---|
| 开发版本 | 使用开发者工具创建的项目,可提交审核 |
| 体验版本 | 可以选择某个开发版作为体验版本 |
| 审核中的版本 | 只能有一份代码处于审核中,审核成功后可发布到线上 |
| 线上版本 | 线上所有用户体验的版本 |
发布上线
上传代码
- 点击开发者工具右上角的上传按钮
- 填写版本号以及备注即可
- 点击上传
- 上传完毕后,会在小程序官网的管理中显示对应的版本
发布上线
- 审核通过后,会在版本管理中显示对应的版本。可以点击发布进行上线
获取小程序码
- 登录小程序管理网站 -> 设置 -> 基本设置 -> 基本信息 -> 小程序码及线下物料下载
查看小程序运营数据
- 登录小程序管理网站 -> 统计可查看
- 使用小程序数据助手查看
六、模板语法
数据绑定
定义数据:在页面对应的js文件中,把数据定义到data对象中即可
使用数据:直接在标签中使用
{{ 数据 }}的格式来使用即可Page({ data: { info: '你好我是数据', list:[1,2,3,4] } }<view>{{ info }}</view> <view>{{ list }}</view>
属性数据绑定
如果要给标签属性绑定值,需要以
属性="{{ 数据 }}"的方式来绑定Page({ data: { imgSrc: 'http://xx.xxxx.xx' } }<image src="{{imgSrc}}"></image>
三元运算符、算数运算符
Page({
data: {
randomData: Math.random() * 10
}
}<view>{{randomData}} - {{randomData ? '数字大于5' : '数字小于5'}}</view>
<view>{{randomData * 10}}</view>vx-if
用法与Vue中的v-if使用方法一致
与vue中的性质一样,初始值为false时则不会渲染
<view wx:if="{{sex == 0}}">女</view> <view wx:elif="{{sex == 1}}">男</view> <view wx:else>其他</view>可以结合
<block>标签一起使用- 如果需要给多个标签一起设置条件渲染,可以在外面加一个block标签。再给这个block添加wx-if
- block这个标签在渲染时不会被渲染成一个真实的节点,类似html中的
<template>
hidden
用法与Vue的v-show一致
与vue中的性质一样,都会渲染,也是使用display: none
wx-for
用法与Vue的v-for有些差别
- 如果需要手动指定索引与当前项的变量名:
- 使用:wx:for-index来指定索引
- 使用:wx:for-item来指定当前项
<view wx:for="{{list}}"> 索引是:{{index}} - 值是:{{item.name}} </view> <view wx:for="{{list}}" wx:for-index="idx" wx:for-item="itemName" wx:key="id"> 索引是:{{idx}} - 值是:{{itemName.name}} </view>- 如果需要手动指定索引与当前项的变量名:
wx:key
与vue中的key一致
<view wx:for="{{list}}" wx:key="id"> 索引是:{{index}} - 值是:{{item.name}} </view>
七、事件绑定
小程序中常用的事件
| 类型 | 绑定方式 | 事件描述 |
|---|---|---|
| tap | bindtap或bind:tap | 手指触摸后马上离开,类似click |
| input | bindinput或bind:input | 文本框输入事件 |
| change | bindchange或bind:change | 状态改变时触发 |
事件对象event
事件触发时,会收到一个事件对象event,有以下属性
| 属性 | 类型 | 说明 |
|---|---|---|
| type | string | 事件类型 |
| timeStamp | Integer | 页面打开到触发事件经过的毫秒数 |
| target | Object | 触发事件的组件的属性 |
| currentTarget | Object | 当前组件的属性 |
| detail | Object | 额外的信息 |
| toches | array | 触摸事件,当前触摸点的信息 |
| changedTouches | array | 触摸事件,当前变化的触摸点信息 |
target与currentTarget
target是触发该事件的源头组件、currentTarget是当前事件所绑定的组件
绑定事件
直接使用对应的函数绑定在标签内即可
<button type="primary" bindtap="tap1">点我执行函数</button>// pages/data/data.js
Page({
data: {},
tap1(e){
console.log(e);
}
})修改数据
使用 this.setData({参数: 修改的值}) 进行修改
Page({
data: { count: 0},
tap1(){
this.setData({
count: this.data.count + 1
})
}
})事件传值
在绑定事件时,不能像vue那样直接使用函数括号的方式传值 @click="btn(5) 这样是不允许的"
只能通过自定义参数传值,例如:
<button type="primary" bindtap="tap2" data-x="{{5}}">传递参数count++</button> 其中tap2是函数名称,data-x是数据。tap2(e) { console.log(e.target.dataset.x); } // 需要通过 e.target.dataset.数据 来接收
bindinput事件
通过event.detail.val获取文本框的值
动态绑定:
<view> <view>{{iptVal}}</view> <input value="{{iptVal}}" type="text" placeholder="请输入内容" bindinput="ipt1" /> </view>Page({ data: { iptVal: '' }, ipt1(e){ this.setData({ iptVal: e.detail.value }) } })
八、模板样式
rpx
什么是rpx尺寸单位
- 是微信小程序独有的尺寸单位,用于解决屏幕适配问题
实现原理
- 将屏幕大小分为750份,即屏幕的总宽度为750rpx
- 在不同设备运行时,会自动转换为对应的像素进行渲染,达到适配效果
样式导入
使用WXSS提供的@import的语法来导入外部样式
@import "公共的样式.wsss" view { // ... }
全局样式与局部样式
- 在app.wxss中的样式会应用于全部页面,在组件.wxss中的样式只会应用于当前组件
- 局部样式会覆盖全局的重名样式
- 如果全局的权重比局部的权重高,则局部的不会覆盖全局的
九、全局配置
更多配置访问: https://developers.weixin.qq.com/miniprogram/dev/reference/configuration/app.html
window
小程序窗口分为:
- 导航栏(顶部的头部):navigationBar
- 背景区域(下拉时的颜色,没有背景颜色时显示该颜色):background
- 页面主体(显示页面的部分):wxml
常用配置:
| 属性名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| navigationBarTitleText | string | 字符串 | 导航栏标题内容 |
| navigationBarBackgroundColor | HexColor | #000000 | 导航栏背景颜色 |
| navigationBarTextStyle | string | white | 导航栏标题颜色,仅支持black/white |
| backgroundColor | HexColor | #ffffff | 窗口的颜色 |
| backgroundTextStyle | string | dark | 下拉刷新的样式,仅支持dark/light |
| enablePullDownRefresh | Boolean | false | 是否开启全局下拉刷新 |
| onReachBottomDistance | Number | 50 | 页面下拉刷新执行的距离,单位为px |
tabBar
tabBar是移动端常见的页面效果,用于实现多页面的快速切换,一般分为两类
- 底部tabBar
- 顶部tabBar
tabBar中只能配置最少两个、最多五个tab页签
当渲染顶部tabBar时,不显示icon图标,只显示文本
tabBar的六个组成部分
- backgroundColor:背景颜色
- selectedIconPath:选中时的图片路径
- iconPath:非选中的图片路径
- borderStyle:上边框的颜色
- selectedColor:选中的文本的颜色
- color:非选中的文本的颜色(默认)
使用tabBar
https://developers.weixin.qq.com/miniprogram/dev/reference/configuration/app.html#tabBar
- 在app.json中配置一项tabBar的配置项,与window同级
| 属性 | 类型 | 必填 | 默认 | 描述 |
|---|---|---|---|---|
| position | string | no | bottom | tabBar位置,仅支持bottom/top |
| borderStyle | string | no | black | tabBar上边框颜色,仅支持black/white |
| color | #000 | no | tab未选择文本的颜色(默认) | |
| selectedColor | #000 | no | tab选中的文本的颜色 | |
| backgroundColor | #000 | no | tabBar的背景色 | |
| list | array | 必填 | tab标签列表,最少两个、最多五个 |
每个list中的配置项:
| 属性 | 类型 | 必填 | 描述 |
|---|---|---|---|
| pagePath | String | 是 | 页面路径,必须先在pages中定义 |
| text | string | 是 | tab上显示的文字 |
| iconPath | string | 否 | 未选择时的图标路径 |
| selectedIconPath | string | 否 | 选中时的图标路径 |
{
"pages":[...],
"window":{...},
"tabBar": {
"position": "top",
"list": [{
"pagePath": "pages/list/list",
"text": "list"
},
{
"pagePath": "pages/data/data",
"text": "data"
}]
},
...
}十、页面配置
创建页面中的json文件,用于单独配置当前页面的窗口表现
https://developers.weixin.qq.com/miniprogram/dev/reference/configuration/page.html
- 配置与全局配置类似
十一、网络数据请求
小程序中的网络数据请求的限制
- 只能请求HTTPS类型的接口
- 必须将接口的域名添加到信任列表中
- 小程序开发者工具 -> 右上角详情 -> 项目配置 -> 域名信息 -> 合法域名中查看
配置合法域名
- 打开微信小程序管理后台
- 进入开发设置
- 进入服务器域名进行配置即可
注意:
- 域名只支持https协议
- 域名不能使用ip地址或localhost
- 域名必须经过ICP备案
- 服务器域名一个月内最多只能申请5次
发起GET/POST请求
调用微信小程序提供的wx.request()方法,可以发起GET/POST请求
请求类型只需要修改method配置为对应类型即可
// 点击按钮的回调,发起get请求
btn(e) {
wx.request({
url: 'https://www.escook.cn/api/get',
method: 'GET',
data:{
name: '张三',
age: 20
},
success: (res)=>{
console.log(res);
}
})
}跳过request合法域名校验
如果后端只提供了http协议的接口,为了不耽误开发进度。可以在微信开发者工具中 -> 右上角详情 -> 本地设置中,临时开启开发环境不校验请求域名、TLS版本以及HTTPS证书选项,跳过request合法域名校验
注意:该设置仅在开发与试验阶段使用
跨域与ajax问题
跨域:跨域问题只存在于基于浏览器的Web开发中,由于小程序运行在微信,所以不存在跨域问题
ajax:ajax核心技术基于浏览器中的XMLHttpRequest对象,由于小程序宿主环境时微信,因此不能叫“发起ajax请求”,而叫发起网络请求
十二、生命周期
执行顺序:小程序启动 -> 页面A生命周期 -> 页面B生命周期 -> ... -> 小程序关闭
应用生命周期
指小程序从启动 -> 运行 -> 销毁的过程;需要在app.js中进行书写
1. onLaunch
小程序初始化完毕后执行一次,全局只触发一次
2. onShow
小程序启动或从后台进入前台时触发
3. onHide
小程序从前台进入后台时触发
页面生命周期
指每个页面的加载 -> 渲染 -> 销毁的过程:在组件.js中书写
1. onLoad
页面加载完毕时执行,只执行一次
// index.js // 获取应用实例 const app = getApp() Page({ data: {}, onLoad(){ this.test() }, test(e) { console.log('执行了') }, })
2. onShow
监听页面显示
3. onReady
页面初次渲染完毕执行,只执行一次
4. onHide
监听页面隐藏
5.onUnload
监听页面销毁,只执行一次
十三、导航跳转
声明式导航
https://developers.weixin.qq.com/miniprogram/dev/component/navigator.html
- 在页面定义一个
<navigator>导航组件 - 通过点击
<navigator>组件实现跳转
跳转到tabBar页面
需要指定
<navigator>组件的url属性和open-type属性url表示跳转的地址,必须以 **/ **开头
open-type表示跳转的方式,必须为switchTab
<navigator url="/xxx/xxx" open-type="switchTab"></navigator>
跳转到非tabBar页面
需要指定
<navigator>组件的url属性和open-type属性url表示跳转的地址,必须以 **/ **开头
open-type表示跳转的方式,必须为navigate,默认值也是navigate
<navigator url="/xxx/xxx"></navigator>
后退导航
如果希望后退到上一个或后退多个页面时
需要open-type属性和delta属性
open-type的值必须是navgateBack
delta的值必须是数字,代表后退的层级,如果不写,默认后退1页
<navigator open-type="navgateBack"></navigator>
声明式路由传参
- navigator通过url进行传参,url后面可以跟上?id=1的形式来声明参数
- 例如:
<navgator src="/pages/home/home?id=100&name=砂糖">
编程式导航
https://developers.weixin.qq.com/miniprogram/dev/api/route/wx.switchTab.html
通过调用小程序的API,实现跳转
跳转到tabBar页面
调用wx.switchTab(options)方法,可以跳转到tabBar页面,其中参数如下
属性 类型 必选 说明 url string 是 跳转的路径 success function 否 成功的函数 fail function 否 失败的函数 complete function 否 执行结束执行的函数 wx.switchTab({ url: '/pages/list/list', complete: (e)=>{ console.log(e); } })
跳转到非tabBar页面
调用wx.navgateTo(options)方法,可以跳转到非tabBar页面其中参数如下
属性 类型 必选 说明 url string 是 跳转的路径 success function 否 成功的函数 fail function 否 失败的函数 complete function 否 执行结束执行的函数 wx.navigateTo({ url: '/pages/info/info', complete: (e)=>{ console.log(e); } })
后退导航
调用
wx.navigateBack(options)即可后退,参数如下属性 类型 必选 说明 delta number 否 后退的层数,默认为1,如果层数大于页面的层数则返回首页 success function 否 成功的函数 fail function 否 失败的函数 complete function 否 执行结束执行的函数 back(){ wx.navigateBack() }
编程式导航传参
通过调用wx.navigateTo函数,配置url中传递参数即可
wx.navigateTo({ url: '/pages/home/home?name=砂糖&age=17' })
接收传递的参数
通过onLoad生命周期函数,可以接收到声明式导航、编程式导航的参数
Page({
onLoad(options){
// options 里包含着传递的参数: {name: "砂糖", age: "17"}
console.log(options);
}
})十四、下拉刷新
启用下拉刷新
全局开启
为app.json的window配置中设置enablePullDownRefresh:'true'
不推荐开启全局的,因为不是每一个页面都需要下拉刷新
局部开启
为页面.json中的配置中设置enablePullDownRefresh:'true'
配置下拉刷新窗口样式
在全局或页面中.json配置:backgroundColor与backgroundTextStyle来配置下拉刷新样式
- backgroundColor:配置下拉刷新的窗口颜色(实际上就是页面的背景颜色)
- backgroundTextStyle:配置下拉刷新加载的样式,值为:dark,light
监听页面的下拉刷新事件
通过onPullDownRefresh()函数监听
onPullDownRefresh(){ console.log('执行了下拉刷新'); }
停止下拉刷新的效果
当开启下拉刷新后,loading效果会一直持续,不会主动消失,需要手动调用wx.stopPullDownRefresh()函数
onPullDownRefresh() { console.log('执行了下拉刷新'); wx.stopPullDownRefresh() }
十五、上拉触底
指的是从页面底部继续往上拉,执行函数的操作
通过onReachBottom()函数监听上拉触底事件
onReachBottom(){ console.log('执行了上拉触底'); }
配置上拉触底距离底部的距离
在全局或页面json文件中,配置onReachBottomDistance配置距离,默认是50px
"onReachBottomDistance": 150节流控制
- 在data中定义一个节流值
- 在发送请求之前将节流设置为true,发送完毕后设置为false
- 判断节流,如果是true则不发送请求
十六、Loading提示框
https://developers.weixin.qq.com/miniprogram/dev/api/ui/interaction/wx.showLoading.html
- 通过wx.showLoading(options)来显示提示框
- 通过wx.hideLoading()来关闭提示框
十七、自定义编译模式
- 打开开发者工具,添加编译模式
- 添加名称,启动页面路径
- 配置完成后,每次热更新或编译都会启动到这个路径
十八、WXS
全称是:WeiXin Script,是小程序独有的脚本语言,结合WXML可以构建出页面结构
wxs不支持es6以上的语法
wxs遵循CommonJS语法
基础语法
与html一样,可以使用<wxs>标签进行定义,该标签必须提供module属性,来指定当前wxs模块名称,方便访问wxml中访问模块中的成员
<!--pages/list/list.wxml-->
<view>userName: {{userName}}</view>
<view>userName1: {{m1.toABC(userName)}}</view>
<wxs module="m1">
module.exports.toABC = function(str){
return str.toUpperCase()
}
</wxs>定义外联的wxs
声明一个xxx.wxs文件,使用module.exports暴露出属性与方法给外部使用
// tools.wxs
function toLower(str) {
return str.toLoweCase()
}
module.exports = {
toLower: toLower
}使用外联的wxs
使用src属性来指定引入的路径,使用module指定模块名称
<wxs module="m2" src="../../utils/tools.wxs"></wxs>WXS的特点
- 不能调用js中定义的函数
- 不能调用小程序的API
- 在ios设备上,微信小程序的WXS会比JS快2~20倍,在安卓上无差异
十九、wx.事件
设置小程序的标题头
wx.setNavigationBarTitle({
title: this.data.query.name
})弹出提示框
https://developers.weixin.qq.com/miniprogram/dev/api/ui/interaction/wx.showToast.html
wx.showToast({
title: '没有更多了呢 亲',
icon: 'none'
})二十、组件
创建组件
- 在项目根目录下创建components -> test文件夹
- 在新建的test文件夹中右键,新建component
- 输入名称后会自动创建4个组件的文件
引用组件
局部引用:
在当前页面中引入,只能在当前页面使用
// 页面的.json文件中
{
"usingComponents": {
"my-test": "/components/test/test"
}
}// 使用标签名即可
<my-test></my-test>全局引入:
在app.json文件中引入配置,在每个小程序中都可使用
// app.json中
"window": {},
"usingComponents": {
"my-test": "/components/test/test"
},
...组件与页面的区别
从文件内容来看
- 组件的.json文件需要声明 "component": true
- 组件的.js文集拿掉用的是Component()函数
- 组件的事件处理函数需要定义到methods节点中
样式
组件样式隔离
默认情况下,自定义组件的样式只对当前组件生效,不会影响到组件之外的Ui结构
例如:
- 组件A的样式不会影响组件B的样式
- 组件A的样式不会影响小程序页面样式
- 小程序页面样式不会影响组件A与C的样式
组件样式隔离注意点
app.wxss中的全局样式对组件无效
只有class选择器会有样式隔离效果、id、el、伪类不受影响
建议:在组件和引用组件的页面使用class选择器,不要用id、属性、标签选择器
修改组件样式隔离选项
如果想要让全局样式影响到组件的样式,可以配置其中一种即可
// 1. 组件.js文件中
Component({
options: {
styleIsolation: 'isolated'
}
})// 2. 组件.json文件中
{
"styleIsolation": "isolated"
}| styleIsolation可选值 | 默认值 | 描述 |
|---|---|---|
| isolated | 是 | 表示启用样式隔离,使用class的样式不会互相影响 |
| apply-shared | 否 | 表示页面样式影响到自定义组件,但自定义组件样式不影响页面样式 |
| shared | 否 | 表示页面样式影响到自定义组件,自定义组件也会影响到页面组件 |
自定义组件的数据、方法、属性
data数据
自定义组件的数据,都需要定义在data配置项中
methods方法
自定义组件的事件处理函数、自定义方法,都需要定义在methods配置项中
properties属性
可能外部会传入一些属性给自定义属性使用,例如<自定义组件 max="10"></自定义组件>
<my-test max="100" min="1"></my-test>// components/test/test.js
Component({
properties: {
// 完整写法
max: {
type: Number, // 限制类型
value: 10 // 默认值
},
// 简写
min: Number // 只能限制类型
}
})使用传递的数据
使用this.properties.数据即可
data与properties的区别
在小程序中,properties是可读可写的
- data更倾向于存储组件的私有数据
- properties更倾向于存储外界传递的数据
实际上,data与properties两个对象指向的是同一个地址
this.data === this.properties // true修改properties的值
与修改data数据方法一致,使用properties
组件生命周期
| 函数 | 参数 | 说明 |
|---|---|---|
| created | 无 | 组件实例被创建时执行 |
| attached | 无 | 组件实例进入dom树时执行 |
| ready | 无 | 组件挂载完毕时执行 |
| moverd | 无 | 组件位置更新时执行 |
| detached | 无 | 组件移除时执行 |
| error | errorObject | 组件方法抛出错误时执行 |
各个阶段的生命周期的特点
created:组件刚被创建完毕
此时还不能调用this.setData
通常为组件实例,添加一些自定义属性与方法
attached:组件进入dom时
此时this.data初始化完毕
可以发送请求,初始化数据
detached:页面离开节点数后
- 退出一个页面时,会触发页面的detached函数
- 做一些清理工作,例如断开ws链接,清除定时器等
lifetimes配置
在小程序组件中,生命周期函数可以直接定义在Component构造器的第一级参数中,也可以在lifetimes中定义
在lifetimes中定义的生命周期优先级最高,如果两种写法都存在 那么旧的写法不会执行
Component({
lifetimes:{
created(){
console.log('初始化完毕');
}
},
// 旧的写法
created(){
console.log('初始化完毕');
}
})组件所在页面的生命周期
有时候,自定义组件的行为依赖于自定义状态的变化,就需要用到组件所在的页面生命周期
通俗点说就是页面的声明周期执行时,组件的生命周期也一起执行
| 生命周期函数 | 参数 | 说明 |
|---|---|---|
| show | / | 页面展示时执行 |
| hide | / | 页面隐藏时执行 |
| resize | object | 页面大小变化时执行 |
组件所在页面的生命周期函数需要定义在pageLifetimes配置项中,例如:
Component({
pageLifetimes
})插槽
与vue中的v-slot类似
使用<slot>标签去定义一个插槽
使用单个插槽
// component中
<view>
<slot></slot>
</view>// 父组件中
<my-test>
<view>我是插槽</view>
</my-test>使用多个插槽
需要在组件.js文件中配置options -> multipleSlots: true,来启用多个插槽
Component({
options:{
multipleSlots: true
}
})定义多个插槽,通过name值来区分不同的插槽,通过slot值来寻找对应的插槽
// component中
<view>
<slot name="A"></slot>
<slot name="B"></slot>
</view>// 父组件中
<my-test>
<view slot="A">我是插槽A</view>
<view slot="B">我是插槽B</view>
</my-test>二十一、数据监听器
类似于vue中的watch
在小程序中需要使用observers配置
语法
// components/test/test.js
Component({
data: {
n1: 0,
n2: 0,
num: 0
},
// 当n1, n2的值发生变化了就会执行函数
observers: {
'n1, n2': function(newN1, newN2){
// 该newN1与newN2代表最新值
this.setData({
num: newN1 + newN2
})
}
}
})监听对象
// components/test/test.js
Component({
data: {
obj:{
a:1,
b:2
}
},
observers: {
'obj.a, obj.b': function(newN1, newN2){
// ...
// 当obj.a、 obj.b 发生变化时会触发
// 当obj对象被赋值了也会触发
}
}
})监听对象中所有属性变化
类似于vue中的deep深度监听
使用**obj.****来定义
// components/test/test.js
Component({
data: {
obj:{
a:1,
b:2
}
},
observers: {
'obj.**': function(newN1, newN2){
// ...
}
}
})纯数据字段
什么是纯数据字段
- 指的是那些不用与页面渲染的data数据,也不传递给其他组件,仅仅在当前组件内部使用的数据,被定义的数据不会被渲染到页面
- 好处:提升性能
使用规则
在component构造器的options配置项中,指定pureDataPattern为一个正则表达式,符合这个表达式的数据即为纯数据字段
例如:
Component({
options:{
// pureDataPattern: /^_/
},
data: {
_name: '张三'
}
})二十二、父子组件传递数据
通讯的三种方式
属性绑定
- 用于父组件向子组件传递数据,仅能设置JSON兼容的数据
// 父节点的数据
data: {
count: 0
}
// 父节点的结构
<test count="{{count}}"></test>// 子组件的数据
properties: {
count: Number
}
// 子组件的结构
<text>{{count}}</text>事件绑定
用于子组件向父组件传递数据,可以传递任意数据
- 在父组件的js中,定义一个函数
- 在父组件的wxml中,这个函数将wxml通过自定义参数的形式传递给子组件
- 在子组件的js中,通过调用this.triggerEvent('自定义事件名称',{参数对象}),将数据发送给父组件
- 在父组件的js中,通过e.detail获取子组件传递过来的值
组件的 bind:sync="syncCount",sync是子组件传递的自定义事件名称
组件的 bind:sync="syncCount",syncCount是响应子组件的回调函数名称
// 1.在父组件的js中,定义一个函数,通过e.detail接收数据
syncCount(e){
console.log(e.detail)
}// 2.在父组件中使用自定义函数接收(推荐第一种)
<test bind:sync="syncCount"></test>
或
<test bindsync="syncCount"></test>// 3.在子组件js中,使用this.triggerEvent('自定义事件名称',{参数对象}) 来触发
this.triggerEvent('sync',{参数对象})获取组件实例
父组件调用this.selectComponent()获取子组件实例对象
这样就可以直接访问子组件的任意数据与方法
使用方法:
- 在父组件里调用this.selectComponent("id或class选择器"),获取组件的实例对象,从而访问组件中的任意数据与方法
<my-test class="myTest"></my-test> <button bindtap="getComponent">点我获取子组件实例</button>// 属性在child身上,方法在child.__proto__身上 getComponent(){ let child = this.selectComponent('.myTest') child.setData({ ... }) },
二十二、behaviors
类似于vue中的mixins,用于实现组件间代码共享的特性
每个behaviors可以包含一组属性、数据、生命周期和方法,组件引用他们时,这些数据会合并到组件中
每个组件可以引入多个behaviors,behaviors可以引入其他behaviors
https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/behaviors.html
创建behaviors
调用Behavior()方法可创建一个共享的behavior实例对象,供所有组件使用
// my-behavior.js
module.exports = Behavior({
data: {
name: '砂糖'
},
methods: {
// ...
}
// ...
})使用behaviors
在组件中,使用require(),导入模块
如果数据名称重复并且是对象类型,则会合并对象
如果数据、方法名称重复并且是普通类型,会覆盖,覆盖规则为:组件 > 父behaviors > 子behaviors > 靠后的behaviors
// 1. 导入
const myBehavor = require('../../behaviors/my-behavior')
Component({
// 2. 使用behaviors挂载
behaviors: [myBehavor],
data: {
_name: '张三'
}
// ...
})二十三、使用npm
小程序对npm的支持与限制
- 限制:
- 不支持依赖于nodejs内置库的包,例如fs
- 不支持依赖于浏览器内置对象的包
- 不支持依赖于c++插件的包
Vant Weapp
什么是Vant Weapp
https://vant-contrib.gitee.io/vant-weapp/#/home
- 是有赞前端团队开源的一套小程序UI组件库,类似于vue中的vant库
- 安装步骤参考官网中的快速上手
更改Vant中组件的样式
例如修改button按钮的样式:在app.wxss中可修改全局的样式
vant全局样式表:https://vant-contrib.gitee.io/vant-weapp/#/theme
/* 使用page是因为 page元素是页面的根节点 */
page {
--button-danger-background-color: #c00000;
--button-danger-barder-color: #D60000;
}API Promise化
基于回调函数的异步API缺点
默认情况下,小程序使用的异步api是基于回调函数来实现的,容易造成回调地狱问题,代码可读性、维护性差
如何将API Promise化
主要依赖于miniprogram-api-promise第三方npm包
安装:
npm i miniprogram-api-promise -S构建npm:
开发者工具 -> 工具 -> 构建npm建议在构建前,把原来的miniprogram_npm文件夹删除
使用:在小程序入口文件app.js中,引入该包
// app.js import { promisifyAll } from 'miniprogram-api-promise' const wxp = wx.p = {} promisifyAll(wx, wxp) App({ // ... }) // promiseifyAll(wx, wxp) 方法会将wx上的方法进行promise化,转到wxp中 // 然后通过wxp = wx.p 的方式在wx全局读写中创建一个p对象存放所有的promise化的方法调用promise化之后的异步API
<button @bindtap="getInfo"></button>async getInfo(){ const res = await wx.p.request({ mathod: "GET", url: 'https://www.escook.cn/api/get', data: { name: '砂糖', age: 17 } }) console.log(res) }
全局数据共享
可以使用mobx-miniprogram与mobx-miniprogram-bindings包实现全局共享
- mobx-miniprogram:用于创建store实例
- mobx-miniprogram-bindings:用于把store中的方法,绑定到组件或页面中使用
安装Mobx相关的包
npm安装:
npm i -S mobx-miniprogram mobx-miniprogram-bindings安装完成后,删除miniprogram_npm文件夹,重新构建npm
创建store实例
创建store文件夹以及store.js文件
// store.js 创建实例对象 import { observable } from 'mobx-miniprogram' export const store = observable({ // 定义数据 data1: 1, data2: 100, // 定义方法 getData1(val){ this.data1 += val console.log(this.data1); }, // 计算属性 get sum(){ return this.data1 + this.data2 } })
页面中使用store仓库
<view>{{data1}}+{{data2}}={{sum}}</view>
<van-button type="primary" bindtap="btnHandler" data-sum="{{1}}">numA+1</van-button>
<van-button type="primary" bindtap="btnHandler" data-sum="{{-1}}">numA-1</van-button>// 页面.js文件中
// pages/home/home.js
import { createStoreBindings } from 'mobx-miniprogram-bindings'
import { store } from '../../store/store'
Page({
onLoad(){
// createStoreBindings第一个参数是页面实例:this
// createStoreBindings第二个参数是配置项:即要导入的仓库、数据和方法
this.storeBindings = createStoreBindings(this, {
store,
// fields 配置项写需要使用的变量
fields: ['data1', 'data2', 'sum'],
// actions 配置项写action的方法
actions: ['updata1']
})
},
onUnload(){
// 销毁仓库
this.storeBindings.destoryStoreBindings()
},
// 点击按钮回调
btnHandler(e){
this.updata1(e.target.dataset.sum)
}
})组件中使用store仓库
<view>{{data1}}+{{data2}}={{sum}}</view>
<van-button type="primary" bindtap="btnHandler" data-sum="{{1}}">numA+1</van-button>
<van-button type="primary" bindtap="btnHandler" data-sum="{{-1}}">numA-1</van-button>// 1. 用于将store数据映射到当前组件中使用
import { storeBindingsBehavior } from 'mobx-miniprogram-bindings'
import { store } from '../../store/store'
Component({
// 2. 通过storeBindingsBehavior实现自动绑定
behaviors: [storeBindingsBehavior],
// 3. 使用storeBindings绑定数据
storeBindings: {
store,
fields: {
data1:()=> store.data1, // 方式1
data1:(store)=> store.data1, // 方式2
sum: 'sum' // 方式3
},
actions: {
// 绑定方法
updateNum2: 'updataNum2'
}
},
data: {},
methods: {
btnHandler(e){
this.updata2(e.target.dataset.sum)
}
}
})二十四、分包
分包是指把一个完整的小程序项目,按照需求划分为不同的子包,用于在使用时,按需加载。类似于vue中组件懒加载
好处:
- 可以优化小程序首次启动的事件
- 多团队开发时可以更好的解耦协作
分包前:小程序项目中所有的页面和资源都会被打包到一起
分包后:小程序项目由1个主包+多个分包组成
- 主包:一般只包含项目的启动页面或TabBar页面、以及公共资源
- 分包:只包含和当前分包有关的页面和私有资源
分包加载规则
- 小程序启动时,默认会下载主包并启动主包内羽绒棉
- 当用户进入分包某个页面时,会将对应的分包下载进行展示
分包体积限制
- 整个小程序不能超过16M,超过就无法发布
- 单个分包/主包大小不能超过2M
使用分包
配置方法
在app.json的subpackages中声明分包结构,保存后会自动生成对应的文件
{
"pages": [
// 主包的所有页面
"pages/home/home",
"pages/list/list",
"pages/my/my"
],
"subPackages": [
// 在这里设置分包的结构
{
"root": "pageA", // 根目录
"pages": [ // 当前分包下的所有页面路径
"pages/cat1/cat1",
"pages/dog1/dog1"
]
}, {
"root": "pageB", // 第二个根目录
"name": "pageB", // 分包的别名
"pages": [ // 当前第二个分包下的所有页面路径
"pages/cat2/cat2",
"pages/dog2/dog2",
"pages/banana2/banana2"
]
}
]
}打包原则
- 小程序会按照subpackages的配置进行分包,subpackages之外的会被放入主包中
- 主包也可以有直接的pages(最外层的pages字段)
- tabBar页面必须在主包内
- 分包之间不能互相嵌套
引用原则
- 主包无法引用分包内的私有资源
- 分包之前不能互相引用私有资源
- 分包可以引用主包内的公共资源
图片未迁移:image-20220714184117067 (C:\Users\dell\AppData\Roaming\Typora\typora-user-images\image-20220714184117067.png)
独立分包
独立分包本质上也是分包,但可以独立与主包和其他分包而单独运行
一个小程序可以有多个独立分包
配置独立分包
只需要在subPackages中的一个分包中加入"independent": true的属性
"subPackages": [
{
"root": "pageA",
"pages": [
"pages/cat1/cat1",
"pages/dog1/dog1"
]
}, {
"root": "pageB",
"name": "pageB",
"pages": [
"pages/cat2/cat2",
"pages/dog2/dog2",
"pages/banana2/banana2"
],
"independent": true // <- 设置这个属性
}
],独立分包和普通分包以及主包之间,是互相隔离的,不能互相引用资源
分包预下载
分包预下载指的是:在进入小程序的某个页面时,由框架自动预下载可能需要的分包,从而提升后续进入页面时的速度
同一个分包中的页面共享预下载大小最多2M
使用:在app.json中配置preloadRule节点
{
"preloadRule": {
// 对象名称为需要预下载的路径
"pageA/pages/cat1": {
/*
network下载的方式
- all 代表移动网络与wifi都下载
- wifi 代表只使用wifi下载
*/
"network": "all",
// packages哪些分包需要被预下载,可以设置root或name对应的值
"packages": ["root"]
}
},
// window: {...}
}自定义tabBar
https://developers.weixin.qq.com/miniprogram/dev/framework/ability/custom-tabbar.html
使用:
在
app.json中的tabBar项指定custom: true字段,同时其余tabBar相关配置也补充完整。在项目根目录新建
custom-tab-bar文件夹在该文件夹中新建Component,文件名为
index接下来可以配合vant中的tabBar组件开发页面,https://vant-contrib.gitee.io/vant-weapp/#/tabbar