前言
如今前端技术日新月异。对于前端开发人员来说,不仅需要掌握最新的前沿技术,还需要保持对基础知识的熟练掌握。而面试则是进入优秀企业的必经之路。在面试中,高频面试题的掌握是获得成功的关键。本文将为大家总结前端高频面试题及其答案,希望能够帮助读者更好地备战面试,也让你在前端领域取得更大的成功。
一、css 相关面试题
1. css如何垂直水平居中一个盒子?
1.1
margin
(外边距) +position
(定位)
将父元素设置相对定位,子元素设置绝对定位,再利用 margin
的属性将子元素向上、向左移动自身宽、高的一半即可。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<style>
.fatherBox {
width: 300px;
height: 300px;
background: cornflowerblue;
position: relative;
}
.sonBox {
width: 100px;
height: 100px;
background: coral;
position: absolute;
top: 50%;
left: 50%;
margin-top: -50px;
margin-left: -50px;
}
</style>
<body>
<div class="fatherBox">
<div class="sonBox"></div>
</div>
</body>
</html>
1.2 利用
flex
的属性设置父元素为弹性盒子,并设置水平主轴上的元素居中,垂直交叉轴上的元素居中即可。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<style>
.fatherBox {
width: 300px;
height: 300px;
background: green;
display: flex;
justify-content: center;
align-items: center;
}
.sonBox {
width: 100px;
height: 100px;
background: white;
}
</style>
<body>
<div class="fatherBox">
<div class="sonBox"></div>
</div>
</body>
</html>
1.3
absolute
(绝对定位)
将 top
、bottom
、left
、right
的值都设置为 0
,使绝对定位相对整个页面定位,同时 margin
为 auto
,完成上下左右外边距等分即可。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<style>
.fatherBox {
width: 300px;
height: 300px;
background: green;
position: relative;
}
.sonBox {
width: 100px;
height: 100px;
background: white;
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
margin: auto;
}
</style>
<body>
<div class="fatherBox">
<div class="sonBox"></div>
</div>
</body>
</html>
效果展示
2. CSS 盒子模型(Box Model)?
概念
所有 HTML
元素可以看作盒子,CSS
盒模型本质上是一个盒子,封装周围的 HTML
元素,它包括:边距、边框、填充和实际内容。下面的图片说明了盒子模型。
盒模型的组成
一个盒子由外到内可以分成四个部分:
margin
:边框外周围的空间border
:围绕在内边距和内容外的边框padding
:内容区和边框之间的空间content
:盒子的内容
盒模型的种类
盒模型分为两种,第一种是 W3C
标准的盒子模型(标准盒模型),第二种是 IE
的盒子模型(怪异盒模型)。
标准盒模型与怪异盒模型的区别
- 标准
盒子的宽度 = 内容宽度 + 左右内边距 + 左右边框 + 左右外边距
盒子的高度 = 内容高度 + 上下内边距 + 上下边框 + 上下外边距
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<style>
div {
width: 100px;
height: 100px;
border-left: 8px solid red;
border-right: 8px solid red;
margin-left: 100px;
padding-left: 20px;
padding-right: 20px;
margin-right: 20px;
background-color: cornflowerblue;
}
</style>
<body>
<div></div>
</body>
</html>
- 怪异
盒子的宽度= 宽度 + 左右外边距(宽度就已经包含了内边距和边框值)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<style>
div {
box-sizing: border-box;
width: 100px;
height: 100px;
border-left: 8px solid red;
border-right: 8px solid red;
padding-left: 20px;
padding-right: 20px;
margin-left: 100px;
margin-right: 20px;
background-color: cornflowerblue;
}
</style>
<body>
<div></div>
</body>
</html>
如何触发两种盒模型?
如果想要切换盒模型也很简单,只需要借助 css3
的 box-sizing
属性。
box-sizing的三个属性:
content-box - 将盒子设置为标准盒子模型
border-box - 将盒子设置为怪异盒子模型
padding-box - 将padding算入width范围
切换盒模型:
box-sizing: content-box; /* W3C标准盒模型 */
box-sizing: border-box; /* IE怪异盒模型 */
3. BFC?
概念
BFC
简单来说就是块级格式化上下文的意思,它是一个独立的渲染区域,其中元素不受外界影响,也不会影响外部。同时它也是一种布局的方式,且我们还可以用它解决外边距重叠的问题。
BFC
有什么特点?
BFC
内部块元素在垂直方向,按序放置;- 如果两个块级元素属于同一个
BFC
,它们的上下外边距就会重叠,以较大的为准。但是如果两个块级元素分别在不同的BFC
中,它们的上下边距就不会重叠了,而是两者之和; BFC
的区域不会与浮动的元素区域重叠,也就是说不会与浮动盒子产生交集,而是紧贴浮动边缘;- 计算
BFC
的高度时,浮动元素也参与计算。BFC
可以包含浮动元素; BFC
是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面的元素。
如何创建一个
BFC
?
float
的值不是none
;position
的值不是static
或者relative
;display
的值是inline-block
、table-cell
、flex
、table-caption
或者inline-flex
;overflow
的值不是visible
。
BFC
有什么用?
1. 解决外边距折叠问题
父子关系的边距重叠,如果子元素设置了外边距,在没有把父元素变成 BFC
的情况下,父元素也会产生外边距。
解决方案:
给父元素添加 overflow:hidden
,这样父元素就变为 BFC
,不会随子元素产生外边距,如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<style>
* {
padding: 0;
margin: 0;
}
.fatherBox {
width: 300px;
height: 300px;
background: cornflowerblue;
/* 解决父子关系边距重叠 */
overflow: hidden;
}
.sonBox {
width: 100px;
height: 100px;
background: coral;
margin-top: 100px;
}
</style>
<body>
<div class="fatherBox">
<div class="sonBox"></div>
</div>
</body>
</html>
2. 同级兄弟关系重叠,同级元素在垂直方向上外边距会出现重叠情况,以值大的为准。
解决方案:
可通过添加空元素或者伪元素,设置 overflow:hidden
解决,如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<style>
* {
padding: 0;
margin: 0;
}
.broBoxF {
width: 300px;
height: 300px;
background: cornflowerblue;
margin-bottom: 50px;
}
.broBoxT {
width: 100px;
height: 100px;
background: coral;
margin-top: 100px;
}
.afferBox {
overflow: hidden;
}
</style>
<body>
<!-- 第一个盒子 -->
<div class="broBoxF"></div>
<!-- 空盒子用来解决外边距重叠问题 -->
<div class="afferBox"></div>
<!-- 第二个盒子 -->
<div class="broBoxT"></div>
</body>
</html>
3. 制作两栏布局
因为 BFC
的区域不会与浮动的元素区域重叠,因此我们可以利用这个特性制作一个两栏布局(左边宽度固定,右边宽度自适应)。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<style>
* {
padding: 0;
margin: 0;
}
.fatherBox {
height: 500px;
background: gainsboro;
}
.sonBoxF {
float: left;
width: 200px;
height: 100px;
background: coral;
}
.sonBoxT {
height: 100px;
background: goldenrod;
overflow: hidden;
}
</style>
<body>
<div class="fatherBox">
<div class="sonBoxF">left</div>
<div class="sonBoxT">right</div>
</div>
</body>
</html>
4. 清除浮动
由于父元素没有设置高度,子元素浮动后脱离文档流,故造成高度塌陷的问题。
解决方案:
父元素设置 overflow:hidden
,将其变成 BFC
,如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<style>
* {
padding: 0;
margin: 0;
}
.fatherBox {
background: cadetblue;
border: 1px solid red;
overflow: hidden;
}
.sonBox {
float: left;
width: 100px;
height: 100px;
background: salmon;
}
</style>
<body>
<div class="fatherBox">
<div class="sonBox"></div>
</div>
</body>
</html>
4. HTML5和CSS3新特性有哪些?
概念
-
HTML5
HTML5
是一种web
标记语言,用于开发网页使用。HTML
是web
应用中一种”超文本标记语言(HTML
)“的第五次重大修改,我们将这次修改后的HTML
标准,称之为"HTML5
"。 -
CSS3
CSS3
是CSS
(层叠样式表)技术的升级版本,于 1999 年开始制订,2001年5月23日W3C
完成了CSS3
的工作草案,主要包括盒子模型、列表模块、超链接方式、语言模块、背景和边框、文字特效、多栏布局等模块。
HTML5
新特性
1. 语义化标签
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body>
<!-- 头部标签 -->
<header>
<!-- 导航区域 -->
<nav></nav>
</header>
<!-- 主体部分 -->
<main>
<!-- 小模块 -->
<section>
<!-- 边栏框 -->
<aside></aside>
<!-- 正文框 -->
<article></article>
</section>
</main>
<!-- 尾部标签 -->
<footer></footer>
</body>
</html>
2. 增强型表单(input
的多个 type
)
<!-- 输入邮箱的 -->
<input type="email">
<!-- 输入网址的 -->
<input type="url" />
<!-- 输入电话号码的 -->
<input type="tel" />
<!-- 输入日期的 -->
<input type="date" />
<input type="datetime-local">
<!-- 滑块 -->
<input type="range" />
<!--颜色选择-->
<input type="color" />
<!-- 提交 -->
<input type="submit" value="提交">
3. 新增音频、视频标签(audio
、video
)
<!-- controls:音频播放控件 -->
<audio src="路径" controls></audio>
4. 本地存储(localStorage
)
<!-- 存储 -->
localStorage.setItem("key", JSON.stringify("要存储的数据"));
<!-- 拿取 -->
var num = JSON.parse(localStorage.getItem("key"));
<!-- 删除 -->
window.localStorage.clear(); //删除localStorage中所有的数据
localStorage.removeItem('key'); //删除localStorage中某个键值对
5. 新的事件
onresize:当浏览器被重置大小时执行;
ondrag:在 <p> 元素开始拖动时执行;
onscroll:<div> 元素滚动时执行;
onmousewheel:鼠标滚轮滚动时执行;
onerror:在文档或图像加载过程中发生错误时执行;
onpause:在视频/音频暂停时执行。
CSS3 新特性
1. 圆角(border-radius
)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<style>
div {
width: 100px;
height: 100px;
background: cadetblue;
border-radius: 20px;
}
</style>
<body>
<div></div>
</body>
</html>
2. 盒阴影(box-shadow
)
box-shadow: h-shadow v-shadow blur spread color inset;
属性 | 描述 |
---|---|
h-shadow | 必需的。水平阴影的位置。允许负值 |
v-shadow | 必需的。垂直阴影的位置。允许负值 |
blur | 可选。模糊距离 |
spread | 可选。阴影的大小 |
color | 可选。阴影的颜色。在CSS颜色值寻找颜色值的完整列表 |
inset | 可选。从外层的阴影(开始时)改变阴影内侧阴影 |
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<style>
div {
width: 100px;
height: 100px;
box-shadow: 20px 0px 10px 0px rgba(0, 0, 0, 0.5)
}
</style>
<body>
<div></div>
</body>
</html>
3. 选择器
css3普通选择器
通配符选择器 选择所有元素
HTML标签选择器 选择HTML标签
ID选择器 选择id属性元素
class类选择器 选择class属性元素
包含选择器 选择后代元素
子选择器 选择子元素
相邻选择器 选择后面紧邻的兄弟元素
兄弟选择器 CSS3选择后面相邻的所有兄弟元素
---------------分割线---------------
css3动态伪类选择器
:link 超链接
:visited 链接已被访问过
:hover 鼠标悬停
:active 被激活时
---------------分割线---------------
css3伪对象选择器
::first-letter CSS3第一个字符的样式
::first-line CSS3第一行的样式
::before CSS3对象前发生的内容
::after CSS3对象后发生的内容
::placeholder CSS3设置文字占位符
::selection CSS3设置选择框样式
::cue CSS3字幕提示
---------------分割线---------------
UI元素状态伪类选择器
:focus 输入焦点
:checked CSS3选中状态的元素
:enabled CSS3可用状态的元素
:disabled CSS3禁用状态的元素
:read-only CSS3只读状态的元素
:read-write CSS3能编辑的元素
:optional CSS3选择非必填元素
:required CSS3选择必填元素
:in-range CSS3选择有限定范围的元素
:indeterminate CSS3选择处于不确定状态的表单元素
:default CSS3默认状态的表单元素
:focus-within css3元素或者后代元素获得焦点
:out-of-range css3当值处于范围之外
---------------分割线---------------
css3结构性伪类选择器
:lang() 选择带有lang属性元素
:not() CSS3排除某类元素
:root CSS3选择根元素
:first-child 第一个元素
:last-child CSS3最后一个子元素
:only-child CSS3仅有的一个子元素
:nth-child() CSS3第n个子元素
:nth-last-child() CSS3倒数第n个子元素
:first-of-type CSS3第一个同级兄弟元素
:last-of-type CSS3最后一个同级兄弟元素
:only-of-type CSS3唯一的一个同级兄弟元素
:nth-of-type() CSS3第n个同级兄弟元素
:nth-last-of-type() CSS3倒数第n个同级兄弟元素
:empty CSS3没有任何子元素
:target CSS3URL指向的元素
---------------分割线---------------
css3属性选择器
[attr] 具有attr属性的元素
[attr=val] 值等于指定值的元素
[attr~=val] 选择包含指定值的元素
[attr^=val] CSS3选择指定值开头的元素
[attr$=val] CSS3选择指定值结尾的元素
[attr*=val] CSS3包含指定值的元素
[attr|=val] 选择指定值开头
4. 渐变
线性渐变(Linear Gradients
)- 向下/向上/向左/向右/对角方向;
径向渐变(Radial Gradients
)- 由它们的中心定义。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<style>
.box1 {
width: 100px;
height: 100px;
background-image: linear-gradient(#e66465, #9198e5);
}
.box2 {
margin-top: 20px;
width: 100px;
height: 100px;
background-image: linear-gradient(to right, red, yellow);
}
</style>
<body>
<div class="box1"></div>
<div class="box2"></div>
</body>
</html>
5. 过渡
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<style>
div {
width: 100px;
height: 100px;
background: cadetblue;
transition: width 2s;
-webkit-transition: width 2s;
}
div:hover {
width: 300px;
}
</style>
<body>
<div></div>
</body>
</html>
此外,css3
还有很多列如:媒体查询、滤镜、动画、形状转换、多重背景等等一系列新增特性。
5. css怎么画一条0.5px的线?
transform
属性
transform
属性向元素应用 2D
或 3D
转换。该属性允许我们对元素进行旋转、缩放、移动或倾斜。就是利用 transform
的特性去实现 0.5px
的线宽,以下是 transform
的几个属性值。
属性 | 描述 |
---|---|
transform:scaleX() | 沿 x 轴方向缩放 |
transform:scaleY() | 沿 y 轴方向缩放 |
transform:scale() | 同时沿 x 轴和 y 轴缩放 |
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<style>
.box1 {
width: 200px;
height: 1px;
background: black;
}
.box2 {
margin-top: 20px;
width: 200px;
height: 1px;
transform: scaleY(0.5);
background: black;
}
</style>
<body>
<div class="box1"></div>
<div class="box2"></div>
</body>
</html>
效果对比图
6. css如何画一个三角形?
设置 div
的宽高都为 0
,这样 div
的内容就是空的,就可以形成三角形的尖角,接着设置三条边的宽度,最后设置 border-left
、border-right
的背景色为透明色即可。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<style>
div {
width: 0px;
height: 0px;
border-left: 100px solid transparent;
border-right: 100px solid transparent;
border-bottom: 100px solid cornflowerblue;
}
</style>
<body>
<div></div>
</body>
</html>
7. 怎么清除浮动?
如何设置浮动?
在 css
中,可以使用 float
属性来设置浮动,语法如下,元素会根据 float
属性的值向左或向右移动,直到它的外边界碰到父元素的内边界或另一个浮动元素的外边界为止,其周围的元素也会重新排列。
属性 | 描述 |
---|---|
float: left | 向左浮动 |
float: right | 向右浮动 |
float: none | 不浮动 |
浮动的特点?
- 脱离标准流;
- 浮动的元素会互相贴靠;
- 浮动的元素有“字围”效果;
- 一个浮动的元素,如果没有设置宽度,那么将自动收缩为内容的宽度。
为什么要浮动?
- 未浮动
- 浮动
浮动的效果显而易见,由此可见浮动可以改变元素标签默认的排列方式,让多个块级元素一行内排列显示,控制多个盒子之间的间隙
为什么要清除浮动?
其实清除浮动并不是清除已经浮动的元素,当子元素浮动后,会完全脱离文档流,清除浮动就是为了解决由于浮动而产生的父元素高度塌陷的问题。
清除浮动的方式?
1. 额外标签
给谁清除浮动,就在后面添加一个空白标签,设置 clear:both
。
优点: 通俗易懂,书写方便。
缺点: 添加许多无意义的标签,结构化比较差。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<style>
.clear {
clear: both;
}
</style>
<body>
<div class="fatherBox">
<div class="sonBox"></div>
<div class="clear"></div>
</div>
</body>
</html>
2. 父元素设置 overflow:hidden
通过触发 BFC
,实现清除浮动。
优点: 代码简洁。
缺点: 内容较多容易造成不会自动换行导致内容无法显示。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<style>
.fatherBox {
overflow: hidden;
}
.sonBox {
float: left;
}
</style>
<body>
<div class="fatherBox">
<div class="sonBox"></div>
</div>
</body>
</html>
3. ::after
伪元素
优点: 符合闭合浮动思想,结构语义化正确。
缺点: ie6-7
不支持,使用 zoom:1
触发 hasLayout
。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<style>
.fatherBox::after {
content: "";
display: block;
height: 0;
clear: both;
visibility: hidden
}
.fatherBox {
*zoom: 1;
}
.sonBox {
float: left;
}
</style>
<body>
<div class="fatherBox">
<div class="sonBox"></div>
</div>
</body>
</html>
4. 父元素定高
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<style>
.fatherBox {
height: 200px;
}
.sonBox {
float: left;
}
</style>
<body>
<div class="fatherBox">
<div class="sonBox"></div>
</div>
</body>
</html>
8. 块级元素有哪些?行内元素有哪些?空元素有那些?行内元素和块级元素有什么区别?
块级元素
块级元素最大的特点就是独自占一行,其后的元素也只能换行,不能两个元素共用一行,元素的高、宽、行高和顶底边距都可以设置,元素的宽如果不设置,默认为父元素的宽。以下是常见的块级元素:
<div></div> 常用块级元素
<p></p> 段落
<h1></h1>---<h6></h6> 标题
<ul></ul> 无序列表
<ol></ol> 有序表单
<table></table> 表格
<form></form> 交互表单
<hr/> 水平分割线
行内元素
行内元素最大的特点就是和其他元素在一行上,高、行高和顶以及底边距都不可改变,宽就是它的文字或图片的宽。以下是常见的行内元素:
<span></span> 常用的内联容器
<a></a> 锚点
<i></i> 斜体
<s></s> 删除线
<u></u> 下划线
<input/> 输入框
<br/> 换行
<img/> 图片
空元素
由于 HTML
元素的内容是开始标签与结束标签之间的内容。而某些 HTML
元素具有空内容。此类元素就是空元素。空元素是在开始标签中关闭的。以下是常见的行内元素:
<br/> 换行
<hr/> 水平分割线
<img/> 图片
<input/> 输入框
<link> 外链
<meta> 网页的描述
块级元素跟行内元素的区别
- 行内元素和其他行内元素都会在一条水平线上排列,块级元素是在新的一行开始排列且独占一行且垂直向下排列;
- 行内元素不可以设置宽高,宽度高度随文本内容的变化而变化,但是可以设置行高(
line-height
),同时在设置外边距margin
上下无效,左右有效,内填充padding
上下无效,左右有效;块级元素可以设置宽高,并且宽度高度以及外边距,内填充都可随意控制; - 块级元素可以包含行内元素和块级元素,还可以容纳内联元素和其他元素;行内元素不能包含块级元素,只能容纳文本或者其他行内元素。
9. CSS选择器及其优先级?
选择器都有哪些?
选择器名称 | 选择器符号 |
---|---|
行内样式 | style=“” |
id选择器 | #id |
类选择器 | .className |
伪类选择器 | :hover |
标签选择器 | p |
伪元素选择器 | ::after |
兄弟选择器 | + |
子选择器 | > |
后代选择器 | div p |
通配符 | * |
优先级
!important > 行内 > id选择器 > 类选择器 > 标签选择器
10. 隐藏页面元素的方法有哪些?
1.
display
属性
通过设置 display
的属性值为 none
应该是最常见的隐藏页面元素的方法了。但是将元素设置为 display:none
后,该元素会在页面上消失,元素本来占有的空间就会被其他元素占有,从而会导致浏览器的重排和重绘。
display:none;
2.
opacity
属性
大家都知道 opacity
属性是设置元素透明度的,所以我们可以利用这个特性设置 opacity
的属性值为 0
,即可实现页面隐藏的效果。
opacity:0;
3.
visibility
属性
通过设置 visibility
的属性值为 hidden
也可将页面元素隐藏,不同于 display:none
的地方在于元素隐藏后,其占据的空间依旧会保留,所以它只会导致浏览器重绘而不会重排。
visibility:hidden;
4.
position
属性
通过设置 position
的属性值为 absolute
,再将 top
和 left
的值设置足够大的负数值即可实现将元素隐藏的效果。
position: absolute;
top: -999px;
left: -999px;
5.
hidden
属性(HTML5
新增)
该方法直接在标签内设置 hidden
的值为 hidden
即可隐藏元素,通过 hidden
属性将元素隐藏后和 display:none
一样,元素会在页面上消失,元素本来占有的空间就会被其他元素占有,从而会导致浏览器的重排和重绘。
<div hidden="hidden">123</div>
11. 常见的图片格式有哪些?
1.
jpg/jpeg
最常用的静态图片格式之一。这种格式的图片能比较好的表现各种色彩,但在压缩时会有所失真。
优点:兼容性强,色彩丰富,压缩率高,图片小;
缺点:不支持动画、透明。
2.
png
png
有三种版本,分别是 png-8
、png-24
、png-32
,以上这些都不支持动画。
优点:像素丰富,支持透明,支持交错,压缩时几乎不失真,兼容性强;
缺点:文件较大,不支持动画。
3.
gif
gif
是一种索引色模式图片,所以 gif
每帧图所表现的颜色最多为 256
种。
优点:支持动画,高兼容性,灰度表现佳,支持交错;
缺点:最多支持8位256色,色阶过渡糟糕,图片具有颗粒感,支持透明,但不支持半透明,边缘有杂边。
4.
base64
图片的 base64
编码就是可以将一副图片数据编码成一串字符串,使用该字符串代替图像地址,图片随着 html
的下载同时下载到本地,不再单独消耗一个 http
来请求图片。
优点:不需要额外的http请求,适用于极小或简单图片,没有跨域问题,无需考虑缓存、文件头或者cookies问题;
缺点:相对于其他图片格式,要大至少1/3,不适用于中等以上图片。
5.
svg
svg
是一种基于 xml
的矢量图形格式,它将图像的内容指定为一组绘图命令,这些命令创建形状、线条、应用颜色、过滤器等等。
优点:你可以对其放大、缩小,而不用担心损失质量,内存更小,支持动画,html、js、css都可以对其操作;
缺点:渲染时间较长,尤其是在它较复杂时,不适用于游戏等高互动动画。
6.
webP
有损 webP
图像平均比视觉上类似压缩级别的 jpeg
图像小 25-35%
。无损耗的 webP
图像通常比 png
格式的相同图像小 26%
。
优点:同等质量更小,压缩之后质量无明显变化,支持无损图像,支持动画;
缺点:相对jpg,png,gif来说兼容性相对较差。
7.
bmp
bmp
是一种位图格式。采用位映射存储格式,除了图像深度可选以外,不采用其他任何压缩。
优点:无损压缩,图质最好;
缺点:文件太大,不利于网络传输。
12. CSS中像素单位的区别及使用场景?
1.
px
px
是像素的意思,可以指定字体大小和元素的宽高,像素是相对于显示器屏幕分辨率而言的。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<style>
div {
width: 100px;
height: 100px;
font-size: 18px;
border: 1px salmon solid;
text-align: center;
line-height: 100px;
}
</style>
<body>
<div>px</div>
</body>
</html>
2.
rem
rem
是 css3
新增的一个相对单位,相对根元素的字体大小。使用 rem
为元素设定字体大小时,仍然是相对大小,但相对的只是 html
根元素。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<style>
body {
font-size: 12px;
}
div {
width: 15rem;
height: 15rem;
font-size: 2rem;
border: 0.01rem salmon solid;
text-align: center;
line-height: 15rem;
}
</style>
<body>
<div>rem</div>
</body>
</html>
3.
em
em
是一个相对单位,是当前元素相对于父元素字体的大小而言的,例如父元素设置 font-size: 16px
,子元素设置 font-size: 1em
,那么子元素的字体大小也是 16px
。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<style>
div {
font-size: 32px;
}
p {
font-size: 1em;
}
</style>
<body>
<div>
<p>em</p>
</div>
</body>
</html>
4.
rpx
rpx
是微信小程序的尺寸单位,rpx
可以根据屏幕宽度进行自适应。小程序规定屏幕宽为 750rpx
。那么在 iPhone6
上,屏幕宽度为 375px
,共有 750
个物理像素,则 750rpx = 375px = 750
物理像素,1rpx = 1px
。
<view>rpx</view>
view{
width: 100rpx;
height: 100rpx;
border: 1rpx solid pink;
text-align: center;
line-height: 100rpx;
}
5.
vh
vh
是一种视窗单位,也是相对单位。相对于视窗的高度,视窗被均分为 100
份。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<style>
div {
width: 100%;
height: 50vh;
background: cadetblue;
color: white;
text-align: center;
line-height: 50vh;
}
</style>
<body>
<div></div>
</body>
</html>
6.
vw
vw
是一个长度单位,相对于视口的宽度,视口会被均分为 100
份,1vw
相当于视口宽度的 1%
,例如浏览器的宽度为 1920px
,则 1vw = 1920px / 100 = 19.2px
。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<style>
div {
width: 50vw;
height: 100vh;
background: cadetblue;
color: white;
text-align: center;
}
</style>
<body>
<div></div>
</body>
</html>
7.
%
百分比是一个相对长度单位,相对于包含块的高宽或字体大小来取值。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<style>
div {
width: 50%;
background: cadetblue;
color: white;
text-align: center;
}
</style>
<body>
<div>%</div>
</body>
</html>
13. 两栏布局、三栏布局怎么实现?
-
两栏布局
两栏布局简单来说就是让一侧的宽度固定,另一侧自适应。
-
三栏布局
三栏布局简单来说就是左右两侧定宽,中间是自适应的。平时我们常说的双飞翼布局、圣杯布局也都属于是三栏布局。
两栏布局如何实现?
1. 左侧盒子浮动,右侧盒子宽度为100%
。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<style>
.leftBox {
float: left;
width: 200px;
height: 200px;
background: cadetblue;
}
.rightBox {
width: 100%;
height: 200px;
background: cornflowerblue;
}
</style>
<body>
<div class="box">
<div class="leftBox">left</div>
<div class="rightBox">right</div>
</div>
</body>
</html>
2. 定位
父级盒子设置 position:relative
,然后固定左边盒子 position:absolute
,右边盒子设置 margin-left:"左盒宽"
即可。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<style>
.box {
position: relative;
}
.leftBox {
position: absolute;
width: 200px;
height: 200px;
background: cadetblue;
}
.rightBox {
margin-left: 200px;
height: 200px;
background: cornflowerblue;
}
</style>
<body>
<div class="box">
<div class="leftBox">left</div>
<div class="rightBox">right</div>
</div>
</body>
</html>
3. flex
布局
直接给父级添加 display: flex; justify-content: space-between;
即可。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<style>
.box {
display: flex;
justify-content: space-between;
}
.leftBox {
width: 200px;
height: 200px;
background: cadetblue;
}
.rightBox {
width: 100%;
height: 200px;
background: cornflowerblue;
}
</style>
<body>
<div class="box">
<div class="leftBox">left</div>
<div class="rightBox">right</div>
</div>
</body>
</html>
效果展示
三栏布局如何实现?
1. 左、右盒子分别向左、向右浮动,中间盒子向左、向右移动左、右盒子的宽度
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<style>
.leftBox {
float: left;
width: 200px;
height: 200px;
background: cadetblue;
}
.rightBox {
float: right;
width: 200px;
height: 200px;
background: cornflowerblue;
}
.centerBox {
margin-left: 200px;
margin-right: 200px;
height: 200px;
background: coral;
}
</style>
<body>
<div class="box">
<div class="leftBox">left</div>
<div class="rightBox">right</div>
<div class="centerBox">center</div>
</div>
</body>
</html>
2. 将父盒子设置相对定位,左、右盒子设置绝对定位,中间盒子通过margin向左、向右移动左、右盒子的宽度即可。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<style>
.box {
position: relative;
}
.leftBox {
position: absolute;
width: 200px;
height: 200px;
background: cadetblue;
}
.rightBox {
position: absolute;
right: 0px;
top: 0px;
width: 200px;
height: 200px;
background: cornflowerblue;
}
.centerBox {
margin-left: 200px;
margin-right: 200px;
height: 200px;
background: coral;
}
</style>
<body>
<div class="box">
<div class="leftBox">left</div>
<div class="rightBox">right</div>
<div class="centerBox">center</div>
</div>
</body>
</html>
3. flex
布局
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<style>
.box {
display: flex;
}
.leftBox {
width: 200px;
height: 200px;
background: cadetblue;
}
.rightBox {
width: 200px;
height: 200px;
background: cornflowerblue;
}
.centerBox {
flex: 1;
height: 200px;
background: coral;
}
</style>
<body>
<div class="box">
<div class="leftBox">left</div>
<div class="centerBox">center</div>
<div class="rightBox">right</div>
</div>
</body>
</html>
效果展示
14. css中link和import的区别?
link
链接方式
链接方式指的是使用 html
头部的 <head>
标签引入外部的 css
文件。
<head>
<link rel="icon" type="text/css" href="style.css">
</head>
import
导入方式
导入方式指的是使用 css
规则引入外部 css
文件。
<style>
@import url(style.css);
</style>
link
和import
的区别?
虽然两种方法都是从外部引用 css
的方式,但还是存在一定的区别:
- 区别1:
link
属于是xhtml
标签,除了加载css
外,还可以定义rss
,定义rel
连接属性等;而@import
属于css
范畴,只能加载css
; - 区别2:
link
引用css
时,在页面载入时同时加载;而@import
需要页面完全载入以后才加载; - 区别3:
link
没有兼容性问题;而@import
是在css2.1
提出的,低版本的浏览器不支持; - 区别4:
link
支持使用javaScript
控制dom
改变样式;而@import
不支持。
15. css Sprite是什么?有什么优缺点?
概念
css Sprite
其实就是雪碧图,它是网页图片应用处理的一种方式,其原理就是把网页中的背景图片整合在一张图片中,再利用 css
的 background-image
属性、background-repeat
属性、background-position
属性将其进行背景定位找到图片的位置。
css Sprite
如何制作?
如今雪碧图完全可以通过一个网站上传图片就能实现,而且就连代码都是自动生成的:在线制作雪碧图。默认情况下每一个小图标都是一个单独的文件。
合成雪碧图后变成了一个文件,如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<style>
.box {
display: flex;
}
.box p:nth-child(1) {
width: 30px;
height: 30px;
background: url("../img/xuebt.png") no-repeat 0px 0px;
}
.box p:nth-child(2) {
width: 30px;
height: 30px;
background: url("../img/xuebt.png") no-repeat -40px 0px;
}
.box p:nth-child(3) {
width: 30px;
height: 30px;
background: url("../img/xuebt.png") no-repeat -80px 0px;
}
.box p:nth-child(4) {
width: 30px;
height: 30px;
background: url("../img/xuebt.png") no-repeat 0px -40px;
}
.box p:nth-child(5) {
width: 30px;
height: 30px;
background: url("../img/xuebt.png") no-repeat -40px -40px;
}
.box p:nth-child(6) {
width: 30px;
height: 30px;
background: url("../img/xuebt.png") no-repeat -80px -40px;
}
</style>
<body>
<div class="box">
<p></p>
<p></p>
<p></p>
<p></p>
<p></p>
<p></p>
</div>
</body>
</html>
css Sprite
的优点?
- 减少网页的
http
请求,提高页面的加载速度; - 减少了图片字节,多张图片合并成
1
张图片的字节小于多张图片的字节总和; - 减少了命名困扰,只需要对一张集合的图片命名,不需要对每一张图片文件进行命名;
- 更换风格方便,只需要在一张或少张图片上修改图片的颜色或样式,整个网页的风格就可以改变,维护起来更加方便。
css Sprite
的缺点?
- 图片合并时,需要把多张图片有序合理的合并成一张图片,还要留好足够的空间,防止板块内出现不必要的背景,而且还要防止宽屏、高分辨率的屏幕下的自适应页面,如果图片不够宽,很容易出现背景断裂;
- 背景设置时,需要得到每一个背景单元的精确位置;
- 由于图片的位置需要固定为某个绝对数值,这就失去了诸如
center
之类的灵活性。
16. 为什么要初始化CSS?
初始化的原因
-
浏览器差异
因为浏览器的兼容问题,不同浏览器对有一些标签的默认值是不同的,那如果没对css
初始化往往会出现浏览器之间的页面显示差异。 -
提高编码质量
初始化css
可以节约网页代码,节约网页的下载时间;还会使得我们开发的网页内容更加方便简洁。如果不初始化,整个页面做完会很乱,重复的css
样式会有很多。
弊端
初始化样式会对 seo
有一定的影响,但是鱼和熊掌不可兼得,但力求影响最小的情况下初始化 css
。
初始化的方法?
其实最简单初始化 css
的方式也很简单,如下:
*{
padding:0;
margin:0;
}
17. css中的position属性及使用场景?
position
属性指定了元素的定位类型。position
属性的五个值:
属性值 | 描述 |
---|---|
static | 正常定位 |
relative | 相对定位 |
absolute | 绝对定位 |
fixed | 固定定位 |
sticky | 粘性定位 |
static
(正常定位)
position
的默认值,遵循正常的文档流对象。静态定位的元素不会受到 top
、bottom
、 left
、 right
影响。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<style>
div p {
position: static;
background: cadetblue;
}
</style>
<body>
<div>
<p>123</p>
</div>
</body>
</html>
relative
(相对定位)
相对定位元素的定位是相对其正常位置。
absolute
(绝对定位)
绝对定位的元素的位置相对于最近的已定位父元素,如果元素没有已定位的父元素,那么它的位置相对于 html
。
fixed
(固定定位)
fixed
定位在 IE7
和 IE8
下需要描述 !DOCTYPE
才能支持,fixed
定位使元素的位置与文档流无关,因此不占据空间,fixed
定位的元素和其他元素会重叠。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<style>
* {
padding: 0;
margin: 0;
}
div {
width: 100%;
height: 50vh;
overflow: hidden;
overflow-y: auto;
background: gainsboro;
}
h2 {
position: fixed;
background-color: cadetblue;
color: white;
right: 0px;
}
</style>
<body>
<div>
<h2>fixed定位的内容</h2>
<h1>内容</h1>
<h1>内容</h1>
<h1>内容</h1>
<h1>内容</h1>
<h1>内容</h1>
<h1>内容</h1>
<h1>内容</h1>
<h1>内容</h1>
<h1>内容</h1>
<h1>内容</h1>
<h1>内容</h1>
</div>
</body>
</html>
sticky
(粘性定位)
sticky
基于用户的滚动位置来定位,在 position:relative
与 position:fixed
定位之间切换,它的行为就像 position:relative
,而当页面滚动超出目标区域时,它的表现就像 position:fixed
,它会固定在目标位置。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<style>
* {
padding: 0;
margin: 0;
}
body {
background: gainsboro
}
.stickyBox {
position: sticky;
top: 0;
background: cornflowerblue;
}
.contantBox {
padding-bottom: 1000px;
}
</style>
<body>
<div>
<h1>顶部填充内容</h1>
<h1>顶部填充内容</h1>
<h1>顶部填充内容</h1>
</div>
<div class="stickyBox">粘性定位</div>
<div class="contantBox">
<p>滚动内容</p>
<p>滚动内容</p>
<p>滚动内容</p>
<p>滚动内容</p>
<p>滚动内容</p>
<p>滚动内容</p>
<p>滚动内容</p>
<p>滚动内容</p>
</div>
</body>
</html>
18. 简述常见的css浏览器兼容问题?
1. 浏览器默认内外边距问题
问题: 因为每个浏览器的 css
默认样式都是不一样的,所以最简单有效的兼容⽅式就是对其进⾏初始化操作,将 margin
和 padding
的值都设置为 0
。
解决:
* {
padding: 0;
margin: 0;
}
2. 图片默认间距问题
问题: 多个 img
标签放在一起时,有些浏览器会有默认间距。
解决: 使用 float
属性为 img
布局。
3.
IE6
双倍外边距问题
问题: 在 IE6
中,让一个向左浮动的元素设置左外边距或者让一个向右浮动的元素设置右外边距时,这个外边距将会是设置值的 2
倍。
解决:
display:inline
4. 最小高度问题
问题: 在 IE6/7
浏览器设置 10px
以下的高度,浏览器高度会超出自己设置的高度。
解决: 给超出高度的标签设置 overflow:hidden
或者设置 line-height
小于设置的高度即可。
5. 透明度问题
问题: 在 IE7
及以下浏览器,不识别 opacity
。
解决:
filter: alpha(opacity=value) (取值范围1100、整数、等号)
6. 鼠标指针问题
问题: cursor
属性的 hand
属性值只有 IE9
以下浏览器识别,其它浏览器不识别。
解决:
cursor:pointer;
7. 列表阶梯问题
问题: 子元素中使用了 float:left
;父元素没有设置浮动属性,li
就会呈现阶梯效果。
解决: 父元素也设置浮动。
8.
IE6
不支持min-height
问题
解决:
min-height:350px;
_height:350px;
9. 浏览器兼容前缀
Opera:-o-
IE:-ms
Firedox:-moz-
Chrome:-webkit-
19. WEB标准以及W3C标准是什么?
WEB
标准
WEB
标准不是一个标准,它是由 W3C
与其他标准化组织指定的一系列标准的集合,主要包括结构、表现和行为三个方面。
W3C
标准
W3C
(万维网联盟) 标准,即一系列标准的集合,他的本质是结构标准语言。就像平时使用的 HTML
、CSS
等都需要遵守这些标准。
WEB
标准的好处?
- 提高兼容性。对于浏览器开发商和
web
程序开发人员在开发新的应用程序时遵守指定的标准更有利于web
更好地发展; - 提高开发效率。 开发人员按照
web
标准制作网页,这样对于开发者来说就更加简单了,因为他们可以很容易了解彼此的编码; - 跨平台。使用
web
标准,将确保所有浏览器正确显示您的网站而无需费时重写; - 加快网页解析速度。遵守标准的
web
页面可以使得搜索引擎更容易访问并收入网页,也可以更容易转换为其他格式,并更易于访问程序代码(如javaScript
和dom
); - 易于维护。页面的样式和布局信息保存在单独的
css
文件中,如果你想改变站点的外观时,仅需要在单独的css
文件中做出更改即可。
W3C
标准的好处?
- 提升网站形象。通过
w3c
认证的网站不足5%
,只有极少的网站会通过w3c
认证。所以通过w3c
认证,会大大提高网站形象; - 有利于提高网站排名。符合
w3c
标准的网页,一般用css/div
呈现,这使网页原始码简洁,结构化程序更高,易于被搜索机器人检索,收录,这会给网站带来更高排名; - 速度更快。因为符合
w3c
标准,网页原始程序简洁,网站页面共享。使得网站大幅度精简,提高浏览速度,使网站显示更快; - 维护容易。采取
w3c
标准的网页设计,则只需要改变css
文件,就能达到全面修改的目的,不必再费力的去修改网页内码。
20. css中有哪些需要优化的(SEO)?
优化的方法有很多种,可以将其分为两大类,第一类是页面级别的优化,如 HTTP
请求数,内联脚本的位置优化等,第二类为代码级别的优化,例 Javascript
中的 DOM
操作优化、CSS
选择符优化、图片优化以及 HTML
结构优化等等。
css
中常见的优化?
- 非装饰性图片必须加
alt
; - 语义化的
html
代码,符合w3c
规范,语义化代码让搜索引擎容易理解网页; - 使用
<link>
标记代替@import
规则; - 使用渐变和
svg
代替图像; - 使用
flexbox
和css
网格布局; - 重要内容
html
代码放在最前:搜索引擎抓取html
顺序是从上到下,有的搜索引擎对抓取长度有限制,保证重要内容肯定被抓取; - 合理的
title
、description
、keywords
,搜索对这三项的权重逐个减小,title
值强调重点即可,重要关键词出现不要超过2
次,而且要靠前,不同页面title
要有所不同;description
把页面内容高度概括,长度合适,不可过分堆砌关键词,不同的页面description
有所不同;keywords
列举出重要关键词即可。
二、js 相关面试题
1. 谈谈你对闭包的理解?
什么是闭包?
闭包,简单来说就是函数嵌套函数,内部的函数可以访问外部函数的变量,闭包是一个环境,具体指的就是外部函数。
闭包有哪些特性?
- 函数内再嵌套函数;
- 内部函数可以引用外层的参数和变量;
- 参数和变量不会被垃圾回收机制回收。
闭包的优点?
- 可以读取函数内部的变量;
- 变量始终保持在内存中;
- 封装对象的私有属性和私有方法,能够实现封装和缓存等。
闭包的缺点?
- 消耗内存,不正当使用会造成内存溢出的问题;
- 不需要的情况下,在其他函数中创建函数是不明智的,因为闭包对脚本性能有负面影响,包括处理速度和内存消耗。
使用闭包需要注意的地方?
由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在 IE
中可能导致内存泄露;解决方法是,在退出函数之前,将不使用的局部变量全部删除。
闭包的主要表现形式?
1. 作为函数返回值被返回。
//作为函数返回值被返回
function demo(){
let a = 100
return function(){
console.log(a)
}
}
let a = 200
demo()
2. 作为参数被传递。
//作为参数被传递
function fn1(fn){
const a = 100
fn()
}
const a = 200
function fn(){
console.log(a)
}
fn1(fn)
2. 说说你对作用域链的理解?
什么是作用域链?
作用域链,简单的说, 作用域是变量与函数的可访问的范围,即作用域控制着变量与函数的可见性和生命周期。函数的作用域内的变量,如果在本作用域内没有找到定义,则会往一层一层上一级查找,直到找到全局作用域,如果都没有则返回 undefined
,这种一层一层的类似锁链的关系,叫作用域链。
作用域链有什么作用?
保证执行环境里有权访问的变量和函数是有序的。
作用域链的访问方式?
作用域链的变量只能向上访问,不允许向下访问。
var a = 100
function fn () {
var b = 200
// 当前作用域没有定义的变量,即"自由变量"
console.log(a)
console.log(b)
}
fn()
console.log(a) //去父级作用域找a自由变量 作用域链
3. JS原型、原型链 ? 有什么特点?
什么是原型?
原型,简单的说就是在 javascript
中,每个函数对象下都有一个 prototype
属性。这个属性就是原型。
什么是原型链?
原型链,简单的说原型链是一种机制,指的是 javascript
每个对象包括原型对象都有一个内置的 __proto__
属性指向创建它的函数对象的原型对象,即 prototype
。
特点?
javascript
对象是通过引用来传递的,我们创建的每个新对象实体中并没有一份属于自己的原型副本。当我们修改原型时,与之相关的对象也会继承这一改变;- 当我们需要一个属性的时,
javascript
引擎会先看当前对象中是否有这个属性, 如果没有的,就会查找他的prototype
对象是否有这个属性,如此递推下去,一直检索到object
内建对象。
//从这个实例能看出来c继承b,b继承a,无形之中就连成了一条"线",这条线就是"原型链"。
function a(){
this.name="a";
}
a.prototype.age = 18;
function b(){
this.apple="5s";
}
b.prototype = new a(); // b 继承 a
b.prototype.foot = "fish";
function c(){
this.computer = "MAC";
}
c.prototype = new b(); // c 继承 b , b 继承 a
var d = new c();
console.log(d.apple); // 5s
console.log(d.name); // a
console.log(d.foot); // fish
console.log(d.age); // 18
4. 请解释什么是事件代理(事件委托)?
什么是事件代理?
事件代理,其实就是事件委托,它是 javascript
中常用绑定事件的常用技巧。顾名思义,“事件代理” 即是把原本需要绑定的事件委托给父元素,让父元素担当事件监听的职务。事件代理的原理是 DOM
元素的事件冒泡。使用事件代理的好处是可以提高性能。
优点?
- 可以大量节省内存占用,减少事件注册,比如在
table
上代理所有td
的click
事件就非常合适; - 可以实现当新增子对象时无需再次对其绑定。
缺点?
如果把所有事件都用事件代理,可能会出现事件误判。
实例
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<style>
span {
display: inline-block;
width: 50px;
height: 50px;
border-radius: 50%;
border: 1px solid gray;
text-align: center;
line-height: 50px;
cursor: pointer;
}
</style>
<body>
<div id='div'>
<span>1</span>
<span>2</span>
<span>3</span>
<span>4</span>
<span>5</span>
<span>6</span>
</div>
</body>
<script>
// 事件委托
function test(event) {
alert(event.target.innerHTML);
}
(function () {
let div = document.getElementById('div');
div.addEventListener('click', test);
})()
</script>
</html>
5. 谈谈this的理解,call、apply、bind?
this
指向
面向对象语言中 this
表示当前对象的一个引用,但在 javascript
中 this
不是固定不变的,它会随着执行环境的改变而改变;在方法中,this
表示该方法所属的对象,但如果单独使用,this
表示全局对象;在事件中,this
表示接收事件的元素。
call
call()
方法是预定义的 javascript
方法,它可以用来调用所有者对象作为参数的方法,通过 call()
,可以使用另一个对象的方法。
<script>
function add(a, b) {
console.log(a + b); //4
}
function sub(a, b) {
alert(a - b);
}
add.call(sub, 3, 1);
</script>
apply
调用一个对象的一个方法,用另一个对象替换当前对象。
<script>
//用apply()找出数组中最大的数
var arr = [1, 5, 9, 4, 56, 99, 28];
function bastbig(arr) {
return Math.max.apply(Math, arr); //第一个参数也可以填this或null
}
console.log(bastbig(arr)); //99
</script>
bind
bind()
方法不会调用函数,返回的是原函数改变 this
之后产生的新函数。
<script>
var name = 'sally';
function sayName() {
return this.name;
}
function sayName2() {
return this.name
}
var o = {
'name': 'John',
sayName: sayName,
sayName2: sayName2.bind(window)
};
console.log(o.sayName()); //John
console.log(o.sayName2()); //sally
</script>
三者之间的区别?
call
、apply
、 bind
都可以改变 this
指向;但不同的是 call
和 apply
会调用函数,bind
不会调用函数; call
和 apply
传递的参数不一样,call
传递参数使用逗号隔开,apply
使用数组传递。
三者的应用场景?
call
经常用做继承;apply
经常跟数组有关系,比如实现数组最大值最小值;bind
不调用函数,但是还想改变 this
指向,比如改变定时器内部的 this
指向。
6. Javascript常见的继承?
1. 原型链继承?
利用原型让一个引用类型继承另一个引用类型的属性和方法。优点是简单易于实现;缺点是不能向超类型的构造函数中传递参数。
<script>
function Father() {
this.name = 'zhangsan'
this.arr = [1, 2, 3]
}
function Son() {
this.age = 18;
}
Son.prototype = new Father()
console.log(new Son)
var a = new Son()
var b = new Son()
a.arr.push(4)
console.log(a.arr) //[1,2,3,4]
console.log(b.arr) //[1,2,3,4]
</script>
2. 构造函数继承?
在子类型的构造函数中调用超类型的构造函数。优点是可以向超类传递参数;缺点是不能函数复用。
<script>
function Father() {
this.name = 'zhangsan'
}
Father.prototype.say = function () {
console.log('father的原型方法')
}
function Son() {
Father.call(this)
this.age = 18
}
var a = new Son()
var b = new Father()
console.log(b.say) // function(){console.log('father的原型方法')}
console.log(a) // Son {name:'zhangsan',age:18}
console.log(a.say) //undefiend
</script>
3. 组合继承(原型链+构造函数)?
使用原型链继承原型的属性和方法 构造函数实行实例属性的继承。优点是实现了函数复用;缺点是无论什么情况下都会调用两次超类构造函数。
<script>
function Father() {
this.name = 'zhangsan'
this.arr = [1, 2, 3]
}
function Son() {
Father.call(this)
this.age = 18
}
Son.prototype = new Father()
var a = new Son()
var b = new Son()
a.arr.push(4)
console.log(a.arr, b.arr) //[1,2,3,4] [1,2,3]
//虽然通过这种方式继承解决了被共享的问题,但是出现了下面的问题。在实例化Son时候,调用了父级的构造函数2次。
</script>
4. 原型式继承(浅拷贝)?
借助原型已有的对象创建新对象,同时还不必创建自定义类型。但是引用类型值的属性会被所有实例共享。
<script>
var person = {
name: 'shids',
sex: 'boy'
}
function create(obj) { //接收一个参数
function Df() { }; //创建空的构造函数
Df.prototype = obj; //将参数对象的属性方法赋给构造函数
return new Df(); //返回该构造函数的实例对象
}
var man = create(person); //试过直接将person赋给man 结果一样
console.log(man.name); //shids
console.log(man.sex); //boy
//适用于 简单继承原有对象的属性方法 但同时也存在引用类型共享的问题
</script>
5. 寄生式继承?
创建了一个函数,然后在函数内部定义一个对象来实现原型式继承方式,然后再给这个对象添加方法或属性值,最后再将这个对象返回。但是做不到函数复用。
<script>
function createAnother(original) {
var clone = Object.create(original); //通过调用函数创建一个新对象
clone.sayHi = function () { //以某种方式来增强这个对象
console.log("Hi");
};
return clone; //返回这个对象
}
var person = {
name: "Bob",
friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = createAnother(person);
anotherPerson.sayHi();
</script>
6. 寄生组合式继承?
借用构造函数继承属性,通过原型链的混成形式继承方法。因为只调用了一次超类型构造函数 所以效率更高。
<script>
function SuperType(name) {
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function () {
// alert(this.name);
conlog(this.name);
};
function SubType(name, age) {
SuperType.call(this, name);
this.age = age;
}
function inheritPrototype(subType, superType) {
var prototype = Object.create(superType.prototype); //创建对象
prototype.constructor = subType; //增强对象
subType.prototype = prototype; //自定对象
}
inheritPrototype(SubType, SuperType);
SubType.prototype.sayAge = function () {
conlog(this.age);
};
var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
conlog(instance1.colors); //"red,blue,green,black"
instance1.sayName(); //"Nicholas"
instance1.sayAge(); //29
function conlog(log) {
console.log(log);
}
</script>
7. Javascript事件模型?
事件模型?
事件模型又称为原始事件模型,在该模型中,事件不会传播,即没有事件流的概念。事件绑定监听函数比较简单, 有两种方式:
DOM0
DOM0
级事件模型是早期的事件模型,所有的浏览器都是支持的,它其中有一个 dom
对象只能注册一个同类型的函数,因为注册多个同类型的函数的话,就会发生覆盖,之前注册的函数就会无效。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body>
<button type="button">点击</button>
</body>
<script>
var $btn = document.querySelector('button');
$btn.onclick = () => { console.log('click') };
//这种事件模型很容易理解, 是唯一的, 后面的事件会覆盖前面的事件, 要移除也很简单
// $btn.onclick = null;
</script>
</html>
DOM2
DOM2
级事件模型是捕获和冒泡模型,就是一个事件,要先捕获到,然后再执行冒泡。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body>
<button type="button">点击</button>
</body>
<script>
const $btn = document.querySelector('button');
const btnClick = () => console.log('click')
$btn.addEventListener('click', btnClick); // 添加事件
// $btn.removeEventListener('click', btnClick); // 解除绑定
</script>
</html>
8. new操作符具体干了什么呢?
new
new
运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象类型之一。
new
具体干了什么?
- 创建一个空对象,并且
this
变量引用该对象,同时还继承了该函数的原型; - 属性和方法被加入到
this
引用的对象中; - 新创建的对象由
this
所引用,并且最后隐式的返回this
。
1. 创建空对象。
//创建一个空对象
varobj=new Object();
2. 设置原型链。
//设置原型链
obj.__proto__= Func.prototype;
3. 让函数中的 this
指向 obj
并执行函数的函数体。
//让Func中的this指向obj,并执行Func的函数体。
var result =Func.call(obj);
4. 判断函数返回值的类型。
//如果是值类型,返回obj。如果是引用类型,就返回这个引用类型的对象。
if (typeof(result) == "object"){
func=result;
}
else{
func=obj;;
}
9. 异步加载JS的方式有哪些?
何为异步加载?
异步加载又称为非阻塞加载,当浏览器在下载 JS
的同时,还会进行后续页面处理。
为什么要异步加载?
主要是因为同步加载存在问题。js
在默认情况下是以同步加载的,但是 js
的执行总是阻塞的。这就会导致如果在页面中加载一些 js
,但其中某个请求迟迟得不到响应,位于此 js
后面的 js
将无法执行,同时页面渲染也不能继续,用户看到的就是白屏。
异步加载的方式?
1. script
标签的 defer="defer"
属性。
script
标签中增加 defer
属性即可异步加载。但要等 dom
文档全部解析完(dom
树生成)才会被执行且只有 IE
能用。
2. onload
时的异步加载。
<script>
(function () {
if (window.attachEvent) {
window.attachEvent("load", asyncLoad);
} else {
window.addEventListener("load", asyncLoad);
}
var asyncLoad = function () {
var ga = document.createElement('script');
ga.type = 'text/javascript';
ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') +
'.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(ga, s);
}
})();
</script>
3. script
标签的 async="async"
属性。
script
标签中增加 async
属性也可异步加载。但是它是加载完就执行,而且 async
只能加载外部脚本,不能把 js
写在 script
标签里,w3c
标准,IE9
以下不支持。
10. 你都用过哪些设计模式?
1. 单例模式
单例模式,是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。一个类只有一个对象实例。
<script>
function SetManager(name) {
this.manager = name;
}
SetManager.prototype.getName = function () {
console.log(this.manager);
};
var SingletonSetManager = (function () {
var manager = null;
return function (name) {
if (!manager) {
manager = new SetManager(name);
}
return manager;
}
})();
SingletonSetManager('a').getName(); // a
SingletonSetManager('b').getName(); // a
SingletonSetManager('c').getName(); // a
</script>
2. 工厂模式
工厂模式是我们最常用的实例化对象模式了,是用工厂方法代替 new
操作的一种模式。工厂函数就是做一个对象创建的封装,并将创建的对象 return
出去。
<script>
// 定义一个工厂函数
function createPerson(name, age, gender) {
// 创建一个新的对象,并且将参数传递给对象的属性
var person = {
name: name,
age: age,
gender: gender,
// 定义一个方法来打印个人信息
printInfo: function () {
console.log("Name: " + this.name + ", Age: " + this.age + ", Gender: " + this.gender);
}
};
// 返回新创建的对象
return person;
}
// 使用工厂函数来创建两个不同的 person 对象
var person1 = createPerson("Tom", 28, "Male");
var person2 = createPerson("Lucy", 25, "Female");
// 调用对象的方法来打印个人信息
person1.printInfo(); // 输出:Name: Tom, Age: 28, Gender: Male
person2.printInfo(); // 输出:Name: Lucy, Age: 25, Gender: Female
</script>
3. 订阅/发布模式
订阅发布模式又称为观察者模式,主要采用数据劫持结合“发布-订阅”模式的方式,通过 Object.defineProperty
的 set
和 get
,在数据变动时发布消息给订阅者触发监听。
<script>
// 订阅
document.body.addEventListener('click', function () {
console.log('click1');
}, false);
document.body.addEventListener('click', function () {
console.log('click2');
}, false);
// 发布
document.body.click(); // click1 click2
</script>
4. 策略模式
定义一系列的算法,将他们一个个封装起来,使他们直接可以相互替换。策略模式可以使代码更加灵活、可复用,并且有利于可以在运行时动态地改变代码的行为。
<script>
// 定义一个策略类
class Calculator {
// 定义一个计算方法
calculate(num1, num2, operation) {
return operation.calculate(num1, num2);
}
}
// 定义一个加法策略类
class AddStrategy {
calculate(num1, num2) {
return num1 + num2;
}
}
// 定义一个减法策略类
class SubtractStrategy {
calculate(num1, num2) {
return num1 - num2;
}
}
// 使用策略模式执行计算
const calculator = new Calculator();
console.log(calculator.calculate(10, 5, new AddStrategy())); // 15
console.log(calculator.calculate(10, 5, new SubtractStrategy())); // 5
</script>
11. 说说你对promise的了解?
说说你对
promise
的了解?
promise
是异步编程的一种解决方案,用于一个异步操作的最终完成(或失败)及其结果值的表示;主要用于异步计算,promise
有三种状态:pending
(等待态),fulfilled
(成功态),rejected
(失败态)。
promise
的特点?
promise
对象的状态不受外界影响;promise
有以上三种状态,只有异步操作的结果可以决定当前是哪一种状态,其他任何操作都无法改变这个状态;promise
的状态一旦改变,就不会再变,只能由pending
变成fulfilled
或者由pending
变成rejected
。
promise
的优缺点?
-
优点:
有了
promise
对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,promise
对象提供统一的接口,使得控制异步操作更加容易。 -
缺点:
无法取消
promise
,一旦新建它就会立即执行,无法中途取消,如果不设置回调函数,promise
内部抛出的错误,不会反应到外部,当处于pending
状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
promise
的使用?
<script>
let p1 = new Promise((resolved, rejected) => {
let randomVal = Math.random();
if (randomVal > 0.5) {
resolved(randomVal + "成功");
} else {
rejected(randomVal + "失败");
}
})
.then((res) => {
console.log(res)//0.6592082041868452成功
})
.catch((err) => {
console.log(err)//0.292053623437289失败
});
</script>
12. ajax的原理?
ajax
简单来说 ajax
是一种无需要重新加载整个网页的情况下,能够更新部分网页的技术。它是通过XmlHttpRequest
对象来向服务器发送一个异步请求,从服务器获取到数据,然后用 javascript
来操作 dom
更新页面。
ajax
的优缺点?
- 优点:
- 通过异步模式,提升了用户体验;
- 优化了浏览器和服务器之间的传输,减少不必要的数据往返,减少了带宽占用;
ajax
在客户端运行,承担了一部分本来由服务器承担的工作,减少了大用户量下的服务器负载;ajax
可以实现动态不刷新 ( 局部刷新 )。
- 缺点:
- 安全问题
ajax
暴露了与服务器交互的细节; - 对搜索引擎的支持比较弱;
- 不容易调试。
ajax
如何实现?
<script>
// 1. 创建连接
var xhr = null;
xhr = new XMLHttpRequest()
// 2. 连接服务器
xhr.open('get', url, true)
// 3. 发送请求
xhr.send(null);
// 4. 接受请求
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
if (xhr.status == 200) {
success(xhr.responseText);
} else { // fail
fail && fail(xhr.status);
}
}
}
</script>
13. JS 哪些操作会造成内存泄露?
1. 意外的全局变量
我们由于使用未声明的变量,而意外的创建了一个全局变量,而使这个变量一直留在内存中无法被回收。
<script>
function fn() {
a = "sds"; //a成为一个全局变量,不会被回收
}
//上面的写法等同于
function fn() {
window.a = 'sds'
}
function fn() {
this.a = 'zzz'
//函数自身发生调用,this指向全局对象window
}
foo();
</script>
-
问题所在:
上面的
a
变量应该是foo()
内部作用域变量的引用,由于没有使用var
来声明这个变量,这时变量a
就被创建成了全局变量,这个就是错误的,会导致内存泄漏。 -
解决方式:
在
js
文件开头添加'use strict'
,开启严格模式,或者一般将使用过后的全局变量设置为null
或者将它重新赋值,这个会涉及的缓存的问题,需要注意。
2. 未清理的
DOM
元素的引用
var a = document.getElementById('id');
document.body.removeChild(a);
-
问题所在:
因为存在变量
a
对它的引用。虽然我们用removeChild
移除了,但是还在对象里保存着#
的引用,即DOM
元素还在内存里面。 -
解决方式:
a = null;
3. 被遗忘的定时器或者回调
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body>
<div id="Node">默认值</div>
</body>
<script>
var a = new Date();
var b = a.getDate()
setInterval(function () {
var node = document.getElementById('Node');
if (node) {
node.innerHTML = JSON.stringify(b)
}
}, 1000)
</script>
</html>
-
问题所在:
定时器
setInterval
或者setTimeout
在不需要使用的时候,没有被clear
,导致定时器的回调函数及其内部依赖的变量都不能被回收,这就会造成内存泄漏。 -
解决方式:
当不需要
interval
或者timeout
的时候,调用clearInterval
或者clearTimeout
清除定时器。
4. 闭包
<script>
function assignHandler() {
var element = document.getElementById("someElement");
element.onclick = function () {
alert(element.id);
};
}
</script>
-
问题所在:
不合理的使用闭包,从而导致某些变量一直被留在内存当中。
-
解决方式:
将事件处理函数定义在外部。
5. 大量的
console.log
控制台日志记录对总体内存内置文件的影响,也是个重大的问题,同时也是容易被忽略的。记录错误的对象,可以将大量的数据保留在内存中。传递给 console.log
的对象是不能被垃圾回收,所以没有去掉 console.log
可能会存在内存泄漏。
怎么避免造成内存泄露?
1)减少不必要的全局变量,或者生命周期较长的对象,及时对无用的数据进行垃圾回收;
2)注意程序逻辑,避免“死循环”之类的 ;
3)避免创建过多的对象,原则:不用了的东西要及时归还。
14. JS 数据类型有哪些?
js
数据类型?
js
的数据类型一共有 7
种,分别是: number
、string
、boolean
、object
、array
、undefined
、null
;其中,js
基本数据类型有 5
种,分别是:number
、string
、boolean
、undefined
、null
。
1. number
js
只有一种数字类型。数字可以带小数点,也可以不带。
var a = 1;
var b = 1.5;
2. string
字符串是存储字符的变量。字符串可以是引号中的任意文本,可以使用单引号或双引号。
var a = "1";
var b = '1';
3. boolean
布尔(逻辑)只能有两个值 true
或者 false
;前者表示真,后者表示假,如果用数字表示,那么,true
可以使用 1
来表示,false
可以使用 0
来表示,布尔型变量的值来源于逻辑性运算符。
var a = true;
var b = false;
4. object
对象由花括号分隔。在括号内部,对象的属性以名称和值对的形式 (name:value
)来定义,属性由逗号分隔。
var a = {name:"sz", age:"21"};
5. array
数组是按次序排列的一组值。每个值的位置都有编号(从 0
开始),整个数组用方括号表示,数组可以先定义后赋值,任何类型的数据,都可以放入数组,本质上,数组属于一种特殊的对象。
var a = new Array("b","c","d");
var b = ["b","c","d"];
6. undefined
undefined
类型也是只有一个 undefined
值,当在编写 js
代码时,如果定义了一个变量,但没有给它赋值,那么,这个变量将返回 undefined
值,这也是变量默认的值,与 null
类型不同之处在于,null
类型是一个空值,而 undefined
类型表示无值。
var a;
console.log(a); //undefined
7. null
null
类型是一个比较特殊的类型,它只有一个值,就是 null
,当引用一个未定义的对象时,则将返回一个这个 null
值, 从严格意义上来说,null
值本质上是一个对象类型,是一个空指针的对象类型。
var a = null;
console.log(a);//null
15. null,undefined 的区别?
null
null
表示一个对象被定义了,值为“空值”,是一个对象(空对象,没有任何属性和方法),例如作为函数的参数,表示该函数的参数不是对象,在验证 null
时,一定要使用 ===
,因为 ==
无法分别 null
和 undefined
。
nudefined
undefined
表示不存在这个值,是一个表示"无"的原始值或者说表示"缺少值",就是此处应该有一个值,但是还没有定义,当尝试读取时会返回 undefined
,例如变量被声明了,但没有赋值时,就等于 undefined
。
两者之间区别?
-
共同点:
都是原始数据类型,保存在栈中变量本地。
-
不同点:
-
类型不同
console.log(typeOf undefined); //undefined console.log(typeOf null); //object
-
转换值不同
console.log(Number(undefined)); //NaN console.log(Number(10+undefined)); //NaN console.log(Number(null)); //0 console.log(Number(10+null)); //10
-
两者表现形式?
==
与===
undefined==null; //true
undefined===null; //false
- JSON
JSON.stringify({a: undefined}) // '{}'
JSON.stringify({b: null}) // '{b: null}'
JSON.stringify({a: undefined, b: null}) // '{b: null}'
16. JS 同步和异步的区别?
为什么
js
是单线程?
JavaScript
主要是用来实现与用户的交互,利用 JavaScript
,我们可以实现对 dom
的各种各样的操作,如果 JavaScript
是多线程的话,一个线程在一个 dom
节点中增加内容,另一个线程要删除这个 dom
节点,那么这个 dom
节点究竟是要增加内容还是删除呢?这会带来很复杂的同步问题,因此,JavaScript
是单线程的。
什么是同步?
同步简单来说就是在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务。
什么是异步?
异步任务是指不进入主线程,而进入任务队列的任务,只有任务队列通知主线程,某个异步任务可以执行了,该任务才会进入主线程。
最基础的异步就是 setTimeout
和 setInterval
函数,很常见,但是很少人有人知道其实这就是异步,因为它们可以控制 JavaScript
的执行顺序。我们也可以简单地理解为:可以改变程序正常执行顺序的操作就可以看成是异步操作。
<script>
console.log( "1" );
setTimeout(function() {
console.log( "2" )
}, 0 );
setTimeout(function() {
console.log( "3" )
}, 0 );
setTimeout(function() {
console.log( "4" )
}, 0 );
console.log( "5" );
</script>
控制台打印
宏任务与微任务
异步任务又可以分为微任务和宏任务。
宏任务一般是:script
、setTimeout
、setInterval
、postMessage
、MessageChannel
;
微任务一般是:Promise.then
、Object.observe
、MutationObserver
。
执行顺序
在代码中,当一个宏任务执行完毕后,会查看当前微任务队列中是否有任务,如果有,则依次执行所有微任务,直到微任务队列为空,然后再执行下一个宏任务。这就是 JavaScript
中的事件循环机制。
17. JS 如何创建对象?
1. 通过
{}
此方法在对象不用重复创建时,是比较简单便捷的。
var obj = {};
obj.age = "18";
2. 通过
new object()
此方法其实跟第一种方式没有什么区别。
var obj = new Object();
obj.age = "18";
3. 通过字面量
{}
里面包含了表达这个具体事物(对象)的属性和方法。{}
里面采取键值对的形式表示,键:相当于属性名;值:相当于属性值。
var obj = {
age: 18,
way: function () {
console.log('hello word!');
}
}
前面三种创建对象的方式存在 2
个问题:
- 代码冗余;
- 对象中的方法不能共享,每个对象中的方法都是独立的。
4. 通过工厂模式
这种方式是使用一个函数来创建对象,减少重复代码,解决了前面三种方式的代码冗余的问题,但是方法不能共享的问题还是存在。
function createObject(name) {
var a = new Object();
a.name = name;
a.way = function () {
};
return a;
}
var b = createObject('sds');
var c = createObject('li');
alert(b.way === c.way);//false
5. 通过构造函数
构造函数,也是普通的函数,不过约定俗成,构造函数的名称首字母大写,普通函数的首字母小写。通过 new
构造函数来创建对象。
function Student(name) {
this.name = name;
this.hello = function () {
alert(this.name);
}
}
var a = new Student('小明');
a.name; // '小明'
a.hello(); // 小明
6. 通过原型方式
通过原型创建对象,把属性和方法绑定到 prototype
上,通过这种方式创建对象,方法是共享的,每个对象调用的是同一个方法。
function Person() {
}
Person.prototype = {
constructor: Person,
name: "sds",
age: 18,
way: function () {
alert(this.name);
}
};
var friend = new Person();
friend.sayName(); //sds
7. 通过原型+构造函数
这种方式是变量类型属性:用构造函数传递,函数类型属性:用原型模式声明。
function Student(name, age) {
this.name = name;
this.age = age;
}
Student.prototype.setName = function (name2) {
this.name = name2;
};
Student.prototype.getName = function () {
return this.name;
};
var stu1 = new Student("sds", 18);
alert(stu1.getName()); //sds
18. 如何通过JS判断一个数组?
1.
instanceof
instanceof
用于检测构造函数的 prototype
属性是否在实例对象的原型链上。如果存在就返回 true
;否则返回 false
。
var arr = [];
arr instanceof Array; // true
2.
constructor
实例的构造函数属性 constructor
指向构造函数,那么通过 constructor
属性也可以判断是否为一个数组。
var arr = [];
arr.constructor == Array; //true
3.
Array.isArray()
Array.isArray()
用于确定传递的值是否是一个数组,返回一个布尔值。如果是数组返回 true
;否则返回 false
。
var a = new Array(123);
var b = new Date();
console.log(Array.isArray(a)); //true
console.log(Array.isArray(b)); //false
4.
object.prototype.tostring.call()
通过 tostring
判断,每个对象都可以通过 object.prototype.tostring.call()
来检测。
let a = [1,2,3]
Object.prototype.toString.call(a) === '[object Array]';//true
object.prototype.tostring.call()
强大的地方在于它不仅仅可以检验是否为数组,还可以检测是否为函数,是否是数字等等。
//检验是否是函数
let a = function () {};
Object.prototype.toString.call(a) === '[object Function]';//true
//检验是否是数字
let b = 1;
Object.prototype.toString.call(b) === '[object Number]';//true
19. async、await?
什么是
async
和await
?
async
和 await
是一种同步的写法,但还是异步的操作,两个内容还是必须同时去写才会生效,await
可以等到你的数据加载过来以后才会去运行下边的 js
内容,而且 await
接收的对象必须还是个 promise
对象。
async
和await
的特点?
async
作为一个关键字放到函数前面,这样普通函数就变为了异步函数;
异步 async
函数调用,跟普通函数的使用方式一样;异步 async
函数返回一个 promise
对象。
async
和await
的优点?
- 方便级联调用:即调用依次发生的场景;
- 同步代码编写方式:
async/await
从上到下,顺序执行,就像写同步代码一样,更符合代码编写习惯; - 多个参数传递:
promise
的then
函数只能传递一个参数,async/await
没有这个限制,可以当做普通的局部变量来处理; - 同步代码和异步代码可以一起编写;
- 解决了回调地狱的问题;
- 支持并发执行。
async
和await
使用场景?
执行第一步,将执行第一步的结果返回给第二步使用。拿到一个接口的返回数据,使用第一步返回的数据执行第二步操作的接口调用,达到异步操作。
//promise版本,将使得参数传递变得非常麻烦。
function fn2() {
const promise1 = convertToBase64Data('http://1.jpg');
const promise2 = convertToBase64Data('http://1.jpg');
Promise.all([promise1,promise2]).then( datas => {
saveToLocal(datas[0]).then( result1 => {
saveToLocal(datas[1]).then(result2 => {
console.log(result2);
})
})
})
}
//使用async、await
async function fn2() {
// 同时处理两张
const promise1 = convertToBase64Data('http://1.jpg');
const promise2 = convertToBase64Data('http://2.jpg');
const [data1, data2] = await Promise.all([promise1, promise2]);
// 先保存第一张
const result = await saveToLocal(data1);
// 再保存第二张
const result2 = await saveToLocal(data2);
}
回调地狱的问题
“回调地域”具体指的是回调函数里面嵌套回调函数。
//地狱回调
setTimeout(function () {
//第一层
console.log("张三"); //等3秒打印张三在执行下一个回调函数
setTimeout(function () {
//第二层
console.log("李四"); //等2秒打印李四在执行下一个回调函数
setTimeout(function () {
//第三层
console.log("王五"); //等一秒打印王五
}, 1000);
}, 2000);
}, 3000);
回调地狱是为了让我们代码执行顺序的一种操作(解决异步),但是它会使我们的可读性非常差。
- promise 解决
function fn(str) {
var promise = new Promise(function (success, error) { //success 是成功的方法 error是失败的方法
//处理异步任务
var flag = true;
setTimeout(function () {
if (flag) {
success(str)
}
else {
error('失败')
}
})
})
return promise;
}
fn('张三')
.then((res) => { //then是成功执行的方法 返回的还是一个promise对象
console.log(res);//打印张三 res是执行
return fn('李四');
})
.then((res) => {
console.log(res);
return fn('王五')
})
.then((res) => {
console.log(res);
})
.catch((res) => { //catch是失败执行的方法
console.log(res);
})
- async 和 await 解决
//封装一个返回promise的异步任务
function fn(str) {
var promise = new Promise(function (success, error) {
var flag = true;
setTimeout(function () {
if (flag) {
success(str)
} else {
error('处理失败')
}
})
})
return promise;
}
//封装一个执行上述异步任务的async函数
async function test() {
var res1 = await fn('张三'); //await直接拿到fn()返回的promise的数据,并且赋值给res
var res2 = await fn('李四');
var res3 = await fn('王五');
console.log(res1);
console.log(res2);
console.log(res3);
}
//执行函数
test();
控制台打印结果
20. map与forEach的区别?
map()
map()
方法返回一个新数组,数组中的元素为原始数组元素调用函数处理后的值。
<script>
var arr = [0,2,4,6,8];
var newArr = arr.map(function(item,index,arr){
console.log(this);
console.log(arr);
return item/2;
},this);
console.log(newArr);
</script>
forEach()
forEach()
方法用于调用数组的每个元素,并将元素传递给回调函数。
<script>
var arr1 = [0,2,4,6,8];
var newArr1 = arr1.forEach(function(item,index,arr1){
console.log(this);
console.log(arr1);
arr1[index] = item/2;
},this);
console.log(arr1);
console.log(newArr1);
</script>
共同点?
- 都是循环遍历数组中的每一项;
- 每次执行匿名函数都支持三个参数,参数分别为
item
(当前每一项),index
(索引值),arr
(原数组); - 匿名函数中的
this
都是指向window
; - 只能遍历数组。
不同点?
map()
会分配内存空间存储新数组并返回,forEach()
不会返回数据。
forEach()
允许 callback
更改原始数组的元素。map()
返回新的数组。
21. let、var、const的区别?
1. 是否存在变量提升
var
声明的变量存在变量提升,即变量可以在声明之前调用,值为 undefined
;let
和 const
不存在变量提升,即它们所声明的变量一定要在声明后使用。
console.log(a); // undefined ===> a已声明还没赋值,默认得到undefined值
var a = 100;
console.log(b); // 报错:b is not defined ===> 找不到b这个变量
let b = 10;
console.log(c); // 报错:c is not defined ===> 找不到c这个变量
变量提升: 函数及变量的声明都将被提升到函数的最顶部。
2. 是否允许重复声明变量
var
允许重复声明变量;let
和 const
在同一作用域不允许重复声明变量。
3. 是否能修改声明的变量
var
和 let
可以;const
声明一个只读的常量。一旦声明,常量的值就不能改变。就必须立即初始化,不能留到以后赋值。
// 1、一旦声明必须赋值,不能使用null占位。
// 2、声明后不能再修改
// 3、如果声明的是复合类型数据,可以修改其属性
const a = 100;
const list = [];
list[0] = 10;
console.log(list); // [10]
const obj = {a:100};
obj.name = 'apple';
obj.a = 10000;
console.log(obj); // {a:10000,name:'apple'}
4. 是否存在暂时性死区
let
和 const
存在暂存死区,var
不存在暂时性死区。
var a = 100;
if(1){
a = 10;
//在当前块作用域中存在a使用let/const声明的情况下,给a赋值10时,只会在当前作用域找变量a,
// 而这时,还未到声明时候,所以控制台Error:a is not defined
let a = 1;
}
5. 是否存在块级作用域
var
不存在块级作用域;let
和 const
存在块级作用域。
if(1){
var a = 100;
let b = 10;
const c = 1;
}
console.log(a); // 100
console.log(b) // 报错:b is not defined ===> 找不到b这个变量
console.log(c) // 报错:c is not defined ===> 找不到c这个变量
22. 谈谈你对ES6的理解?
1. 新增数据类型
es6
引入了一种新的数据类型 symbol
,表示独一无二的值。symbol
值通过 symbol
函数生成。也就是说,对象的属性名现在可以有两种类型,一种是原来就有的字符串,另一种就是新增的 symbol
类型。凡是属性名属于 symbol
类型,就都是独一无二的,可以保证不会与其他属性名产生冲突。
let a = Symbol();
console.log(typeof a);//symbol
2. 箭头函数
箭头函数是匿名函数,不能作为构造函数,不能使用 new
,箭头函数不绑定 this
,会捕获其所在的上下文的 this
值,作为自己的 this
值;箭头函数通过 call()
或 apply()
方法调用一个函数时,只传入了一个参数,对 this
并没有影响;箭头函数没有原型属性。
const pipeline = (...funcs) =>
val => funcs.reduce((a, b) => b(a), val)
const plus1 = a => a + 1
const mult2 = a => a * 2
const addThenMult = pipeline(plus1, mult2)
let r = addThenMult(5)
console.log(r); //12
3.
for-of
for...of
语句创建一个循环来迭代可迭代的对象。在 es6
中引入的 for...of
循环,以替代 for...in
和 forEach()
,并支持新的迭代协议。or...of
允许你遍历 arrays
(数组), strings
(字符串), maps
(映射), sets
(集合)等可迭代的数据结构等。
<script>
var arr = [
{ name:'pdd', age:10 },
{ name:'tb', age:18 },
{ name:'tm', age:13 },
{ name:'jd', age:20 }
];
for(var item of arr){
console.log(item.name,item.age);
}
</script>
4. 模板字符串
新写法使用了 (``)
用来替代引号,${}
中放置变量名。这使得代码更有条理和结构性。
//传统写法
var name = 'World'
console.log('Hello ' + name);
//使用模板字符串
let name = 'World'
console.log(`Hello ${name}`);
5. 展开运算符
(...)
将对象或者数组中的数据展开放到另外的数组或对象。
let arr = [1,2,3,4,5]
let arr2 = [6,7,8,...arr]
console.log(arr2)
let obj = {
id:9527,
name:"张三",
age:15
}
let obj2 = {
...obj,
phone:1999999
}
console.log(obj2)
6.
Set
es6
提供了新的数据结构 set
。它类似于数组,但是成员的值都是唯一的,没有重复的值。set
本身是一个构造函数,用来生成 set
数据结构。
const s = new Set();
同时,set
函数可以接受一个数组作为参数,用来初始化。
const set = new Set([1,2,3,4,4]);
三、vue 相关面试题
1. vue中的自定义指令?
自定义指令
自定义指令分为:全局自定义指令,局部自定义指令。它有两个参数:参数1:指令的名称;参数2:是一个对象,这个对象身上,有钩子函数。
全局自定义指令
通过 Vue.directive()
函数注册一个全局的指令。
// 注册一个全局自定义指令 `v-focus`
Vue.directive('focus', {
// 当被绑定的元素插入到 DOM 中时……
inserted: function (el) {
// 聚焦元素
el.focus()
}
})
在 main.js
中引入。
import focus from '@/utils/focus'
局部自定义指令
通过组件的 directives
属性,对该组件添加一个局部的指令。
directives: {
focus: {
// 指令的定义
inserted: function (el) {
el.focus()
}
}
}
然后你可以在组件中任何元素上使用 v-focus
自定义的指令,如下:
<input v-focus/>
钩子函数
1. bind
只调用一次,指令第一次绑定到元素时调用。
2. inserted
被绑定元素插入父节点时调用。
3. update
所在组件的虚拟节点更新时调用。
4. componentUpdated
所在组件的虚拟节点及其子虚拟节点全部更新时调用。
5. upbind
只调用一次,指令与元素解绑时调用。
使用场景
复制粘贴指令、长按指令、防抖指令、输入框自动聚焦指令、全屏指令等等。
2. vue中的provide和inject(依赖注入)?
provide
和inject
是什么?
provide/inject
这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在其上下游关系成立的时间里始终生效。通俗说就是可以用父组件给祖孙组件进行传值,也就是可以隔代传值,不论子组件有多深,只要调用了 inject
那么就可以注入 provide
中的数据,而不是局限于只能从当前父组件的 props
属性来获取数据,这也是 provide/inject
最大的特性。
provide
提供变量
provide
是一个对象,或者是一个返回对象的函数。里面呢就包含要给子孙后代的东西,也就是属性和属性值。
inject
注入变量
inject
是一个字符串数组,或者是一个对象。属性值可以是一个对象,包含 form
和 default
默认值。
类型
-
provide
Object | () => Object
-
inject
Array<string> | { [key: string]: string | Symbol | Object }
代码执行顺序
data->provide->created(在这个阶段$el还未生成,在这先处理privide的逻辑,子孙组件才可以取到inject的值)->mounted
注意:
provide/inject
绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的 property
还是可响应的。根据官方要求,我们只需要传入对象过去,并且对象是响应式的,定义在 data
或者计算属性里都可以。
provide
和inject
的缺点?
在项目中通常追求有清晰的数据流向和合理的组件层级关系,以便于调试和维护,然而 provide/inject
支持任意层级都能访问的特性,导致数据追踪比较困难,你压根不知道是哪一个层级声明了 provide
,或者不知道哪一个层级或若干个层级使用了 inject
,后期容易造成比较大的维护成本。因此, provide/inject
在常规应用下并不建议使用,vue
更建议使用 vuex
解决。但是在做组件库开发时,不对 vuex
进行依赖,且不知道用户使用环境的情况下可以很好的使用 provide/inject
。官方也着重说明 provide/inject
主要为高阶插件/组件库提供用例,并不推荐直接用于应用程序代码中。
3. vue中key的作用?
使用
key
的场景
在 vue
中我们可能在两种情况下使用 key
,第一种情况下就是在 v-if
中,第二种情况下就是在 v-for
中使用 key
。
v-if
中使用key
首先我们先看在 vue
中出现的一种情况,我们在 vue
中如果使用 v-if
进行切换时,此时 vue
为了更加高效的渲染,此时会进行前后比较,如果切换前后都存在的元素,则直接复用。如果我们在模板中切换前后都存在 input
框,此时我们在 input
框中写入一些数据,并且进行页面切换,则原有的数据就会保存。
此时我们就可以使用 key
,给每一个 input
框,添加一个唯一的标识 key
,来表示元素的唯一性。
v-for
中使用key
对于用 v-for
渲染的列表数据来说,数据量可能一般很庞大,而且我们经常还要对这个数据进行一些增删改操作。那么整个列表都要重新进行渲染一遍,那样就会很费事。而 key
的出现就尽可能的回避这个问题,提高效率。v-for
默认使用就地复用的策略,列表数据修改的时候,他会根据 key
值去判断某一个值是否修改,如果修改则重新渲染该项,否则复用之前的元素。在 v-for
中我们的 key
一般为 id
,也就是唯一的值,但是一般不要使用 index
作为 key
。
总结
key
的作用主要是为了高效的更新虚拟 dom
。保证某个元素的 key
在其同级元素中具有唯一性。在 diff
算法中 会借助元素的 key
值来判断该元素是新近创建的还是被移动而来的元素,从而减少不必要的元素重渲染。
4. vue有哪些事件修饰符?
1.
stop
阻止事件冒泡
<a v-on:click.stop = "pressOn">点击</a>
阻止事件冒泡,阻止事件向上级 dom
元素传递。比如一个 div
里面有一个按钮,div
和按钮都绑定了点击事件,点击按钮时会先触发自身的事件,再触发 div
的事件,即为冒泡。
2.
prevent
阻止事件默认行为
<a @click.prevent = "pressOn">点击</a>
比如一个 a
标签有超链接也有点击事件,使用该修饰符后,点击 a
标签时只会触发点击事件,而不会再链接到 href
的地址,否则,会先触发点击事件,再链接到目标地址
3.
capture
触发事件捕获
<div @click.capture = "pressOn">点击</div>
捕获冒泡,即有冒泡发生时,有该修饰符的 dom
元素会先执行,如果有多个,从外到内依次执行,然后再按自然顺序执行触发的事件。
4.
self
触发回调
<div v-on:click.self="pressOn">点击</div>
将事件绑定到自身,只有自身才能触发,通常用于避免冒泡事件的影响。
5.
once
事件只执行一次
<button @click.once="pressOn">点击</button>
指定的事件只触发一次。
v-model
中的修饰符
1. .number
自动将用户的输入值转化为数值类型。
<input type="text" v-model.number="inputModel">
2. .trim
自动过滤用户输入的首尾空格。
<input type="text" v-model.trim="nameModel">
键盘修饰符
项目中经常需要监听一些键盘事件来触发程序的执行,而 vue
允许在监听的时候添加关键修饰符:
.enter
、.tab
、.delete
、.esc
、.space
、.up
、.down
、.left
、.right
等等。
element
修饰符
对于 element
的 input
,我们需要在后面加上 .native
, 因为 element
对 input
进行了封装,原生的事件不起作用。
<input v-model="form.name" placeholder="昵称" @keyup.enter="submit">
<el-input v-model="form.name" placeholder="昵称" @keyup.enter.native="submit"></el-input>
5. vue-router?
1.
hash
模式(默认)
hash
即浏览器 url
中 #
后面的内容,包含 #
。hash
是 url
中的锚点,代表的是网页中的一个位置,单单改变 #
后的部分,浏览器只会加载相应位置的内容,不会重新加载页面,hash
有两种方法,push()
,replace()
。
2.
history
模式
HTML5 History API
提供了一种功能,能在不刷新整个页面的情况下修改站点的 url
,就是利用 history.pushState API
来完成 url
跳转而无须重新加载页面。
路由跳转?
1. router-link
//不带传参写法
<router-link :to="{name:'home'}">点击我跳转</router-link>
<router-link :to="{path:'/home'}">点击我跳转</router-link>
注意: 当使用 name
时,要与路由文件中的 name
值一致。
//带传参写法
<router-link :to="{path:'./home', query: {id:'你好'}}">点击我跳转</router-link>
<router-link :to="{name:'home', params: {id:1000}}">点击我跳转</router-link> //只能用 name
//接参写法
this.$route.params.id
this.$route.query.id
2. this.$router.push()
//不带传参写法
this.$router.push("/home");
注意: 路由配置 path:"/home/:id"
或者 path:"/home:id"
,在 router.js
文件中配置,不配置 path
,刷新页面参数会消失。
//带传参写法
this.$router.push({path: "/home", query: {id: "1"}});
this.$router.push({name: "home", params: {id: "1"}}); //只能用 name
//接参写法
this.$route.params.id
this.$route.query.id
3. this.$router.go(n)
向前或者向后跳转 n
个页面,n
可为正整数或负整数。
4. this.$router.replace()
跳转到指定 url
路径,但是 history
栈中不会有记录,点击后会返回到上上一个页面,就是直接替换了当前页面,多用于 404
页面。
嵌套路由?
当路由越来越多时,把全部路由都以平级的关系罗列在一起显得很冗繁,也让路由变得难以维护,为此,我们可以让存在上下级逻辑关系的路由组合成嵌套路由。
需要在 home.vue
组件中增加触发路由的 <router-link>
,并且在这个页面中放置一个 <router-view>
,当子路由被匹配到的时候,对应的页面会被渲染到这里。
home.vue
文件
<template>
<div>
<router-link tag="li" to="/home/one">一级</router-link>
<router-link tag="li" to="/home/two">二级</router-link>
<router-link tag="li" to="/home/three">三级</router-link>
<!--当路由匹配成功,组件one/two/three会被渲染到这里-->
<router-view></router-view>
</div>
</template>
router/idnex.js
文件
import home from '@/views/home'
// 引入嵌套的三个子路由
import One from '@/components/One'
import Two from '@/components/Two'
import Three from '@/components/Three'
const routes = [
{
path: '/home',
component: home,
// 路由配置 children 字段
children: [{
path: 'one',
component: One
},
{
path: 'two',
component: Two
},
{
path: 'three',
component: Three
}
]
},
]
路由重定向?
重定向也是通过 router
配置来完成,通过 redirect
实现。
{
path: '/a',
redirect: '/b'
},
路由懒加载?
vue
打包后的 js
文件越来越大,这会是影响加载时间的重要因素。当构建的项目比较大的时候,懒加载可以分割代码块,提高页面的初始加载效率。
1. 通过 resolve
的异步机制,用 require
代替 import
,实现按需加载。
{
path: '/',
name: 'home',
component: resolve => require(['@/views/home'], resolve)
},
2. 官网提供了一种方法,可以理解也是为通过 promise
的 resolve
机制。因为 promise
函数返回的 promise
为 resolve
组件本身,而我们又可以使用 import
来导入组件。
{
path: '/',
name: 'home',
component: () => import('@/views/home.vue')
},
3. 通过 require.ensure
方法。这种模式可以通过参数中的 webpackChunkName
将 js
分开打包。
{
path: '/',
name: 'home',
component: resolve => require.ensure([], () => resolve(require('@/views/' + componentName)), 'webpackChunkName')
},
路由守卫?
路由守卫分为全局路由守卫、组件路由守卫、路由独享的守卫。
-
参数
to:即将要进入的目标路由对象; from:当前导航正要离开的路由; next:执行下一步。
-
全局前置守卫
可以通过router.beforeEach
注册一个全局前置守卫。const router = new VueRouter({ ... }) router.beforeEach((to, from, next) => { // ... })
-
全局后置钩子
可以注册全局后置钩子,然而和守卫不同的是,这些钩子不会接受next
函数也不会改变导航本身。只有两个参数,to
和from
。router.afterEach((to, from) => { // ... })
-
组件内的守卫
beforeRouteEnter 路由进入 beforeRouteUpdate 路由更新(参数的更新) beforeRouteLeave 离开
-
路由独享守卫
可以在路由配置上直接定义beforeEnter
守卫。与全局路由守卫用法一致,但是只能针对一个页面使用。const router = new VueRouter({ routes: [ { path: '/home', component: home, beforeEnter: (to, from, next) => { // ... } } ] })
6. 为何组件中的data必须是一个函数?
什么是函数?
在 js
中,函数是一段被封装好的代码,可以被反复使用(调用),函数就是包裹在花括号中的代码块,前面使用了关键词 function
,当调用该函数时,会执行函数内的代码。
为何组件中的
data
必须是一个函数?
其实在讲这个问题前,我们需要先了解原型链的知识,问题的根源也是取决于 js
,而非 vue
。
先看下面这段代码:
function wayPort() {}
wayPort.prototype.data = {
sex: '男',
age: 18,
}
var a = new wayPort();
var b = new wayPort();
a.data.sex = '女';
b.data.age = '23';
console.log(a.data, b.data)
从控制台的输出结果我们可以很清晰的看到,a
、b
的 data
指向了同一个内存地址,但神奇的是 a
改变的 sex
值,b
的值也跟着一起变了,b
改变的 age
值,a
的值也跟着一起变了。由此我们可得出结论:如果两个实例同时引用一个对象,那么当你修改其中一个属性的时候,另外一个实例也会跟着改。
再来看下面这段代码:
function wayPort() {
this.data = this.data()
}
wayPort.prototype.data = function () {
return {
sex: '男',
age: 18,
}
}
var a = new wayPort();
var b = new wayPort();
a.data.sex = '女';
b.data.age = '23';
console.log(a.data, b.data)
此时我们再看控制台的输出不难发现,a
、b
之间的 data
相互独立,a
的 age
和 sex
值分别是 18
和 女 ,b
的 age
和 sex 值分别是 23
和 男 ,两者之间没有相互影响。
其实看到这里,也就很好解释为何组件中的 data
必须是一个函数了。
总结
如果 data
是一个函数的话,这样每复用一次组件,就会返回一份新的 data
,类似于给每个组件实例创建一个私有的数据空间,让各个组件实例维护各自的数据。而单纯的写成对象形式,就使得所有组件实例共用了一份 data
,就会造成一个变了全都会变的结果。所以说 vue
组件的 data
必须是一个函数。
7. vue2.0和vue3.0的区别?
1. 数据绑定
vue2
的双向数据绑定是利用 es5
的一个 API Object.defineProperty()
对数据进行劫持、结合、发布订阅模式的方式来实现的;
vue3
中使用了 es6
的 ProxyAPI
对数据代理实现的。相比与 vue2
,使用 ProxyAPI
优势有:defineProperty
只能监听某个属性,不能对全对象进行监听;可以省去 for in
、闭包等内容来提升效率(直接绑定整个对象即可);可以监听数组,不用再去单独的对数组做特异性操作,vue3
可以检测到数组内部数据的变化。
2. 默认懒观察
vue2
中,不管数据多大,都会在一开始就为其创建观察者。当数据很大时,这可能会在页面载入时造成明显的性能压力;
vue3
中,只会对「被用于渲染初始可见部分的数据」创建观察者,而且 vue3
的观察者更高效。
3. 更精准的变更通知
vue2
中,使用 vue.set
来给对象新增一个属性时,这个对象的所有 watcher
都会重新运行;
vue3
中,只有依赖那个属性的 watcher
才会重新运行。
4. 组件变化
vue2
与 vue3
最大的区别是 vue2
使用选项类型 API(OptionsAPI)
对比 vue3
合成型 API(Composition API)
。旧的选项型 api
在代码里分割了不同的属性:data
、computed
、methods
等;新的合成型 api
能让我们使用方法来分割,相比于旧的 api
使用属性来分组,这样代码会更加简便和整洁。
5. 生命周期
在 vue2
中我们可以直接在组件属性中调用 vue
的生命周期的钩子;
而 vue3
生周期钩子不是全局可调用的了,需要另外从 vue
中引入,在 setup()
方法里面使用 onMounted
挂载的钩子。
vue2 -> vue3
beforeCreate -> setup()
created -> setup()
beforeMount -> onBeforeMount
mounted -> onMounted
beforeUpdate -> onBeforeUpdate
updated -> onUpdated
beforeDestroyed -> onBeforeUnmount
destroyed -> onUnmounted
activated -> onActivated
deactivated -> onDeactivated
6. 根节点
在 vue2
中只能有一个根节点,在根节点里面再去添加其他节点;
而 vue3
是允许有多个根节点的,即 template
里面可以同时写多个节点并列,在给组件上编写 class
名时,由于组件中可以有多个根节点,那么需要定义哪部分将接收这个 class
。可以使用 $attrs
组件 property
执行此操作。
7. 建立
data
数据
vue2
是把数据直接放入 data
中使用即可;
vue3
就需要使用一个新的 setup()
方法,此方法在组件初始化构造的时候触发。使用步骤如下:
- 从
vue
引入reactive
; - 使用
reactive()
方法来声明数据为响应性数据; - 使用
setup()
方法来返回我们的响应性数据,从而template
可以获取这些响应性数据。
4.
v-if
和v-for
的优先级
-
vue2
<!-- v-for 优先于 v-if 生效 --> <div v-if="code == 1" v-for="(item,index) in list" :key="index">{{item}}</div
-
vue3
<!-- v-if 优先于 v-for 生效 --> <div v-if="code == 1" v-for="(item,index) in list" :key="index">{{item}}</div>
温馨提示:
我们最好不要把 v-if
和 v-for
同时用在一个元素上,这样会带来性能的浪费(每次都要先渲染才会进行条件判断)。
8. 虚拟dom和diff算法?
真实
dom
渲染
- 构建
dom
树。通过html parser
解析处理html
标记,将它们构建为dom
树,当解析器遇到非阻塞资源(图片,样式),会继续解析,但是如果遇到script
标签(特别是没有dasync
和defer
属性,会阻塞渲染并停止html
的解析,这就是为啥最好把script
标签放在body
下面的原因。 - 构建
cssDom
树。与构建dom
类似,浏览器也会将样式规则,构建成cssDom
。浏览器会遍历css
中的规则集,根据css
选择器创建具有父子,兄弟等关系的节点树。 - 构建
render
树。这一步将dom
和cssDom
关联,确定每个dom
元素应该应用什么css
规则。将所有相关样式匹配到dom
树中的每个可见节点,并根据css
级联确定每个节点的计算样式,不可见节点(head
,属性包括display:none
的节点)不会生成到render
树中。 - 布局/回流(
Layout/Reflow
)。浏览器第一次确定节点的位置以及大小叫布局,如果后续节点位置以及大小发生变化,这一步触发布局调整,也就是回流。 - 绘制/重绘(
Paint/Repaint
)。将元素的每个可视部分绘制到屏幕上,包括文本、颜色、边框、阴影和替换的元素(如按钮和图像)。如果文本、颜色、边框、阴影等这些元素发生变化时,会触发重绘(Repaint
)。为了确保重绘的速度比初始绘制的速度更快,屏幕上的绘图通常被分解成数层。将内容提升到GPU
层(可以通过tranform
、filter
、will-change
、opacity
触发)可以提高绘制以及重绘的性能。 - 合成(
Compositing
)。这一步将绘制过程中的分层合并,确保它们以正确的顺序绘制到屏幕上显示正确的内容。
虚拟
dom
虚拟 DOM(Virtual Dom)
也就是我们常说的虚拟节点,是用 js
对象来模拟真实 Dom
中的节点,该对象包含了真实 Dom
的结构及其属性,用于对比虚拟 Dom
和真实 Dom
的差异,从而进行局部渲染来达到优化性能的目的。
- 通过
js
建立节点描述对象; diff
算法比较分析新旧两个虚拟DOM
差异;- 将差异
patch
到真实dom
上实现更新。
diff
算法
diff
算法就是在虚拟 Dom
中,在 Dom
的状态发生变化时,虚拟 Dom
会进行 diff
运算来更新只需要被替换的 Dom
,而不是全部重绘。在 diff
算法中,只平层的比较前后两棵 Dom
树的节点,没有进行深度的遍历。
- 创建新节点;
- 删除废节点;
- 更新已有节点。
9. vue常用指令?
为什么要用指令?
其实指令的最大作用是当表达式的值改变时,相应地将某些行为应用到 dom
上。
常用
vue
指令
1. v-model
数据双向绑定。在表单控件或着组件上创建双向绑定。
<template>
<div>
<input v-model="inputModel" />
{{inputModel}}
</div>
</template>
<script>
export default {
data() {
return {
inputModel: "",
};
},
};
</script>
2. v-if
根据表达式值的真假条件渲染元素,表达式返回 true
值的时候被渲染。
<template>
<div>
<p v-if="code == 0">hello!</p>
</div>
</template>
<script>
export default {
data() {
return {
code: 0,
};
},
};
</script>
3. v-else
必须和 v-if
一起使用 不能单独使用,而且必须在 v-if
下面,中间有别的标签也会报错。
<template>
<div>
<p v-if="codeType>1">第一种</p>
<p v-else>第二种</p>
</div>
</template>
<script>
export default {
data() {
return {
codeType: 0,
};
},
};
</script>
4. v-else-if
当 v-if
、v-else
两个指令无法满足多条件的业务需求,可以使用 v-else-if
增加多种情况的判断。
<template>
<div>
<p v-if="codeType>=90">优秀</p>
<p v-else-if="codeType>=60">及格</p>
<p v-else>不及格</p>
</div>
</template>
<script>
export default {
data() {
return {
codeType: 20,
};
},
};
</script>
5. v-show
判断某个元素是否显示或隐藏。
<template>
<div>
<p v-show="code == 0">hello!</p>
</div>
</template>
<script>
export default {
data() {
return {
code: 0,
};
},
};
</script>
6. v-for
循环,根据遍历数组来进行循环渲染元素。
<template>
<div>
<p v-for="(item,index) in list" :key="index">{{item}}</p>
</div>
</template>
<script>
export default {
data() {
return {
list: ["1", "2", "3", "4", "5"],
};
},
};
</script>
7. v-bind
用于动态的绑定数据和元素属性的。
<template>
<div>
<img v-bind:title="txtModel" />
<input type="button" value="修改状态" @click="changeOn" />
</div>
</template>
<script>
export default {
data() {
return {
txtModel: "默认",
};
},
methods: {
changeOn() {
this.txtModel = "修改";
},
},
};
</script>
8. v-text
更新元素的 content text
。用于将数据填充到标签中,作用于插值表达式类似,但是没有闪动问题 (如果数据中有 html
标签会将 html
标签一并输出 )
注意: 此处为单向绑定,数据对象上的值改变,插值会发生变化;但是当插值发生变化并不会影响数据对象的值。
<template>
<div>
<div v-text="message"></div>
</div>
</template>
<script>
export default {
data() {
return {
message: "Hello Word!",
};
},
};
</script>
9. v-html
后台返回给我们一段原生的 html
代码,需要渲染出来,如果直接通过 {{}}
渲染,会把这个 html
代码当做字符串,这时候我们就可以通过 v-html
指令来实现。
<template>
<div>
<div v-html="item"></div>
</div>
</template>
<script>
export default {
data() {
return {
item: "<h1>Hello Word!</h1>",
};
},
};
</script>
10. v-on
绑定事件监听器,给元素进行事件绑定。
<template>
<div>
<button v-on:click="myclick">点击我</button>
</div>
</template>
<script>
export default {
methods: {
myclick() {
console.log("111111");
},
},
};
</script>
11. v-once
能执行一次性地插值,当数据改变时,插值处的内容不会更新。但会影响到该节点上的其它数据绑定。
<template>
<div>
<p v-once>{{msg}}</p>
<p>{{msg}}</p>
<input type="text" v-model="msg">
</div>
</template>
<script>
export default {
data() {
return {
msg: '原始值',
};
},
};
</script>
10. computed、methods、watch的区别?
computed
computed
属性的结果会被缓存,只有当依赖的值改变才会重新计算,主要当作属性来使用。当页面中有某些数据依赖其他数据进行变动的时候,可以使用计算属性。
<template>
<div></div>
</template>
<script>
export default {
data() {
return {
num1: 5,
num2: 10,
nums: 15,
};
},
computed: {
addNum: function () {
return Number(this.nums) + Number(this.num1) + Number(this.num2);
},
},
mounted() {
console.log(this.addNum);// 30
},
};
</script>
methods
methods
是一个事件方法,调用一次,执行一次,结果不会缓存。
<template>
<div>
<el-button @click="addOn">点击</el-button>
</div>
</template>
<script>
export default {
data() {
return {
count: 0,
};
},
methods: {
addOn() {
this.count++;
console.log(this.count);
},
},
};
</script>
watch
watch
一个对象,键是需要观察的表达式,值是对应回调函数。主要用于观察和监听页面上的 vue
实例,它是一个监听属性,一个值的改变,需要另一个值的改变而改变,结果不会缓存。
<template>
<div>
<el-input v-model="inputValue"></el-input>
</div>
</template>
<script>
export default {
data() {
return {
inputValue: "yuanshi",
};
},
watch: {
inputValue: "methOn",
},
methods: {
methOn() {
console.log(111);
},
},
};
</script>
computed
和methods
对比
-
属性调用
computed
定义的方法我们是以属性访问的形式调用。addOn:{}
methods
定义的方法,我们必须要加上 () 来调用。addOn(){}
-
缓存功能
computed
会缓存,只有当计算属性所依赖的属性发生改变时,才会重新去计算。
methods
不会被缓存,方法每次都会去重新计算结果。
11. vuex?
vuex
是什么?
用官方的话来说,vuex
是一个专为 vue.js
应用程序开发的状态管理模式 + 库。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
什么是状态管理?
状态就是数据。状态管理可以简单理解为把需要多个组件共享的变量全部存储在一个对象里面。然后将这个对象放在顶层的 vue
实例中,让其他组件可以使用。通过使用 vuex
可集中管理组件之间的数据(状态),使组件之间的数据共享变得简单。
vuex
核心
1. state
状态,用户界面会随着 state
变化而变化。
2. getter
计算属性。对 state
数据进行计算(会被缓存),类似于 vue
组件中的 computed
,对 state
里的数据进行一些过滤、改造等等,例如我们要在 state.count
的基础上派生出一个新的状态 newCount
出来,就适合使用我们的 getter
属性。
3. mutation
改变状态,唯一可修改 state
数据的场所。
4. action
异步操作,提交 mutation
执行异步操作(不是直接变更状态)。主要是用来解决异步流程来改变 state
数据。因为 mutation
是同步操作,如果你在 mutation
里进行异步操作,你会发现并不会起任何效果,故只能通过 action=>mutation=>state
流程修改。
5. module
模块化管理 state
(仓库),让每个模块拥有自己的 state
、mutation
、action
、getter
。
vuex
辅助函数(语法糖)
辅助函数可以把 vuex
中的数据和方法映射到 vue
组件中。达到简化操作的目的。
1. mapState(state)
<template>
<div>
<!-- 直接使用 -->
{{name}}
{{age}}
{{count}}
</div>
</template>
<script>
import { mapState } from "vuex"; //从vuex中按需导入mapstate函数
export default {
// 辅助函数使用
computed: {
...mapState(["name", "age", "count"]),
},
};
</script>
2. mapMutations(mutation)
<template>
<div>
<el-button @click="addOn">加</el-button>
<el-button @click="minusOn">减</el-button>
</div>
</template>
<script>
import { mapMutations } from "vuex"; //从vuex中按需导入mapMutations函数
export default {
methods: {
...mapMutations(["addWay", "minusWay"]),
addOn() {
this.addWay(1); //直接调用
},
minusOn() {
this.minusWay();// 直接调用
},
},
};
</script>
3. mapActions(action)
<template>
<div>
<el-button @click="minusOn">减</el-button>
</div>
</template>
<script>
import { mapActions } from "vuex"; //从vuex中按需导入mapActions函数
export default {
methods: {
...mapActions(["asyncAdd"]),
minusOn() {
this.asyncAdd(); // 直接调用
},
},
};
</script>
4. mapGetters(getter)
<template>
<div></div>
</template>
<script>
import { mapGetters } from "vuex"; //从vuex中按需导入mapGetters函数
export default {
computed: {
...mapGetters(["newCount"]),
},
mounted() {
console.log(this.newCount);// 30
},
};
</script>
12. v-if/v-show区别?
共同的点
v-if
指令与 v-show
指令都可以动态控制 dom
元素的显示与隐藏。
不同的点
dom
元素
v-if
控制dom
元素的显示隐藏是将dom
元素整个添加或删除;
v-show
控制dom
的显示隐藏是为dom
元素添加css
的样式display
,设置none
或者是block
,dom
元素还是存在的。- 性能
v-if
指令有更高的切换消耗;
v-show
指令有更高的初始渲染消耗。
13. vue组件通信?
1. 父子传值
在父组件中给子组件标签上绑定一个属性, 属性上挂载需要传递的值,在子组件通过 props:["自定义属性名"]
来接收数据。
- 父组件
index.vue
<template>
<!-- 父组件 -->
<div>
<Child :message="informtion"></Child>
</div>
</template>
<script>
// 引入子组件
import Child from "./subassembly/seed.vue";
export default {
data() {
return {
informtion: "传递给子组件的数据", //要传递给子组件的数据
};
},
//一定要注册组件
components: {
Child,
},
};
</script>
- 子组件
seed.vue
<template>
<!-- 子组件 -->
<h2>我是子组件<br />接收父组件值:{{ value }}</h2>
</template>
<script>
export default {
data() {
return {
value: "",//接收的值
};
},
props: {
// message用于接收
message: {
type: String, //验证类型,也可以验证其他类型
default: "", //如果父组件传值,则用父组件的值渲染,反之使用默认值
},
},
watch: {
message: {
handler(newName, oldName) {
this.value = newName;
},
deep: true, //深度监听
immediate: true, //首次绑定的时候,是否执行 handler
},
},
};
</script>
2. 子父传值
在子组件中自定义一个事件,调用这个事件后,子组件通过 this.$emit('自定义事件名',要传递的数据)
发送父组件可以监听的数据,最后父组件监听子组件事件,调用事件并接收传递过来的数据。
- 子组件
seed.vue
<template>
<!-- 子组件 -->
<button @click="seedOnclick">我是子组件的按钮</button>
</template>
<script>
export default {
data() {
return {
seedCode: "Romantic never die!", //子传父要传递的值
};
},
methods: {
// 子组件事件方法
seedOnclick() {
this.$emit("seed", this.seedCode); //参数1:自定义事件 参数2:要传递的值
},
},
};
</script>
- 父组件
index.vue
<template>
<!-- 父组件 -->
<div>
<Child @seed="seedAccept"></Child>
</div>
</template>
<script>
// 引入子组件
import Child from "./subassembly/seed.vue";
export default {
//一定要注册组件
components: {
Child,
},
methods: {
seedAccept(data) {
console.log(data, "子组件传给父组件的值");
},
},
};
</script>
兄弟传值
在第一个组件中使用 $bus
传递自定义方法或者参数,然后在另一个同级组件中通过 $on
接收传过来的方法和参数。
-
全局注册
- 安装
npm install --save vue-bus cnpm install --save vue-bus
- 引入并注册
// main.js import VueBus from 'vue-bus' Vue.use(VueBus)
- 使用
seed.vue
子组件1<template> <div> <h3>子组件1</h3> <el-button type="primary" size="mini" @click="firstly">点击传值</el-button> </div> </template> <script> export default { data() { return { message: "子组件1定义的变量", }; }, methods: { firstly() { // 传了一个自定义方法 getTargetPointBUS 和参数 this.message this.$bus.$emit("getTargetPointBUS", this.message); }, }, }; </script>
sons.vue
子组件2<template> <div> <h3>子组件2<br />接收子组件1的值:{{ message }}</h3> </div> </template> <script> export default { data() { return { message: "", }; }, mounted() { // 触发自定义方法 getTargetPointBUS,同时触发自身的方法 getTargetPoint this.$bus.on("getTargetPointBUS", (data) => { console.log(data, "子组件1传的值"); this.message = data; this.getTargetPoint(data); }); }, //组件销毁接触事件绑定 destroyed: function () { this.$bus.off("getTargetPointBUS"); }, methods: { // 触发方法 getTargetPoint(data) { console.log("触发子组件2方法"); }, }, }; </script>
index.vue
父组件<template> <!-- 父组件 --> <div> <Child></Child> <Electronic></Electronic> </div> </template> <script> // 引入子组件 import Child from "./subassembly/seed.vue"; import Electronic from "./subassembly/sons.vue"; export default { //一定要注册组件 components: { Child, Electronic, }, }; </script>
-
局部注册
- 新建一个
eventBus.js
文件
import Vue from 'vue' const eventBus = new Vue() export default eventBus
- 在使用的组件中引入
import eventBus from "../eventBus";
- 使用
seed.vue
子组件1
<template> <div> <h3>子组件1</h3> <el-button type="primary" size="mini" @click="firstly">点击传值</el-button> </div> </template> <script> import eventBus from "../eventBus"; export default { data() { return { message: "子组件1定义的变量", }; }, methods: { firstly() { // 传了一个自定义方法 getTargetPointBUS 和参数 this.message eventBus.$emit("getTargetPointBUS", this.message); }, }, }; </script>
sons.vue
子组件2<template> <div> <h3>子组件2<br />接收子组件1的值:{{ message }}</h3> </div> </template> <script> import eventBus from "../eventBus"; export default { data() { return { message: "", }; }, mounted() { // 触发自定义方法 getTargetPointBUS,同时触发自身的方法 getTargetPoint eventBus.$on("getTargetPointBUS", (data) => { console.log(data, "子组件1传的值"); this.message = data; this.getTargetPoint(data); }); }, //组件销毁接触事件绑定 destroyed: function () { eventBus.$off("getTargetPointBUS"); }, methods: { // 触发方法 getTargetPoint(data) { console.log("触发子组件2方法"); }, }, }; </script>
index.vue
父组件<template> <!-- 父组件 --> <div> <Child></Child> <Electronic></Electronic> </div> </template> <script> // 引入子组件 import Child from "./subassembly/seed.vue"; import Electronic from "./subassembly/sons.vue"; export default { //一定要注册组件 components: { Child, Electronic, }, }; </script>
- 新建一个
父组件如何调用子组件的方法?
1. $refs
方法
在父组件中,通过 ref
直接调用子组件的方法。
- 父组件
<template>
<!-- 父组件 -->
<div>
<button @click="clickOn">我是父组件的按钮</button>
<Child ref="child"></Child>
</div>
</template>
<script>
import Child from "./subassembly/seed.vue";
export default {
data() {
return {};
},
components: {
Child,
},
methods: {
clickOn() {
this.$refs.child.wayOn();
},
},
};
</script>
- 子组件
<template>
<!-- 子组件 -->
<div>
{{num}}
</div>
</template>
<script>
export default {
data() {
return {
num: 0,
};
},
methods: {
wayOn() {
this.num += 5;
console.log("父组件调用");
},
},
};
</script>
2. emit
、on
方法
在父组件中,通过组件的 $emit
、$on
方法来调用。
- 父组件
<template>
<!-- 父组件 -->
<div>
<button @click="clickOn">我是父组件的按钮</button>
<Child ref="child"></Child>
</div>
</template>
<script>
import Child from "./subassembly/seed.vue";
export default {
data() {
return {};
},
components: {
Child,
},
methods: {
clickOn() {
this.$refs.child.$emit("wayOn")
},
},
};
</script>
- 子组件
<template>
<!-- 子组件 -->
<div>
{{num}}
</div>
</template>
<script>
export default {
data() {
return {
num: 0,
};
},
mounted() {
this.$nextTick(() => {
this.$on("wayOn", () => {
this.num += 5;
console.log("父组件调用");
});
});
},
};
</script>
子组件如何调用父组件的方法?
1. $parent
方法
$parent
方法是直接在子组件中通过 this.$parent.event
调用父组件的方法,如下:
- 父组件
<template>
<div>
<!-- 子组件标签 -->
<child></child>
</div>
</template>
<script>
import child from "./dialog/dialog"; //引入子组件
export default {
components: {
// 注册组件
child,
},
methods: {
//父组件方法
parentMethod() {
console.log("子组件点击后触发此方法");
},
},
};
</script>
- 子组件
<template>
<div>
<button @click="childOnclick()">子组件点击事件</button>
</div>
</template>
<script>
export default {
methods: {
// 子组件点击事件
childOnclick() {
// 通过$parent方法调用父组件方法
this.$parent.parentMethod();
},
},
};
</script>
2. $emit
方法
$emit
方法是在子组件里用 $emit
向父组件触发一个事件,父组件监听这个事件即可,如下:
- 父组件
<template>
<div>
<!-- 子组件标签 -->
<child @fatherMethod="parentMethod"></child>
</div>
</template>
<script>
import child from "./dialog/dialog"; //引入子组件
export default {
components: {
// 注册组件
child,
},
methods: {
//父组件方法
parentMethod() {
console.log("子组件点击后触发此方法");
},
},
};
</script>
- 子组件
<template>
<div>
<button @click="childOnclick()">子组件点击事件</button>
</div>
</template>
<script>
export default {
methods: {
// 子组件点击事件
childOnclick() {
// 通过$emit方法调用父组件方法
this.$emit("fatherMethod");
},
},
};
</script>
3. 将方法传入子组件中
简单来说就是在父组件把方法传入子组件内,然后再在子组件中调用这个方法,如下:
- 父组件
<template>
<div>
<!-- fatherMethod 传递的方法-->
<child :fatherMethod="parentMethod"></child>
</div>
</template>
<script>
import child from "./dialog/dialog"; //引入子组件
export default {
components: {
// 注册组件
child,
},
methods: {
//父组件方法
parentMethod() {
console.log("子组件点击后触发此方法");
},
},
};
</script>
- 子组件
<template>
<div>
<button @click="childOnclick()">子组件点击事件</button>
</div>
</template>
<script>
export default {
props: {
fatherMethod: {
type: Function, //验证类型,也可以验证其他类型
default: null, //如果父组件传值,则用父组件的值渲染,反之使用默认值
},
},
methods: {
// 子组件点击事件
childOnclick() {
if (this.fatherMethod) {
this.fatherMethod();
}
},
},
};
</script>
14. mvc跟mvvm?
MVC
后端的开发思想——单向通信
MVC
: 是后台的框架模式,MVC
模式的意思是:软件可以分成三个部分。
M
:(model
模型) 是应用程序中用于处理应用程序数据逻辑的部分,通常模型对象负责在数据库中存取数据;
V
:(view
视图) 是应用程序中处理数据显示的部分,通常视图是依据模型数据创建的;
C
:(controller
控制器) 是应用程序中处理用户交互的部分,通常控制器负责从视图读取数据,控制用户输入,并向模型发送数据。
各部分之间的通信方式如下:
view
传送指令到 controller
;controller
完成业务逻辑后,要求 model
改变状态;model
将新的数据发送到 view
,用户得到反馈。
MVVM
前端的开发思想——双向绑定
MVVM
: 是为了实现 mvc
中的 v
,它采用双向绑定,view
的变动,自动反映在 viewModel
。
MVVM
: m
(model
数据) v
(view
视图) vm
(viewModel
控制数据的改变和控制视图)。
优点
耦合低,view
和 model
完全分离;
维护性高,易维护,上手快;
双向绑定,视图发生变化数据自动同步,数据发生变化视图也自动同步;
减少了 dom
的操作,可以更多的关注业务逻辑。
mvc
和mvvm
的区别?
mvvm
各部分的通信是双向的,而 mvc
各部分通信是单向的;
mvvm
是真正将页面与数据逻辑分离放到 js
里去实现,而 mvc
里面未分离。
15. vue生命周期?
beforeCreate
组件实例创建前,el
和data
并没有初始化;created
组件实例创建后,完成了data
数据的初始化;beforeMount
,dom
挂载前,render
函数首次被调用;mounted
,dom
挂载后,模块中的html
渲染到html
页面;beforeUpdate
数据更新前,发生在虚拟dom
重新渲染之前;updated
数据更新后虚拟dom
重新渲染只会调用,组件dom
已经更新;beforeDestroy
在实例销毁之前调用,实例仍然可用;destroy
组件销毁后,所有的事件监听器会被移出。
初次加载会触发哪些钩子函数?
beforeCreate
created
beforeMount
mounted
dom
渲染在哪个周期完成?
mounted
生命周期的使用场景?
beforeCreate:可以在这里加loading事件,在加载实例时触发;
created: 进行ajax请求异步数据的获取、初始化数据(异步请求);
mounted: 挂载元素,获取到dom节点;
updated: 任何数据的更新,如果对数据统一处理,在这里写上相应的函数;
beforeDestroy:清空定时器等。
16. vue双向数据绑定?
什么是双向数据绑定?
vue
实现双向数据绑定 主要采用"数据劫持结合"、"发布-订阅"模式的方式,通过 Object.defineProperty
的 set
和 get
,在数据变动时发布消息给订阅者触发监听。
怎么实现一个双向数据绑定?
第一步
需要 observe
的数据对象进行递归遍历,包括子属性对象的属性,都加上 setter
和 geter
,这样的话,给这个对象的某个值赋值,就会触发 setter
,那么就能监听到了数据变化。
第二步
compile
解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图。
第三步
watcher
订阅者是 observe
和 compile
之间通信的桥梁。
第四步
MVVM
作为数据绑定的入口,整合 observe
、compile
和 watcher
三者,通过 observe
来监听自己的 model
数据变化,通过 compile
来解析编译模板指令,最终利用 watcher
搭起 observe
和 compile
之间的通信桥梁,达到数据变化,视图更新;视图交互变化,数据 model
变更的双向绑定效果。
四、浏览器相关面试题
1. Doctype的作用?
什么是
DOCTYPE
?
doctype
标签是一种标准通用标记语言的文档类型声明。
DOCTYPE
的作用?
doctype
标签的作用是告诉浏览器的解析器用什么文档类型规范来解析这个文档。
严格模式和混杂模式区别?
说到 doctype
标签又不得不提一下严格模式与混杂模式。严格模式是浏览器根据 web
标准去解析页面,是一种要求严格的 DTD
,不允许使用任何表现层的语法;在混杂模式中,页面以宽松的向后兼容的方式展示。模拟老式浏览器的行为以防止站点无法工作。
存在的意义?
其实严格模式和混杂模式存在的意义也是为了让 html5
之前版本的文档能和 DTD
声明的标准对应,从而正确的解析;也为了兼容向后兼容,防止一些站点无法工作。
2. 从输入URL到浏览器显示页面发生了什么?
1. 地址栏输入
url
地址
用户在地址栏中输入 url
地址,例:
https://www.baidu.com/
2.
dns
解析域名
浏览器会先检查本地是否有对应的 ip
地址,若找到则返回响应的 ip
地址。若没找到则请求上级 dns
服务器,直至找到或到根节点。
3. 浏览器与服务器建立
tcp
连接
解析出 ip
地址后,根据 ip
地址和默认 80
端口,和服务器建立 tcp
连接。
4. 浏览器向服务器发送
http
请求
浏览器发起读取文件的 http
请求,该请求报文作为 tcp
三次握手的第三次数据发送给服务器。
5. 服务器接收请求并响应,返回
html
文件
服务器响应请求并返回结果,并把对应的 html
文件发送给浏览器。关闭 tcp
连接,通过四次挥手释放 tcp
连接。
6. 浏览器接收服务器返回的数据并渲染
浏览器解析 html
内容并渲染页面。
3. 常见的http状态码有哪些?
什么是
HTTP
状态码
HTTP
状态码是服务器返回给客户端的,其核心作用是 web
服务器来告诉客户端,当前网页发生的什么事,或者说当前 web
服务器的响应状态。所以 HTTP
状态码常用来判断和分析当前 web
服务器的运行状况。
分类 | 描述 |
---|---|
1xx | 信息。服务器收到请求,需要请求者继续执行操作 |
2xx | 成功。操作被成功接收并处理 |
3xx | 重定向。需要进一步操作以完成请求 |
4xx | 客户端错误。请求包含语法错误或无法完成请求 |
5xx | 服务器错误。服务器处理请求过程发生了错误 |
4. localStorage、sessionStorage、cookies的区别?
localStorage
localStorage
生命周期是永久,存放数据大小为一般为 5MB
,不参与和服务器的通信,不能跨浏览器使用。应用场景:历史记录、登录。
sessionStorage
sessionStorage
仅在当前会话下有效,关闭页面或浏览器后被清除。存放数据大小为一般为 5MB
,不参与和服务器的通信,不能跨浏览器使用。应用场景:页面之间的传值、敏感账号一次性登录。
cookies
cookies
数据存放在客户的浏览器上,单个 cookies
保存的数据不能超过 4K
;具有极高的扩展性和可用性,不能跨浏览器使用。应用场景:判断用户是否登陆过网站、保存上次登录的时间等信息、浏览计数。
共同点
localStorage
、sessionStorage
、cookies
这三者都可以被用来在浏览器端存储数据,而且都是字符串类型的键值对。
区别
- 存储大小不同
localStorage:5MB左右
sessionStorage:5MB左右
cookies:不超过4K
- 数据有效期不同
localStorage:始终有效
sessionStorage:仅在当前浏览器窗口关闭前有效
cookies:设置的cookie过期时间之前一直有效,即使窗口或浏览器关闭
- 作用域不同
localStorage:在所有同源窗口中都是共享的
sessionStorage:不在不同的浏览器窗口中共享,即使是同一个页面
cookies:在所有同源窗口中都是共享的
5. http的几种请求方法用途?
http
协议的概念
Hyper Text Transfer Protocol
超文本传输协议。
http
协议的作用
http
最大的作用就是确定了请求和响应数据的格式。浏览器发送给服务器的数据:请求报文;服务器返回给浏览器的数据:响应报文。
http
协议的组成
http
协议由请求和响应两部分构成,请求是由客户端往服务器传输数据,响应是由服务器往客户端传输数据。
http
常见的请求方式
1. get
方法
发送一个请求来取得服务器上的某一资源。
-
特点:
没有请求体; 参数拼接在 URL 地址后面; 参数在浏览器地址栏能够直接被看到; 在 URL 地址后面携带请求参数,数据容量非常有限,数据量大时容易丢失。
2. post
方法
向 url
指定的资源提交数据或附加新的数据。
-
特点:
有请求体; 参数放在请求体中; 请求体发送数据的大小没有限制; 可以发送各种不同类型的数据; 由于请求体参数是放在请求体中,所以浏览器地址栏看不到。
3. put
方法
put
方法跟 post
方法很像,也是向服务器提交数据,但是 put
方法指向了资源在服务器上的位置,而 post
方法没有。
4. head
方法
只请求页面的首部。
5. delete
方法
删除服务器上的某资源。
6. options
方法
options
方法用于获取当前 url
所支持的方法。如果请求成功,会有一个 allow
的头包含类似 get
、post
这样的信息。
7. trace
方法
trace
方法被用于激发一个远程的,应用层的请求消息回路。
8. connect
方法
把请求连接转换到透明的 tcp/ip
通道。
6. 说一下你对浏览器内核的理解?
概念
浏览器内核主要分成两个部分:渲染引擎和 js
引擎。
1. 渲染引擎
负责取得网页的内容,如:html
、xml
、图像 等,整理讯息,如:css
,以及计算网页的显示方式,然后输出到显示器或打印机。浏览器的内核的不同对于网页的语法解释会有不同,所以渲染的效果也不同。所有网页浏览器、电子邮件客户端以及它需要编辑、显示网络内容的应用程序都需要内核。
2. js
引擎
解析和执行 javaScript
来实现网页的动态效果。
常见的浏览器内核
1. ie
浏览器
trident
内核,也是俗称的 ie
内核。
2. chrome
浏览器
统称为 chromium
内核或 chrome
内核,以前是 webkit
内核,现在是 blink
内核。
3. firebox
浏览器
gecko
内核,俗称 firebox
内核。
4. safari
浏览器
webkit
内核。
5. opera
浏览器
最初是自己的 presto
内核,后来是 webkit
,现在是 blink
内核。
7. 浏览器重绘和重排的区别?
重绘
重绘是一个元素外观的改变导致的浏览器行为,浏览器会根据元素新的属性呈现新的外观。
-
常见的触发重绘的操作:
background-color(背景色属性) border-color(边框色属性) visibility(可见属性) outline(轮廓属性)
重排
重排是 dom
元素被 js
触发某种变化,渲染树需要重新计算。浏览器对 dom
树进行重新排列。
-
常见的触发重排的操作:
页面初始渲染 dom 操作(元素添加、删除、修改或者元素顺序的改变) 元素位置、尺寸变化(更改类的属性),元素内容发生变化(如图片、文本) 浏览器窗口动作(拖拽、拉伸等)
重绘不一定需要重排,重排必然会导致重绘。重绘重排的代价就是会造成加载缓慢,耗时严重,从而导致浏览器卡顿。
怎么减少重排?
- 将多次改变样式属性的操作,合成一次操作;
- 减少直接操作
dom
元素,改用类名用于控制; - 尽量减少
table
使用,table
属性变化会直接导致布局重排或者重绘; - 不要把
dom
节点的属性值放在一个循环里当成循环里的变量; - 在需要经常获取那些引起浏览器重排的属性值时,要缓存到变量。
8. 什么是跨域?跨域的解决方案?
什么是跨域?
跨域,简单来说就是指一个域名去请求另外一个域名的资源,即跨域名请求,而跨域时,浏览器不能执行其他域名网站的脚本,是由浏览器的同源策略造成的,是浏览器施加的安全限制。跨域严格一点来说就是只要协议,域名,端口有任何一个的不同,就被当作是跨域。
同源策略是什么?
同源策略简单来说就是指 “协议+域名+端口” 三者相同,即便两个不同的域名指向同一个 ip
地址,也非同源。
为什么要跨域?
现实工作开发中经常会有跨域的情况,因为公司会有很多项目,也会有很多子域名,各个项目或者网站之间需要相互调用对方的资源,避免不了跨域请求。
解决跨域的方案?
1. jsonp
原理
利用 script
元素的开放策略,网页可以得到从其他来源动态产生的 json
数据。但是 jsonp
请求一定需要对方的服务器做支持才可以。
-
优点
兼容性好,可用于解决主流浏览器的跨域数据访问的问题。 -
缺点
仅支持get
方法,具有局限性
2. cors
原理
实现 cors
通信的关键是服务器,需要在服务器端做改造,只要服务器实现了 cors
接口,就可以跨源通信。
3. njinx
浏览器在访问受限时,可通过不受限的代理服务器访问目标站点。proxy
代理是前端用的最多的解决跨域的方法。通过配置文件设置请求响应头 Access-Control-Allow-Origin
等字段。
9. 浏览器的垃圾回收机制?
垃圾回收的方式
浏览器通常使用的垃圾回收方法有两种:标记清除,引用计数。
标记清除
当变量进入环境(例如,在函数中声明一个变量)时,将这个变量标记为 “进入环境” 。从逻辑上讲,永远不能释放进入环境的变量所占用的内存,因为我们在这个环境中可能随时会用到它们。当变量离开环境时,则将其标记为 “离开环境”。
function test() {
var a = 10; //被标记"进入环境"
var b = "hello"; //被标记"进入环境"
}
test(); //执行完毕后之后,a和b又被标记"离开环境",被回收
引用计数
引用计数就是追踪每个值被引用的次数,当引用计数为 0 时,就会回收他所占用的内存。
function test() {
var a = {}; //a的引用次数为0
var b = a; //a的引用次数+1,为1
var c = a; //a的引用次数再+1,为2
var b = {}; //a的引用次数-1,为1
}
但是使用引用计数会有一点小问题,就是在循环引用的对象时,这些对象的引用计数永远都不可能是 1
,所以回收不了这些对象,就会有内存泄漏。
哪些操作会造成内存泄漏
- 不合理的使用闭包,从而导致某些变量一直被留在内存当中;
- 设置了
setInterval
定时器,而忘记取消它,如果循环函数有对外部变量的引用的话,那么这个变量会被一直留在内存中,而无法回收; - 获取一个
dom
元素的引用,而后面这个元素被删除,由于我们一直保留了对这个元素的引用,所以它无法被回收; - 由于使用未声明的变量,而意外的创建了一个全局变量,而使这个变量一直留在内存中无法被回收。
10. 为什么JavaScript是单线程?
什么是进程?
进程简单来说就是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。
什么是线程?
线程简单来说就是操作系统能够进行运算调度的最小单位,被包含在进程之中,是进程中的实际运作单位。
JavaScript
为什么要设计成单线程?
作为浏览器脚本语言,JavaScript
的主要用途是与用户互动,以及操作 dom
。这也就决定了它只能是单线程,否则会带来很复杂的同步问题。假如 JavaScript
同时有两个线程,一个线程在某个 dom
节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?所以,为了避免复杂性,从诞生起,JavaScript
就是单线程。
持续更新中...