【干货分享】一篇文章帮你搞定前端高频面试题

前言

如今前端技术日新月异。对于前端开发人员来说,不仅需要掌握最新的前沿技术,还需要保持对基础知识的熟练掌握。而面试则是进入优秀企业的必经之路。在面试中,高频面试题的掌握是获得成功的关键。本文将为大家总结前端高频面试题及其答案,希望能够帮助读者更好地备战面试,也让你在前端领域取得更大的成功。


一、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 (绝对定位)

topbottomleftright 的值都设置为 0,使绝对定位相对整个页面定位,同时 marginauto,完成上下左右外边距等分即可。

<!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>

如何触发两种盒模型?

如果想要切换盒模型也很简单,只需要借助 css3box-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 有什么特点?

  1. BFC 内部块元素在垂直方向,按序放置;
  2. 如果两个块级元素属于同一个 BFC,它们的上下外边距就会重叠,以较大的为准。但是如果两个块级元素分别在不同的 BFC 中,它们的上下边距就不会重叠了,而是两者之和;
  3. BFC 的区域不会与浮动的元素区域重叠,也就是说不会与浮动盒子产生交集,而是紧贴浮动边缘;
  4. 计算 BFC 的高度时,浮动元素也参与计算。BFC 可以包含浮动元素;
  5. BFC 是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面的元素。

如何创建一个 BFC

  • float 的值不是 none
  • position 的值不是 static 或者 relative
  • display 的值是 inline-blocktable-cellflextable-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 标记语言,用于开发网页使用。HTMLweb 应用中一种”超文本标记语言(HTML)“的第五次重大修改,我们将这次修改后的 HTML 标准,称之为"HTML5"。

  • CSS3
    CSS3CSS(层叠样式表)技术的升级版本,于 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. 新增音频、视频标签(audiovideo

<!-- 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 属性向元素应用 2D3D 转换。该属性允许我们对元素进行旋转、缩放、移动或倾斜。就是利用 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-leftborder-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. 脱离标准流;
  2. 浮动的元素会互相贴靠;
  3. 浮动的元素有“字围”效果;
  4. 一个浮动的元素,如果没有设置宽度,那么将自动收缩为内容的宽度。

为什么要浮动?

  • 未浮动

在这里插入图片描述


  • 浮动

在这里插入图片描述

浮动的效果显而易见,由此可见浮动可以改变元素标签默认的排列方式,让多个块级元素一行内排列显示,控制多个盒子之间的间隙

为什么要清除浮动?

其实清除浮动并不是清除已经浮动的元素,当子元素浮动后,会完全脱离文档流,清除浮动就是为了解决由于浮动而产生的父元素高度塌陷的问题。

清除浮动的方式?

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,再将 topleft 的值设置足够大的负数值即可实现将元素隐藏的效果。

position: absolute;
top: -999px;
left: -999px;

5. hidden 属性(HTML5新增)

该方法直接在标签内设置 hidden 的值为 hidden 即可隐藏元素,通过 hidden 属性将元素隐藏后和 display:none 一样,元素会在页面上消失,元素本来占有的空间就会被其他元素占有,从而会导致浏览器的重排和重绘。

<div hidden="hidden">123</div>

11. WEB标准以及W3C标准是什么?

WEB 标准

WEB 标准不是一个标准,它是由 W3C 与其他标准化组织指定的一系列标准的集合,主要包括结构、表现和行为三个方面。

W3C 标准

W3C(万维网联盟) 标准,即一系列标准的集合,他的本质是结构标准语言。就像平时使用的 HTMLCSS 等都需要遵守这些标准。

WEB 标准的好处?

  • 提高兼容性。对于浏览器开发商和 web 程序开发人员在开发新的应用程序时遵守指定的标准更有利于 web 更好地发展;
  • 提高开发效率。 开发人员按照 web 标准制作网页,这样对于开发者来说就更加简单了,因为他们可以很容易了解彼此的编码;
  • 跨平台。使用 web 标准,将确保所有浏览器正确显示您的网站而无需费时重写;
  • 加快网页解析速度。遵守标准的 web 页面可以使得搜索引擎更容易访问并收入网页,也可以更容易转换为其他格式,并更易于访问程序代码(如 javaScriptdom);
  • 易于维护。页面的样式和布局信息保存在单独的 css 文件中,如果你想改变站点的外观时,仅需要在单独的 css 文件中做出更改即可。

W3C 标准的好处?

  • 提升网站形象。通过 w3c 认证的网站不足 5%,只有极少的网站会通过 w3c 认证。所以通过 w3c 认证,会大大提高网站形象;
  • 有利于提高网站排名。符合 w3c 标准的网页,一般用 css/div 呈现,这使网页原始码简洁,结构化程序更高,易于被搜索机器人检索,收录,这会给网站带来更高排名;
  • 速度更快。因为符合 w3c 标准,网页原始程序简洁,网站页面共享。使得网站大幅度精简,提高浏览速度,使网站显示更快;
  • 维护容易。采取 w3c 标准的网页设计,则只需要改变 css 文件,就能达到全面修改的目的,不必再费力的去修改网页内码。

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

remcss3 新增的一个相对单位,相对根元素的字体大小。使用 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>

linkimport 的区别?

虽然两种方法都是从外部引用 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 其实就是雪碧图,它是网页图片应用处理的一种方式,其原理就是把网页中的背景图片整合在一张图片中,再利用 cssbackground-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 的优点?

  1. 减少网页的 http 请求,提高页面的加载速度;
  2. 减少了图片字节,多张图片合并成 1 张图片的字节小于多张图片的字节总和;
  3. 减少了命名困扰,只需要对一张集合的图片命名,不需要对每一张图片文件进行命名;
  4. 更换风格方便,只需要在一张或少张图片上修改图片的颜色或样式,整个网页的风格就可以改变,维护起来更加方便。

css Sprite 的缺点?

  1. 图片合并时,需要把多张图片有序合理的合并成一张图片,还要留好足够的空间,防止板块内出现不必要的背景,而且还要防止宽屏、高分辨率的屏幕下的自适应页面,如果图片不够宽,很容易出现背景断裂;
  2. 背景设置时,需要得到每一个背景单元的精确位置;
  3. 由于图片的位置需要固定为某个绝对数值,这就失去了诸如 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 的默认值,遵循正常的文档流对象。静态定位的元素不会受到 topbottomleftright 影响。

在这里插入图片描述

<!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 定位在 IE7IE8 下需要描述 !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:relativeposition: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 默认样式都是不一样的,所以最简单有效的兼容⽅式就是对其进⾏初始化操作,将 marginpadding 的值都设置为 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) (取值范围1­100、整数、等号)

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. css中有哪些需要优化的(SEO)?

优化的方法有很多种,可以将其分为两大类,第一类是页面级别的优化,如 HTTP 请求数,内联脚本的位置优化等,第二类为代码级别的优化,例 Javascript 中的 DOM 操作优化、CSS 选择符优化、图片优化以及 HTML 结构优化等等。

css 中常见的优化?

  1. 非装饰性图片必须加 alt
  2. 语义化的 html 代码,符合 w3c 规范,语义化代码让搜索引擎容易理解网页;
  3. 使用 <link> 标记代替 @import 规则;
  4. 使用渐变和 svg 代替图像;
  5. 使用 flexboxcss 网格布局;
  6. 重要内容 html 代码放在最前:搜索引擎抓取 html 顺序是从上到下,有的搜索引擎对抓取长度有限制,保证重要内容肯定被抓取;
  7. 合理的 titledescriptionkeywords,搜索对这三项的权重逐个减小,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 上代理所有 tdclick 事件就非常合适;
  • 可以实现当新增子对象时无需再次对其绑定。

缺点?

如果把所有事件都用事件代理,可能会出现事件误判。

实例

在这里插入图片描述

<!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 表示当前对象的一个引用,但在 javascriptthis 不是固定不变的,它会随着执行环境的改变而改变;在方法中,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>

三者之间的区别?

callapplybind 都可以改变 this 指向;但不同的是 callapply 会调用函数,bind 不会调用函数; callapply 传递的参数不一样,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.definePropertysetget,在数据变动时发布消息给订阅者触发监听。

<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 的优缺点?

  • 优点:
  1. 通过异步模式,提升了用户体验;
  2. 优化了浏览器和服务器之间的传输,减少不必要的数据往返,减少了带宽占用;
  3. ajax 在客户端运行,承担了一部分本来由服务器承担的工作,减少了大用户量下的服务器负载;
  4. ajax 可以实现动态不刷新 ( 局部刷新 )。
  • 缺点:
  1. 安全问题 ajax 暴露了与服务器交互的细节;
  2. 对搜索引擎的支持比较弱;
  3. 不容易调试。

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 种,分别是: numberstringbooleanobjectarrayundefinednull;其中,js 基本数据类型有 5 种,分别是:numberstringbooleanundefinednull

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 时,一定要使用 === ,因为 == 无法分别 nullundefined

nudefined

undefined 表示不存在这个值,是一个表示"无"的原始值或者说表示"缺少值",就是此处应该有一个值,但是还没有定义,当尝试读取时会返回 undefined,例如变量被声明了,但没有赋值时,就等于 undefined

两者之间区别?

  • 共同点:

    都是原始数据类型,保存在栈中变量本地。

  • 不同点:

    1. 类型不同

      console.log(typeOf undefined); //undefined
      console.log(typeOf null); //object
      
    2. 转换值不同

      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 是单线程的。

什么是同步?

同步简单来说就是在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务。

什么是异步?

异步任务是指不进入主线程,而进入任务队列的任务,只有任务队列通知主线程,某个异步任务可以执行了,该任务才会进入主线程。

最基础的异步就是 setTimeoutsetInterval 函数,很常见,但是很少人有人知道其实这就是异步,因为它们可以控制 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>

控制台打印

在这里插入图片描述

宏任务与微任务

异步任务又可以分为微任务和宏任务。

宏任务一般是:scriptsetTimeoutsetIntervalpostMessageMessageChannel

微任务一般是:Promise.thenObject.observeMutationObserver

执行顺序

在代码中,当一个宏任务执行完毕后,会查看当前微任务队列中是否有任务,如果有,则依次执行所有微任务,直到微任务队列为空,然后再执行下一个宏任务。这就是 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 个问题:

  1. 代码冗余;
  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?

什么是 asyncawait

asyncawait 是一种同步的写法,但还是异步的操作,两个内容还是必须同时去写才会生效,await 可以等到你的数据加载过来以后才会去运行下边的 js 内容,而且 await 接收的对象必须还是个 promise 对象。

asyncawait 的特点?

async 作为一个关键字放到函数前面,这样普通函数就变为了异步函数;
异步 async 函数调用,跟普通函数的使用方式一样;异步 async 函数返回一个 promise 对象。

asyncawait 的优点?

  • 方便级联调用:即调用依次发生的场景;
  • 同步代码编写方式: async/await 从上到下,顺序执行,就像写同步代码一样,更符合代码编写习惯;
  • 多个参数传递: promisethen 函数只能传递一个参数,async/await 没有这个限制,可以当做普通的局部变量来处理;
  • 同步代码和异步代码可以一起编写;
  • 解决了回调地狱的问题;
  • 支持并发执行。

asyncawait 使用场景?

执行第一步,将执行第一步的结果返回给第二步使用。拿到一个接口的返回数据,使用第一步返回的数据执行第二步操作的接口调用,达到异步操作。

//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 声明的变量存在变量提升,即变量可以在声明之前调用,值为 undefinedletconst 不存在变量提升,即它们所声明的变量一定要在声明后使用。

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 允许重复声明变量;letconst 在同一作用域不允许重复声明变量。

3. 是否能修改声明的变量

varlet 可以;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. 是否存在暂时性死区

letconst 存在暂存死区,var 不存在暂时性死区。

var a = 100;
if(1){
    
    
    a = 10;
    //在当前块作用域中存在a使用let/const声明的情况下,给a赋值10时,只会在当前作用域找变量a,
    // 而这时,还未到声明时候,所以控制台Error:a is not defined
    let a = 1;
}

5. 是否存在块级作用域

var 不存在块级作用域;letconst 存在块级作用域。

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...inforEach() ,并支持新的迭代协议。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]);

23. 防抖节流函数?

防抖

简单来说就是指触发事件后在 n 秒内函数只能执行一次(输入框搜索自动补全事件、频繁点赞和取消点赞、提交事件等等);

function debounce(func, delay) {
    
    
  let timer;
  return function () {
    
    
    const context = this;
    const args = arguments;
    clearTimeout(timer);
    timer = setTimeout(() => {
    
    
      func.apply(context, args);
    }, delay);
  };
}

这个函数接受两个参数:需要执行的函数和延迟时间。它返回一个新的函数,这个新函数会在延迟时间过后执行传入的函数。

节流

简单来说就是指连续触发事件但是在 n 秒中只执行一次函数(发送验证码、表单验证、鼠标移动事件等等)。

function throttle(func, delay) {
    
    
  let timer;
  return function () {
    
    
    const context = this;
    const args = arguments;
    if (!timer) {
    
    
      timer = setTimeout(() => {
    
    
        func.apply(context, args);
        timer = null;
      }, delay);
    }
  };
}

这个函数接受两个参数:需要执行的函数和延迟时间。它返回一个新的函数,这个新函数会限制函数执行的频率,确保函数不会在短时间内被重复执行。


24. JS数组去重?

1. set

使用 Set 直接将数组转换成 Set,由于 Set 不允许出现重复元素,因此可以实现数组去重。最后再将 Set 转换成数组即可。

const arr = [1, 2, 2, 3, 3, 4, 5, 5];
const uniqueArr = [...new Set(arr)];
console.log(uniqueArr); // [1, 2, 3, 4, 5]

2. filter

使用 filter 方法,将数组中每个元素的第一次出现的位置和当前位置进行比较,如果相同则保留,如果不同则过滤掉。这样就可以实现数组去重。

const arr = [1, 2, 2, 3, 3, 4, 5, 5];
const uniqueArr = arr.filter((item, index) => arr.indexOf(item) === index);
console.log(uniqueArr); // [1, 2, 3, 4, 5]

3. reduce

使用 reduce 方法,遍历数组中的每一个元素,如果当前元素不存在于前面的数组中,则将其添加到数组中,最后返回去重后的数组。

const arr = [1, 2, 2, 3, 3, 4, 5, 5];
const uniqueArr = arr.reduce((prev, cur) => prev.includes(cur) ? prev : [...prev, cur], []);
console.log(uniqueArr); // [1, 2, 3, 4, 5]

4. Map

使用 Map,将数组中每个元素作为 Mapkey,因为 Mapkey 值不能重复,所以可以实现数组去重。最后将 Mapkey 转换成数组即可。

const arr = [1, 2, 2, 3, 3, 4, 5, 5];
const map = new Map();
const uniqueArr = [];
for (let i = 0; i < arr.length; i++) {
    
    
  if (!map.has(arr[i])) {
    
    
    map.set(arr[i], true);
    uniqueArr.push(arr[i]);
  }
}
console.log(uniqueArr); // [1, 2, 3, 4, 5]

5. Object 键值对

使用 Object 键值对,将数组中每个元素作为 Objectkey,因为 Objectkey 值不能重复,所以可以实现数组去重。最后将 Objectkey 转换成数组即可。

const arr = [1, 2, 2, 3, 3, 4, 5, 5];
const obj = {
    
    };
const uniqueArr = [];
for (let i = 0; i < arr.length; i++) {
    
    
  if (!obj[arr[i]]) {
    
    
    obj[arr[i]] = true;
    uniqueArr.push(arr[i]);
  }
}
console.log(uniqueArr); // [1, 2, 3, 4, 5]

6. indexOf

使用 indexOf 方法判断元素是否已经存在于新数组中,不存在则将该元素推入新数组。

const arr = [1, 2, 3, 1, 2, 3];
const uniqueArr = [];
arr.forEach((item) => {
    
    
  if (uniqueArr.indexOf(item) === -1) {
    
    
    uniqueArr.push(item);
  }
});
console.log(uniqueArr); // [1, 2, 3]

7. includes

includes 方法也可以判断元素是否已经存在于新数组中,不存在则将该元素推入新数组。

const arr = [1, 2, 3, 1, 2, 3];
const uniqueArr = [];
arr.forEach(item => {
    
    
  if (!uniqueArr.includes(item)) {
    
    
    uniqueArr.push(item);
  }
});
console.log(uniqueArr); // [1, 2, 3]

三、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(依赖注入)?

provideinject 是什么?

provide/inject 这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在其上下游关系成立的时间里始终生效。通俗说就是可以用父组件给祖孙组件进行传值,也就是可以隔代传值,不论子组件有多深,只要调用了 inject 那么就可以注入 provide 中的数据,而不是局限于只能从当前父组件的 props 属性来获取数据,这也是 provide/inject 最大的特性。

provide 提供变量

provide 是一个对象,或者是一个返回对象的函数。里面呢就包含要给子孙后代的东西,也就是属性和属性值。

inject 注入变量

inject 是一个字符串数组,或者是一个对象。属性值可以是一个对象,包含 formdefault 默认值。

类型

  • provide

    Object | () => Object
    
  • inject

    Array<string> | {
          
           [key: string]: string | Symbol | Object }
    

代码执行顺序

data->provide->created(在这个阶段$el还未生成,在这先处理privide的逻辑,子孙组件才可以取到inject的值)->mounted

注意:
provide/inject 绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的 property 还是可响应的。根据官方要求,我们只需要传入对象过去,并且对象是响应式的,定义在 data 或者计算属性里都可以。

provideinject 的缺点?

在项目中通常追求有清晰的数据流向和合理的组件层级关系,以便于调试和维护,然而 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 修饰符

对于 elementinput,我们需要在后面加上 .native , 因为 elementinput 进行了封装,原生的事件不起作用。

<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# 后面的内容,包含 #hashurl 中的锚点,代表的是网页中的一个位置,单单改变 # 后的部分,浏览器只会加载相应位置的内容,不会重新加载页面,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. 官网提供了一种方法,可以理解也是为通过 promiseresolve 机制。因为 promise 函数返回的 promiseresolve 组件本身,而我们又可以使用 import 来导入组件。

{
    
    
  path: '/',
  name: 'home',
  component: () => import('@/views/home.vue')
},

3. 通过 require.ensure 方法。这种模式可以通过参数中的 webpackChunkNamejs 分开打包。

{
    
    
  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 函数也不会改变导航本身。只有两个参数,tofrom

    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)

在这里插入图片描述

从控制台的输出结果我们可以很清晰的看到,abdata 指向了同一个内存地址,但神奇的是 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)

在这里插入图片描述

此时我们再看控制台的输出不难发现,ab 之间的 data 相互独立,aagesex 值分别是 18 和 女 ,bage 和 sex 值分别是 23 和 男 ,两者之间没有相互影响。
其实看到这里,也就很好解释为何组件中的 data 必须是一个函数了。

总结

如果 data 是一个函数的话,这样每复用一次组件,就会返回一份新的 data,类似于给每个组件实例创建一个私有的数据空间,让各个组件实例维护各自的数据。而单纯的写成对象形式,就使得所有组件实例共用了一份 data,就会造成一个变了全都会变的结果。所以说 vue 组件的 data 必须是一个函数。


7. vue2.0和vue3.0的区别?

1. 数据绑定

vue2 的双向数据绑定是利用 es5 的一个 API Object.defineProperty() 对数据进行劫持、结合、发布订阅模式的方式来实现的;
vue3 中使用了 es6ProxyAPI 对数据代理实现的。相比与 vue2,使用 ProxyAPI 优势有:defineProperty 只能监听某个属性,不能对全对象进行监听;可以省去 for in 、闭包等内容来提升效率(直接绑定整个对象即可);可以监听数组,不用再去单独的对数组做特异性操作,vue3 可以检测到数组内部数据的变化。

2. 默认懒观察

vue2 中,不管数据多大,都会在一开始就为其创建观察者。当数据很大时,这可能会在页面载入时造成明显的性能压力;
vue3 中,只会对「被用于渲染初始可见部分的数据」创建观察者,而且 vue3 的观察者更高效。

3. 更精准的变更通知

vue2 中,使用 vue.set 来给对象新增一个属性时,这个对象的所有 watcher 都会重新运行;
vue3 中,只有依赖那个属性的 watcher 才会重新运行。

4. 组件变化

vue2vue3 最大的区别是 vue2 使用选项类型 API(OptionsAPI) 对比 vue3 合成型 API(Composition API)。旧的选项型 api 在代码里分割了不同的属性:datacomputedmethods 等;新的合成型 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() 方法,此方法在组件初始化构造的时候触发。使用步骤如下:

  1. vue 引入 reactive
  2. 使用 reactive() 方法来声明数据为响应性数据;
  3. 使用 setup() 方法来返回我们的响应性数据,从而 template 可以获取这些响应性数据。

4. v-ifv-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-ifv-for 同时用在一个元素上,这样会带来性能的浪费(每次都要先渲染才会进行条件判断)。


8. 虚拟dom和diff算法?

真实 dom 渲染

  1. 构建 dom 树。通过 html parser 解析处理 html 标记,将它们构建为 dom 树,当解析器遇到非阻塞资源(图片,样式),会继续解析,但是如果遇到 script 标签(特别是没有 dasyncdefer 属性,会阻塞渲染并停止 html 的解析,这就是为啥最好把 script 标签放在 body 下面的原因。
  2. 构建 cssDom 树。与构建 dom 类似,浏览器也会将样式规则,构建成 cssDom。浏览器会遍历 css 中的规则集,根据 css 选择器创建具有父子,兄弟等关系的节点树。
  3. 构建 render 树。这一步将 domcssDom 关联,确定每个 dom 元素应该应用什么 css 规则。将所有相关样式匹配到 dom 树中的每个可见节点,并根据 css 级联确定每个节点的计算样式,不可见节点(head ,属性包括 display:none 的节点)不会生成到 render 树中。
  4. 布局/回流(Layout/Reflow)。浏览器第一次确定节点的位置以及大小叫布局,如果后续节点位置以及大小发生变化,这一步触发布局调整,也就是回流。
  5. 绘制/重绘(Paint/Repaint)。将元素的每个可视部分绘制到屏幕上,包括文本、颜色、边框、阴影和替换的元素(如按钮和图像)。如果文本、颜色、边框、阴影等这些元素发生变化时,会触发重绘( Repaint)。为了确保重绘的速度比初始绘制的速度更快,屏幕上的绘图通常被分解成数层。将内容提升到 GPU 层(可以通过 tranformfilterwill-changeopacity 触发)可以提高绘制以及重绘的性能。
  6. 合成(Compositing)。这一步将绘制过程中的分层合并,确保它们以正确的顺序绘制到屏幕上显示正确的内容。

虚拟 dom

虚拟 DOM(Virtual Dom) 也就是我们常说的虚拟节点,是用 js 对象来模拟真实 Dom 中的节点,该对象包含了真实 Dom 的结构及其属性,用于对比虚拟 Dom 和真实 Dom 的差异,从而进行局部渲染来达到优化性能的目的。

  1. 通过 js 建立节点描述对象;
  2. diff 算法比较分析新旧两个虚拟 DOM 差异;
  3. 将差异 patch 到真实 dom 上实现更新。

diff 算法

diff 算法就是在虚拟 Dom 中,在 Dom 的状态发生变化时,虚拟 Dom 会进行 diff 运算来更新只需要被替换的 Dom ,而不是全部重绘。在 diff 算法中,只平层的比较前后两棵 Dom 树的节点,没有进行深度的遍历。

  1. 创建新节点;
  2. 删除废节点;
  3. 更新已有节点。

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-ifv-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>

computedmethods 对比

  • 属性调用

    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(仓库),让每个模块拥有自己的 statemutationactiongetter

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 或者是 blockdom 元素还是存在的。
  • 性能
    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 接收传过来的方法和参数。

在这里插入图片描述

  • 全局注册

    1. 安装
    npm install --save vue-bus
    cnpm install --save vue-bus
    
    1. 引入并注册
    // main.js
    import VueBus from 'vue-bus'
    Vue.use(VueBus)		
    
    1. 使用

    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>
    

  • 局部注册

    1. 新建一个 eventBus.js 文件
    import Vue from 'vue'
    const eventBus = new Vue()
    export default eventBus
    
    1. 在使用的组件中引入
    import eventBus from "../eventBus";
    
    1. 使用
      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. emiton 方法
在父组件中,通过组件的 $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 传送指令到 controllercontroller 完成业务逻辑后,要求 model 改变状态;model 将新的数据发送到 view,用户得到反馈。

MVVM

前端的开发思想——双向绑定
MVVM: 是为了实现 mvc 中的 v,它采用双向绑定,view 的变动,自动反映在 viewModel
MVVM: mmodel 数据) vview 视图) vmviewModel 控制数据的改变和控制视图)。

优点
耦合低,viewmodel 完全分离;
维护性高,易维护,上手快;
双向绑定,视图发生变化数据自动同步,数据发生变化视图也自动同步;
减少了 dom 的操作,可以更多的关注业务逻辑。

mvcmvvm 的区别?

mvvm 各部分的通信是双向的,而 mvc 各部分通信是单向的;
mvvm 是真正将页面与数据逻辑分离放到 js 里去实现,而 mvc 里面未分离。


15. vue生命周期?

  • beforeCreate 组件实例创建前,eldata 并没有初始化;
  • created 组件实例创建后,完成了 data 数据的初始化;
  • beforeMountdom 挂载前,render 函数首次被调用;
  • mounteddom 挂载后,模块中的 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.definePropertysetget,在数据变动时发布消息给订阅者触发监听。

怎么实现一个双向数据绑定?

第一步
需要 observe 的数据对象进行递归遍历,包括子属性对象的属性,都加上 settergeter ,这样的话,给这个对象的某个值赋值,就会触发 setter,那么就能监听到了数据变化。

第二步
compile 解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图。

第三步
watcher 订阅者是 observecompile 之间通信的桥梁。

第四步
MVVM 作为数据绑定的入口,整合 observecompilewatcher 三者,通过 observe 来监听自己的 model 数据变化,通过 compile 来解析编译模板指令,最终利用 watcher 搭起 observecompile 之间的通信桥梁,达到数据变化,视图更新;视图交互变化,数据 model 变更的双向绑定效果。


17. 深拷贝和浅拷贝?

深拷贝和浅拷贝是只针对 ObjectArray 这样的引用数据类型的。

  • 深拷贝

    深拷贝相对于对象方法的时候,是全部拷贝,意思就是直接复制在一份完整的对象或者方法。就好比我创建了一个 a,然后利用深拷贝复制出了 b,但是 b 不会对原来的 a 数据有任何影响,只是把原有 a 的值完完整整复制到 b 里面,两个互不干涉没有影响。

  • 浅拷贝

    而浅拷贝相比于深拷贝,对对象和方法则是引用,将原对象或原数组的引用直接赋给新对象/新数组,新对象/数组只是原对象的一个引用。

浅拷贝实现?

1.Object.assign()

const obj = {
    
    a: 1, b: 2};
const copy = Object.assign({
    
    }, obj);
console.log(copy); // {a: 1, b: 2}

2. 扩展运算符

const obj = {
    
    a: 1, b: 2};
const copy = {
    
    ...obj};
console.log(copy); // {a: 1, b: 2}

深拷贝实现?

1. 递归实现

function deepClone(obj) {
    
    
  if (obj === null) return null;
  if (typeof obj !== "object") return obj;
  if (obj.constructor === Date) return new Date(obj);
  if (obj.constructor === RegExp) return new RegExp(obj);
  const newObj = new obj.constructor();
  for (let key in obj) {
    
    
    if (obj.hasOwnProperty(key)) {
    
    
      newObj[key] = deepClone(obj[key]);
    }
  }
  return newObj;
}

const obj = {
    
    a: 1, b: {
    
    c: 2}};
const copy = deepClone(obj);
console.log(copy); // {a: 1, b: {c: 2}}

2. JSON.parse(JSON.stringify(obj))

const obj = {
    
    a: 1, b: {
    
    c: 2}};
const copy = JSON.parse(JSON.stringify(obj));
console.log(copy); // {a: 1, b: {c: 2}}

需要注意的是,JSON.stringify() 方法无法拷贝函数、循环引用的数据等特殊情况。同时,使用递归实现深拷贝会有性能问题,因为需要不断的进行函数调用,特别是在数据量较大的情况下。


四、浏览器相关面试题

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;具有极高的扩展性和可用性,不能跨浏览器使用。应用场景:判断用户是否登陆过网站、保存上次登录的时间等信息、浏览计数。

共同点

localStoragesessionStoragecookies 这三者都可以被用来在浏览器端存储数据,而且都是字符串类型的键值对。

区别

  1. 存储大小不同
localStorage:5MB左右
sessionStorage:5MB左右
cookies:不超过4K
  1. 数据有效期不同
localStorage:始终有效
sessionStorage:仅在当前浏览器窗口关闭前有效
cookies:设置的cookie过期时间之前一直有效,即使窗口或浏览器关闭
  1. 作用域不同
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 的头包含类似 getpost 这样的信息。

7. trace 方法
trace 方法被用于激发一个远程的,应用层的请求消息回路。

8. connect 方法
把请求连接转换到透明的 tcp/ip 通道。


6. 说一下你对浏览器内核的理解?

概念

浏览器内核主要分成两个部分:渲染引擎和 js 引擎。

1. 渲染引擎
负责取得网页的内容,如:htmlxml、图像 等,整理讯息,如: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 操作(元素添加、删除、修改或者元素顺序的改变)
    元素位置、尺寸变化(更改类的属性),元素内容发生变化(如图片、文本)
    浏览器窗口动作(拖拽、拉伸等)
    

重绘不一定需要重排,重排必然会导致重绘。重绘重排的代价就是会造成加载缓慢,耗时严重,从而导致浏览器卡顿。

怎么减少重排?

  1. 将多次改变样式属性的操作,合成一次操作;
  2. 减少直接操作 dom 元素,改用类名用于控制;
  3. 尽量减少 table 使用,table 属性变化会直接导致布局重排或者重绘;
  4. 不要把 dom 节点的属性值放在一个循环里当成循环里的变量;
  5. 在需要经常获取那些引起浏览器重排的属性值时,要缓存到变量。

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 就是单线程。


持续更新中...

猜你喜欢

转载自blog.csdn.net/Shids_/article/details/130867622