必背面试题-手写题
css系列
面试官:说说你对盒子模型的理解
一、是什么
所有元素都可以有像盒子一样的平面空间和外形
一个盒子由四部分组成:context ,padding,margin,border
content:实际内容,显示文本和图像
padding:内边距,清除内容周边的区域,内边距是透明的,不能取负值,受盒子的background属性影响
margin:外边距,在元素外创建额外的空白,空白通常指不能放其他元素的区域
下面来段代码:
<style>
.box {
width: 200px;
height: 100px;
padding: 20px;
}
</style>
<div class="box">
盒子模型
</div>
当我们在浏览器查看元素时,却发现元素的大小变成了240px
这是因为,在CSS
中,盒子模型可以分成:
- W3C 标准盒子模型
- IE 怪异盒子模型
默认情况下,盒子模型为W3C
标准盒子模型
二、标准盒子模型
标准盒子模型,是浏览器默认的盒子模型
-
盒子总宽度 = width + padding + border + margin;
-
盒子总高度 = height + padding + border + margin
也就是,width/height
只是内容高度,不包含 padding
和 border
值
所以上面问题中,设置width
为200px,但由于存在padding
,但实际上盒子的宽度有240px
三、IE 怪异盒子模型
-
盒子总宽度 = width + margin;
-
盒子总高度 = height + margin;
也就是,width/height
包含了 padding
和 border
值
Box-sizing
CSS 中的 box-sizing 属性定义了引擎应该如何计算一个元素的总宽度和总高度
box-sizing: content-box|border-box|inherit:
- content-box 默认值,元素的 width/height 不包含padding,border,与标准盒子模型表现一致
- border-box 元素的 width/height 包含 padding,border,与怪异盒子模型表现一致
- inherit 指定 box-sizing 属性的值,应该从父元素继承
回到上面的例子里,设置盒子为 border-box 模型
<style>
.box {
width: 200px;
height: 100px;
padding: 20px;
box-sizing: border-box;
}
</style>
<div class="box">
盒子模型
</div>
这时,盒子所占据的宽度为200px
面试官:css选择器有哪些?优先级?哪些属性可以继承?
一、选择器
CSS选择器是CSS规则的第一部分
它是元素和其他部分组合起来告诉浏览器哪个HTML元素应当是被选为应用规则中的CSS属性值的方式
选择器所选择的元素,叫做“选择器的对象”
关于css
属性选择器常用的有:
-
id选择器(#box),选择id为box的元素
-
类选择器(.one),选择类名为one的所有元素
-
标签选择器(div),选择标签为div的所有元素
-
后代选择器(#box div),选择id为box元素内部所有的div元素
-
子选择器(.one>one_1),选择父元素为.one的所有.one_1的元素
-
相邻同胞选择器(.one+.two),选择紧接在.one之后的所有.two元素
-
群组选择器(div,p),选择div、p的所有元素
还有一些使用频率相对没那么多的选择器:
- 伪类选择器
:link :选择未被访问的链接
:visited:选取已被访问的链接
:active:选择活动链接
:hover :鼠标指针浮动在上面的元素
:focus :选择具有焦点的
:first-child:父元素的首个子元素
- 伪元素选择器
:first-letter :用于选取指定选择器的首字母
:first-line :选取指定选择器的首行
:before : 选择器在被选元素的内容前面插入内容
:after : 选择器在被选元素的内容后面插入内容
- 属性选择器
[attribute] 选择带有attribute属性的元素
[attribute=value] 选择所有使用attribute=value的元素
[attribute~=value] 选择attribute属性包含value的元素
[attribute|=value]:选择attribute属性以value开头的元素
在CSS3
中新增的选择器有如下:
- 层次选择器(p~ul),选择前面有p元素的每个ul元素
- 伪类选择器
:first-of-type 表示一组同级元素中其类型的第一个元素
:last-of-type 表示一组同级元素中其类型的最后一个元素
:only-of-type 表示没有同类型兄弟元素的元素
:only-child 表示没有任何兄弟的元素
:nth-child(n) 根据元素在一组同级中的位置匹配元素
:nth-last-of-type(n) 匹配给定类型的元素,基于它们在一组兄弟元素中的位置,从末尾开始计数
:last-child 表示一组兄弟元素中的最后一个元素
:root 设置HTML文档
:empty 指定空的元素
:enabled 选择可用元素
:disabled 选择被禁用元素
:checked 选择选中的元素
:not(selector) 选择与 <selector> 不匹配的所有元素
- 属性选择器
[attribute*=value]:选择attribute属性值包含value的所有元素
[attribute^=value]:选择attribute属性开头为value的所有元素
[attribute$=value]:选择attribute属性结尾为value的所有元素
二、优先级
!important >内联 > ID选择器 > 类选择器 > 标签选择器
三、继承属性
在css
中,继承是指的是给父元素设置一些属性,后代元素会自动拥有这些属性
关于继承属性,可以分成:
- 字体系列属性
font:组合字体
font-family:规定元素的字体系列
font-weight:设置字体的粗细
font-size:设置字体的尺寸
font-style:定义字体的风格
font-variant:偏大或偏小的字体
- 文本系列属性
text-indent:文本缩进
text-align:文本水平对刘
line-height:行高
word-spacing:增加或减少单词间的空白
letter-spacing:增加或减少字符间的空白
text-transform:控制文本大小写
direction:规定文本的书写方向
color:文本颜色
- 元素可见性
visibility
- 表格布局属性
caption-side:定位表格标题位置
border-collapse:合并表格边框
border-spacing:设置相邻单元格的边框间的距离
empty-cells:单元格的边框的出现与消失
table-layout:表格的宽度由什么决定
- 列表属性
list-style-type:文字前面的小点点样式
list-style-position:小点点位置
list-style:以上的属性可通过这属性集合
- 引用
quotes:设置嵌套引用的引号类型
- 光标属性
cursor:箭头可以变成需要的形状
继承中比较特殊的几点:
-
a 标签的字体颜色不能被继承
-
h1-h6标签字体的大下也是不能被继承的
无继承的属性
-
display
-
文本属性:vertical-align、text-decoration
-
盒子模型的属性:宽度、高度、内外边距、边框等
-
背景属性:背景图片、颜色、位置等
-
定位属性:浮动、清除浮动、定位position等
-
生成内容属性:content、counter-reset、counter-increment
-
轮廓样式属性:outline-style、outline-width、outline-color、outline
-
页面样式属性:size、page-break-before、page-break-after
面试官:元素水平垂直居中的方法有哪些?如果元素不定宽高呢?
一、背景
在开发中经常遇到这个问题,即让某个元素的内容在水平和垂直方向上都居中,内容不仅限于文字,可能是图片或其他元素
居中是一个非常基础但又是非常重要的应用场景,实现居中的方法存在很多,可以将这些方法分成两个大类:
- 居中元素(子元素)的宽高已知
- 居中元素宽高未知
二、实现方式
实现元素水平垂直居中的方式:
-
利用定位+margin:auto
-
利用定位+margin:负值
-
利用定位+transform
-
table布局
-
flex布局
-
grid布局
利用定位+margin:auto
先上代码:
<style>
.father{
width:500px;
height:300px;
border:1px solid #0a3b98;
position: relative;
}
.son{
width:100px;
height:40px;
background: #f0a238;
position: absolute;
top:0;
left:0;
right:0;
bottom:0;
margin:auto;
}
</style>
<div class="father">
<div class="son"></div>
</div>
父级设置为相对定位,子级绝对定位 ,并且四个定位属性的值都设置了0,那么这时候如果子级没有设置宽高,则会被拉开到和父级一样宽高
这里子元素设置了宽高,所以宽高会按照我们的设置来显示,但是实际上子级的虚拟占位已经撑满了整个父级,这时候再给它一个margin:auto
它就可以上下左右都居中了
利用定位+margin:负值
绝大多数情况下,设置父元素为相对定位, 子元素移动自身50%实现水平垂直居中
<style>
.father {
position: relative;
width: 200px;
height: 200px;
background: skyblue;
}
.son {
position: absolute;
top: 50%;
left: 50%;
margin-left:-50px;
margin-top:-50px;
width: 100px;
height: 100px;
background: red;
}
</style>
<div class="father">
<div class="son"></div>
</div>
- 初始位置为方块1的位置
- 当设置left、top为50%的时候,内部子元素为方块2的位置
- 设置margin为负数时,使内部子元素到方块3的位置,即中间位置
这种方案不要求父元素的高度,也就是即使父元素的高度变化了,仍然可以保持在父元素的垂直居中位置,水平方向上是一样的操作
但是该方案需要知道子元素自身的宽高,但是我们可以通过下面transform
属性进行移动
利用定位+transform
实现代码如下:
<style>
.father {
position: relative;
width: 200px;
height: 200px;
background: skyblue;
}
.son {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%,-50%);
width: 100px;
height: 100px;
background: red;
}
</style>
<div class="father">
<div class="son"></div>
</div>
translate(-50%, -50%)
将会将元素位移自己宽度和高度的-50%
这种方法其实和最上面被否定掉的margin负值用法一样,可以说是margin
负值的替代方案,并不需要知道自身元素的宽高
table布局
设置父元素为display:table-cell
,子元素设置 display: inline-block
。利用vertical
和text-align
可以让所有的行内块级元素水平垂直居中
<style>
.father {
display: table-cell;
width: 200px;
height: 200px;
background: skyblue;
vertical-align: middle;
text-align: center;
}
.son {
display: inline-block;
width: 100px;
height: 100px;
background: red;
}
</style>
<div class="father">
<div class="son"></div>
</div>
flex弹性布局
还是看看实现的整体代码:
<style>
.father {
display: flex;
justify-content: center;
align-items: center;
width: 200px;
height: 200px;
background: skyblue;
}
.son {
width: 100px;
height: 100px;
background: red;
}
</style>
<div class="father">
<div class="son"></div>
</div>
css3
中了flex
布局,可以非常简单实现垂直水平居中
这里可以简单看看flex
布局的关键属性作用:
-
display: flex时,表示该容器内部的元素将按照flex进行布局
-
align-items: center表示这些元素将相对于本容器水平居中
-
justify-content: center也是同样的道理垂直居中
grid网格布局
<style>
.father {
display: grid;
align-items:center;
justify-content: center;
width: 200px;
height: 200px;
background: skyblue;
}
.son {
width: 10px;
height: 10px;
border: 1px solid red
}
</style>
<div class="father">
<div class="son"></div>
</div>
这里看到,gird
网格布局和flex
弹性布局都简单粗暴
面试官:怎么理解回流跟重绘?什么场景下会触发?
一、是什么
在HTML
中,每个元素都可以理解成一个盒子,在浏览器解析过程中,会涉及到回流与重绘:
-
回流:布局引擎会根据各种样式计算每个盒子在页面上的大小与位置
-
重绘:当计算好盒模型的位置、大小及其他属性后,浏览器根据每个盒子特性进行绘制
在页面初始渲染阶段,回流不可避免的触发,可以理解成页面一开始是空白的元素,后面添加了新的元素使页面布局发生改变
当我们对 DOM
的修改引发了 DOM
几何尺寸的变化(比如修改元素的宽、高或隐藏元素等)时,浏览器需要重新计算元素的几何属性,然后再将计算的结果绘制出来
当我们对 DOM
的修改导致了样式的变化(color
或background-color
),却并未影响其几何属性时,浏览器不需重新计算元素的几何属性、直接为该元素绘制新的样式,这里就仅仅触发了回流
二、如何触发
回流触发时机
回流这一阶段主要是计算节点的位置和几何信息,那么当页面布局和几何信息发生变化的时候,就需要回流,如下面情况:
- 添加或删除可见的DOM元素
- 元素的位置发生变化
- 元素的尺寸发生变化(包括外边距、内边框、边框大小、高度和宽度等)
- 内容发生变化,比如文本变化或图片被另一个不同尺寸的图片所替代
- 页面一开始渲染的时候(这避免不了)
- 浏览器的窗口尺寸变化(因为回流是根据视口的大小来计算元素的位置和大小的)
还有一些容易被忽略的操作:获取一些特定属性的值
offsetTop、offsetLeft、 offsetWidth、offsetHeight、scrollTop、scrollLeft、scrollWidth、scrollHeight、clientTop、clientLeft、clientWidth、clientHeight
这些属性有一个共性,就是需要通过即时计算得到。因此浏览器为了获取这些值,也会进行回流
除此还包括getComputedStyle
方法,原理是一样的
重绘触发时机
触发回流一定会触发重绘
可以把页面理解为一个黑板,黑板上有一朵画好的小花。现在我们要把这朵从左边移到了右边,那我们要先确定好右边的具体位置,画好形状(回流),再画上它原有的颜色(重绘)
除此之外还有一些其他引起重绘行为:
-
颜色的修改
-
文本方向的修改
-
阴影的修改
浏览器优化机制
由于每次重排都会造成额外的计算消耗,因此大多数浏览器都会通过队列化修改并批量执行来优化重排过程。浏览器会将修改操作放入到队列里,直到过了一段时间或者操作达到了一个阈值,才清空队列
当你获取布局信息的操作的时候,会强制队列刷新,包括前面讲到的offsetTop
等方法都会返回最新的数据
因此浏览器不得不清空队列,触发回流重绘来返回正确的值
三、如何减少
我们了解了如何触发回流和重绘的场景,下面给出避免回流的经验:
- 如果想设定元素的样式,通过改变元素的
class
类名 (尽可能在 DOM 树的最里层) - 避免设置多项内联样式
- 应用元素的动画,使用
position
属性的fixed
值或absolute
值(如前文示例所提) - 避免使用
table
布局,table
中每个元素的大小以及内容的改动,都会导致整个table
的重新计算 - 对于那些复杂的动画,对其设置
position: fixed/absolute
,尽可能地使元素脱离文档流,从而减少对其他元素的影响 - 使用css3硬件加速,可以让
transform
、opacity
、filters
这些动画不会引起回流重绘 - 避免使用 CSS 的
JavaScript
表达式
在使用 JavaScript
动态插入多个节点时, 可以使用DocumentFragment
. 创建后一次插入. 就能避免多次的渲染性能
但有时候,我们会无可避免地进行回流或者重绘,我们可以更好使用它们
例如,多次修改一个把元素布局的时候,我们很可能会如下操作
const el = document.getElementById('el')
for(let i=0;i<10;i++) {
el.style.top = el.offsetTop + 10 + "px";
el.style.left = el.offsetLeft + 10 + "px";
}
每次循环都需要获取多次offset
属性,比较糟糕,可以使用变量的形式缓存起来,待计算完毕再提交给浏览器发出重计算请求
// 缓存offsetLeft与offsetTop的值
const el = document.getElementById('el')
let offLeft = el.offsetLeft, offTop = el.offsetTop
// 在JS层面进行计算
for(let i=0;i<10;i++) {
offLeft += 10
offTop += 10
}
// 一次性将计算结果应用到DOM上
el.style.left = offLeft + "px"
el.style.top = offTop + "px"
我们还可避免改变样式,使用类名去合并样式
const container = document.getElementById('container')
container.style.width = '100px'
container.style.height = '200px'
container.style.border = '10px solid red'
container.style.color = 'red'
使用类名去合并样式
<style>
.basic_style {
width: 100px;
height: 200px;
border: 10px solid red;
color: red;
}
</style>
<script>
const container = document.getElementById('container')
container.classList.add('basic_style')
</script>
前者每次单独操作,都去触发一次渲染树更改(新浏览器不会),
都去触发一次渲染树更改,从而导致相应的回流与重绘过程
合并之后,等于我们将所有的更改一次性发出
我们还可以通过通过设置元素属性display: none
,将其从页面上去掉,然后再进行后续操作,这些后续操作也不会触发回流与重绘,这个过程称为离线操作
const container = document.getElementById('container')
container.style.width = '100px'
container.style.height = '200px'
container.style.border = '10px solid red'
container.style.color = 'red'
离线操作后
let container = document.getElementById('container')
container.style.display = 'none'
container.style.width = '100px'
container.style.height = '200px'
container.style.border = '10px solid red'
container.style.color = 'red'
...(省略了许多类似的后续操作)
container.style.display = 'block'
面试官:什么是响应式设计?响应式设计的基本原理是什么?如何做?
一、是什么
响应式网站设计(Responsive Web design)是一种网络页面设计布局,页面的设计与开发应当根据用户行为以及设备环境(系统平台、屏幕尺寸、屏幕定向等)进行相应的响应和调整
描述响应式界面最著名的一句话就是“Content is like water”
大白话便是“如果将屏幕看作容器,那么内容就像水一样”
响应式网站常见特点:
-
同时适配PC + 平板 + 手机等
-
标签导航在接近手持终端设备时改变为经典的抽屉式导航
-
网站的布局会根据视口来调整模块的大小和位置
二、实现方式
响应式设计的基本原理是通过媒体查询检测不同的设备屏幕尺寸做处理,为了处理移动端,页面头部必须有meta
声明viewport
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no”>
属性对应如下:
-
width=device-width: 是自适应手机屏幕的尺寸宽度
-
maximum-scale:是缩放比例的最大值
-
inital-scale:是缩放的初始化
-
user-scalable:是用户的可以缩放的操作
实现响应式布局的方式有如下:
- 媒体查询
- 百分比
- vw/vh
- rem
媒体查询
CSS3
中的增加了更多的媒体查询,就像if
条件表达式一样,我们可以设置不同类型的媒体条件,并根据对应的条件,给相应符合条件的媒体调用相对应的样式表
使用@Media
查询,可以针对不同的媒体类型定义不同的样式,如:
@media screen and (max-width: 1920px) { ... }
当视口在375px - 600px之间,设置特定字体大小18px
@media screen (min-width: 375px) and (max-width: 600px) {
body {
font-size: 18px;
}
}
通过媒体查询,可以通过给不同分辨率的设备编写不同的样式来实现响应式的布局,比如我们为不同分辨率的屏幕,设置不同的背景图片
比如给小屏幕手机设置@2x图,为大屏幕手机设置@3x图,通过媒体查询就能很方便的实现
百分比
通过百分比单位 " % " 来实现响应式的效果
比如当浏览器的宽度或者高度发生变化时,通过百分比单位,可以使得浏览器中的组件的宽和高随着浏览器的变化而变化,从而实现响应式的效果
height
、width
属性的百分比依托于父标签的宽高,但是其他盒子属性则不完全依赖父元素:
-
子元素的top/left和bottom/right如果设置百分比,则相对于直接非static定位(默认定位)的父元素的高度/宽度
-
子元素的padding如果设置百分比,不论是垂直方向或者是水平方向,都相对于直接父亲元素的width,而与父元素的height无关。
-
子元素的margin如果设置成百分比,不论是垂直方向还是水平方向,都相对于直接父元素的width
-
border-radius不一样,如果设置border-radius为百分比,则是相对于自身的宽度
可以看到每个属性都使用百分比,会照成布局的复杂度,所以不建议使用百分比来实现响应式
vw/vh
vw
表示相对于视图窗口的宽度,vh
表示相对于视图窗口高度。 任意层级元素,在使用vw
单位的情况下,1vw
都等于视图宽度的百分之一
与百分比布局很相似,在以前文章提过与%
的区别,这里就不再展开述说
rem
在以前也讲到,rem
是相对于根元素html
的font-size
属性,默认情况下浏览器字体大小为16px
,此时1rem = 16px
可以利用前面提到的媒体查询,针对不同设备分辨率改变font-size
的值,如下:
@media screen and (max-width: 414px) {
html {
font-size: 18px
}
}
@media screen and (max-width: 375px) {
html {
font-size: 16px
}
}
@media screen and (max-width: 320px) {
html {
font-size: 12px
}
}
为了更准确监听设备可视窗口变化,我们可以在css
之前插入script
标签,内容如下:
//动态为根元素设置字体大小
function init () {
// 获取屏幕宽度
var width = document.documentElement.clientWidth
// 设置根元素字体大小。此时为宽的10等分
document.documentElement.style.fontSize = width / 10 + 'px'
}
//首次加载应用,设置一次
init()
// 监听手机旋转的事件的时机,重新设置
window.addEventListener('orientationchange', init)
// 监听手机窗口变化,重新设置
window.addEventListener('resize', init)
无论设备可视窗口如何变化,始终设置rem
为width
的1/10,实现了百分比布局
除此之外,我们还可以利用主流UI
框架,如:element ui
、antd
提供的栅格布局实现响应式
三、总结
响应式布局优点可以看到:
- 面对不同分辨率设备灵活性强
- 能够快捷解决多设备显示适应问题
缺点:
- 仅适用布局、信息、框架并不复杂的部门类型网站
- 兼容各种设备工作量大,效率低下
- 代码累赘,会出现隐藏无用的元素,加载时间加长
- 其实这是一种折中性质的设计解决方案,多方面因素影响而达不到最佳效果
- 一定程度上改变了网站原有的布局结构,会出现用户混淆的情况
面试官:如果要做优化,CSS提高性能的方法有哪些?
一、前言
css
主要是用来完成页面布局的,像一些细节或者优化,
减少css嵌套,最好不要套三层以上。
不要在ID选择器前面进行嵌套,ID本来就是唯一的而且人家权值那么大,嵌套完全是浪费性能。
建立公共样式类,把相同样式提取出来作为公共类使用,比如我们常用的清除浮动等。
不用css表达式,css表达式对性能的浪费可能是超乎你的想象的
二、实现方式
实现方式有很多种,主要有如下:
- 内联首屏关键CSS
- 异步加载CSS
- 资源压缩
- 合理使用选择器
- 减少使用昂贵的属性
- 不要使用@import
内联首屏关键CSS
在打开一个页面,页面首要内容出现在屏幕的时间影响着用户的体验,而通过内联css
关键代码能够使浏览器在下载完html
后就能立刻渲染
而如果外部引用css
代码,在解析html
结构过程中遇到外部css
文件,才会开始下载css
代码,再渲染
所以,CSS
内联使用使渲染时间提前
注意:但是较大的css
代码并不合适内联(初始拥塞窗口、没有缓存),而其余代码则采取外部引用方式
异步加载CSS
在CSS
文件请求、下载、解析完成之前,CSS
会阻塞渲染,浏览器将不会渲染任何已处理的内容
前面加载内联代码后,后面的外部引用css
则没必要阻塞浏览器渲染。这时候就可以采取异步加载的方案,主要有如下:
// 创建link标签
const myCSS = document.createElement( "link" );
myCSS.rel = "stylesheet";
myCSS.href = "mystyles.css";
// 插入到header的最后位置
document.head.insertBefore( myCSS, document.head.childNodes[ document.head.childNodes.length - 1 ].nextSibling );
- 设置link标签media属性为noexis,浏览器会认为当前样式表不适用当前类型,会在不阻塞页面渲染的情况下再进行下载。加载完成后,将
media
的值设为screen
或all
,从而让浏览器开始解析CSS
<link rel="stylesheet" href="mystyles.css" media="noexist" onload="this.media='all'">
- 通过rel属性将link元素标记为alternate可选样式表,也能实现浏览器异步加载。同样别忘了加载完成之后,将rel设回stylesheet
<link rel="alternate stylesheet" href="mystyles.css" onload="this.rel='stylesheet'">
资源压缩
利用webpack
、gulp/grunt
、rollup
等模块化工具,将css
代码进行压缩,使文件变小,大大降低了浏览器的加载时间
合理使用选择器
css
匹配的规则是从右往左开始匹配,例如#markdown .content h3
匹配规则如下:
- 先找到h3标签元素
- 然后去除祖先不是.content的元素
- 最后去除祖先不是#markdown的元素
如果嵌套的层级更多,页面中的元素更多,那么匹配所要花费的时间代价自然更高
所以我们在编写选择器的时候,可以遵循以下规则:
- 不要嵌套使用过多复杂选择器,最好不要三层以上
- 使用id选择器就没必要再进行嵌套
- 通配符和属性选择器效率最低,避免使用
减少使用昂贵的属性
在页面发生重绘的时候,昂贵属性如box-shadow
/border-radius
/filter
/透明度/:nth-child
等,会降低浏览器的渲染性能
不要使用@import
css样式文件有两种引入方式,一种是link
元素,另一种是@import
@import
会影响浏览器的并行下载,使得页面在加载时增加额外的延迟,增添了额外的往返耗时
而且多个@import
可能会导致下载顺序紊乱
比如一个css文件index.css
包含了以下内容:@import url("reset.css")
那么浏览器就必须先把index.css
下载、解析和执行后,才下载、解析和执行第二个文件reset.css
面试官:说说JavaScript中的数据类型?存储上的差别?
js的数据类型分为两类,一个是基本数据类型,一个是引用数据类型
基本数据类型有undefined、
null、
boolean、
number、
string、
symbol
引用数据类型有 object
在js的执行过程中,主要有三种数据类型内存空间,分别是代码空间,栈空间,堆空间,其中的代码空间主要是存储可执行代码的,原始类型的数据值都是直接保存在栈中的,引用数据类型的值是存放在堆空间中的, 原始数据类型存储的是变量的值,而引用数据类型存储的是其在堆空间中的地址
JavaScript系列
面试官:typeof 与 instanceof 区别
一、typeof
typeof 对于原始数据类型来说,除了null都可以正确的显示类型
使用方法如下:
typeof operand
typeof(operand)
operand
表示对象或原始值的表达式,其类型将被返回
举个例子
typeof 1 // 'number'
typeof '1' // 'string'
typeof undefined // 'undefined'
typeof true // 'boolean'
typeof Symbol() // 'symbol'
typeof null // 'object'
typeof [] // 'object'
typeof {} // 'object'
typeof console // 'object'
typeof console.log // 'function'
从上面例子,前6个都是基础数据类型。虽然typeof null
为object
,但这只是JavaScript
存在的一个悠久 Bug
,不代表null
就是引用数据类型,并且null
本身也不是对象
所以,null
在 typeof
之后返回的是有问题的结果,不能作为判断null
的方法。如果你需要在 if
语句中判断是否为 null
,直接通过===null
来判断就好
同时,可以发现引用类型数据,用typeof
来判断的话,除了function
会被识别出来之外,其余的都输出object
如果我们想要判断一个变量是否存在,可以使用typeof
:(不能使用if(a)
, 若a
未声明,则报错)
if(typeof a != 'undefined'){
//变量存在
}
二、instanceof
instanceof 可以正确显示数据类型, 因为它是通过对象的原型链来进行判断的,
使用如下:
object instanceof constructor
object
为实例对象,constructor
为构造函数
构造函数通过new
可以实例对象,instanceof
能判断这个对象是否是之前那个构造函数生成的对象
// 定义构建函数
let Car = function() {}
let benz = new Car()
benz instanceof Car // true
let car = new String('xxx')
car instanceof String // true
let str = 'xxx'
str instanceof String // false
关于instanceof
的实现原理,可以参考下面:
function myInstanceof(left, right) {
// 这里先用typeof来判断基础数据类型,如果是,直接返回false
if(typeof left !== 'object' || left === null) return false;
// getProtypeOf是Object对象自带的API,能够拿到参数的原型对象
let proto = Object.getPrototypeOf(left);
while(true) {
if(proto === null) return false;
if(proto === right.prototype) return true;//找到相同原型对象,返回true
proto = Object.getPrototypeof(proto);
}
}
也就是顺着原型链去找,直到找到相同的原型对象,返回true
,否则为false
三、区别
typeof
与instanceof
都是判断数据类型的方法,区别如下:
-
typeof
会返回一个变量的基本类型,instanceof
返回的是一个布尔值 -
instanceof
可以准确地判断复杂引用数据类型,但是不能正确判断基础数据类型 -
而
typeof
也存在弊端,它虽然可以判断基础数据类型(null
除外),但是引用数据类型中,除了function
类型以外,其他的也无法判断
可以看到,上述两种方法都有弊端,并不能满足所有场景的需求
如果需要通用检测数据类型,可以采用Object.prototype.toString
,调用该方法,统一返回格式“[object Xxx]”
的字符串
如下
Object.prototype.toString({}) // "[object Object]"
Object.prototype.toString.call({}) // 同上结果,加上call也ok
Object.prototype.toString.call(1) // "[object Number]"
Object.prototype.toString.call('1') // "[object String]"
Object.prototype.toString.call(true) // "[object Boolean]"
Object.prototype.toString.call(function(){}) // "[object Function]"
Object.prototype.toString.call(null) //"[object Null]"
Object.prototype.toString.call(undefined) //"[object Undefined]"
Object.prototype.toString.call(/123/g) //"[object RegExp]"
Object.prototype.toString.call(new Date()) //"[object Date]"
Object.prototype.toString.call([]) //"[object Array]"
Object.prototype.toString.call(document) //"[object HTMLDocument]"
Object.prototype.toString.call(window) //"[object Window]"
了解了toString
的基本用法,下面就实现一个全局通用的数据类型判断方法
function getType(obj){
let type = typeof obj;
if (type !== "object") { // 先进行typeof判断,如果是基础数据类型,直接返回
return type;
}
// 对于typeof返回结果是object的,再进行如下的判断,正则返回结果
return Object.prototype.toString.call(obj).replace(/^\[object (\S+)\]$/, '$1');
}
使用如下
getType([]) // "Array" typeof []是object,因此toString返回
getType('123') // "string" typeof 直接返回
getType(window) // "Window" toString返回
getType(null) // "Null"首字母大写,typeof null是object,需toString来判断
getType(undefined) // "undefined" typeof 直接返回
getType() // "undefined" typeof 直接返回
getType(function(){}) // "function" typeof能判断,因此首字母小写
getType(/123/g) //"RegExp" toString返回
面试官:说说你对闭包的理解?闭包使用场景
闭包就是可以访问其他函数内部变量的函数,我们通常用它来定义私有化的变量和方法,创建一个闭包最简单的方法就是在一个函数内创建一个函数,它有三个特性是 函数内可以再嵌套函数,内部函数可以访问外部的方法和变量,方法和变量不会被垃圾回收机制回收,
一、是什么
闭包最简单的方法就是在一个函数内创建一个函数,闭包让你可以在一个内层函数中访问到其外层函数的作用域
二、使用场景
任何闭包的使用场景都离不开这两点:
- 创建私有变量
- 延长变量的生命周期
一般函数的词法环境在函数返回后就被销毁,但是闭包会保存对创建时所在词法环境的引用,即便创建时所在的执行上下文被销毁,但创建时所在词法环境依然存在,以达到延长变量的生命周期的目的
创建私有变量:好比vue里的data 每个data都是一个闭包所以他们互不干扰
三、优缺点
它的优点就是可以实现封装和缓存,缺点就是可能会造成内存泄漏的问题
面试官:bind、call、apply 区别?如何实现一个bind?
关于call、apply、bind函数,它们主要用来改变this指向的
call的用法
fn.call(thisArg, arg1, arg2, arg3, ...)
调用fn.call时会将fn中的this指向修改为传入的第一个参数thisArg;将后面的参数传入给fn,并立即执行函数fn。
let obj = {
name: "xiaoming",
age: 24,
sayHello: function (job, hobby) {
console.log(`我叫${this.name},今年${this.age}岁。我的工作是: ${job},我的爱好是: ${hobby}。`);
}
}
obj.sayHello('程序员', '看美女'); // 我叫xiaoming,今年24岁。我的工作是: 程序员,我的爱好是: 看美女。
let obj1 = {
name: "lihua",
age: 30
}
// obj1.sayHello(); // Uncaught TypeError: obj1.sayHello is not a function
obj.sayHello.call(obj1, '设计师', '画画'); // 我叫lihua,今年30岁。我的工作是: 设计师,我的爱好是: 画画。
apply的用法
apply(thisArg, [argsArr])
fn.apply的作用和call相同:修改this指向,并立即执行fn。区别在于传参形式不同,apply接受两个参数,第一个参数是要指向的this对象,第二个参数是一个数组,数组里面的元素会被展开传入fn,作为fn的参数。
bind的用法
bind(thisArg, arg1, arg2, arg3, ...)
fn.bind的作用是只修改this指向,但不会立即执行fn;会返回一个修改了this指向后的fn。需要调用才会执行:bind(thisArg, arg1, arg2, arg3, ...)()
。bind的传参和call相同。
obj.sayHello.bind(obj1, '设计师', '画画'); // 无输出结果
obj.sayHello.bind(obj1, '设计师', '画画')(); // 我叫lihua,今年30岁。我的工作是: 设计师,我的爱好是: 画画。
bind、call、apply的区别
1、相同点
三个都是用于改变this指向;
接收的第一个参数都是this要指向的对象;
都可以利用后续参数传参。
2、不同点
call和bind传参相同,多个参数依次传入的;
apply只有两个参数,第二个参数为数组;
call和apply都是对函数进行直接调用,而bind方法不会立即调用函数,而是返回一个修改this后的函数。
面试官:说说你对事件循环的理解
一、是什么
JavaScript
是一门单线程的语言,在JavaScript
中,所有的任务都可以分为
-
同步任务:立即执行的任务,同步任务一般会直接进入到主线程中执行
-
异步任务:异步执行的任务,比如
ajax
网络请求,setTimeout
定时函数等
同步任务与异步任务的运行流程图如下:
从上面我们可以看到,同步任务进入主线程,即主执行栈,异步任务进入任务队列,主线程内的任务执行完毕为空,会去任务队列读取对应的任务,推入主线程执行。上述过程的不断重复就事件循环
二、宏任务与微任务
微任务
一个需要异步执行的函数,执行时机是在主函数执行结束之后、当前宏任务结束之前
常见的微任务有:
-
Promise.then
-
MutaionObserver
-
Object.observe(已废弃;Proxy 对象替代)
-
process.nextTick(Node.js)
宏任务
宏任务的时间粒度比较大,执行的时间间隔是不能精确控制的,对一些高实时性的需求就不太符合
常见的宏任务有:
- script (可以理解为外层同步代码)
- setTimeout/setInterval
- UI rendering/UI事件
- postMessage、MessageChannel
- setImmediate、I/O(Node.js)
这时候,事件循环,宏任务,微任务的关系如图所示
三、async与await
async
async
是异步的意思,await
则可以理解为 async wait
。所以可以理解async
就是用来声明一个异步方法,而 await
是用来等待异步方法执行
async
函数返回一个promise
对象,下面两种方法是等效的
function f() {
return Promise.resolve('TEST');
}
// asyncF is equivalent to f!
async function asyncF() {
return 'TEST';
}
await
正常情况下,await
命令后面是一个 Promise
对象,返回该对象的结果。如果不是 Promise
对象,就直接返回对应的值
async function f(){
// 等同于
// return 123
return await 123
}
f().then(v => console.log(v)) // 123
不管await
后面跟着的是什么,await
都会阻塞后面的代码
async function fn1 (){
console.log(1)
await fn2()
console.log(2) // 阻塞
}
async function fn2 (){
console.log('fn2')
}
fn1()
console.log(3)
上面的例子中,await
会阻塞下面的代码(即加入微任务队列),先执行 async
外面的同步代码,同步代码执行完,再回到 async
函数中,再执行之前阻塞的代码
所以上述输出结果为:1
,fn2
,3
,2
面试官:DOM常见的操作有哪些?
一、DOM
任何 HTML
或XML
文档都可以用 DOM
表示为一个由节点构成的层级结构
<div>
<p title="title">
content
</p >
</div
上述结构中,div
、p
就是元素节点,content
就是文本节点,title
就是属性节点
二、操作
DOM
操作才能有助于我们理解框架深层的内容
下面就来分析DOM
常见的操作,主要分为:
- 创建节点
- 查询节点
- 更新节点
- 添加节点
- 删除节点
创建节点
createEleme
创建新元素,接受一个参数,即要创建元素的标签名
const divEl = document.createElement("div");
createTextNode
创建一个文本节点
const textEl = document.createTextNode("content");
获取节点
querySelector
传入任何有效的css
选择器,即可选中单个 DOM
元素(首个):
document.querySelector('.element')
document.querySelector('#element')
document.querySelector('div')
document.querySelector('[name="username"]')
document.querySelector('div + p > span')
如果页面上没有指定的元素时,返回 null
关于获取DOM
元素的方法还有如下,就不一一述说
document.getElementById('id属性值');返回拥有指定id的对象的引用
document.getElementsByClassName('class属性值');返回拥有指定class的对象集合
document.getElementsByTagName('标签名');返回拥有指定标签名的对象集合
document.getElementsByName('name属性值'); 返回拥有指定名称的对象结合
document/element.querySelector('CSS选择器'); 仅返回第一个匹配的元素
document/element.querySelectorAll('CSS选择器'); 返回所有匹配的元素
document.documentElement; 获取页面中的HTML标签
document.body; 获取页面中的BODY标签
document.all['']; 获取页面中的所有元素节点的对象集合型
更新节点
innerHTML
不但可以修改一个DOM
节点的文本内容,还可以直接通过HTML
片段修改DOM
节点内部的子树
// 获取<p id="p">...</p >
var p = document.getElementById('p');
// 设置文本为abc:
p.innerHTML = 'ABC'; // <p id="p">ABC</p >
// 设置HTML:
p.innerHTML = 'ABC <span style="color:red">RED</span> XYZ';
// <p>...</p >的内部结构已修改
innerText、textContent
两者的区别在于读取属性时,innerText
不返回隐藏元素的文本,而textContent
返回所有文本
添加节点
innerHTML
如果这个DOM节点是空的,例如,<div></div>
,那么,直接使用innerHTML = '<span>child</span>'
就可以修改DOM
节点的内容,相当于添加了新的DOM
节点
如果这个DOM节点不是空的,那就不能这么做,因为innerHTML
会直接替换掉原来的所有子节点
appendChild
把一个子节点添加到父节点的最后一个子节点
举个例子
<!-- HTML结构 -->
<p id="js">JavaScript</p>
<div id="list">
<p id="java">Java</p>
<p id="python">Python</p>
<p id="scheme">Scheme</p>
</div>
<!--添加一个p元素-->
<script>
const js = document.getElementById('js')
js.innerHTML = "JavaScript"
const list = document.getElementById('list');
list.appendChild(js);
</script>
现在HTML
结构变成了下面
<!-- HTML结构 -->
<div id="list">
<p id="java">Java</p >
<p id="python">Python</p >
<p id="scheme">Scheme</p >
<p id="js">JavaScript</p > <!-- 添加元素 -->
</div>
上述代码中,我们是获取DOM
元素后再进行添加操作,这个js
节点是已经存在当前文档树中,因此这个节点首先会从原先的位置删除,再插入到新的位置
如果动态添加新的节点,则先创建一个新的节点,然后插入到指定的位置
const list = document.getElementById('list'),
const haskell = document.createElement('p');
haskell.id = 'haskell';
haskell.innerText = 'Haskell';
list.appendChild(haskell);
insertBefore
把子节点插入到指定的位置,使用方法如下:
parentElement.insertBefore(newElement, referenceElement)
子节点会插入到referenceElement
之前
删除节点
删除一个节点,首先要获得该节点本身以及它的父节点,然后,调用父节点的removeChild
把自己删掉
// 拿到待删除节点:
const self = document.getElementById('to-be-removed');
// 拿到父节点:
const parent = self.parentElement;
// 删除:
const removed = parent.removeChild(self);
removed === self; // true
删除后的节点虽然不在文档树中了,但其实它还在内存中,可以随时再次被添加到别的位置
面试官:说说你对BOM的理解,常见的BOM对象你了解哪些?
一、是什么
BOM
(Browser Object Model),浏览器对象模型,提供了内容与浏览器窗口进行交互的对象
其作用就是跟浏览器做一些交互效果,比如如何进行页面的后退,前进,刷新
二、window
Bom
的核心对象是window
,它表示浏览器的一个实例
在浏览器中 即是浏览器窗口的一个接口,又是全局对象
三、location
window对象给我们提供了一个location属性用于获取或设置窗体的URL,并且可以用于解析URL。
四、navigator
navigator
对象主要用来获取浏览器的属性,区分浏览器类型。属性较多,且兼容性比较复杂
五、screen
保存的是浏览器窗口外面的客户端显示器的信息,比如像素宽度和像素高度
六、history
history
对象主要用来操作浏览器URL
的历史记录,可以通过参数向前,向后,或者向指定URL
跳转
面试官:Javascript本地存储的方式有哪些?区别及应用场景?
javaScript
本地缓存的方法我们主要讲述以下四种:
- cookie
- sessionStorage
- localStorage
- indexedDB
cookie
Cookie
,用户发送请求时会携带cookie到服务端,服务端会判断cookie来识别用户,cookie存储一般不超过 4KB 的小型文本数据,但是cookie
在每次请求中都会被发送,如果不使用 HTTPS
并对其加密,其保存的信息很容易被窃取,
localStorage
HTML5
新方法,IE8及以上浏览器都兼容
特点
- 生命周期:持久化的本地存储,除非主动删除数据,否则数据是永远不会过期的
- 存储的信息在同一域中是共享的
- 当本页操作(新增、修改、删除)了
localStorage
的时候,本页面不会触发storage
事件,但是别的页面会触发storage
事件。 - 大小:5M(跟浏览器厂商有关系)
localStorage
本质上是对字符串的读取,如果存储内容多的话会消耗内存空间,会导致页面变卡- 受同源策略的限制
sessionStorage
sessionStorage
和 localStorage
使用方法基本一致,唯一不同的是生命周期,一旦页面(会话)关闭,sessionStorage
将会删除数据
扩展的前端存储方式
indexedDB
是一种低级API,用于客户端存储大量结构化数据(包括, 文件/ blobs)。
面试官:什么是防抖和节流?有什么区别?如何实现?
一、是什么
本质上是优化高频率执行代码的一种手段
为了优化体验,需要对这类事件进行调用次数的限制,对此我们就可以采用 防抖(debounce) 和 节流(throttle) 的方式来减少调用频率
定义
- 节流: n 秒内只运行一次,若在 n 秒内重复触发,只有一次生效
- 防抖: n 秒后在执行该事件,若在 n 秒内被重复触发,则重新计时
一个经典的比喻:
电梯第一个人进来后,15秒后准时运送一次,这是节流
电梯第一个人进来后,等待15秒。如果过程中又有人进来,15秒等待重新计时,直到15秒后开始运送,这是防抖
二、区别
相同点:
- 都可以通过使用
setTimeout
实现 - 目的都是,降低回调执行频率。节省计算资源
不同点:
- 函数防抖,在一段连续操作结束后,处理回调,利用
clearTimeout
和setTimeout
实现。函数节流,在一段连续操作中,每一段时间只执行一次,频率较高的事件中使用来提高性能 - 函数防抖关注一定时间连续触发的事件,只在最后执行一次,而函数节流一段时间内只执行一次
面试官:如何通过js判断一个数组?
1.通过instanceof判断
instanceof运算符用于检验构造函数的prototype属性是否出现在对象的原型链中的任何位置,返回一个布尔值
2.通过constructor判断
实例的构造函数属性constructor指向构造函数,通过constructor属性可以判断是否为一个数组
3.通过Object.prototype.toString.call()判断
Object.prototype.toString.call()可以获取到对象的不同类型
4.通过Array.isArray()判断
Array.isArray()用于确定传递的值是否是一个数组,返回一个布尔值
面试官:说说你对作用域链的理解?
一、作用域
我们一般将作用域分成:
-
全局作用域
-
函数作用域
-
块级作用域
二、作用域链
function fn(){
let age=18;
function inner(){
// 函数内访问变量时,优先使用自己内部声明变量
// 如果没有,尝试访问外部函数作用域的变量
// 如果外部函数作用域也没有这个变量,继续往下找
// 知道找到全局,如果全局都没有,就报错
let count=100;
console.log(count);
console.log(age);
console.log(name);
}
inner()
}
fn()
如上图:
- inner函数访问自身的变量时,没有找到。则访问外部中的变量,还是没有找到就往全局找
- 这样一层一层形成的链叫做作用域链
- 当然全局还没有找到最外面一层是null 会报错
面试官:什么是浏览器缓存?
浏览器缓存就是把一个已经请求过的web资源(如html页面,图片,JS,数据)拷贝一份放在浏览器中。缓存会根据进来的请求保存输入内容的副本。当下一个请求到来的时候,如果是相同的URL,浏览器会根据缓存机制决定是直接使用副本响应访问请求还是向源服务器再次发起请求。
使用缓存的原因
(1)减少网络带宽消耗
(2)降低服务器压力
(3)减少网络延迟
浏览器的缓存机制
过期机制:指的是缓存副本的有效期。一个缓存的副本必须满足以下条件,浏览器会认为它是有效的,足够新的:
1.含有完整的过期时间控制头信息(HTTP协议报头),并且仍在有效期内
2.浏览器已经使用过这个缓存的副本,并且会在一个会话中已经检查过新鲜度(即服务器上的资源是否发生改变)
满足以上两种情况的一种,浏览器会直接从缓存中获取副本进行渲染
校验值(验证机制):服务器返回资源的时候有时在控制头信息带上这个资源的实体标签Etag(Entity Tag),它可以用来作为浏览器再次请求过程中的校验标识,如果发现校验标识不匹配,说明资源已经被修改或者过期,浏览器需要重新获取资源内容。
浏览器缓存的控制
(1)使用meta标签
Web开发者可以在HTML页面的节点中加入标签,代码如下:
<meta http-equiv="Pragma" content="no-cache">
<!- Pragma是http1.0版本中给客户端设定缓存方式之一,具体作用会在后面详细介绍 -->
上述代码的作用是告诉浏览器当前页面不被缓存,每次访问都需要去服务器上拉取。但是这种禁用缓存的形式很有限:
1.仅有IE才能识别这段meta标签的含义,其他主流浏览器仅识别”Cache-Control:no-store”的meta标签
2.在IE中识别到该meta标签的含义,并不一定会在请求字段中加上Pragma,但的确会让当前页面每次都发起新请求(仅限页面,页面上的资源则不受影响)
(2)使用缓存有关的HTTP消息报头
在HTTP请求和响应的消息报头中,常见与缓存有关的消息报头有:
不同字段间的比较:
在配置Last-Modified/Etag的情况下,浏览器再次访问统一的URI资源,还是会发送请求到服务器询问文件是否已经修改,如果没有,服务器只发送一个304给浏览器,告诉浏览器直接从自己的本地缓存取数据,如果修改过,那就将整个数据重新发送给浏览器。
Cache-Control/Expires则不同,如果检测到本地的缓存还在有效的时间范围内,浏览器直接使用本地副本,不会发送任何请求。两者一起使用的时候,Cache-Control/Expires的优先级要高于Last-Modified/Etag。
即当当地副本根据Cache-Control/Expires发现还在有效期内,则不会再次发送请求去服务器询问修改时间(Last-Modified)或者实体标识符(Etag)了。
一般情况下,使用Cache-Control/Expires会配合Last-Modified/Etag一起使用,因为即使浏览器设置缓存时间,当用户点击“刷新”按钮时,浏览器会忽略缓存继续向服务器发送请求,这是Last-Modified/Etag将能够很好的利用304,从而减少响应开销。
浏览器向服务器请求资源的过程
关于缓存的两个概念
强缓存:
用户发送的请求,直接从客户端缓存中获取,不发送请求到服务器,不与服务器发生交互行为。
协商缓存:
用户发送请求,发送到服务器之后,由服务器判定是否从缓存中获取资源。
两者共同点:客户端获取的数据最后都是熊客户端的缓存中取得。
两者区别:从名字就可以看出,强缓存不与服务器发生交互,而协商缓存则需要需服务器发生交互。
面试官:什么是原型和原型链?
一、原型
①所有引用类型都有一个__proto__(隐式原型) 属性,属性值是一个普通对象
②所有函数都有一个prototype(原型),属性值是一个普通对象
③所有引用类型的__proto__属性指向他的构造函数的prototype
var a = [1,2,3];
a.__proto__ === Array.prototype; // true
二、原型链
当访问一个对象的某个属性时,会先在这个对象本身属性上查找,如果没有找到,则会去它的__proto__隐式原型上查找,即它的构造函数的prototype,如果还没有找到就会再在构造函数的prototype的__proto__中查找,这样一层一层向上查找就会形成一个链式结构,我们称为原型链。
举例,有以下代码
function Parent(month){
this.month = month;
}
var child = new Parent('Ann');
console.log(child.month); // Ann
console.log(child.father); // undefined
在child中查找某个属性时,会执行下面步骤
:
访问链路
为:
①一直往上层查找,直到到null还没有找到,则返回
undefined
②Object.prototype.__proto__ === null
③所有从原型或更高级原型中的得到、执行的方法,其中的this在执行时,指向当前这个触发事件执行的对象
面试官:JS中null和undefined的区别
-
相同点
都是是 JavaScript 基本类型之一
if 判断语句中,两者都会被转换为false -
不同点
Number转换的值不同Number(null)输出为0, Number(undefined)输出为NaN
Undefined,当声明的变量还未被初始化时,变量的默认值为undefined。
Null,null用来表示尚未存在的对象,常用来表示函数企图返回一个不存在的对象。
面试官:JS中的“use strict” 严格模式
JavaScript 除了提供正常模式外,还提供了严格模式(strict mode)。ES5 的严格模式是采用具有限制性JavaScript变体的一种方式,即在严格的条件下运行 JS 代码。
严格模式在 IE10 以上版本的浏览器中才会被支持,旧版本浏览器中会被忽略。
严格模式对正常的 JavaScript 语义做了一些更改:
消除了 Javascript 语法的一些不合理、不严谨之处,减少了一些怪异行为。
消除代码运行的一些不安全之处,保证代码运行的安全。
提高编译器效率,增加运行速度。
禁用了在 ECMAScript 的未来版本中可能会定义的一些语法,为未来新版本的 Javascript 做好铺垫。比如一些保留字如:class,enum,export, extends, import, super 不能做变量名
面试官:同步与异步的区别
同步:同步是指一个进程在执行某个请求的时候,如果该请求需要一段时间才能返回信息,那么这个进程会一直等待下去,直到收到返回信息才继续执行下去。
异步:异步是指进程不需要一直等待下去,而是继续执行下面的操作,不管其他进程的状态,当有信息返回的时候会通知进程进行处理,这样就可以提高执行的效率了,即异步是我们发出的一个请求,该请求会在后台自动发出并获取数据,然后对数据进行处理,在此过程中,我们可以继续做其他操作,不管它怎么发出请求,不关心它怎么处理数据。
打个比方:同步的时候,你在写程序,然后你妈妈叫你马上拖地,你就必须停止写程序然后拖地,没法同时进行。而异步则不需要按部就班,可以在等待那个动作的时候同时做别的动作,打个比方:你在写程序,然后你妈妈让你马上拖地,而这时你就贿赂你弟弟帮你拖地,于是结果同样是拖好地,你可以继续敲你的代码而不用管地是怎么拖
面试官:箭头函数与普通函数的区别
一,普通函数可以有匿名函数,也可以有具体名函数,但是箭头函数都是匿名函数。
// 具名函数
function func(){
// code
}
// 匿名函数
let func=function(){
// code
}
// 箭头函数全都是匿名函数
let func=()=>{
// code
}
二、箭头函数不能用于构造函数,不能使用new
普通函数可以用于构造函数,以此创建对象实例。
三、箭头函数中this的指向不同
在普通函数中,this总是指向调用它的对象,如果用作构造函数,this指向创建的对象实例。
箭头函数没有prototype
(原型),所以箭头函数本身没有this
箭头函数的this
指向在定义的时候继承自外层第一个普通函数的this
面试官:JS 对象和数组的遍历方法,以及方式的比较
一、JS 遍历数组
1、for 循环遍历数组
// 1、for循环
let arr = ['d', 'a', 'w', 'n'];
for (let i = 0; i < arr.length; i++){
console.log(arr[i]);
}
这种直接使用for循环的方法是最普遍的遍历数组和对象的方法;
2、使用for ……in 遍历数组
//2、for……in 循环
let arr = ['d', 'a', 'w', 'n'];
for (let key in arr) {
console.log(arr[key]);
}
3、for……of 遍历数组
//3、for……of 遍历数组
let arr = ['d', '