HTML
1、什么是HTML语义化?
在编程中,语义指的是一段代码的含义,这个HTML元素有什么作用,扮演了什么样的角色。简单的概括为:在适当的位置使用适当的标签,用正确的标签做正确的事情
2、HTML5新增语义元素?
Header nav article section aside footer main stong em small
3、为什么要语义化?
- 清晰的代码结构: 使页面没有css的情况下,也能够呈现出很好的内容结构
- 有利于SEO: 爬虫依赖标签来确定关键字的权重,因此可以和搜索引擎建立良好的沟通,帮助爬虫抓取更多的有效信息
- 提升用户体验: 例如title、alt可以用于解释名称或者解释图片信息,以及label标签的灵活运用。
- 便于团队开发和维护: 语义化使得代码更具有可读性,让其他开发人员更加理解你的html结构,减少差异化。
- 方便其他设备解析: 如屏幕阅读器、盲人阅读器、移动设备等,以有意义的方式来渲染网页。
4、b和strong的区别?
两者虽然在网页中显示效果一样,但实际目的不同。
<b>
这个标签对应bold ,即文本加粗,其目的仅仅是为了加粗显示文本,是一种样式/风格需求
<strong>
这个标签意思是加强,表示该文本比较重要,提醒读者/终端注意。为了达到这个目的,浏览器等终端将其加粗显示;
<b>
为了加粗而加粗,<strong>
为了标明重点而加粗。
最重要的区别的就是样式标签与语义化标签的区别。最容易理解的场景就是盲人朋友使用阅读设备阅读网页时: <strong>
会重读,<b>
不会
5、Iframe有哪些缺点?
iframe会阻塞主页面的Onload事件;搜索引擎的检索程序无法解读这种页面,不利于SEO;
iframe和主页面共享连接池,而浏览器对相同域的连接有限制,所以会影响页面的并行加载;
使用iframe之前需要考虑这两个缺点。如果需要使用iframe,最好是通过javascript动态给iframe添加src属性值,这样可以绕开以上两个问题。
6、说说减少DOM数量的方法?
- 可以使用伪元素,阴影实现的内容尽不使用DOM实现,如清除浮动、样式实现等;
- 按需加载,减少不必要的渲染;
- 结构合理,语义化标签,减少代码;
7、一次性给你大量DOM元素怎么优化?
1.缓存dom对象:首先不管在什么场景下。操作Dom一般首先会去访问Dom ,尤其是像循环遍历这种时间复杂度可能会比较高的操作。那么可以在循环之前就将主节点,不必循环的Dom节点先获取到,那么在循环里就可以直接引用,而不必去重新查询
// 不好的做法
for (let i = 0; i < 10; i++) {
document.getElementById("temp").innerHTML = ""
document.getElementById("temp").innerHTML += "<p>temp</p>";
}
// 改进的做法
let temp = document.getElementById(temp);
for (let i = 0; i < 10; i++) {
temp.innerHTML = "";
temp.innerHTML += "<p>temp</p>";
}
//再改进
let temp = document.getElementById("temp");
let fragments = ""
for (let i = 0; i < 10; i++) {
fragments += "<p>temp</p>";
}
temp.innerHTML = "";
temp.innerHTML = fragments;
2.文档片段: 利用document.createDocumentFragment()方法创建文档碎片节点,创建的是一个虚拟的节点对象。向这个节点添加dom节点,修改dom节点并不会影响到真实的dom结构。我们可以利用这一点先将我们需要修改的dom一并修改完,保存至文档碎片中,然后用文档碎片一次性的替换真实的dom节点。与虚拟dom类似,同样达到了不频繁修改dom而导致的重排跟重绘的过程。
//创建10个段落,常规的方式
for (let i = 0; i < 10; i++) {
let p = document.createElement("p");
let oTxt = document.createTextNode("段落" + i);
p.appendChild(oTxt);
document.body.appendChild(p);
}
//使用了createDocumentFragment()的程序
let pFragment = document.createDocumentFragment();
for (let i = 0; i < 10; i++) {
let p = document.createElement("p");
let oTxt = document.createTextNode("段落" + i);
p.appendChild(oTxt);
pFragment.appendChild(p);
}
document.body.appendChild(pFragment);
3.用innerhtml代替高频的appendChild
4.虚拟dom
8、Html5有哪些新特性?
1.拖拽释放(Drag and drop) API
2.语义化更好的内容标签( header,nav,footer,aside,article,section,main )
3.音频、视频API(audio,video)
4.画布(Canvas) API
5.地理(Gealocation) API
6.本地离线存储localStorage长期存储数据,浏览器关闭后数据不丢失;
7.sessionStorage的数据在浏览器关闭后自动删除
8.表单控件, calendar、date、 time、 email、url、search
9.新的技术webworker, websocket, Geolocation
9、如何区分html和html5?
1.文档类型声明
<!-- html声明 -->
<!DOCTYPE html PUBLIC "-/ /W3C/ /DTD XHTML 1.0 Transitional/ /EN" "http://mw.w3.org/TR/xhtn11/DTD/xhtml transitional.dtd">
<!-- html5声明 -->
<!DOCTYPE html>
2.结构语义
Html:没有体现结构语义化的标签,通常都是这样来命名的<div id="header"></div>
, 这样表示网站的头部
Html5:在语义上却有很大的优势,提供了一些新的HTML5标签比如: article、 footer、header、nav、 section , 这些通俗易懂
10、前端如何处理网站seo?
1.突出重要内容
合理的设计title 、description 和keywords
<title>
标题:只强调重点即可,尽量做到每个页面的<title>
标题中不要设置相同的内容。
<meta keywords>
标签:关键词,列举出几个页面的重要关键字即可,切记过分堆砌。
<meta description>
标签:网页描述,需要高度概括网页内容,切记不能太长、 过分堆砌关键词,每个页面也要有所不同。
2.语义化书写HTML代码,符合W3C标准
尽量让代码语义化,在适当的位置使用适当的标签,用正确的标签做正确的事。让阅读源码者和”蜘蛛”都一目了然。比如: h1-h6是用于标题类的,<nav>
标签是用来设置页面主导航,列表形式的代码使用ul或ol ,重要的文字使用strong等。
3.<a>
链接
页内链接,要加title 属性加以说明,让访客和”蜘蛛”知道。而外部链接,链接到其他网站的,则需要加上rel=“nofollow” 属性,告诉"蜘蛛” 不要爬,因为一旦"蜘蛛”爬了外部链接之后,就不会再回来了。
4.正文标题要用h1标签
h1标签自带权重,"蜘蛛”认为它最重要,一个页面有且最多只能有一个H1标签,放在该页面最重要的标题上面,如首页的logo.上可以加H1标签。副标题用<h2>
标签,而其它地方不应该随便乱用h标题标签。
5.<img>
应使用"alt"属性加以说明
当网络速度很慢,或者图片地址失效的时候,就可以体现出alt属性的作用,他可以让用户在图片没有显示的时候知道这个图片的作用。同时为图片设置高度和宽度,可提高页面的加载速度。
6.表格应该使用<caption>
表格标题标签
caption元素定义表格标题。caption 标签必须紧随table标签之后
7.<strong>
、<em>
标签
需要强调是使用<strong>
标签在搜索引擎中能够得到高度的重视,它能突出关键词,表现重要的内容,<em>
标签强调效果仅次于<strong>
标签; <b>
、<i>
标签:只是用于显示效果时使用,在SEO中不会起任何效果。
8.重要内容不要用JS输出
因为”蜘蛛" 不会读取JS里的内容,所以重要内容必须放在HTML里。前端框架针对SEO的缺陷,可通过服务端渲染弥补
9.尽少使用iframe框架
因为"蜘蛛”一般不会读取其中的内容。
10.搜索引擎会过滤掉display:none其中的内容
11.蜘蛛只能抓取a标签中href
<a href= "Default.aspx?id=1" >测试</a>
最好后面不要带参数<a href= "Default.aspx" >测试</a>
如果带上参数蜘蛛不会考虑的。这样的话,就需要用到URL写了。
12.蜘蛛不会执行JavaScript
换句话说如果在a标签中使用了onclick蜘蛛是不会抓到的。
13.蜘蛛只能抓到get请求的页面,不会抓到post请求的页面
14.我们希望网页的前台页面全部被蜘蛛抓到
但是不希望后台页面被蜘蛛抓到,蜘蛛可没有那么智能,知道你的网站哪个是前台页面,哪个是后台页面。这里就需要创建一个名为"robots.txt” (注意robots.txt是一个协议,不是命令, -般最好要遵守的robots.txt是搜索引擎搜索该网站时的第一个文件。
11、JavaScript标签的async和defer属性的区别?
两者共同点都是使脚本异步加载
defer 属性在文档解析完成后再执行这个脚本文件,这样的话就能使页面的渲染不被阻塞。多个设置了 defer 属性的脚本是顺序执行的
async 属性当脚本加载完成后立即执行js脚本,这个时候如果文档没有解析完成的话同样会阻塞。多个 async 属性的脚本的执行顺序是不可预测的,一般不会按照代码的顺序依次执行。
当一个script标签内同时包含defer与async属性时,只会触发async ,不会触发defer ,除非浏览器不兼容async。
12、src和href的区别?
src 用于替换当前元素,href 用于在当前文档和引用资源之间确立联系。
src 是 source 的缩写,指向外部资源的位置,指向的内容将会嵌入到文档中当前标签所在位置;在请求 src 资源时会将其指向的资源下载并应用到文档内,例如 js 脚本,img 图片和 frame 等元素。当浏览器解析到该元素时,会暂停其他资源的下载和处理,直到将该资源加载、编译、执行完毕,图片和框架等元素也如此,类似于将所指向资源嵌入当前标签内。这也是为什么将 js 脚本放在底部而不是头部。
href 是 Hypertext Reference 的缩写,指向网络资源所在位置,建立和当前元素(锚点)或当前文档(链接)之间的链接,如果我们在文档中添加<link href="common.css" rel="stylesheet"/>
那么浏览器会识别该文档为 css 文件,就会并行下载资源并且不会停止对当前文档的处理。这也是为什么建议使用 link 方式来加载 css,而不是使用@import 方式。
13、Doctype的作用?严格模式和混杂模式的区别?
DOCTYPE是document type (文档类型) 的缩写。<!DOCTYPE >
声明位于文档的最前面,处于标签之前,它不是html标签。主要作用是告诉浏览器的解析器使用哪种HTML规范或者XHTML规范来解析页面。
严格模式和混杂模式都是浏览器的呈现模式,浏览器究竟使用混杂模式还是严格模式呈现页面与网页中的DTD(文件类型定义)有关,DTD里面包含了文档的规则。
严格模式:又称标准模式,是指浏览器按照W3C标准来解析代码,呈现页面
混杂模式:又称为怪异模式或者兼容模式,是指浏览器按照自己的方式来解析代码,使用一种比较宽松的向后兼容的方式来显示页面。
14、Meta有哪些属性,作用是什么?
meta标签用于描述网页的元信息,如网站作者、描述、关键词,meta通过name=xxx和content=xxx的形式来定义信息,常用设置如下:
meta元素包含四大属性
charset属性 声明了页面的字符编码 常用的值: UTF-8(Unicode字符编码)、 ISO-8859-1(拉J字母表的字符编码)
content属性 通常配合name或http-equiv使用,能够给这两个属性提供一个值
http-equiv属性可用用做 HTTP头部的某些作用,通过定义该属性可以改变服务器和用户代理的行为。
name属性 用于定义页面的元数据。他不能与http-equiv、charset共存。通常是content配合使用。
15、前端性能优化
内容优化
(1)减少HTTP请求数:这条策略是最重要最有效的,因为一个完整的请求要经过DNS寻址,与服务器建立连接,发送数据,等待服务器响应,接收数据这样一个消耗时间成本和资源成本的复杂的过程。常见方法:合并多个CSS文件和js文件,利用CSS Sprites整合图像,Inline Images(使用 data:URL scheme在实际的页面嵌入图像数据 ),合理设置HTTP缓存等。
(2)减少DNS查找
(3)避免重定向(302.303)
(4)使用Ajax缓存
(5)延迟加载组件,预加载组件
(6)减少DOM元素数量:页面中存在大量DOM元素,会导致javascript遍历DOM的效率变慢。
(7)最小化iframe的数量:iframes 提供了一个简单的方式把一个网站的内容嵌入到另一个网站中。但其创建速度比其他包括JavaScript和CSS的DOM元素的创建慢了1-2个数量级。
(8)避免404:HTTP请求时间消耗是很大的,因此使用HTTP请求来获得一个没有用处的响应(例如404没有找到页面)是完全没有必要的,它只会降低用户体验而不会有一点好处。
Cookie优化
(1)减小Cookie大小
(2)针对Web组件使用域名无关的Cookie
css优化
(1)将CSS代码放在HTML页面的顶部(在文档内加载你的样式表,这样做的好处是:提高网页渲染性能,避免网页出现白屏或者是没有样式的内容)
(2)避免使用CSS表达式
(3)使用<link>
来代替@import
(4)避免使用Filters
js优化
(1)将JavaScript脚本放在页面的底部。
(2)将JavaScript和CSS作为外部文件来引用:在实际应用中使用外部文件可以提高页面速度,因为JavaScript和CSS文件都能在浏览器中产生缓存。
(3)缩小JavaScript和CSS
(4)删除重复的脚本
(5)最小化DOM的访问:使用JavaScript访问DOM元素比较慢。
(6)减少作用域链查找
(7)在 Javascript中使用”+”号来拼接字符串效率是比较低的,因为每次运行都会开辟新的内存并生成新的字符串变量,然后将拼接结果赋值给新变量。
图片优化
(1)合理控制图片大小
(2)通过CSS Sprites优化图片 (这是减少图像请求的有效方法,把所有的背景图像都放到一个图片文件中,然后通过CSS的background-image和background-position属性来显示图片的不同部分;合并后的图片会比分离的图片总和要小,这是因为它降低了图片自身的开销)
(3)不要在HTML中使用缩放图片
(4)图标尽量使用矢量图标
16、优雅降级和渐进增强?
渐进增强:
针对低版本浏览器进行构建页面,保证最基本的功能,然后再针对高级浏览器进行效果、交互等改进和追加功能达到更好的用户体验。
优雅降级:
一开始就构建完整的功能,然后再针对低版本浏览器进行兼容。
区别:
1.优雅降级是从复杂的现状开始,并试图减少用户体验的供给
2.渐进增强则是从一个非常基础的,能够起作用的版本开始,并不断扩充,以适应未来环境的需要
3.降级(功能衰减)意味着往回看;而渐进增强则意味着朝前看,同时保证其根基处于安全地带
CSS
1、让一个div水平垂直居中
div {
position: absolute;
left: 50%;
top: 50%;
margin-left: -250px;
margin-top: -250px;
width: 500px;
height: 500px;
background: yellow;
z-index: 1;
}
div {
position: absolute;
left: 50%;
top: 50%;
width: 500px;
height: 500px;
background: yellow;
z-index: 1;
transform: translate3d(-50%, -50%, 0);
}
.parent {
display: flex;
}
.child {
margin: auto;
}
2、介绍一下BFC 以及如何触发BFC
什么是BFC?
BFC全称 Block Formatting Context 即块级格式上下文,简单的说,BFC是页面上的一个隔离的独立容器,不受外界干扰或干扰外界
BFC的渲染规则是什么?
BFC是页面上的一个隔离的独立容器,不受外界干扰或干扰外界
计算BFC的高度时,浮动子元素也参与计算(即内部有浮动元素时也不会发生高度塌陷)
BFC的区域不会与float的元素区域重叠
BFC内部的元素会在垂直方向上放置
BFC内部两个相邻元素的margin会发生重叠
如何触发BFC?
根元素,即HTML元素
float不为 none
overflow的值不为 visible
position 为 absolute 或 fixed
display的值为 inline-block 或 table-cell 或 table-caption 或 grid
BFC在布局中的应用?
防止margin重叠(塌陷)
自适应多栏布局
清除内部浮动
3、display:none和visibility:hidden的区别
display:none
隐藏后的元素不占据任何空间
给子元素设置display:none 不会显示出来
修改后会引起回流
会影响计数器的计数(ol)
visibility:hidden
隐藏后的元素空间依旧保留
visibility具有继承性,给父元素设置visibility:hidden;子元素也会继承这个属性。但是如果重新给子元素设置visibility: visible,则子元素又会显示出来
修改后不会引起回流
不会影响计数器的计数(ol)
4、清除浮动的方法
设置父元素高度
设置父元素的高度解决的是元素浮动产生的父元素高度塌陷问题,其内部元素浮动影响并未彻底清除,且需要进行计算然后再设置, 比较固定,一旦元素的高度发生改变,父元素的高度也需要再次计算设置,不够灵活。除非内容高度固定一成不变,否则不推荐使用。
clear +空元素
在浮动元素下方添加空div,并给该元素写css样式:
{clear:both;height:0;veflow.hidden;}
伪元素+clear
使用自带的属性可以非常好的解决浮动影响。该方法直观有效,哪里需要清除就在哪里添加一个兄弟元素,设置clear属性即可,一般属性值都设置为both清除两侧的浮动,也可以根据实际需要清除左侧或右侧,灵活方便直观。在bootstrap 4.0框架中的clearfix应用了该访法,其是在父元素中设置了伪元素,并设置了伪元素隐藏。
.cearfix:after {
display: block;
clear: both;
content: "";
}
利用BFC
根据BFC的规则,计算BFC的高度时,浮动元素也参与计算。因此清除浮动,只需要触发一个BFC即可
5、Css盒模型
盒模型是css中的一种基础设计模式, web页面中的所有元素都可以当做一个盒模型,每一个盒模型都是由content, margin , padding和border等属性组合所构成的
CSS中的盒模型有两种: W3C标准模型和IE的传统模型。不同之处在于两者的计算方式不同。给元素设置宽度width和高度height,在w3c盒子模型中,width和height只是content部分,在IE盒模型中,这个width和height包括了 content、padding和border三个部分
6、Css选择器有哪些? 哪些属性可以继承?
选择器
标签名选择器
类选择器
ID选择器
相邻选择器(h1+p )
子选择器(ul> li)
后代选择器(li a)
通配符选择器(* )
属性选择器( a[rel=" external"] )
伪类选择器( a:hover, li:nth-child )
群组选择器( div,p )
继承属性
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:表格的宽度由什么决定
quotes:设置嵌套引用的引号类型
cursor:箭头可以变成需要的形状
非继承属性
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
7、flex:1的完整写法是?
flex属性是flex-grow、 flex-shrink、 flex-basis 的简写
flex-grow:定义项目的放大比例,默认为0 ,即如果存在剩余空间,也不放大
flex-shrink:定义了项目的缩小比例,默认为1 ,即如果空间不足,该项目将缩小
flex-basis:定义的是元素在主轴上的初始尺寸,所谓的初始尺寸就是元素在flex-grow和flex-shrink生效前的尺寸,浏览器根据这个属性,计算主轴是否有多余空间,默认值为auto,即项目的本来大小
flex:1的完整写法是
flex-grow: 1;
flex-shrink: 1;
flex-basis: 0%;
8、行内元素和块级元素什么区别
行内元素会在一条直线上排列(默认宽度只与内容有关) , 都是同一行的,水平方向排列。
块级元素各占据一行(默认宽度是它本身父容器的100% (和父元素的宽度一致 , 与内容无关) ,垂直方向排列。块级元素从新行开始,结束接着一个断行。
块级元素可以包含行内元素和块级元素。行内元素不能包含块级元素,只能包含文本或者其它行内元素。
行内元素与块级元素属性的不同,主要是盒模型属性上,行内元素设置width无效, height无效(可以设置line-height) , margin上下无效,padding上下无效
9、Link和@important的区别
link是XHTML标签,除了加载CSS外,还可以定义RSS等其他事务; @import属于CSS范畴,只能加载CSS。
link引用CSS时,在页面载入时同时加载; @import需要页面网页完全载入以后加载。所以会出现一开始没有css样式,闪烁一下出现样式后的页面(网速慢的情况下)
link是XHTML标签,无兼容问题;@import是在CSS2.1提出的,低版本的浏览器不支持。
link支持使用Javascript控制DOM去改变样式;而@import不支持。
10、响应式和自适应的区别
响应式开发一套界面,通过检测视口分辨率,针对不同客户端在客户端做代码处理,来展现不同的布局和内容;
自适应需要开发多套界面,通过检测视口分辨率,来判断当前访问的设备是pc端、平板、手机,从而请求服务层,返回不同的页面。
11、响应式布局实现方案
媒体查询
CSS3媒体查询可以让我们针对不同的媒体类型定义不同的样式,当重置浏览器窗口大小的过程中,页面也会根据浏览器的宽度和高度重新渲染页面。
百分比布局
通过百分比单位,可以使得浏览器中组件的宽和高随着浏览器的高度的变化而变化,从而实现响应式的效果。Bootstrap里面的栅格系统就是利用百分比来定义元素的宽高,CSS3支持最大最小高,可以将百分比和max(min)一起结合使用来定义元素在不同设备下的宽高。
缺点:计算困难。可以看出,各个属性中如果使用百分比,相对父元素的属性并不是唯一的。比如width和height相对于父元素的width和height,而margin、padding不管垂直还是水平方向都相对比父元素的宽度、border-radius则是相对于元素自身等等,造成我们使用百分比单位容易使布局问题变得复杂。
rem布局
REM是CSS3新增的单位,rem单位都是相对于根元素html的font-size来决定大小的,根元素的font-size相当于提供了一个基准,当页面的size发生变化时,只需要改变font-size的值,那么以rem为固定单位的元素的大小也会发生响应的变化。 因此,如果通过rem来实现响应式的布局,只需要根据视图容器的大小,动态的改变font-size即可(而em是相对于父元素的)。
视口单位
css3中引入了一个新的单位vw/vh,与视图窗口有关,vw表示相对于视图窗口的宽度,vh表示相对于视图窗口高度,除了vw和vh外,还有vmin和vmax两个相关的单位
从对比中我们可以发现,vw单位与百分比类似,但确有区别,前面我们介绍了百分比单位的换算困难,这里的vw更像"理想的百分比单位"。任意层级元素,在使用vw单位的情况下,1vw都等于视图宽度的百分之一。
Flex弹性布局,兼容性较差
Grid网格布局,兼容性较差
Columns栅格系统,往往需要依赖某个UI库,如Bootstrap
12、重排和重绘?如何避免?
什么是重绘重排
当我们改变了一个元素的尺寸位置属性时,会重新进行样式计算(computedstyle)布局(layout)绘制( paint )以及后面的所有流程,这种行为称为重排。
当改变了某个元素的颜色属性时不会重新触发布局,但还是会触发样式计算和绘制这就是重绘。
我们可以发现重排和重绘都会占用主线程,还有JS也会运行在主线程,所以就会出现抢占执行时间的问题,如果你写了一个不断导致重排重绘的动画,浏览器则需要在每一帧都运行样式计算布局和绘制的操作。
触发的一些因素
1.页面首次进入的渲染。
2.浏览器resize
3.元素位置和尺寸发生改变的时候
4.可见元素的增删
5.内容发生改变
6.字体的font的改变
7.CSS伪类激活
css避免
使用transform替代top等位移;
使用visibility替换display: none
避免使用table布局
尽可能在DOM树的最末端改变class
避免设置多层内联样式,尽量层级扁平
将动画效果应用到position属性为absolute或fixed的元素上
避免使用CSS表达式
将频繁重绘或者回流的节点设置为图层,比如video , iframe
CSS3硬件加速( GPU加速) , 可以是transform:translateZ(0)、opacity、 filters、 will-change,Will-change提前告诉浏览器元素会发生什么变化;
Javascript避免
避免频繁操作样式,合并操作
避免频繁操作DOM ,合并操作;
防抖节流控制频率;
避免频繁读取会引发回流/重绘的属性
对具有复杂动画的元素使用绝对定位
13、Css预处理器
CSS预处理器
为CSS增加编程特性的拓展语言,可以使用变量、简单逻辑判断、函数等基本编程技巧;CSS预处理器编译输出还是标准的CSS样式
Less、 Sass都是是动态的样式语言,是CSS预处理器,CSS上的一种抽象层。他们是一种特殊的语法/语言而编译成CSS。less变量符号是@ , Sass变量符号是$;
解决的问题
CSS语法不够强大,因为无法嵌套导致有很多重复的选择器
没有变量和合理的样式复用机制,导致逻辑上相关的属性值只能以字面量的形式重复输出,难以维护
常用规范
变量、嵌套语法、混入、@import. 运算、函数、继承
CSS预处理器带来的好处
CSS代码更加整洁,更易维护,代码量更少
修改更快,基础颜色使用变量,一处动处处动.
常用代码使用代码块,节省大量代码
CSS嵌套减少了大量的重复选择器,避免一些低级错误
变量、混入大大提升了样式的复用性
额外的工具类似颜色函数( lighten,darken ,transparentize等等) , mixins , loops ,这些方法使css更像一个真正的编程语言,让开发者能够有能力生成更加复杂的css样式。
14、em px rem vh vw的区别
px:表示像素,所谓像素就是呈现在我们显示器上的一个个小点,每个像素点都是大小等同的,所以像素为计量单位被分在了绝对长度单位中
em:是相对长度单位。相对于当前对象内文本的字体尺寸。如当前对行内文本的字体尺寸未被人为设置,则相对于浏览器的默认字体尺寸(1em = 16px)
rem:相对单位,相对的只是HTML根元素font-size的值。同理,如果想要简化font-size的转化,我们可以在根元素html中加入font-size: 62.5%
vw:就是根据窗口的宽度,分成100等份,100vw就表示满宽,50vw就表示一半宽。(vw 始终是针对窗口的宽),同理,vh则为窗口的高度
15、三栏布局(左右固定100px,中间自适应)
浮动+margin
左float:left; width:100px
右float:right; width:100px
中margin: 0 100px
绝对定位+margin
左top:0; left:0; width:100px
右top:0; right:0; width:100px
中margin: 0 100px
flex布局
左 width:100px;
右 width: 100px;
中 flex:1;
float + calc计算
左 width:100px; float: left;
右 width: 100px; float: right;
中 width: calc(100%-200px)
table布局
父 display:table table-layout: fixed;
左中右 display: table-cell;
左右 width:100px;
中 width: 100%
grid布局
父 display:grid grid-template-columns: 100px auto 100px;
16、弹性盒模型flexbox
Flexible Box 简称 flex,意为”弹性布局”,可以简便、完整、响应式地实现各种页面布局。采用flex布局的元素称为容器,容器中默认存在两条轴,主轴和交叉轴,呈90度关系。项目默认沿主轴排列
容器属性:
flex-direction::决定主轴的方向(即项目的排列方向)
flex-wrap::弹性元素永远沿主轴排列,那么如果主轴排不下,通过flex-wrap决定容器内项目是否可换行
flex-flow:flex-direction属性和flex-wrap属性的简写形式,默认值为row nowrap
justify-content:定义了项目在主轴上的对齐方式
align-items:定义项目在交叉轴上如何对齐
align-content:定义了多根轴线的对齐方式。如果项目只有一根轴线,该属性不起作用
容器成员属性:
order:定义项目的排列顺序。数值越小,排列越靠前,默认为0
flex-grow: 定义项目的放大比例(容器宽度>元素总宽度时如何伸展)默认为0,即如果存在剩余空间,也不放大
flex-shrink:定义了项目的缩小比例(容器宽度<元素总宽度时如何收缩),默认为1,即如果空间不足,该项目将缩小
flex-basis:设置的是元素在主轴上的初始尺寸,所谓的初始尺寸就是元素在flex-grow和flex-shrink生效前的尺寸,浏览器根据这个属性,计算主轴是否有多余空间,默认值为auto,即项目的本来大小,如设置了width则元素尺寸由width/height决定(主轴方向),没有设置则由内容决定
align-self:允许单个项目有与其他项目不一样的对齐方式,可覆盖align-items属性,默认值为auto,表示继承父元素的align-items属性,如果没有父元素,则等同于stretch
17、Grid网格布局
Grid 布局即网格布局,是一个二维的网格布局方式,由纵横相交的两组网格线形成的框架性布局结构,能够同时处理行与列。擅长将一个页面划分为几个主要区域,以及定义这些区域的大小、位置、层次等关系
18、单行溢出、多行溢出
单行溢出
p {
overflow: hidden;
line-height: 40px;
width: 400px;
height: 40px;
border: 1px solid red;
text-overflow: ellipsis;
white-space: nowrap;
}
多行溢出
p {
width: 400px;
border-radius: 1px solid red;
-webkit-line-clamp: 2;
display: -webkit-box;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
}
19、如何让浏览器支持一个小于12px的字体
zoom
zoom 的字面意思是“变焦”,可以改变页面上元素的尺寸,属于真实尺寸
zoom:50%,表示缩小到原来的一半
zoom:0.5,表示缩小到原来的一半
.span1 {
font-size: 12px;
display: inline-block;
zoom: 0.8;
}
-webkit-transform:scale()
针对chrome浏览器,加webkit前缀,用transform:scale()这个属性进行缩放
注意的是,使用scale属性只对可以定义宽高的元素生效,所以,下面代码中将span元素转为行内块元素
.span1 {
font-size: 12px;
display: inline-block;
-webkit-transform: scale(0.8);
}
20、display有哪些值及作用
block:块对象的默认值。用该值为对象之后添加新行
none:隐藏对象。与visibility属性的hidden值不同,其不为被隐藏的对象保留其物理空间
inline:内联对象的默认值。用该值将从对象中删除行
compact:分配对象为块对象或基于内容之上的内联对象
marker:指定内容在容器对象之前或之后。要使用此参数,对象必须和:after及:before 伪元素一起使用
inline-table:将表格显示为无前后换行的内联对象或内联容器
list-item:将块对象指定为列表项目。并可以添加可选项目标志
run-in:分配对象为块对象或基于内容之上的内联对象
table:将对象作为块元素级的表格显示
21、position的值
static(默认):在一般情况下,我们不需要特别的去声明它,但有时候遇到继承的情况,我们不愿意见到元素所继承的属性影响本身,从而可以用position:static取消继承,即还原元素定位的默认值。设置为 static 的元素,它始终会处于页面流给予的位置(static 元素会忽略任何 top、 bottom、left 或 right 声明)。一般不常用。
relative(相对定位):相对定位是相对于元素默认的位置的定位,它偏移的top,right,bottom,left 的值都以它原来的位置为基准偏移,而不管其他元素会怎么样。注意 relative 移动后的元素在原来的位置仍占据空间。
absolute(绝对定位):设置为 absolute 的元素,如果它的父容器设置了 position 属性,并且 position 的属性值为 absolute 或者 relative,那么就会依据父容器进行偏移。如果其父容器没有设置 position 属性,那么偏移是以 body 为依据。注意设置 absolute 属性的元素在标准流中不占位置。
fixed(固定定位):位置被设置为 fixed 的元素,可定位于相对于浏览器窗口的指定坐标。不论窗口滚动与否,元素都会留在那个位置。它始终是以 body 为依据的。注意设置 fixed 属性的元素在标准流中不占位置。
22、css3有哪些新特性
新增选择器 p:nth-child(n){color: rgba(255, 0, 0, 0.75)}
弹性盒模型 display: flex;
多列布局 column-count: 5;
媒体查询 @media(max-width: 480px){.box: {column-count: 1;}}
个性化字体 @font-face{font-family:BorderWeb;src:url(BORDERW0.eot);}
颜色透明度 color: rgba(255, 0, 0, 0.75);
圆角 border-radius: 5px;
渐变 background:linear-gradient(red, green, blue);
阴影 box-shadow:3px 3px 3px rgba(0, 64, 128, 0.3);
倒影 box-reflect: below 2px;
文字装饰 text-stroke-color: red;
文字溢出 text-overflow:ellipsis;
背景效果 background-size: 100px 100px;
边框效果 border-image:url(bt_blue.png) 0 10;
旋转 transform: rotate(20deg);
倾斜 transform: skew(150deg, -10deg);
位移 transform:translate(20px, 20px);
缩放 transform: scale(.5);
平滑过渡 transition: all .3s ease-in .1s;
动画 @keyframes anim-1 {50% {border-radius: 50%;}} animation: anim-1 1s;
JavaScript
1、javascript数据类型?存储差别?
基本数据类型:js 一共有六种基本数据类型,分别是 Undefined、Null、Boolean、Number、String,还有在 ES6 中新增的 Symbol 和 ES10 中新增的 BigInt 类型。Symbol 代表创建后独一无二且不可变的数据类型,它的出现我认为主要是为了解决可能出现的全局变量冲突的问题。BigInt 是一种数字类型的数据,它可以表示任意精度格式的整数,使用 BigInt 可以安全地存储和操作大整数,即使这个数已经超出了 Number 能够表示的安全整数范围。
复杂数据类型:指的是 Object 类型,所有其他的如 Array、Date 等数据类型都可以理解为 Object 类型的子类。
存储差别
原始数据类型直接存储在栈(stack)中的简单数据段,占据空间小、大小固定,属于被频繁使用数据,所以放入栈中存储。
引用数据类型存储在堆(heap)中的对象,占据空间大、大小不固定。如果存储在栈中,将会影响程序运行的性能;引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。
2、Javascript的原生对象、内置对象、宿主对象?
原生对象:独立于宿主环境的 ECMAScript 实现提供的对象
avaScript中的原生对象有Object、Function、Array、String、Boolean、Math、Number、Date、RegExp、Error、EvalError、RangeError、ReferenceError、SyntaxError、TypeError、URIError和Global。
内置对象:由 ECMAScript 实现提供的、独立于宿主环境的所有对象,在 ECMAScript 程序开始执行时出现
目前定义的内置对象只有两个:Global 和 Math
宿主对象:由 ECMAScript 实现的宿主环境提供的对象
所有的 BOM 和 DOM 对象都是宿主对象
3、数组常用的方法?
增:push()、unshift()、splice()、concat()
删:pop()、shift()、splice()、slice()
改:splice()
查:includes()、indexOf()、find()
排序:reverse()、sort()
转换:join()
迭代:some() every() forEach() filter() map()
4、字符串常用方法?
增: +、${}、concat
删: slice()、substr()、substring()
改:trim()、trimLeft()、trimRight()、repeat()
查:chatAt()、indexOf()、includes()、startWith()
转换:toLowerCase()、toUpperCase()、split()
迭代:some()、every()、forEach()、filter()、map()
5、==和===的区别
等于操作符用两个等于号( == )表示,如果操作数相等,则会返回 true
在JavaScript中存在隐式转换。等于操作符(==)在比较中会先进行类型转换,再确定操作数是否相等
全等操作符由 3 个等于号( === )表示,只有两个操作数在不转换的前提下相等才返回 true。即类型相同,值也需相同
6、深拷贝和浅拷贝的区别?
浅拷贝—浅拷贝是指复制对象的时候,只对第一层键值对进行独立的复制,如果对象内还有对象,则只能复制嵌套对象的地址
深拷贝—深拷贝是指复制对象的时候完全的拷贝一份对象,即使嵌套了对象,两者也相互分离,修改一个对象的属性,也不会影响另一个。其实只要递归下去,把那些属性的值仍然是对象的再次进入对象内部一 一进行复制即可。
浅拷贝
1.Object.assign()
2.slice()
3.concat()
4.扩展运算符
深拷贝
1.JSON.stringify(JSON.parse())
对象上的 value 值为 undefined 、 symbol和函数的键值对会被忽略,
NaN、无穷大、无穷小会被转为 null
2.递归深拷贝函数
// 深拷贝函数封装
function deepCopy(obj) {
// 根据obj的类型判断是新建一个数组还是对象
let newObj = Array.isArray(obj)? [] : {};
// 判断传入的obj存在,且类型为对象
if (obj && typeof obj === 'object') {
for(key in obj) {
// 如果obj的子元素是对象,则进行递归操作
if(obj[key] && typeof obj[key] ==='object') {
newObj[key] = deepCopy(obj[key])
} else {
// // 如果obj的子元素不是对象,则直接赋值
newObj[key] = obj[key]
}
}
}
return newObj // 返回新对象
}
// 对象的深拷贝案例
let obj1 = {
a: '1',
b: '2',
c: {
name: 'Demi'
}
}
let obj2 = deepCopy(obj1) //将obj1的数据拷贝到obj2
obj2.c.name = 'dingFY'
console.log(obj1) // {a: "1", b: "2", c: {name: 'Demi'}}
console.log(obj2) // {a: "1", b: "2", c: {name: 'dingFY'}}
7、什么是闭包?闭包的作用是什么?
定义:当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行。
三个特性:
函数嵌套函数
函数内部可以引用外部的参数和变量
参数和变量不会被垃圾回收机制回收
作用:使用闭包主要是为了设计私有的方法和变量,闭包的优点是可以避免全局变量的污染,缺点是闭包会常驻内存,会增大内存使用量,使用不当很容易造成内存泄露。
8、原型和原型链
在 js 中我们是使用构造函数来新建一个对象的,每一个构造函数的内部都有一个 prototype 属性值,这个属性值是一个对象,这个对象包含了可以由该构造函数所有实例共享的属性和方法。当我们使用构造函数新建一个实例后,在这个实例的内部将包含一个指针指向构造函数的 prototype 属性对应的值,在 ES5 中这个指针被称为对象的原型。
当我们访问一个对象的属性时,如果这个对象内部不存在这个属性,那么它就会去它的原型对象里找这个属性,这个原型对象又会有自己的原型,于是就这样一直找下去,也就是原型链的概念。原型链的终点是null
获取原型的方法
p._proto_
p.constructor.prototype
Object.getPrototypeOf§
9、作用域和作用域链
作用域,即变量和函数生效(也就是能被访问)的区域或集合
作用域分类
全局作用域:任何不在函数中或是大括号中声明的变量,都是在全局作用域下,全局作用域下声明的变量可以在程序的任意位置访问
函数作用域:函数作用域也叫局部作用域,如果一个变量是在函数内部声明的它就在一个函数作用域下面。这些变量只能在函数内部访问,不能在函数以外去访问
块级作用域:ES6引入了let和const关键字,和var关键字不同,在大括号中使用let和const声明的变量存在于块级作用域中。在大括号之外不能访问这些变量
作用域链
当在Javascript中使用一个变量的时候,首先Javascript引擎会尝试在当前作用域下去寻找该变量,如果没找到,再到它的上层作用域寻找,以此类推直到找到该变量或是已经到了全局作用域。如果在全局作用域里仍然找不到该变量,它就会在全局范围内隐式声明该变量或是直接报错
10、ES5实现继承的几种方法
原型链继承
原型链继承的原理很简单,直接让子类的原型对象指向父类实例,当子类实例找不到对应的属性和方法时,就会往它的原型对象,也就是父类实例上找,从而实现对父类的属性和方法的继承
// 父类
function Parent() {
this.name = 'Demi'
}
// 父类的原型方法
Parent.prototype.getName = function () {
return this.name
}
// 子类
function Child() {}
// 让子类的原型对象指向父类实例, 这样一来在Child实例中找不到的属性和方法就会到原型对象(父类实例)上寻找
Child.prototype = new Parent()
Child.prototype.constructor = Child // 根据原型链的规则,顺便绑定一下constructor, 这一步不影响继承, 只是在用到constructor时会需要
// 然后Child实例就能访问到父类及其原型上的name属性和getName()方法
const child = new Child()
child.name // 'Demi'
child.getName() // 'Demi'
缺点:
1.由于所有Child实例原型都指向同一个Parent实例, 因此对某个Child实例的父类引用类型变量修改会影响所有的Child实例
2.在创建子类实例时无法向父类构造传参, 即没有实现super()的功能
// 示例
function Parent() {
this.name = ['Demi']
}
Parent.prototype.getName = function () {
return this.name
}
function Child() {}
Child.prototype = new Parent()
Child.prototype.constructor = Child
// 测试
const child1 = new Child()
const child2 = new Child()
child1.name[0] = 'foo'
console.log(child1.name) // ['foo']
console.log(child2.name) // ['foo'] (预期是['Demi'], 对child1.name的修改引起了所有child实例的变化)
构造函数继承
构造函数继承,即在子类的构造函数中执行父类的构造函数,并为其绑定子类的this,让父类的构造函数把成员属性和方法都挂到子类的this上去,这样既能避免实例之间共享一个原型实例,又能向父类构造方法传参
function Parent(name) {
this.name = [name]
}
Parent.prototype.getName = function () {
return this.name
}
function Child() {
Parent.call(this, 'Demi') // 执行父类构造方法并绑定子类的this, 使得父类中的属性能够赋到子类的this上
}
//测试
const child1 = new Child()
const child2 = new Child()
child1.name[0] = 'foo'
console.log(child1.name) // ['foo']
console.log(child2.name) // ['Demi']
child2.getName() // 报错,找不到getName(), 构造函数继承的方式继承不到父类原型上的属性和方法
缺点:找不到父类原型上的属性和方法
组合式继承
既然原型链继承和构造函数继承各有互补的优缺点, 那么我们为什么不组合起来使用呢, 所以就有了综合二者的组合式继承
function Parent(name) {
this.name = [name]
}
Parent.prototype.getName = function () {
return this.name
}
function Child() {
// 构造函数继承
Parent.call(this, 'Demi')
}
//原型链继承
Child.prototype = new Parent()
Child.prototype.constructor = Child
//测试
const child1 = new Child()
const child2 = new Child()
child1.name[0] = 'foo'
console.log(child1.name) // ['foo']
console.log(child2.name) // ['Demi']
child2.getName() // ['Demi']
缺点:找每次创建子类实例都执行了两次构造函数(Parent.call()和new Parent()),虽然这并不影响对父类的继承,但子类创建实例时,原型中会存在两份相同的属性和方法,这并不优雅
寄生式继承
为了解决构造函数被执行两次的问题, 我们将指向父类实例改为指向父类原型, 减去一次构造函数的执行
function Parent(name) {
this.name = [name]
}
Parent.prototype.getName = function () {
return this.name
}
function Child() {
// 构造函数继承
Parent.call(this, 'Demi')
}
//原型链继承
// Child.prototype = new Parent()
Child.prototype = Parent.prototype //将`指向父类实例`改为`指向父类原型`
Child.prototype.constructor = Child
//测试
const child1 = new Child()
const child2 = new Child()
child1.name[0] = 'foo'
console.log(child1.name) // ['foo']
console.log(child2.name) // ['Demi']
child2.getName() // ['Demi']
11、ES6继承
class Parent {
constructor(name) {
this.name = name;
}
getName() {}
}
class Child extends Parent {
constructor(name, age) {
super(name);
this.age = age;
}
getAge() {}
}
let person = new Child('Demi', 24)
console.log(person) // {name: 'Demi', age: 24}
12、this对象的理解?
每一个方法或函数都会有一个this对象,this对象是方法(或函数)在执行时的那个环境,也可以说是这个函数在那个作用域下运行的。说的更通俗一点:this就相当于咱们平时说话时候说的“我”,“我家”的概念。就是说当一个方法在运行的时候,它是属于谁的。它在运行的时候它的家是谁家。
在 ES5 中,其实 this 的指向,始终坚持一个原理:this 永远指向最后调用它的那个对象
在实际开发中,this 的指向可以通过四种调用模式来判断。
1.第一种是函数调用模式,当一个函数不是一个对象的属性时,直接作为函数来调用时,this 指向全局对象。
2.第二种是方法调用模式,如果一个函数作为一个对象的方法来调用时,this 指向这个对象。
3.第三种是构造器调用模式,如果一个函数用 new 调用时,函数执行前会新创建一个对象实例,this 指向这个新创建的对象实例
4.第四种是 apply 、 call 和 bind 调用模式,这三个方法都可以显示的指定调用函数的 this 指向。其中 apply 方法接收两个参数:一个是 this 绑定的对象,一个是参数数组。call 方法接收的参数,第一个是 this 绑定的对象,后面的其余参数是传入函数执行的参数。也就是说,在使用 call() 方法时,传递给函数的参数必须逐个列举出来。bind 方法通过传入一个对象,返回一个 this 绑定了传入对象的新函数。这个函数的 this 指向除了使用 new 时会被改变,其他情况下都不会改变。
5.普通函数在执行的时候才绑定this。箭头函数在定义的时候就确定this,它的this指向在定义的时候继承自外层第一个普通函数的this。
13、执行上下文和执行栈
执行上下文
执行上下文就是当前 JavaScript 代码被解析和执行时所在环境的抽象概念, JavaScript 中运行任何的代码都是在执行上下文中运行。
执行上下文分类
全局执行上下文:这是默认的、最基础的执行上下文。不在任何函数中的代码都位于全局执行上下文中。它做了两件事:1. 创建一个全局对象,在浏览器中这个全局对象就是 window 对象。2. 将this指针指向这个全局对象。一个程序中只能存在一个全局执行上下文
函数执行上下文:每次调用函数时,都会为该函数创建一个新的执行上下文。每个函数都拥有自己的执行上下文,但是只有在函数被调用的时候才会被创建。一个程序中可以存在任意数量的函数执行上下文。每当一个新的执行上下文被创建,它都会按照特定的顺序执行一系列步骤,具体过程将在本文后面讨论。
Eval 函数执行上下文: 指的是运行在 eval 函数中的代码,很少用而且不建议使用
执行栈
执行栈,在其他编程语言中也被叫做调用栈,具有 LIFO(后进先出)结构,用于存储在代码执行期间创建的所有执行上下文。
当Javascript引擎开始执行你第一行脚本代码的时候,它就会创建一个全局执行上下文然后将它压到执行栈中
每当引擎碰到一个函数的时候,它就会创建一个函数执行上下文,然后将这个执行上下文压到执行栈中
引擎会执行位于执行栈栈顶的执行上下文(一般是函数执行上下文),当该函数执行结束后,对应的执行上下文就会被弹出,然后控制流程到达执行栈的下一个执行上下文
14、new操作符干了什么?
创建一个新的对象obj
将对象与构造函数通过原型链连接起来
将构造函数中的this绑定到新建的对象obj上
根据构造函数返回类型作判断,如果构造方法返回了一个对象,那么返回该对象,否则返回第一步创建的新对象
function myNew(Func, ...args) {
// 1.创建一个新对象
const obj = {}
// 2.新对象原型指向构造函数原型对象
obj.__proto__ = Func.prototype
// 3.将构建函数的this指向新对象
let result = Func.apply(obj, args)
// 4.根据返回值判断
return result instanceof object ? result : obj
}
//测试
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.say = function () {
console.log(this.name)
}
let p = myNew(Person, "Demi", 123)
console.log(p) // Person {name: "Demi", age: 123}
15、事件模型
事件是用户操作网页时发生的交互动作或者网页本身的一些操作,现代浏览器一共有三种事件模型。
事件流三个阶段
事件捕获阶段(capture phase)
事件目标阶段(target phase)
事件冒泡阶段(bubbling phase)
事件模型
原始事件模型(DOM0级)
IE事件模型(基本不用)
标准事件模型(DOM2级)
1.第一种事件模型是最早的 DOM0 级模型,这种模型不会传播,所以没有事件流的概念,但是现在有的浏览器支持以冒泡的方式实现,它可以在网页中直接定义监听函数,也可以通过 js 属性来指定监听函数。这种方式是所有浏览器都兼容的。
2.第二种事件模型是 IE 事件模型,在该事件模型中,一次事件共有两个过程,事件处理阶段,和事件冒泡阶段。事件处理阶段会首先执行目标元素绑定的监听事件。然后是事件冒泡阶段,冒泡指的是事件从目标元素冒泡到 document,依次检查经过的节点是否绑定了事件监听函数,如果有则执行。这种模型通过 attachEvent 来添加监听函数,可以添加多个监听函数,会按顺序依次执行。
3.第三种是 DOM2 级事件模型,在该事件模型中,一次事件共有三个过程,第一个过程是事件捕获阶段。捕获指的是事件从 document 一直向下传播到目标元素,依次检查经过的节点是否绑定了事件监听函数,如果有则执行。后面两个阶段和 IE 事件模型的两个阶段相同。这种事件模型,事件绑定的函数是 addEventListener,其中第三个参数可以指定事件是否在捕获阶段执行。
16、typeof和instanceof的区别
typeof 操作符返回一个字符串,表示未经计算的操作数的类型
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'
instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上
//定义构建函数
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
typeof与instanceof都是判断数据类型的方法,区别如下:
typeof会返回一个变量的基本类型,instanceof返回的是一个布尔值
instanceof 可以准确地判断复杂引用数据类型,但是不能正确判断基础数据类型
而typeof 也存在弊端,它虽然可以判断基础数据类型(null 除外),但是引用数据类型中,除了function 类型以外,其他的也无法判断
17、什么是事件代理?应用场景
事件代理(Event Delegation),又称之为事件委托。“事件代理”即是把原本需要绑定在子元素的响应事件(click、keydown…)委托给父元素,让父元素担当事件监听的职务。事件代理的原理是DOM元素的事件冒泡。
应用场景
如果我们有一个列表,列表之中有大量的列表项,我们需要在点击列表项的时候响应一个事件,如果给每个列表项一一都绑定一个函数,那对于内存消耗是非常大的。这时候就可以事件委托,把点击事件绑定在父级元素ul上面,然后执行事件的时候再去匹配目标元素li
如果用户能够随时动态的增加或者去除列表项元素,那么在每一次改变的时候都需要重新给新增的元素绑定事件,给即将删去的元素解绑事件。如果用了事件委托就没有这种麻烦了,因为事件是绑定在父层的,和目标元素的增减是没有关系的,执行到目标元素是在真正响应执行事件函数的过程中去匹配的
18、ajax原理是什么?如何实现?
AJAX全称(Async Javascript and XML)
即异步的JavaScript 和XML,是一种创建交互式网页应用的网页开发技术,可以在不重新加载整个网页的情况下,与服务器交换数据,并且更新部分网页
Ajax的原理
简单来说通过XmlHttpRequest对象来向服务器发异步请求,从服务器获得数据,然后用JavaScript来操作DOM而更新页面
const request = new XMLHttpRequest()
request.onreadystatechange = function (e) {
if (request.readyState === 4) { //整个请求过程完毕
if (request.status >= 200 && request.status <= 300) {
console.log(request.responseText) // 服务端返回的结果
} else if (request.status >= 400) {
console.log("错误信息:" + request.status)
}
}
}
request.open('POST', 'http://xxx')
request.send()
19、ajax属性和方法
1.XMLHttpRequest对象
XMLHttpRequest对象在大部分浏览器上已经实现而且拥有一个简单的接口允许数据从客户端传递到服务端,但并不会打断用户当前的操作。使用XMLHttpRequest传送的数据可以是任何格式,虽然从名字上建议是XML格式的数据。
XMLHttpRequest最早是在IE5中以ActiveX组件的形式实现的。非W3C标准。创建XMLHttpRequest对象(由于非标准所以实现方法不统一)。Internet Explorer把XMLHttpRequest实现为一个ActiveX对象其他浏览器(Firefox、Safari、Opera…)把它实现为一个本地的JavaScript对象。
XMLHttpRequest在不同浏览器上的实现是兼容的,所以可以用同样的方式访问XMLHttpRequest实例的属性和方法,而不论这个实例创建的方法是什么。
XMLHttpReques对象方法
方法 | 描述 |
---|---|
abort() | 停止当前请求 |
getAllResponseHeaders() | 把http请求的所有响应首部作为键/值对返回 |
getResponseHeader(“headerLabel”) | 返回指定首部的串值 |
open(method,url,async) | 规定请求的类型、URL 以及是否异步处理请求。method:请求的类型;GET 或 POSTurl:文件在服务器上的位置async:true(异步)或 false(同步) |
send(content) | 向服务器发送请求 |
setRequestHeader(“label”, “value”) | 把指定首部设置为所提供的值。在设置任何首部之前必须先调用open() |
XMLHttpRequest对象属性
2.发送请求–方法和属性介绍
利用XMLHttpRequest实例与服务器进行通信包含以下3个关键部分:onreadystatechange事件处理函数、open 方法、send 方法 。
onreadystatechange:
该事件处理函数由服务器触发,而不是用户;在 Ajax 执行过程中,服务器会通知客户端当前的通信状态。这依靠更新 XMLHttpRequest 对象的 readyState 来实现。改变 readyState属性是服务器对客户端连接操作的一种方式。每次 readyState 属性的改变都会触发 readystatechange事件!
open(method, url, asynch):
XMLHttpRequest对象的 open 方法允许程序员用一个Ajax调用向服务器发送请求。method:请求类型,类似 “GET”或”POST”的字符串。若只想从服务器检索一个文件,而不需要发送任何数据,使用GET(可以在GET请求里通过附加在URL上的查询字符串来发送数据,不过数据大小限制为2000个字符)。若需要向服务器发送数据,用POST。在某些情况下,有些浏览器会把多个XMLHttpRequest请求的结果缓存在同一个URL。如果对每个请求的响应不同,这就会带来不好的结果。把当前时间戳追加到URL的最后,就能确保URL的惟一性,从而避免浏览器缓存结果。比如:
var url = “GetExample?timeStamp=” + new Date().getTime();
url:路径字符串,指向你所请求的服务器上的那个文件。可以是绝对路径或相对路径。asynch:表示请求是否要异步传输,默认值为true(异步)。指定true,在读取后面的脚本之前,不需要等待服务器的相应。指定false,当脚本处理过程经过这点时,会停下来,一直等到Ajax请求执行完毕再继续执行。
send(data):
open 方法定义了 Ajax 请求的一些细节。send 方法可为已经待命的请求发送指令data:将要传递给服务器的字符串。若选用的是 GET请求,则不会发送任何数据, 给 send 方法传递 null 即可:request.send(null);当向send()方法提供参数时,要确保open()中指定的方法是POST,如果没有数据作为请求体的一部分发送,则使用null.
setRequestHeader(header,value)
当浏览器向服务器请求页面时,它会伴随这个请求发送一组首部信息。这些首部信息是一系列描述请求的元数据(metadata)。首部信息用来声明一个请求是 GET 还是 POST。
Ajax 请求中,发送首部信息的工作可以由setRequestHeader完成.参数header:首部的名字; 参数value:首部的值。如果用 POST请求向服务器发送数据,需要将 “Content-type” 的首部设置为 “application/x-www-form-urlencoded”.它会告知服务器正在发送数据,并且数据已经符合URL编码了。该方法必须在open()之后才能调用.
3.接收–方法和属性介绍
用 XMLHttpRequest 的方法可向服务器发送请求。在 Ajax 处理过程中,XMLHttpRequest 的如下属性可被服务器更改:readyState、status、responseText、responseXML。
status
服务器发送的每一个响应也都带有首部信息。三位数的状态码是服务器发送的响应中最重要的首部信息,并且属于超文本传输协议中的一部分。
常用状态码及其含义:
404没找到页面(notfound)
403禁止访问(forbidden)
500内部服务器出错(internalservice error)
200一切正常(ok)
304没有被修改(notmodified)(服务器返回304状态,表示源文件没有被修改)
在XMLHttpRequest对象中,服务器发送的状态码都保存在 status 属性里。通过把这个值和 200 或 304 比较,可以确保服务器是否已发送了一个成功的响应
responseText
XMLHttpRequest 的 responseText 属性包含了从服务器发送的数据。它是一个HTML,XML或普通文本,这取决于服务器发送的内容。
当 readyState 属性值变成 4 时, responseText 属性才可用,表明 Ajax 请求已经结束。
responseXML
如果服务器返回的是 XML, 那么数据将储存在 responseXML 属性中。只用服务器发送了带有正确首部信息的数据时,responseXML 属性才是可用的。MIME 类型必须为 text/xml
AJAX开发框架
AJAX实质上也是遵循Request/Server模式,所以这个框架基本的流程是:对象初始化、发送请求、服务器接收、服务器返回、客户端接收、修改客户端页面内容。只不过这个过程是异步的。
20、ajax同步模式和异步模式的区别
同步是阻塞模式,异步是非阻塞模式。
同步就是指一个进程在执行某个请求的时候,若该请求需要一段时间才能返回信息,那么这个进程将会一直等待下去,直到收到返回信息才继续执行下去;
异步是指进程不需要一直等下去,而是继续执行下面的操作,不管其他进程的状态。当有消息返回时系统会通知进程进行处理,这样可以提高执行的效率。
21、XMLHttpRequest.readyState状态码
状态0(请求未初始化):XMLHttpRequest)对象已经创建或已被abort()方法重置,但还没有调用open()方法
状态1(载入服务器连接已建立):已经调用open() 方法,但是send()方法未调用,尚未发送请求
状态2(载入完成,请求已接收):send()方法已调用,HTTP请求已发送到web服务器,请求已经发送完成,未接收到响应
状态3(交互,请求处理中):所有响应头部都已经接收到。响应体开始接收但未完成,即可以接收到部分响应数据
状态4(请求完成,且响应已就绪):已经接收到了全部数据,并且连接已经关闭
22、ajax请求为什么会乱码,怎么解决?
ajax请求乱码是由于客户端请求与服务器端相应编码不一致。
1.在你的页面上,你需要指定页面的编码,如:
meta http-equiv="Content-Type" content="text/html; charset=utf-8"
2.在你的服务器端也要指定输出编码,如:
response.charset = "utf-8";
3.你的前端页面和后台处理的页面字符编码必须同样为utf-8。
这样一般就不会乱码了,如果还乱,那么你就用escape()将请求进行编码后再发送,而后台也使用unescape()将请求进行解码后再处理就可以了。
输出时候的中文呢?在Action里面。
如果是html页面:
response.setContentType("text/html") ;
response.setCharacterEncoding("utf8")
;//这句会对输出字符进行设置
如果是xml,把text/html改成text/xml。
23、bind、call、applay的区别?如何实现一个call
call、apply、bind作用是改变函数执行时的上下文,简而言之就是改变函数运行时的this指向
applay
apply接受两个参数,第一个参数是this的指向,第二个参数是函数接受的参数,以数组的形式传入,改变this指向后原函数会立即执行,且此方法只是临时改变this指向一次
call
call方法的第一个参数也是this的指向,后面传入的是一个参数列表,改变this指向后原函数会立即执行,且此方法只是临时改变this指向一次
bind
bind方法和call很相似,第一参数也是this的指向,后面传入的也是一个参数列表,改变this指向后不会立即执行,而是返回一个永久改变this指向的函数
实现call
Function.prototype.myCall = function (obj, ...args) {
const fn = Symbol('fn') // 声明一个独有的Symbol属性, 防止fn覆盖已有属性
obj = obj || window // 若没有传入this, 默认绑定window对象
obj[fn] = this // this指向调用call的对象,即我们要改变this指向的函数
const result = obj[fn](...args) // 执行当前函数
delete obj[fn] // 删除我们声明的fn属性
return result // 返回函数执行结果
}
//测试
let obj = {
name: 'Demi'
}
// 全局函数
function getName(arg) {
console.log(this.name)
console.log(arg)
}
// 将全局函数this指向obj
getName.myCall(obj, 'arg') // name arg
24、说说你对事件循环的理解?
JavaScript是一门单线程的语言,意味着同一时间内只能做一件事,而实现单线程非阻塞的方法就是事件循环。在代码执行的时候,通过将不同函数的执行上下文压入执行栈中来保证代码的有序执行。
在JavaScript中,所有的任务都可以分为同步任务和异步任务。同步任务也就是立即执行的任务,同步任务一般会直接进入到主线程中执行。异步任务是异步执行的任务,比如ajax网络请求,setTimeout定时函数等
在执行同步任务的时候,如果遇到了异步事件,js 引擎并不会一直等待其返回结果,而是会将这个事件挂起,继续执行执行栈中的其他任务。当异步事件执行完毕后,再将异步事件对应的回调加入到与当前执行栈中不同的另一个任务队列中等待执行。任务队列可以分为宏任务队列和微任务队列,当当前执行栈中的事件执行完毕后,js 引擎首先会判断微任务队列中是否有任务可以执行,如果有就将微任务队首的事件压入栈中执行。当微任务对列中的任务都执行完成后再去判断宏任务对列中的任务。
微任务包括了 promise 的回调、node 中的 process.nextTick 、对 Dom 变化监听的 MutationObserver。
宏任务包括了 script 脚本的执行、setTimeout ,setInterval ,setImmediate 一类的定时事件,还有如 I/O 操作、UI 渲染等。
25、DOM的常见操作
文档对象模型 (DOM) 是 HTML 和 XML 文档的编程接口。它提供了对文档的结构化的表述,并定义了一种方式可以使从程序中对该结构进行访问,从而改变文档的结构,样式和内容。任何 HTML或XML文档都可以用 DOM表示为一个由节点构成的层级结构
创建节点
createElement:创建新元素,接受一个参数,即要创建元素的标签名
createTextNode:创建一个文本节点
createDocumentFragment:用来创建一个文档碎片,它表示一种轻量级的文档,主要是用来存储临时节点,然后把文档碎片的内容一次性添加到DOM中
createAttribute:创建属性节点,可以是自定义属性
查询节点
querySelector:传入任何有效的css 选择器,即可选中单个 DOM元素(首个)querySelectorAll:返回一个包含节点子树内所有与之相匹配的Element节点列表,如果没有相匹配的,则返回一个空节点列表
getElementById(‘id’): 返回拥有指定id的对象的引用getElementsByClassName(‘class’): 返回拥有指定的对象集合getElementsByTagName(‘标签名’):返回拥有指定标签名的对象集合getElementsByName(‘name属性值’): 返回拥有指定名称的对象结合
更新节点
innerHTML: 不但可以修改一个DOM节点的文本内容,还可以直接通过HTML片段修改DOM节点内部的子树
innerText: 自动对字符串进行HTML编码,保证无法设置任何HTML标签, 不返回隐藏元素的文本
textContent: 自动对字符串进行HTML编码,保证无法设置任何HTML标签, 返回所有文本
style: DOM节点的style属性对应所有的CSS,可以直接获取或设置。遇到需要转化为驼峰命名
添加节点
innerHTML:不但可以修改一个DOM节点的文本内容,还可以直接通过HTML片段修改DOM节点内部的子树
appendChild:把一个子节点添加到父节点的最后一个子节点
insertBefore:把子节点插入到指定的位置,子节点会插入到referenceElement之前
setAttribute:在指定元素中添加一个属性节点,如果元素中已有该属性改变属性值
删除节点
removeChild:删除一个节点,首先要获得该节点本身以及它的父节点,然后,调用父节点的removeChild把自己删掉
26、说说你对BOM的理解?常见的BOM对象有哪些?
BOM (Browser Object Model),浏览器对象模型,提供了独立于内容与浏览器窗口进行交互的对象。其作用就是跟浏览器做一些交互效果,比如如何进行页面的后退,前进,刷新,浏览器的窗口发生变化,滚动条的滚动,以及获取客户的一些信息如:浏览器品牌版本,屏幕分辨率
Window
open:既可以导航到一个特定的url,也可以打开一个新的浏览器窗口
close:仅用于通过关闭 window.open() 打开的窗口
Window.location
location对象主要用来获取url的信息
hash:url中#后面的字符,没有则返回空串
host:域名和端口号
hostname:域名,不带端口号
href:完整url
pathname:服务器下面的文件路径
port:url的端口号,没有则为空
protocol:使用的协议
search:url的查询字符串,通常为?后面的内容
Window.history
history对象主要用来操作浏览器URL的历史记录,可以通过参数向前,向后,或者向指定URL跳转
history.go():接收一个整数数字或者字符串参数:向最近的一个记录中包含指定字符串的页面跳转
history.forward():向前跳转一个页面
history.back():向后跳转一个页面
history.length:获取历史记录数
27、JavaScript中内存泄漏的几种情况
内存泄漏(Memory leak)是在计算机科学中,由于疏忽或错误造成程序未能释放已经不再使用的内存
常见的内存泄漏
- setTimeout 的第一个参数使用字符串而非函数的话,会引发内存泄漏。
- 闭包(dom元素删除, 闭包中还在引用)
- 控制台日志
- 循环(在两个对象彼此引用且彼此保留时,就会产生一个循环)
- 意外的全局变量
解决内存泄漏
1.使用严格模式,可以避免意外的全局变量
2.清理dom元素, dom = null
3.setTimeout第一个参数为函数
28、JavaScript本地存储的几种方式?区别和应用场景?
sessionStorage、localStorage和cookie的区别
1.相同点是都是保存在浏览器端、且同源的
2.cookie数据始终在同源的http请求中携带(即使不需要),即cookie在浏览器和服务器间来回传递,而sessionStorage和localStorage不会自动把数据发送给服务器,仅在本地保存。cookie数据还有路径(path)的概念,可以限制cookie只属于某个路径下
3.存储大小限制也不同,cookie数据不能超过4K,同时因为每次http请求都会携带cookie、所以cookie只适合保存很小的数据,如会话标识。sessionStorage和localStorage虽然也有存储大小的限制,但比cookie大得多,可以达到5M或更大
4.数据有效期不同,sessionStorage:仅在当前浏览器窗口关闭之前有效;localStorage:始终有效,窗口或浏览器关闭也一直保存,因此用作持久数据;cookie:只在设置的cookie过期时间之前有效,即使窗口关闭或浏览器关闭
5.作用域不同,sessionStorage不在不同的浏览器窗口中共享,即使是同一个页面;localstorage在所有同源窗口中都是共享的;cookie也是在所有同源窗口中都是共享的
6.sessionStorage和localStorage支持事件通知机制,可以将数据更新的通知发送给监听者
7.sessionStorage和localStorage的api接口使用更方便
应用场景
标记用户与跟踪用户行为的情况,推荐使用cookie
适合长期保存在本地的数据(令牌),推荐使用localStorage
敏感账号一次性登录,推荐使用sessionStorage
存储大量数据的情况、在线文档(富文本编辑器)保存编辑历史的情况,推荐使用indexedDB
29、Cookie的优缺点?
优点:
1.极高的扩展性和可用性
2.通过编程方式,控制保存在cookie中的session对象的大小
3.通过加密和SSL(安全传输技术),减少cookie被破解的可能性
4.只在cookie中存放不敏感数据,被盗也不会有重大的损失
5.控制cookie的生命周期,可用让其永远有效,偷盗者就有可能拿到一个过期的cookie
缺点:
1.cookie数量和长度的限制,每个最多只能有20条的cookie,长度不能超过4kb,超过就会被截取
2.安全性问题,被人拦截,就可以得到cookie的所有信息,只要原样转发就可以达到目的
30、函数式编程的理解?
函数式编程中的函数这个术语不是指计算机中的函数,而是指数学中的函数,即自变量的映射。也就是说一个函数的值仅决定于函数参数的值,不依赖其他状态。
函数式编程就是一种抽象程度很高的编程范式,纯粹的函数式编程语言编写的函数没有变量,因此,任意一个函数,只要输入是确定的,输出就是确定的,这种纯函数我们称之为没有副作用。而允许使用变量的程序设计语言,由于函数内部的变量状态不确定,同样的输入,可能得到不同的输出,因此,这种函数是有副作用的。
函数式编程的一个特点就是,允许把函数本身作为参数传入另一个函数,还允许返回一个函数!
31、JavaScript数字精度丢失问题
当计算机计算 0.1+0.2 的时候,实际上计算的是这两个数字在计算机里所存储的二进制,0.1 和 0.2 在转换为二进制表示的时候会出现位数无限循环的情况。js 中是以 64 位双精度格式来存储数字的,只有 53 位的有效数字,超过这个长度的位数会被截取掉这样就造成了精度丢失的问题。这是第一个会造成精度丢失的地方。
在对两个以 64 位双精度格式的数据进行计算的时候,首先会进行对阶的处理,对阶指的是将阶码对齐,也就是将小数点的位置对齐后,再进行计算,一般是小阶向大阶对齐,因此小阶的数在对齐的过程中,有效数字会向右移动,移动后超过有效位数的位会被截取掉,这是第二个可能会出现精度丢失的地方。
当两个数据阶码对齐后,进行相加运算后,得到的结果可能会超过 53 位有效数字,因此超过的位数也会被截取掉,这是可能发生精度丢失的第三个地方。
解决
1.对于这样的情况,我们可以将其转换为整数后再进行运算,运算后再转换为对应的小数,以这种方式来解决这个问题。
2.使用bigInt()进行计算
32、面向过程和面向对象的区别
面向过程: 分析出解决问题所需要的步骤,强调的是解决问题的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了;
面向对象: 是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。
优缺点比较:
面向过程优点:
1.流程化使得编程任务明确,在开发之前基本考虑了实现方式和最终结果,具体步骤清楚,便于节点分析。
2.效率高,面向过程强调代码的短小精悍,善于结合数据结构来开发高效率的程序。
面向过程缺点:
1.需要深入的思考,耗费精力,代码重用性低,扩展能力差,后期维护难度比较大。
面向对象优点:
1.结构清晰,程序是模块化和结构化,更加符合人类的思维方式;
2.易扩展,代码重用率高,可继承,可覆盖,可以设计出低耦合的系统;
3.易维护,系统低耦合的特点有利于减少程序的后期维护工作量。
面向对象缺点:
1.开销大,当要修改对象内部时,对象的属性不允许外部直接存取,所以要增 加许多没有其他意义、只负责读或写的行为。这会为编程工作增加负担,增加运行开销,并且使程序显得臃肿。
2.性能低,由于面向更高的逻辑抽象层,使得面向对象在实现的时候,不得不做出性能上面的牺牲,计算时间和空间存储大小都开销很大。
得出结论
面向过程:用函数来定义解决问题的步骤
面向对象:用类和对象的方法来定义解决问题的行为或者说功能
面向过程性能比面向对象高。因为类调用时需要实例化,开销比较大,比较消耗资源,所以当性能是最重要的考量因素的时候,比如单片机、嵌入式开发、Linux/Unix 等一般采用面向过程开发。但是,面向过程没有面向对象易维护、易复用、易扩展。
33、如何实现函数缓存
函数缓存,就是将函数运算过的结果进行缓存,本质上就是用空间(缓存存储)换时间(计算过程)。实现函数缓存主要依靠闭包、柯里化、高阶函数
34、什么是防抖和节流?有什么区别?如何实现?
函数防抖是指在事件被触发 n 秒后再执行回调,如果在这 n 秒内事件又被触发,则重新计时。这可以使用在一些点击请求的事件上,避免因为用户的多次点击向后端发送多次请求。
function debounce(func, wait) {
let timeout = null
return function () {
let context = this // 保存this指向
let args = arguments // 拿到event对象
if (timeout) clearTimeout(timeout)
timeout = setTimeout(() => {
func.apply(context, args)
}, wait)
}
}
函数节流是指规定一个单位时间,在这个单位时间内,只能有一次触发事件的回调函数执行,如果在同一个单位时间内某事件被触发多次,只有一次能生效。节流可以使用在 scroll 函数的事件监听上,通过事件节流来降低事件调用的频率。
function throttle(func, wait) {
let timeout = null
return function () {
let context = this
let args = arguments
if (!timeout) {
timeout = setTimeout(() => {
timeout = null
func.apply(context, args)
}, wait)
}
}
}
35、说说var、let、const之间的区别
变量提升
var声明的变量存在变量提升,即变量可以在声明之前调用,值为undefined
let和const不存在变量提升,即它们所声明的变量一定要在声明后使用,否则报错
暂时性死区
var不存在暂时性死区
let和const存在暂时性死区,只有等到声明变量的那一行代码出现,才可以获取和使用该变量
块级作用域
var不存在块级作用域
let和const存在块级作用域
重复声明
var允许重复声明变量
let和const在同一作用域不允许重复声明变量
修改声明的变量
var和let可以
const声明一个只读的常量。一旦声明,常量的值就不能改变
使用
能用const的情况尽量使用const,其他情况下大多数使用let,避免使用var
36、Set和Map两种数据结构
Set是一种叫做集合的数据结构,集合是由一堆无序的、相关联的,且不重复的内存结构【数学中称为元素】组成的组合
Map是一种叫做字典的数据结构,字典是一些元素的集合。每个元素有一个称作key 的域,不同元素的key 各不相同
Set是es6新增的数据结构,类似于数组,但是成员的值都是唯一的,没有重复的值,我们一般称为集合
new Set()
Set本身是一个构造函数,用new Set()来生成 Set 数据结构
const s = new Set();
add()
添加某个值,返回 Set 结构本身,当添加实例中已经存在的元素,set不会进行处理添加
s.add(1).add(2).add(2); // 2只被添加了一次
delete()
删除某个值,返回一个布尔值,表示删除是否成功
s.delete(1)
has()
返回一个布尔值,判断该值是否为Set的成员
s.has(2)
clear()
清除所有成员,没有返回值
s.clear()
Map类型是键值对的有序列表,而键和值都可以是任意类型
new Map()
map本身是一个构造函数,用来生成 Map 数据结构
const map = new Map()
size()
size属性返回 Map 结构的成员总数。
const map = new Map()
map.set('foo', true)
map.set('bar', false)
map.size // 2
set()
设置键名key对应的键值为value,然后返回整个 Map 结构,如果key已经有值,则键值会被更新,否则就新生成该键,同时返回的是当前Map对象,可采用链式写法
const map = new Map()
map.set('edition', 6) // 键是字符串
map.set(262, 'standard') // 键是数值
map.set(undefined, 'test') // 键是undefined
map.set(1, 'a').set(2, 'b').set(3, 'c') // 链是操作
get()
get方法读取key对应的键值,如果找不到key,返回undefined
const map = new Map();
const hello = function () {
console.log('hello');
};
map.set(hello, 'Hello ES6!') //键是函数
map.get(hel1o) // Hello ES6!
delete()
delete方法删除某个键,返回true。如果删除失败,返回false
const map = new Map();
map.set(undefined, 'nah');
map.has(undefined) // true
map.delete(undefined)
map.has(undefined) // false
clear()
清除所有成员,没有返回值
let map = new Map();
map.set('foo', true);
map.set('bar', false);
map.size // 2
map.clear()
map.size // 0
37、null和undefined的区别
首先 Undefined 和 Null 都是基本数据类型,这两个基本数据类型分别都只有一个值,就是 undefined 和 null。
undefined 代表的含义是未定义,null 代表的含义是空对象。一般变量声明了但还没有定义的时候会返回undefined,null主要用于赋值给一些可能会返回对象的变量,作为初始化。
undefined 在 js 中不是一个保留字,这意味着我们可以使用 undefined 来作为一个变量名,这样的做法是非常危险的,它会影响我们对 undefined 值的判断。但是我们可以通过一些方法获得安全的 undefined 值,比如说 void 0。
当我们对两种类型使用 typeof 进行判断的时候,Null 类型化会返回 “object”,这是一个历史遗留的问题。当我们使用双等号对两种类型的值进行比较时会返回 true,使用三个等号时会返回 false。
38、说几条写 JavaScript 的基本规范?
- 一个函数作用域中所有的变量声明应该尽量提到函数首部。
- 代码中出现地址、时间等字符串时需要使用常量代替。
- 在进行比较的时候吧,尽量使用===, !==代替==, !=。
- 不要在内置对象的原型上添加方法,如 Array, Date。
- switch 语句必须带有 default 分支。
- for 循环必须使用大括号。
- if 语句必须使用大括号。
- 不要在同一行声明多个变量。
- 使用对象字面量替代new Object这种形式。
- 函数不应该有时候有返回值,有时候没有返回值。
- 命名规则中构造器函数首字母大写,如function Person(){}。
- 写注释。
39、Es6代码转成es5的实现思路
说到 ES6 代码转成 ES5 代码,我们肯定会想到 Babel。那么 Babel 是如何把 ES6 转成 ES5 呢,其大致分为三步:
1.将代码字符串解析成抽象语法树,即所谓的 AST
2.对 AST 进行处理,在这个阶段可以对 ES6 代码进行相应转换,即转成 ES5 代码
3.根据处理后的 AST 再生成代码字符串
40、异步的发展历程?
1.回调函数(callback)
优点:解决了同步的问题(只要有一个任务耗时很长,后面的任务都必须排队等着,会拖延整个程序的执行。)
缺点:回调地狱,不能用 try catch 捕获错误,不能 return。缺乏顺序性,回调地狱导致的调试困难,和大脑的思维方式不符。嵌套函数存在耦合性,一旦有所改动,就会牵一发而动全身,即(控制反转)嵌套函数过多的多话,很难处理错误
ajax('xxx1', () => {
// callback函数体
ajax('xxx2', () => {
// callback 函数体
ajax('xxx3', () => {
// callback函数体
})
})
})
2.Promise
Promise就是为了解决callback的问题而产生的。Promise 实现了链式调用,也就是说每次 then 后返回的都是一个全新 Promise,如果我们在 then 中 return ,return 的结果会被 Promise.resolve() 包装
优点:解决了回调地狱的问题
缺点:无法取消 Promise ,错误需要通过回调函数来捕获
let promise = new Promise((resolve, reject) => {
//在这里执行异步操作
if ( /*异步操作成功*/ ) {
resolve(success)
} else {
reject(error)
}
})
3.Generator
特点:可以控制函数的执行,可以配合 co 函数库使用
优点:函数体内外的数据交换、错误处理机制
缺点:流程管理不方便
function* fetch() {
yield ajax('xxx1', () => {})
yield ajax('xxx2', () => {})
yield ajax('xxx3', () => {})
}
let it = fetch()
let result1 = it.next()
let result2 = it.next()
let result3 = it.next()
4.async/await
async、await 是异步的终极解决方案
优点是:代码清晰,不用像Promise写一大堆 then 链,处理了回调地狱的问题
缺点:await 将异步代码改造成同步代码,如果多个异步操作没有依赖性而使用 await 会导致性能上的降低。
async function test() {
//以下代码没有依赖性的话,完全可以使用Promise.all的方式
//如果有依赖性的话,其实就是解决回调地狱的例子了
await fetch('XXX1')
await fetch('XXX2 ')
await fetch('XXX3')
}
41、promise介绍
promise三种状态
pending:进行中
fulfilled :已成功
rejected 已失败
promise缺点
1.无法取消Promise,一旦新建它就会立即执行,无法中途取消
2.如果不设置回调函数(没有捕获错误),Promise内部抛出的错误,不会反应到外部
3.当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)
promise基本用法
let promise = new Promise((resolve, reject) => {
//在这里执行异步操作
if ( /*异步操作成功*/ ) {
resolve(success)
} else {
reject(error)
}
})
1.Promise接收一个函数作为参数,函数里有resolve和reject两个参数
2.resolve方法的作用是将Promise的pending状态变为fulfilled,在异步操作成功之后调用,可以将异步返回的结果作为参数传递出去。
3.reject方法的作用是将Promise的pending状态变为rejected,在异步操作失败之后调用,可以将异步返回的结果作为参数传递出去。
他们之间只能有一个被执行,不会同时被执行,因为Promise只能保持一种状态。
Promise.prototype.then()
Promise实例确定后,可以用then方法分别指定fulfilled状态和rejected状态的回调函数。它的基本用法如下:
promise.then((success) => {
//异步操作成功在这里执行
//对应于上面的resolve(success)方法
}, (error) => {
//异步操作先败在这里执行
//对应于上面的reject(error)方法
})
//还可以写成这样(推荐使用这种写法)
promise.then((success) => {
//异步操作成功在这里执行
//对应于上面的resolve (success)方法
}).catch((error) => {
//异步操作先败在这里执行
//对应于上面的reject(error)方法
})
Promise.prototype.catch()
catch方法是.then(null,onrejected)的别名,用于指定发生错误时的回调函数。作用和then中的onrejected一样,不过它还可以捕获onfulfilled抛出的错,这是onrejected所无法做到的
Promise错误具有"冒泡"的性质,如果不被捕获会一直往外抛,直到被捕获为止;而无法捕获在他们后面的Promise抛出的错。
Promise.prototype.finally()
finally方法用于指定不管Promise对象最后状态如何,都会执行的操作。finally方法不接受任何参数,故可知它跟Promise的状态无关,不依赖于Promise的执行结果
createPromise('p1', 0).then((success) => {
console.log(success)
}).catch((error) => {
console.log(error) // p1 fail
}).finally(() => {
console.log('finally') // finally
})
createPromise('p1', 1).then((success) => {
console.log(success) // p1 ok
}).catch((error) => {
console.log(error)
}).finally(() => {
console.log('finally') // finally
})
Promise.all()
Promise.all方法接受一个数组作为参数,但每个参数必须是一个Promise实例。Promise的all方法提供了并行执行异步操作的能力,并且在所有异步操作都执行完毕后才执行回调,只要其中一个异步操作返回的状态为rejected那么Promise.all()返回的Promise即为rejected状态,此时第一个被reject的实例的返回值,会传递给Promise.all的回调函数
function createPromise(p, arg) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (arg === 0) {
reject(p + 'fail')
} else {
resolve(p + 'ok')
}
}, 0);
})
}
// test: 两个Promise都成功
Promise.all([createPromise('p1', 1), createPromise('p2', 1)]), then((success) => {
console.log(success) // ['p1 ok', 'p2 ok']
}).catch((error) => {
console.log(error)
})
// test: 其中一个Promise先败
Promise.all([createPromise('p1', 0), createPromise('p2', 1)]), then((success) => {
console.log(success)
}).catch((error) => {
console.log(error) // p1 fail
})
// test: 两个Promise都失败
Promise.all([createPromise('p1', 0), createPromise('p2', 0)])
.then((success) => {
console.log(success)
}).catch((error) => {
console.log(error) // p1 fail 只打印第一个失败的异步操作信息
})
Promise.race()
Promise的race方法和all方法类似,都提供了并行执行异步操作的能力。顾名思义,race就是赛跑的意思,意思就是说Promise.race([p1, p2, p3])里面哪个结果获得的快,就返回那个结果,不管结果本身是成功状态还是失败状态,以下就是race的执行过程
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('success')
}, 1000)
})
let p2 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('failed')
}, 500)
})
Promise.race([p1, p2]).then((success) => {
console.log(success)
}).catch((error) => {
console.log(error) // failed
})
42、promise.all()和promise.race的区别?
promise.all 是数组里面所有的 promise对象执行结束之后会返回一个存储所有 promise对象的结果
promise.race 顾名思义 race就是比赛的意思 只会返回一个执行速度最快的那个promise对象返回的结果
let runA = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('runA')
resolve('a')
}, 3000)
})
let runB = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('runB')
resolve('b');
}, 4000)
})
// Promise.all([runA, runB]).then((res) => { console.log(res) }); //输出了["a", "b"],打印了runA和runB
// Promise.race([runA, runB]).then((res) => { console.log(res) }); //输出了a,打印了runA和runB
// Promise.race()其他的异步函数照样还是会执行的,只是不会再执行resolve和reject,也不会返回结果了,但函数还是会执行的
//上面代码还是会打印console.log('runB') 只会不会执行下面的resolve('b')
43、promise.all中,其中一个promise出错,如何确保执行到最后?
在promise.all队列中,使用map过滤每一个promise任务,其中任意一个报错后,return一个返回值,p.catch方法返回值会被promise.reslove()包裹,这样传进promise.all的数据全都是resolved状态的,确保promise能正常执行走到.then中
let p1 = new Promise((resolve, reject) => {
resolve({
code: 200,
list: []
});
});
let p2 = new Promise((resolve, reject) => {
resoLve({
code: 200,
list: []
});
});
let p3 = new Promise((resolve, reject) => {
reject({
code: 500,
msg: "服务异常"
});
});
Promise.all([p1, p2, p3].map(p => p.catch(e => e)))
.then(res => {
console.log(res);
/*
打印结果:
{code: 200, list: Array(0)}
{code: 200, list: Array(0)}
{code: 500, msg:”服务异常”}
*/
}).catch(err => {
console.log(err);
})
44、普通函数和箭头函数的区别?
语法更加简洁、清晰
箭头函数没有 prototype (原型),所以箭头函数本身没有this
//箭头函教
let a = () => {};
console.log(a.prototype); // undefined
//普通函数
function a() {};
console.log(a.prototype); // {constructor:f}
箭头函数不会创建自己的this
箭头函数没有自己的this,箭头函数的this指向在定义(注意:是定义时,不是调用时)的时候继承自外层第一个普通函数的this。所以,箭头函数中 this 的指向在它被定义的时候就已经确定了,之后永远不会改变。
let obj = {
a: 10,
b: () => {
console.log(this.a); // undefined
console.log(this); // Window {postMessage: f, blur: f, focus: f, close: f, frames: Window, ...}
},
c: function () {
console.log(this.a); // 10
console.log(this); // {a: 10, b: f, c: f}
}
}
obj.b();
obj.c();
call | apply | bind 无法改变箭头函数中this的指向
call | apply | bind方法可以用来动态修改函数执行时this的指向,但由于箭头函数的this定义时就已经确定且永远不会改变。所以使用这些方法永远也改变不了箭头函数this的指向。
var id = 10;
let fun = () => {
console.log(this.id)
};
fun(); // 10
fun.call({ id: 20 }); // 10
fun.apply({ id: 20 }); // 10
fun.bind({ id: 20 })(); // 10
箭头函数不能作为构造函数使用
我们先了解一下构造函数的new都做了些什么?简单来说,分为四步:
① JS内部首先会先生成一个对象;
② 再把函数中的this指向该对象;
③ 然后执行构造函数中的语句;
④ 最终返回该对象实例。
但是!!因为箭头函数没有自己的this,它的this其实是继承了外层执行环境中的this,且this指向永远不会随在哪里调用、被谁调用而改变,所以箭头函数不能作为构造函数使用,或者说构造函数不能定义成箭头函数,否则用new调用时会报错!
let Fun = (name, age) => {
this.name = name;
this.age = age;
};
//报错
let p = new Fun('dingFY', 24);
箭头函数不绑定arguments,取而代之用rest参数…代替arguments对象,来访问箭头函数的参数列表
箭头函数没有自己的arguments对象。在箭头函数中访问arguments实际上获得的是外层局部(函数)执行环境中的值。
// 普通函数
function A(a) {
console.log(arguments);
}
A(1, 2, 3, 4, 5, 8); // [1, 2, 3, 4, 5, 8, callee: f, Symbol (Symbol. iterator): f]
// 箭头函数
let B = (b) => {
console.log(arguments);
}
B(2, 92, 32, 32); // Uncaught ReferenceError: arguments is not def ined
// rest参效...
let C = (...c) => {
console.log(c);
}
C(3, 82, 32, 11323); // [3, 82, 32, 11323]
45、JavaScript中的装箱和拆箱操作
装箱操作: 把基本数据类型转换为对应的引用类型的操作
拆箱操作: 把引用类型转换为基本数据类型的操
基本类型:Number,String,Boolean,Null,Undefined,Symbol、BigInt
访问:基本数据类型的值是按值访问的。
存储:基本类型的变量是存放在栈内存(Stack)里的。
引用类型:Object
访问:引用类型的值是按引用访问的。
存储:引用类型的值是保存在堆内存(Heap)中的对象(Object)
js中的三个基本包装类型:Number、String、Boolean
typeof: 经常用来检测一个变量是不是最基本的数据类型
instanceof: 用来判断某个构造函数的 prototype 属性所指向的对象是否存在于另外一个要检测对象的原型链上。简单说就是判断一个引用类型的变量具体是不是某种类型的对象
装箱
每当读取一个基本类型的时候,后台就会创建一个对应的基本包装类型对象,从而让我们能够调用一些方法来操作这些数据。
var str = "hello world";
var strRes = str.split(" ");
console.log(strRes) //["hello", "world"]
如上面代码所示,变量str是一个基本类型值,不是一个对象,就不存在方法,但上面代码却显示可以正常调用方法。实际上这一切都是js内部做了以下处理(装箱操作),使得它能够调用方法
1.创建String类型的一个实例;
2.在实例上调用指定的方法;
3.销毁这个实例;
var str = "hello world";
var strRes = str.split(" ");
str = null
拆箱
拆箱操作中主要有两个方法,valueOf()方法和toString()方法。这两个方法主要用来检测你返回的是不是一个基本类型的值。一般是先用valueOf()来检测,如果返回的不是一个基本类型的值,是对象自身,则会继续用toString()来检测,如果检测结果不是一个基本类型的值,则会报错(Uncaught SyntaxError: Invalid or unexpected token)
valueOf
1.valueOf() 方法返回指定对象的原始值。
2.JavaScript调用valueOf方法将对象转换为原始值。你很少需要自己调用valueOf方法;当遇到要预期的原始值的对象时,JavaScript会自动调用它。
3.默认情况下,valueOf方法由Object后面的每个对象继承。 每个内置的核心对象都会覆盖此方法以返回适当的值。如果对象没有原始值,则valueOf将返回对象本身。
4.JavaScript的许多内置对象都重写了该函数,以实现更适合自身的功能需要。因此,不同类型对象的valueOf()方法的返回值和返回值类型均可能不同
toSting
1.toString() 方法返回一个表示该对象的字符串。
2.每个对象都有一个 toString() 方法,当该对象被表示为一个文本值时,或者一个对象以预期的字符串方式引用时自动调用。
3.默认情况下,toString() 方法被每个 Object 对象继承。如果此方法在自定义对象中未被覆盖,toString() 返回 “[object type]”,其中 type 是对象的类型。
[] + [] // ""
{} + {} // "[object Object][object Object]"
[] + {} // "[object Object]"
1.[]+[],[]自身是一个空数组,即是一个对象,[]会先被valueOf()检测返回自身,还是[],然后使用toString()检测返回空字符"“,实际最终是”“+”“,所以最终结果还是一个”"
2.{}+{},在js中{}可以表示一个代码块,也可以表示一个对象。在此处作为一个对象来运算,({}).valueOf()检测结果为自身,继续检测,({}).toString()检测结果为"[object Object]“,所以{}+{}相当于”[object Object]“+”[object Object]“,故结果为”[object Object][object Object]
"
3.[]+{},从上面分析可以知道,这个相当于"“+”[object Object]“,所以结果为”[object Object]"
46、Object.defineProperty()
语法
Object.defineProperty(obj, prop, descriptor)
参数
obj:必需,目标对象
prop:必需,需定义或修改的属性的名字
descriptor:必需,将被定义或修改的属性的描述符,是一个对象
descriptor参数解析
函数的第三个参数 descriptor 所表示的属性描述符有两种形式:数据描述符和存取描述符
数据描述:当修改或定义对象的某个属性的时候,给这个属性添加一些特性,数据描述中的属性都是可选的
存取描述:当使用存取器描述属性的特性的时候,允许设置以下特性属性
数据描述:
value:属性对应的值,可以使任意类型的值,默认为undefined
writable:属性的值是否可以被重写。设置为true可以被重写;设置为false,不能被重写。默认为false
enumerable:此属性是否可以被枚举(使用for…in或Object.keys())。设置为true可以被枚举;设置为false,不能被枚举。默认为false
configurable:是否可以删除目标属性或是否可以再次修改属性的特性(writable, configurable, enumerable)。设置为true可以被删除或可以重新设置特性;设置为false,不能被可以被删除或不可以重新设置特性。默认为false。这个属性起到两个作用:1、目标属性是否可以使用delete删除 2、目标属性是否可以再次设置特性
存取描述:
get:属性的 getter 函数,如果没有 getter,则为 undefined。当访问该属性时,会调用此函数。执行时不传入任何参数,但是会传入 this 对象(由于继承关系,这里的this并不一定是定义该属性的对象)。该函数的返回值会被用作属性的值。默认为 undefined。
set:属性的 setter 函数,如果没有 setter,则为 undefined。当属性值被修改时,会调用此函数。该方法接受一个参数(也就是被赋予的新值),会传入赋值时的 this 对象。默认为 undefined。
47、await后面跟的对象
await 后面可以跟任何js表达式都不会报错,但是,只有当后面跟的是promise对象时,才会解决异步问题,实现同步
后面跟普通js表达式
对于非promise对象,比如箭头函数,同步表达式等等,会立即执行,而不是等待其执行结果
function waitme() {
setTimeout(() => {
console.log(111111);
}, 3000)
}
async function go() {
await waitme();
console.log(222222);
}
go(); // 输出结果为22222 111111
后面跟promise对象
对于promise对象,await会阻塞函数执行,等待promise的resolve返回值,作为await的结果,然后再执行下下一个表达式
function waitme() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(111111);
resolve()
}, 3000)
})
}
async function go() {
await waitme();
console.log(222222);
}
go() //输出结果为111111 22222
48、异步和同步的区别?
同步和异步的差别就在于这条流水线上各个流程的执行顺序不同。
同步任务: 指的是在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务
异步任务: 指的是不进入主线程、而进入"任务队列"(task queue)的任务,只有等主线程任务执行完毕,"任务队列"开始通知主线程,请求执行任务,该任务才会进入主线程执行。
49、图片懒加载实现原理?
先将img标签的src链接设为同一张图片(比如空白图片),然后给img标签设置自定义属性(比如 data-src),然后将真正的图片地址存储在data-src中,当JS监听到该图片元素进入可视窗口时,将自定义属性中的地址存储到src属性中。达到懒加载的效果。
50、js请求,一般会有哪些地方有缓存处理?
1.DNS缓存:短时间内多次访问某个网站,在限定时间内,不用多次访问DNS服务器。
2.CDN缓存:内容分发网络(人们可以在就近的代售点取火车票了,不用非得到火车站去排队)
3.浏览器缓存:浏览器在用户磁盘上,对最新请求过的文档进行了存储。
4.服务器缓存:将需要频繁访问的Web页面和对象保存在离用户更近的系统中,当再次访问这些对象的时候加快了速度。
51、typeScript的优势有哪些?
1.类型检测:在Typescript中为变量指定具体类型时,IDE会做出类型检测,这个特性减少在开发阶段犯错几率。
2.语法提示:在IDE里编写Typescript代码时, IDE会根据你当前的上下文,把你能用的类、变量、方法和关键字都给你提示出来。直接选择,这个特性提高开发效率。
3.便于重构:重构是说你可以很方便的去修改你的变量或者方法的名字或者是文件的名字,当你做出这些修改的时候, IDE会帮你自动引用这个变量或者调用这个方法地方的代码自动帮你修改掉
4.活跃社区:Typescript拥抱es6的规范,也支持部分ESNext草案规范,大部分的第三方库提供Typescript类型定义的文件
TypeScript的最大特点是静态类型,不同于javascript的动态类型,静态类型有以下优势:
1.其一,静态类型检查可以做到early fail ,即你编写的代码即使没有被执行到,一旦你编写代码时发生类型不匹配,语言在编译阶段(解释执行也一样,可以在运行前)即可发现。
2.其二,静态类型对阅读代码是友好的,针对大型应用,方法众多,调用关系复杂,不可能每个函数都有人编写细致的文档,所以静态类型就是非常重要的提示和约束。此外TS还实现了类,接口,枚举,泛型,方法重载等语法糖,方便了前端开发;
52、jQuery特点?
jQuery:它是轻量级的js库 ,它兼容CSS3,还兼容各种浏览器,jQuery使用户能更方便地处理HTML、events、实现动画效果,并且方便地为网站提供AJAX交互。jQuery还有一个比较大的优势是,它的文档说明很全,而且各种应用也说得很详细,同时还有许多成熟的插件可供选择。jQuery能够使用户的html页面保持代码和html内容分离,也就是说,不用再在html里面插入一堆js来调用命令了,只需要定义id即可。
jQuery特点
1.jQ是在js基础上进行的封装,可以相互转换,不是全新的语言;
2.jQ最核心的理念就是“用最少的代码,做最多的事情”“write less do more”;
3.jQ最大特点就是具有强大的兼容性,不在为各种浏览器兼容性问题而费神费力;
4.jQ使用的是链式写法,可以把多行代码写在一行,方便简洁;
5.jQ还简化了js操作css的代码,并且代码的可读性也比js要强;
6.jQ简化了AJAX操作,后台只需返回一个JSON格式的字符串就能完成与前台的通信。
7.jQuery提供了扩展接口:JQuery.extend(object),可以在JQuery的命名空间上增加新函数。JQuery的所有插件都是基于这个扩展接口开发的;
8.jQuery有着丰富的第三方的插件,例如:树形菜单、日期控件、图片切换插件、弹出窗口等等基本前台页面上的组件都有对应插件,并且用JQuery插件做出来的效果很炫,并且可以根据自己需要去改写和封装插件,简单实用。
53、Axios是什么?有封装过axios吗?
定义
Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中
特点
支持从浏览器中创建 XMLHttpRequests
支持从 node.js 创建 http 请求
支持 Promise API
能拦截请求和响应
能转换请求数据和响应数据
能取消请求
自动转换 JSON 数据
客户端支持防御 CSRF
为什么要封装
axios 的 API 很友好,你完全可以很轻松地在项目中直接使用。
不过随着项目规模增大,如果每发起一次HTTP请求,就要把这些比如设置超时时间、设置请求头、根据项目环境判断使用哪个请求地址、错误处理等等操作,都需要写一遍,这种重复劳动不仅浪费时间,而且让代码变得冗余不堪,难以维护。为了提高我们的代码质量,我们应该在项目中二次封装一下 axios 再使用
如何封装
封装的同时,需要和后端协商好一些约定,请求头,状态码,请求超时时间…
设置接口请求前缀:根据开发、测试、生产环境的不同,前缀需要加以区分
请求头:来实现一些具体的业务,必须携带一些参数才可以请求(例如:会员业务)
状态码:根据接口返回的不同status ,来执行不同的业务,这块需要和后端约定好
请求方法:根据get、post等方法进行一个再次封装,使用起来更为方便
请求拦截器:根据请求的请求头设定,来决定哪些请求可以访问
响应拦截器:这块就是根据 后端返回来的状态码判定执行不同业务
54、JSON和XML之间的区别
1、JSON是JavaScript Object Notation;XML是可扩展标记语言。
2、JSON是基于JavaScript语言;XML源自SGML。
3、JSON是一种表示对象的方式;XML是一种标记语言,使用标记结构来表示数据项。
4、JSON不提供对命名空间的任何支持;XML支持名称空间。
5、JSON支持数组;XML不支持数组。
6、XML的文件相对难以阅读和解释;与XML相比,JSON的文件非常易于阅读。
7、JSON不使用结束标记;XML有开始和结束标签。
8、JSON的安全性较低;XML比JSON更安全。
9、JSON不支持注释;XML支持注释。
10、JSON仅支持UTF-8编码;XML支持各种编码。
55、前后端分离?
开发模式
请求方式
前后端分离优势
1.可以实现真正的前后端解耦,前端服务器使用nginx。
2.发现bug,可以快速定位是谁的问题,不会出现互相踢皮球的现象。
3.在大并发情况下,可以同时水平扩展前后端服务器。
4.减少后端服务器的并发/负载压力
5.即使后端服务暂时超时或者宕机了,前端页面也会正常访问,只不过数据刷不出来而已。
6.多端应用
7.页面显示的东西再多也不怕,因为是异步加载。
8.nginx支持页面热部署,不用重启服务器,前端升级更无缝。
9.增加代码的维护性&易读性(前后端耦在一起的代码读起来相当费劲)。
10提升开发效率,因为可以前后端并行开发,而不是像以前的强依赖。
11.在nginx中部署证书,外网使用https访问,并且只开放443和80端口,其他端口一律关闭(防止黑客端口扫描),内网使用http,性能和安全都有保障。
12.前端大量的组件代码得以复用,组件化,提升开发效率,抽出来!
开发人员分离
后端工程师:
把精力放在java基础,设计模式,jvm原理,spring+springmvc原理及源码,linux,mysql事务隔离与锁机制,mongodb,http/tcp,多线程,分布式架构,弹性计算架构,微服务架构,java性能优化,以及相关的项目管理等等。
后端追求的是:三高(高并发,高可用,高性能),安全,存储,业务等等
前端工程师:
把精力放在html5,css3,jquery,angularjs,bootstrap,reactjs,vuejs,webpack,less/sass,gulp,nodejs,Google V8引擎,javascript多线程,模块化,面向切面编程,设计模式,浏览器兼容性,性能优化等等。
前端追求的是:页面表现,速度流畅,兼容性,用户体验等等
总结:
前后端分离并非仅仅只是一种开发模式,而是一种架构模式(前后端分离架构)。千万不要以为只有在撸代码的时候把前端和后端分开就是前后端分离了,需要区分前后端项目。前端项目与后端项目是两个项目,放在两个不同的服务器,需要独立部署,两个不同的工程,两个不同的代码库,不同的开发人员。
前后端工程师需要约定交互接口,实现并行开发,开发结束后需要进行独立部署,前端通过ajax来调用http请求调用后端的restful api。前端只需要关注页面的样式与动态数据的解析&渲染,而后端专注于具体业务逻辑。
浏览器
1、从输入URL到返回页面是怎样一个过程?
1.用户输入url并回车
2.浏览器进程检查url,组装协议,构成完整的url
3.浏览器进程通过进程问通信(IPC) 把url请求发送给网络进程
4.网络进程接收到url请求后检查本地缓存是否缓存了该请求资源,如果有则将该资源返回给浏览器进程
5.如果没有,网络进程向web服务器发起http请求(网络请求),请求流程如下:
1)进行DNS解析,获取服务器ip地址
2)利用ip地址和服务器建立tcp连接
3)构建请求头信息
4)发送请求头信息
5)服务器响应后,网络进程接收响应头和响应信息,并解析响应内容
6.网络进程解析响应流程:
1)检查状态码,如果是301/302,则需要重定向,从Location自动中读取地址,重新进行第4步
2)200响应处理,检查响应类型Content- Type,如果是字节流类型,则将该请求提交给下载管理器,该导航流程结束,不再进行。后续的渲染,如果是htm则通知浏览器进程准备渲染进程准备进行道染。
7.准备渲染进程
浏览器进程检查当前url是否和之前打开的道染进程根域名是否相同,如果相同,则复用原来的进程,如果不同,则开启新的渲染进程
8.传输数据、更新状态
1)渲染进程准备好后,浏览器向渲染进程发起"提交文档”的消息,渲染进程接收到消息和网络进程建立传输数据的“管道"
2)渲染进程接收完数据后,向浏览器发送“确认提交”
3)浏览器进程接收到确认消息后更新浏览器界面状态:安全、地址栏url、 前进后退的历史状态、更新web页面。
2、浏览器渲染HTML的步骤
1.HTML被HTML解析器解析成DOM Tree, CSS则被CSS解析器解析成CSSOM Tree
2.DOM Tree和CSSOM Tree解析完成后,被附加到一起,形成渲染树(Render Tree)
3.节点信息计算(重排),这个过程被叫做Layout(Webkit)或者Reflow(Mozilla)。即根据渲染树计算每个节点的几何信息生成布局
4.渲染绘制(重绘),这个过程被叫做(Painting 或者 Repaint)。即根据计算好的信息绘制整个页面
5.Display显示到屏幕上
3、什么是重绘和重排?
重排:当DOM的变化引发了元素几何属性的变化,比如改变元素的宽高,元素的位置,导致浏览器不得不重新计算元素的几何属性,并重新构建渲染树,这个过程称为“重排”
重绘:当DOM样式的改变并不影响它在文档流中的位置时,例如更改了字体颜色,浏览器会将新样式赋予给元素并重新绘制的过程称,这个过程就是“重绘”
会引起重排的操作:
页面首次渲染。
浏览器窗口大小发生改变——resize事件发生时。
元素尺寸或位置发生改变——定位、边距、填充、边框、宽度和高度。
元素内容变化(文字数量或图片大小等等)。
元素字体大小变化。
添加或者删除可见的DOM元素。
激活CSS伪类(例如::hover)。
设置style属性
查询某些属性或调用某些方法
优化策略:
减少DOM操作
1.最小化DOM访问次数,尽量缓存访问DOM的样式信息,避免过度触发回流。
2.如果在一个局部方法中需要多次访问同一个dom,可以在第一次获取元素时用变量保存下来,减少遍历时间。
3.用事件委托来减少事件处理器的数量。
4.用querySelectorAll()替代getElementByXX()。
减少重排
1.避免设置大量的style内联属性,因为通过设置style属性改变结点样式的话,每一次设置都会触发一次reflow,所以最好是使用class属性
2.不要使用table布局,因为table中某个元素一旦触发了reflow,那么整个table的元素都会触发reflow。那么在不得已使用table的场合,可以设置table-layout:auto;或者是table-layout:fixed这样可以让table一行一行的渲染,这种做法也是为了限制reflow的影响范围
3.尽量少使用display:none可以使用visibility:hidden代替,display:none会造成重排,visibility:hidden只会造成重绘。
4.使用resize事件时,做防抖和节流处理。
css及优化动画
1.少用css表达式
2.减少通过JavaScript代码修改元素样式,尽量使用修改class名方式操作样式或动画;
3.可以把动画效果应用到position属性为absolute或fixed的元素上,这样对其他元素影响较小
4.动画实现的速度的选择。比如实现一个动画,以1个像素为单位移动这样最平滑,但是reflow就会过于频繁,大量消耗CPU资源,如果以3个像素为单位移动则会好很多。
5.开启动画的GPU加速,把渲染计算交给GPU。
4、介绍一下浏览器缓存策略?
什么是浏览器缓存
浏览器的缓存机制指的是通过在一段时间内保留已接收到的 web 资源的一个副本,如果在资源的有效时间内,发起了对这个资源的再一次请求,那么浏览器会直接使用缓存的副本,而不是向服务器发起请求。使用 web 缓存可以有效地提高页面的打开速度,减少不必要的网络带宽的消耗。web 资源的缓存策略一般由服务器来指定,可以分为两种,分别是强缓存策略和协商缓存策略。
强缓存
使用强缓存策略时,如果缓存资源有效,则直接使用缓存资源,不必再向服务器发起请求。强缓存策略可以通过两种方式来设置,分别是 http 响应头信息中的 Expires 属性和 Cache-Control 属性。其中Cache-Control优先级比Expires高。
服务器通过在响应头中添加 Expires 属性,来指定资源的过期时间。在过期时间以内,该资源可以被缓存使用,不必再向服务器发送请求。这个时间是一个绝对时间,它是服务器的时间,因此可能存在这样的问题,就是客户端的时间和服务器端的时间不一致,或者用户可以对客户端时间进行修改的情况,这样就可能会影响缓存命中的结果。
Expires 是 http1.0 中的方式,因为它的一些缺点,在 http 1.1 中提出了一个新的头部属性就是 Cache-Control 属性,它提供了对资源的缓存的更精确的控制。它有很多不同的值,常用的比如我们可以通过设置 max-age 来指定资源能够被缓存的时间的大小,这是一个相对的时间,它会根据这个时间的大小和资源第一次请求时的时间来计算出资源过期的时间,因此相对于 Expires来说,这种方式更加有效一些。常用的还有比如 private ,用来规定资源只能被客户端缓存,不能够代理服务器所缓存。还有如 no-store ,用来指定资源不能够被缓存,no-cache 代表该资源能够被缓存,但是立即失效,每次都需要向服务器发起请求。
一般来说只需要设置其中一种方式就可以实现强缓存策略,当两种方式一起使用时,Cache-Control 的优先级要高于 Expires 。
协商缓存
使用协商缓存策略时,会先向服务器发送一个请求,如果资源没有发生修改,则返回一个 304 状态,让浏览器使用本地的缓存副本。如果资源发生了修改,则返回修改后的资源。协商缓存也可以通过两种方式来设置,分别是 http 头信息中的 Etag 和 Last-Modified 属性。
服务器通过在响应头中添加 Last-Modified 属性来指出资源最后一次修改的时间,当浏览器下一次发起请求时,会在请求头中添加一个 If-Modified-Since 的属性,属性值为上一次资源返回时的 Last-Modified 的值。当请求发送到服务器后服务器会通过这个属性来和资源的最后一次的修改时间来进行比较,以此来判断资源是否做了修改。如果资源没有修改,那么返回 304 状态,让客户端使用本地的缓存。如果资源已经被修改了,则返回修改后的资源。使用这种方法有一个缺点,就是 Last-Modified 标注的最后修改时间只能精确到秒级,如果某些文件在1秒钟以内,被修改多次的话,那么文件已将改变了但是 Last-Modified 却没有改变,这样会造成缓存命中的不准确。
因为 Last-Modified 的这种可能发生的不准确性,http 中提供了另外一种方式,那就是 Etag 属性。服务器在返回资源的时候,在头信息中添加了 Etag 属性,这个属性是资源生成的唯一标识符,当资源发生改变的时候,这个值也会发生改变。在下一次资源请求时,浏览器会在请求头中添加一个 If-None-Match 属性,这个属性的值就是上次返回的资源的 Etag 的值。服务接收到请求后会根据这个值来和资源当前的 Etag 的值来进行比较,以此来判断资源是否发生改变,是否需要返回资源。通过这种方式,比 Last-Modified 的方式更加精确。
当 Last-Modified 和 Etag 属性同时出现的时候,Etag 的优先级更高。使用协商缓存的时候,服务器需要考虑负载平衡的问题,因此多个服务器上资源的 Last-Modified 应该保持一致,因为每个服务器上 Etag 的值都不一样,因此在考虑负载平衡时,最好不要设置 Etag 属性。
总结
在实际的缓存机制中,强缓存策略和协商缓存策略是一起合作使用的。浏览器首先会根据请求的信息判断,若强制缓存(Expires和Cache-Control)生效则直接使用缓存资源,若不生效则进行协商缓存(Last-Modified / If-Modified-Since和Etag / If-None-Match),协商缓存由服务器决定是否使用缓存,若协商缓存失效,那么代表该请求的缓存失效,重新获取请求结果和标识,再存入浏览器缓存中;生效则返回304,继续使用缓存
5、V8引擎如何执行一段js代码
预解析:检查语法错误但不生成AST
生成AST:经过词法/语法分析,生成抽象语法树
生成字节码:基线编译器(Ignition)将AST转换成字节码
生成机器码:优化编译器(Turbofan)将字节码转换成优化过的机器码,此外在逐行执行字节码的过程中,如果一段代码经常被执行,那么V8会将这段代码直接转换成机器码保存起来,下一次执行就不必经过字节码,优化了执行速度
6、介绍一下引用计数和标记清除
引用计数:给一个变量赋值引用类型,则该对象的引用次数+1,如果这个变量变成了其他值,那么该对象的引用次数-1,垃圾回收器会回收引用次数为0的对象。但是当对象循环引用时,会导致引用次数永远无法归零,造成内存无法释放。
标记清除:垃圾收集器先给内存中所有对象加上标记,然后从根节点开始遍历,去掉被引用的对象和运行环境中对象的标记,剩下的被标记的对象就是无法访问的等待回收的对象。
7、V8如何进行垃圾回收
栈内存调用栈上下文切换后就被回收,比较简单
V8的堆内存分为新生代内存和老生代内存,新生代内存是临时分配的内存,存在时间短,老生代内存存在时间长
新生代内存回收机制
新生代内存容量小,64位系统下仅有32M。新生代内存分为From、To两部分,进行垃圾回收时,先扫描From,将非存活对象回收,将存活对象顺序复制到To中,之后调换From/To,等待下一次回收
老生代内存回收机制
晋升:如果新生代的变量经过多次回收依然存在,那么就会被放入老生代内存中
标记清除:老生代内存会先遍历所有对象并打上标记,然后对正在使用或被强引用的对象取消标记,回收被标记的对象
整理内存碎片:把对象挪到内存的一端
8、如何加快首屏速度
减少 HTTP 请求数
1.合并 JavaScript、CSS 等文件,将浏览器一次访问需要的javascript和CSS合并成一个文件,这样浏览器就只需要一次请求。
2.使用CSS Sprite:将背景图片合并成一个文件,通过background-image 和 background-position 控制显示。逐步被 Icon Font 和 SVG Sprite 取代。
3.内容分片,将请求划分到不同的域名上。
4.LazyLoad Images (这条策略实际上并不一定能减少 HTTP请求数,但是却能在某些条件下或者页面刚加载时减少 HTTP请求数)
5.使用浏览器缓存,将CSS、javascript、logo、图标这些更新频率较低的静态资源文件缓存在浏览器中
减少 DNS 查询
首次访问、没有相应的 DNS 缓存时,域名越多,查询时间越长。所以应尽量减少域名数量。但基于并行下载考虑,把资源分布到 2 个域名上(最多不超过 4 个)。这是减少 DNS 查询同时保证并行下载的折衷方案。
避免重定向
客户端收到服务器的重定向响应后,会根据响应头中 Location 的地址再次发送请求。重定向会影响用户体验,尤其是多次重定向时,用户在一段时间内看不到任何内容,只看到浏览器进度条一直在刷新。
缓存 Ajax 请求
最重要的的优化方式是缓存响应结果
延迟加载 | 延迟渲染
将首屏以外的 HTML 放在不渲染的元素中,如隐藏的 ,或者 type 属性为非执行脚本的
预先加载
预先加载利用浏览器空闲时间请求将来要使用的资源,以便用户访问下一页面时更快地响应。
减少 DOM 元素数量
复杂的页面不仅下载的字节更多,JavaScript DOM 操作也更慢。例如,同是添加一个事件处理器,500 个元素和 5000 个元素的页面速度上会有很大区别。
划分内容到不同域名
浏览器一般会限制每个域的并行线程(一般为 6 个,甚至更少),使用不同的域名可以最大化下载线程,但注意保持在 2-4 个域名内,以避免 DNS 查询损耗。
尽量减少 iframe 使用
使用 iframe 可以在页面中嵌入 HTML 文档,但有利有弊。
避免 404 错误
一些网站设计很酷炫、有提示信息的 404 页面,有助于提高用户体验,但还是浪费服务器资源。尤其糟糕的是外部脚本返回 404,不仅阻塞其他资源下载,浏览器还会尝试把 404 页面内容当作 JavaScript 解析,消耗更多资源。
使用 CDN
相比分布式架构的复杂和巨大投入,静态内容分发网络(CDN)可以以较低的投入,获得加载速度有效提升。
启用 Gzip
Gzip 压缩通常可以减少 70% 的响应大小,对某些文件更可能高达 90%,比 Deflate 更高效。主流 Web 服务器都有相应模块,而且绝大多数浏览器支持 gzip 解码。所以,应该对 HTML、CSS、JS、XML、JSON 等文本类型的内容启用压缩。
避免图片 src 为空
虽然 src 属性为空字符串,但浏览器仍然会向服务器发起一个 HTTP 请求
减少 Cookie 大小
Cookie 被用于身份认证、个性化设置等诸多用途。Cookie 通过 HTTP 头在服务器和浏览器间来回传送,减少 Cookie 大小可以降低其对响应速度的影响。
把样式表放在 head 中
把样式表放在 中可以让页面渐进渲染,尽早呈现视觉反馈,给用户加载速度很快的感觉。这对内容比较多的页面尤为重要,用户可以先查看已经下载渲染的内容,而不是盯着白屏等待。
不要使用 CSS 表达式
CSS 表达式可以在 CSS 里执行 JavaScript,仅 IE5-IE7 支持,IE8 标准模式已经废弃。CSS 表达式超出预期的频繁执行,页面滚动、鼠标移动时都会不断执行,带来很大的性能损耗。
使用 link标签替代 @import
对于 IE 某些版本,@import 的行为和 放在页面底部一样。所以,不要用它
把脚本放在页面底部
浏览器下载脚本时,会阻塞其他资源并行下载,即使是来自不同域名的资源。因此,最好将脚本放在底部,以提高页面加载速度。
使用外部 JavaScript 和 CSS
外部 JavaScript 和 CSS 文件可以被浏览器缓存,在不同页面间重用,也能降低页面大小。当然,实际中也需要考虑代码的重用程度。如果仅仅是某个页面使用到的代码,可以考虑内嵌在页面中,减少 HTTP 请求数。另外,可以在首页加载完成以后,预先加载子页面的资源。
移除重复脚本
重复的脚本不仅产生不必要的 HTTP 请求,而且重复解析执行浪费时间和计算资源。
减少 DOM 操作
JavaScript 操作 DOM 很慢,尤其是 DOM 节点很多时。
9、浏览器内核
定义
浏览器最重要或者说核心的部分是“Rendering Engine”,可大概译为“渲染引擎”,不过我们一般习惯将之称为“浏览器内核”。负责对网页语法的解释(如标准通用标记语言下的一个应用HTML、JavaScript)并渲染网页。 所以,通常所谓的浏览器内核也就是浏览器所采用的渲染引擎,渲染引擎决定了浏览器如何显示网页的内容以及页面的格式信息。不同的浏览器内核对网页编写语法的解释也有不同,因此同一网页在不同的内核的浏览器里的渲染效果也可能不同,这也是网页编写者需要在不同内核的浏览器中测试网页显示效果的原因。
常见的浏览器内核
IE浏览器:Trident内核,也是俗称的IE内核;
Firefox浏览器:Gecko内核,俗称Firefox内核;
Safari浏览器:Webkit内核;
Opera浏览器:最初是自己的Presto内核,后来是Webkit,现在是Blink内核
Chrome浏览器:以前是Webkit内核,现在是Blink内核;
360浏览器、猎豹浏览器:IE+Chrome双内核;
搜狗、遨游、QQ浏览器:Trident(兼容模式)+Webkit(高速模式);
百度浏览器、世界之窗:IE内核;
2345浏览器:以前是IE内核,现在也是IE+Chrome双内核
10、浏览器兼容问题
1.不同浏览器的标签默认的margin和padding不同
解决方案:css 里增加通配符 * { margin: 0; padding: 0; }
2.文字大小不一致字体大小在不同浏览上不一致。例如font-size:14px,在 IE 中的实际行高是16px,下面有3px留白;在 Firefox 中的实际行高是17px,下面有3px留白,上边1px留白;在 opera 中就更不一样了。
html {
font-size: 14px;
line-height: 14px;
}
3.IE6双边距问题;在 IE6中设置了float , 同时又设置margin , 就会出现边距问题
解决方案:设置display:inline;
4.当标签的高度设置小于10px,在IE6、IE7中会超出自己设置的高度
解决方案:超出高度的标签设置overflow:hidden,或者设置line-height的值小于你的设置高度
5.图片默认有间距
解决方案:使用float 为img 布局(所有图片左浮)
6.IE9以下浏览器不能使用opacity
.box {
/* 一点其他的样式... */
background-color: #000;
opacity: 0.5;
/* 兼容Firefox浏览器 */
-moz-opacity: 0.5;
filter: alpha(opacity=50);
/* IE6 */
filter: progid:DXImageTransform.Microsoft.Alpha(style=0, opacity=50);
7.边距重叠问题;当相邻两个元素都设置了margin 边距时,margin 将取最大值,舍弃最小值;
解决方案:为了不让边重叠,可以给子元素增加一个父级元素,并设置父级元素为overflow:hidden;
8.cursor:hand 显示手型在safari 上不支持
解决方案:统一使用 cursor:pointer
9.两个块级元素,父元素设置了overflow:auto;子元素设置了position:relative ;且高度大于父元素,在IE6、IE7会被隐藏而不是溢出;
解决方案:父级元素设置position:relative
10.css3新属性,加浏览器前缀兼容早期浏览器
-moz- :火狐浏览器
-webkit- : Safari, 谷歌浏览器等使用Webkit引擎的浏览器
-o- Opera:浏览器(早期)
-ms- :IE
11.IE8以下不支持CSS3的background-size属性。
解决方案:使用滤镜filter
12.const问题
Firefox下,可以使用const关键字来定义常量;IE下,只能使用var关键字来定义常量。
解决方案:统一使用var关键字来定义常量。
13.Firefox不支持innerText解决方案:使用textContent:
if (navigator.appName.indexOf("Explorer") > -1) {
document.getElementById('element').innerText = "text";
} else {
document.getElementById('element').textContent = "text";
}
14.事件绑定
IE: dom.attachEvent();
标准浏览器: dom.addEventListener(‘click’,function(event){},false);
15.event事件对象问题
解决方案:var e = e||window.event
16.event事件源对象
解决方案:var srcObj = event.srcElement?event.srcElement:event.target
17.IE浏览器div最小宽度和最小高度不生效的问题IE不认得min-这个定义,但实际上它把正常的width和height当作有min的情况来使。这样问题就大了,如果只用宽度和高度,正常的浏览器里这两个值就不会变,如果只用min-width和min-height的话,IE下面根本等于没有设置宽度和高度。比如要设置背景图片,这个最小宽度是比较重要的。
#box {
width: 80px;
height: 35px;
}
html>body #body {
width: auto;
height: auto;
min-width: 80px;
min-height: 35px;
}
- 超链接访问过后hover样式就不出现的问题被点击访问过的超链接样式不在具有hover和active了,很多人应该都遇到过这个问题,解决技巧是改变CSS属性的排列顺序: L-V-H-A
a:link {}
a:visited {}
a:hover {}
a:active {}
19.css hack解决浏览器兼容性不同浏览器,识别不同的样式,csshack本身就是处理浏览器兼容的
/* 0是留给ie8的 */
background-color: yellow0;
/* + ie7定了 */
+background-color: pink;
/* _专门留给神奇的ie6 */
_background-color: orange;
20.获取元素的非行间样式值
//获取元素属性值的兼容写去
function getStyle(obj, attr) {
if (obj.currentStyle) {
//兼容IE
obj.currentStyle[attr];
return obj.currentstyle[attr];
} else {
//IE
return window.getComputedStyle(obj, null)[attr];
}
}
21.阻止事件冒泡传播
// js阻止事件传播,这里使用click事件为例
document.onclick = function (e) {
var e = e || Window.event;
if (e.stopPropagation) {
e.stopPropagation(); // W3C标准
} else {
e.cancelBubble = true; // IE... .
}
}
22.阻止事件默认行为
// js阻止默认事件,一般阻止a链接href, form表单submit提交
document.onclick = function (e) {
var e = e || Window.event;
if (e.preventDefault) {
e.preventDefault(); // W3C标准
} else {
e.returnValue = false; // IE... .
}
}
23.ajax兼容问题
var oAjax = null;
if (window.XMLHttpRequest) {
oAjax = new XMLHttpRequest();
} else {
//只支持E6浏览器
oAjax = new ActiveXObject("Microsoft.XMLHTTP");
}
11、同源策略,跨域
同源策略
同源策略(Sameoriginpolicy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说Web是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。同源策略会阻止一个域的javascript脚本和另外一个域的内容进行交互。所谓同源(即指在同一个域)就是两个页面具有相同的协议(protocol),主机(host)和端口号(port)
同源限制
无法读取非同源网页的 Cookie、LocalStorage 和 IndexedDB
无法接触非同源网页的 DOM
无法向非同源地址发送 AJAX 请求
解决方案
1.设置相同的document.domain,两个页面就可以共享Cookie(此方案仅限主域相同,子域不同的跨域应用场景。)
2.调用postMessage方法实现父窗口http://test1.com
向子窗口http://test2.com
发消息(子窗口同样可以通过该方法发送消息给父窗口)
// 父窗口打开一个子窗口
var openWindow = window.open('http://test2.com', 'title');
// 父窗口向子窗口发消息(第一个参数代表发送的内容,第二个参数代表接收消息窗口的url)
openWindow.postMessage('Nice to meet you!', 'http://test2.com');
// 监听 message 消息
window.addEventListener('message', function (e) {
console.log(e.source); // e.source 发送消息的窗口
console.log(e.origin); // e.origin 消息发向的网址
console.log(e.data); // e.data 发送的消息
},false);
3.jsonp
JSONP 是服务器与客户端跨源通信的常用方法。最大特点就是简单适用,兼容性好(兼容低版本IE),缺点是只支持get请求,不支持post请求。
核心思想:网页通过添加一个<script>
元素,向服务器请求 JSON 数据,服务器收到请求后,将数据放在一个指定名字的回调函数的参数位置传回来。原生ajax
<script src="http://test.com/data.php?callback=dosomething"></script>
// 向服务器test.com发出请求,该请求的查询字符串有一个callback参数,用来指定回调函数的名字
// 处理服务器返回回调函数的数据
<script type="text/javascript">
function dosomething(res){
// 处理获得的数据
console.log(res.data)
}
</script>
jQuery ajax
$.ajax({
url: 'http://www.test.com:8080/login',
type: 'get',
dataType: 'jsonp', // 请求方式为jsonp
jsonpCallback: "handleCallback", // 自定义回调函数名
data: {}
});
vue-resource
this.$http.jsonp('http://www.domain2.com:8080/login', {
params: {},
jsonp: 'handleCallback'
}).then((res) => {
console.log(res);
})
4.cors
CORS 是跨域资源分享(Cross-Origin Resource Sharing)的缩写。它是 W3C 标准,属于跨源 AJAX 请求的根本解决方法。
1、普通跨域请求:只需服务器端设置Access-Control-Allow-Origin
2、带cookie跨域请求:前后端都需要进行设置,前端设置根据xhr.withCredentials字段判断是否带有cookie
原生ajax
var xhr = new XMLHttpRequest(); // IE8/9需用window.XDomainRequest兼容
// 前端设置是否带cookie
xhr.withCredentials = true;
xhr.open('post', 'http://www.domain2.com:8080/login', true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send('user=admin');
xhr.onreadystatechange = function() {
if (xhr.readyState == 4 && xhr.status == 200) {
alert(xhr.responseText);
}
};
jQuery ajax
$.ajax({
url: 'http://www.test.com:8080/login',
type: 'get',
data: {},
xhrFields: {
withCredentials: true // 前端设置是否带cookie
},
crossDomain: true, // 会让请求头中包含跨域的额外信息,但不会含cookie
});
vue-resource
Vue.http.options.credentials = true
axios
axios.defaults.withCredentials = true
网络
1、网络七层模型
2、常见的http状态码
1xx 开头(请求已被接受)
2xx 开头(请求成功)
200 OK:客户端发送给服务器的请求被正常处理并返回
3xx 开头(重定向)
301 Moved Permanently:永久重定向,请求的网页已永久移动到新位置。 服务器返回此响应时,会自动将请求者转到新位置
302 Moved Permanently:临时重定向,请求的网页已临时移动到新位置。服务器目前从不同位置的网页响应请求,但请求者应继续使用原有位置来进行以后的请求
304 Not Modified:未修改,自从上次请求后,请求的网页未修改过。服务器返回此响应时,不会返回网页内容
4xx 开头(客户端错误)
400 Bad Request:错误请求,服务器不理解请求的语法,常见于客户端传参错误
401 Unauthorized:未授权,表示发送的请求需要有通过 HTTP 认证的认证信息,常见于客户端未登录
403 Forbidden:禁止,服务器拒绝请求,常见于客户端权限不足
404 Not Found:未找到,服务器找不到对应资源
5xx 开头(服务端错误)
500 Inter Server Error:服务器内部错误,服务器遇到错误,无法完成请求
501 Not Implemented:尚未实施,服务器不具备完成请求的功能
502 Bad Gateway:作为网关或者代理工作的服务器尝试执行请求时,从上游服务器接收到无效的响应。
503 service unavailable:服务不可用,服务器目前无法使用(处于超载或停机维护状态)。通常是暂时状态。
3、请求报文和响应报文介绍
请求报文
一个HTTP请求报文由请求行(request line)、请求头(header)、空行和请求体4个部分组成
响应报文
一个HTTP响应报文主要由状态行、响应头部、空行和响应正文4部分组成
4、http1.0和http1.1有什么区别
长连接: HTTP/1.1支持长连接和请求的流水线,在一个TCP连接上可以传送多个HTTP请求,避免了因为多次建立TCP连接的时间消耗和延时。在HTTP1.1中默认开启Connection : keep-alive ,一定程度上弥补了HTTP1.0每次请求都要创建连接的缺点。
缓存处理: 在HTTP1.0中主要使用header里的If-Modified-Since Expires来做为缓存判断的标准HTTP1.1则引入了更多的缓存控制策略例如Entitytag , If-Unmodified-Since, If-Match, If-None Match等更多可供选择的缓存头来控制缓存策略。
带宽优化及网络连接的使用: HTTP1.0中,存在一些浪费带宽的现象,例如客户端只是需要某个对象的一部分,而服务器却将整个对象送过来了,并且不支持断点续传功能, HTTP1.1则在请求头引入了range头域,它允许只请求资源的某个部分,即返回码是206 ( Partial Content) , 这样就方便了开发者自由的选择以便于充分利用带宽和连接。
错误通知的管理: 在HTTP1.1中新增了24个错误状态响应码,如409( Conflict )表示请求的资源与资源的当前状态发生冲突; 410 ( Gone )表示服务器上的某个资源被永久性的删除。
Host头处理: 在HTTP1.0中认为每台服务器都绑定一个唯一的IP地址 ,因此,请求消息中的URL并没有传递主机名( hostname)。但随着虚拟主机技术的发展,在一台物理服务器上可以存在多个虚拟主机( Multi-homed Web Servers) , 并且它们共享一个IP地址。HTTP1.1的请 求消息和响应消息都应支持Host头域,且请求消息中如果没有Host头域会报告一个错误 ( 400 Bad Request)。
5、介绍一下http2.0新特性
多路复用: 即多个请求都通过一个TCP连接并发地完成,多路复用即连接共享,即每一个request都是是用作连接共享机制的。一个request对应一个id ,这样一个连接上可以有多个request ,每个连接的request可以随机的混杂在一起,接收方可以根据request的id将request再归属到各自不同的服务端请求里面。
服务端推送: 在http2中,服务端可以在客户端的某个请求后,根据这个请求,主动推送其他的资源。比如一个html页面中还带上可一个css和js的资源请求,http1.x 时,要发送三次请求,在http2中,不用请求三次,服务器发现html中包含了Css和js资源,便会将三个资源都返回给客户端,这样只需要一次通信,就可以获得全部资源
新的二进制格式: HTTP/2采用二进制格式传输数据,相比于HTTP/1.1的文本格式,二进制格式具有更好的解析性和拓展性
header压缩: HTTP/2压缩消息头,减少了传输数据的大小。HTTP1 x的header带有大量信息,而且每次都要重复发送,HTTP2.0使用encoder来减少需要传输的header大小,通讯双方各自cache一份header fields表,既避免了重复header的传输,又减小了需要传输的大小。
6、HTTP2.0的多路复用和HTTP1.X中的长连接复用有什么区别?
HTTP/1.0: 一次请求/响应,建立一个tcp连接,用完关闭,每一个请求都要建立一个tcp连接,也就意味着每个请求都要进行三次握手,这会造成资源的浪费
HTTP/1.1长连接: HTTP/1.1支持长连接和请求的流水线,在一个TCP连接上可以传送多个HTTP请求,避免了因为多次建立TCP连接的时间消耗和延时。
HTTP/1.1管道: 解决方式为,若干个请求排队串行化单线程处理,后面的请求等待前面请求的返回才能获得执行机会,一旦有某请求超时等,后续请求只能被阻塞,无办法,也就是人们常说的线头阻塞
HTTP/2多路复用: 多个请求可同时在一个连接上并行执行。某个请求任务耗时严重,不会影响到其它连接的正常执行;
7、http2.0多路复用基本原理以及解决的问题
TCP慢启动: TCP连接建立后,会经历一个先慢后快的发送过程,就像汽车启动一般,如果我们的网页文件(HTML/JS/CSS/icon)都经过一次慢启动,对性能是不小的损耗。另外慢启动是TCP为了减少网络拥塞的一种策略,我们是没有办法改变的。
多条TCP连接竞争带宽: 如果同时建立多条TCP连接,当带宽不足时就会竞争带宽,影响关键资源的下载。
HTTP/1.1队头阻塞: 尽管HTTP/1.1长链接可以通过一个TCP连接传输多个请求,但同一时刻只能处理一个请求,当前请求未结束前,其他请求只能处于阻塞状态。
为了解决以上几个问题,HTTP/2一个域名只使用一个TCP连接来传输数据,而且请求直接是并行的、非阻塞的,这就是多路复用
实现原理: HTTP/2引入了一个二进制分帧层,客户端和服务端进行传输时,数据会先经过二进制分帧层处理,转化为一个个带有请求ID的帧,这些帧在传输完成后根据ID组合成对应的数据。
8、http和https有何区别
- https协议需要到CA申请证书,一般免费证书较少,因而需要一定费用
- http是超文本传输协议,信息是明文传输, https则具有安全性的ssl/ tls加密传输协议
- http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80 ,后者是443
- http的连接很简单,是无状态的; HTTPS协议是由SSL TLS+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全
- https比http慢,因为https除了tcp握手的三个包,还要加上SSL握手的九个包
9、https是如何进行加密的
HTTP是应用层协议,位于HTTP协议之下是传输协议TCP。
TCP负责传输,HTTP则定义了数据如何进行包装。
HTTPS相对于HTTP有哪些不同呢?其实就是在HTTP跟TCP中间加多了一层加密层TLS/SSL。
什么是TLS/SSL?
通俗的讲,TLS、SSL其实是类似的东西,SSL是个加密套件,负责对HTTP的数据进行加密。TLS是SSL的升级版。现在提到HTTPS,加密套件基本指的是TLS。
传输加密的流程
原先是应用层将数据直接给到TCP进行传输,现在改成应用层将数据给到TLS/SSL,将数据加密后,再给到TCP进行传输,而不是任由数据在复杂而又充满危险的网络上明文裸奔
SSL/TLS协议的基本过程是这样的:
(1)客户端向服务器端索要并验证公钥。
(2)双方协商生成"对话密钥"。
(3)双方采用"对话密钥"进行加密通信。
非对称加密算法:
(1)乙方生成两把密钥(公钥和私钥)。公钥是公开的,任何人都可以获得,私钥则是保密的。
(2)甲方获取乙方的公钥,然后用它对信息加密。
(3)乙方得到加密后的信息,用私钥解密。
公钥加密的信息只有私钥解得开,那么只要私钥不泄漏,通信就是安全的
10、TCP三次握手
TCP 协议是面向连接的通信协议,即在传输数据前先在发送端和接收端建立逻辑连接,然后再传输数据,它提供了两台计算机之间可靠无差错的数据传输。
在 TCP 连接中必须要明确客户端与服务器端,由客户端向服务端发出连接请求,每次连接的创建都需要经过“三次握手”
第一次握手,客户端向服务器端发送一个带 SYN 标志的数据包,等待服务器确认
第二次握手,服务器端向客户端回传一个带有 SYN/ACK 标志的数据包,通知客户端收到了连接请求
第三次握手,客户端再次向服务器端回传一个带 ACK 标志的数据包,确认连接,“握手”结束。
上述每一次握手的作用如下:
第一次握手:客户端发送网络包,服务端收到了,这样服务端就能得出结论:客户端的发送能力、服务端的接收能力是正常的。
第二次握手:服务端发包,客户端收到了 这样客户端就能得出结论:服务端的接收、发送能力,客户端的接收、发送能力是正常的。不过此时服务器并不能确认客户端的接收能力是否正常
第三次握手:客户端发包,服务端收到了。 这样服务端就能得出结论:客户端的接收、发送能力正常,服务器自己的发送、接收能力也正常
通过三次握手,就能确定双方的接收和发送能力是正常的。之后就可以正常通信了
为什么不是两次握手?
如果是两次握手,发送端可以确定自己发送的信息能对方能收到,也能确定对方发的包自己能收到,但接收端只能确定对方发的包自己能收到 无法确定自己发的包对方能收到
并且两次握手的话, 客户端有可能因为网络阻塞等原因会发送多个请求报文,延时到达的请求又会与服务器建立连接,浪费掉许多服务器的资源
11、TCP四次挥手
1、客户端向服务器发送一个断开连接的请求(不早了,我该走了);
2、服务器接到请求后发送确认收到请求的信号(知道了);
3、服务器向客户端发送断开通知(我也该走了);
4、客户端接到断开通知后断开连接并反馈一个确认信号(嗯,好的),服务器收到确认信号后断开连接;
第一次挥手:主动关闭方发送一个 FIN,用来关闭主动方到被动关闭方的数据传送,也就是主动关闭方告诉被动关闭方:我已经不会再给你发数据了(当然,在 fin 包之前发送出去的数据,如果没有收到对应的 ack 确认报文,主动关闭方依然会重发这些数据),但是,此时主动关闭方还可 以接受数据。
第二次挥手:被动关闭方收到 FIN 包后,发送一个 ACK 给对方,确认序号为收到序号+1(与 SYN 相同,一个 FIN 占用一个序号)。
第三次挥手:被动关闭方发送一个 FIN,用来关闭被动关闭方到主动关闭方的数据传送,也就是告诉主动关闭方,我的数据也发送完了,不会再给你发数据了。
第四次挥手:主动关闭方收到 FIN 后,发送一个 ACK 给被动关闭方,确认序号为收到序号+1,至此,完成四次挥手。
四次挥手的原因
服务端在收到客户端断开连接Fin报文后,并不会立即关闭连接,而是先发送一个ACK包先告诉客户端收到关闭连接的请求,只有当服务器的所有报文发送完毕之后,才发送FIN报文断开连接,因此需要四次挥手
12、UDP和TCP的区别?应用场景?
- TCP面向连接(如打电话要先拨号建立连接) ,UDP是无连接的,即发送数据之前不需要建立连接
- TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付
- TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流:UDP是面向报文的,UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如IP电话,实时视频会议等)
4.每一条TCP连接只能是点到点的,UDP支持一对一, 一对多,多对一和多对多的交互通信
- TCP首部开销20字节;UDP的首部开销小,只有8个字节
- TCP的逻辑通信信道是全双工的可靠信道, UDP则是不可靠信道
应用场景
TCP 应用场景适用于对效率要求低,对准确性要求高或者要求有链接的场景,而UDP 适用场景为对效率要求高,对准确性要求低的场景
13、cookie 和 token 都存放在 header 中,为什么不会劫持 token?
原因是浏览器会自动带上cookie,而token需要设置header才可
xss: 用户通过各种方式将恶意代码注入到其他用户的页面中。就可以通过脚本劫持cookie或者localStorage等信息,发起请求,之类的操作。从而伪造用户身份相关信息。前端层面token会存在哪儿?不外乎cookie localStorage sessionStorage,这些东西都是通过js代码获取到的。解决方案:过滤标签<>,不信任用户输入, 对用户身份等cookie层面的信息进行http-only处理。
csxf: 跨站请求攻击,简单地说,是攻击者通过一些技术手段欺骗用户的浏览器去访问一个自己曾经认证过的网站并运行一些操作(如发邮件,发消息,甚至财产操作如转账和购买商品)。由于浏览器曾经认证过,所以被访问的网站会认为是真正的用户操作而去运行。这利用了web中用户身份验证的一个漏洞:简单的身份验证只能保证请求发自某个用户的浏览器,却不能保证请求本身是用户自愿发出的。csrf并不能够拿到用户的任何信息,它只是欺骗用户浏览器,让其以用户的名义进行操作。。解决方案也很简单,对于cookie不信任,对每次请求都进行身份验证,比如token的处理。
被xss攻击了,不管是token还是cookie,都能被拿到,所以对于xss攻击来说,cookie和token没有什么区别。但是对于csrf来说就有区别了。
cookie:用户点击了链接,cookie未失效,导致发起请求后后端以为是用户正常操作,于是进行扣款操作。
token:用户点击链接,由于浏览器不会自动带上token,所以即使发了请求,后端的token验证不会通过,所以不会进行扣款操作。
14、http请求方法
1.GET方法:发送一个请求来取得服务器上的某一资源
2.POST方法:向URL指定的资源提交数据或附加新的数据
3.PUT方法:跟POST方法很像,也是想服务器提交数据。但是,它们之间有不同。PUT指定了资源在服务器上的位置,而POST没有
4.HEAD方法:只请求页面的首部
5.DELETE方法:删除服务器上的某资源
6.OPTIONS方法:它用于获取当前URL所支持的方法。如果请求成功,会有一个Allow的头包含类似“GET,POST”这样的信息
7.TRACE方法:TRACE方法被用于激发一个远程的,应用层的请求消息回路
8.CONNECT方法:把请求连接转换到透明的TCP/IP通道
15、get和post的区别
GET在浏览器回退时是无害的,而POST会再次提交请求
GET产生的URL地址可以被收藏,而POST不可以
GET请求会被浏览器主动缓存,而POST不会,除非手动设置
GET请求只能进行url编码,而POST支持多种编码方式
GET请求参数会被完整保留在浏览器历史记录里, 而PQST中的参数不会被保留
GET请求在URL中传送的参数是有长度限制的,而POST没有限制
对参数的数据类型,GET只接受ASCII字符,而POST没有限制
GET比POST更不安全,因为参数直接暴露在URL上,所以不能用来传递敏感信息
GET参数通过URL传递,POST放在Request body中
16、WebSocket
WebSocket是什么?
WebSocket 是一种网络通信协议。RFC6455定义了它的通信标准。
WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。
为什么需要WebSocket?
我们知道,传统的HTTP协议是无状态的,每次请求(request)都要由客户端(如 浏览器)主动发起,服务端进行处理后返回response结果,而服务端很难主动向客户端发送数据;这种客户端是主动方,服务端是被动方的传统Web模式 对于信息变化不频繁的Web应用来说造成的麻烦较小,而对于涉及实时信息的Web应用却带来了很大的不便。
因此,随着HTML5的诞生,一种新的通信协议应运而生—WebSocket,他最大的特点就是服务端可以主动向客户端推送消息,客户端也可以主动向服务端发送消息,实现了真正的平等。
WebSocket的特点
(1)建立在 TCP 协议之上,服务器端的实现比较容易。
(2)与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。
(3)数据格式比较轻量,性能开销小,通信高效。
(4)可以发送文本,也可以发送二进制数据。
(5)没有同源限制,客户端可以与任意服务器通信。
(6)协议标识符是ws(如果加密,则为wss),服务器网址就是 URL。
在vue中创建websocket
<template>
<div>
<el-input v-model="params" clearable/>
<el-button type="primary" @click="send">发消息</el-button>
</div>
</template>
<script>
export default {
data() {
return {
params: '',
path: "ws://localhost:8080/websocket",
socket: ""
}
},
mounted() {
// 初始化
this.init()
},
destroyed() {
// 销毁监听
this.socket.onclose = this.close
},
methods: {
init: function () {
if (typeof (WebSocket) === "undefined") {
console.log("您的浏览器不支持socket")
} else {
// 实例化socket
this.socket = new WebSocket(this.path)
// 监听socket连接成功回调
this.socket.onopen = this.open
// 监听socket连接失败回调
this.socket.onerror = this.error
// 监听后台返回的socket消息
this.socket.onmessage = this.getMessage
}
},
open: function () {
console.log("socket连接成功")
},
error: function () {
console.log("连接错误")
},
getMessage: function (msg) {
console.log(msg.data)
},
send: function () {
this.socket.send(params)
},
close: function () {
console.log("socket已经关闭")
}
}
}
</script>
Webpack
1、前端工程化?
什么是工程化?
前端工程本质上是软件工程的一种。软件工程化关注的是性能、稳定性、可用性、可维护性等方面,注重基本的开发效率、运行效率的同时,思考维护效率。一切以这些为目标的工作都是“前端工程化”。工程化是一种思想而不是某种技术。
2、web标准—可用性、可维护性、可访问性
可用性: 可用性指的是:产品是否容易上手,用户能否完成任务,效率如何,以及这过程中用户的主观感受可好,是从用户的角度来看产品的质量。可用性好意味着产品质量高,是企业的核心竞争力。
可维护性: 可维护性一般包含两个层次,一是当系统出现问题时,快速定位并解决问题的成本,成本低则可维护性好。二是代码是否容易被人理解,是否容易修改和增强功能。可维护性和可复用性、可扩展性等有交叉的地方。构建可维护性好的代码,对企业的长期发展非常重要。
可访问性: Web内容对于残障用户的可阅读和可理解性。提高可访问性也能让普通用户更容易理解Web内容。具体而言,要考虑以下两方面:
1.无论用户是否残障,都得通过用户代理(User Agent)来访问Web内容。因此要提高可访问性,首先得考虑各种用户代理 :桌面浏览器、语音浏览器、移动电话、车载个人电脑等等。在Google, 专门聘请了一些残障雇员,来帮助提高产品的可访问性。
2.还得考虑用户访问Web内容时的环境限制 。比如:嘈杂的环境、过暗或过亮的房间、或者是免提等各种情况。这是更高一层次的可访问性要求,做到了,能造就产品在特定领域的核心竞争力。
3、什么是模块化
所谓的模块化开发就是封装细节,提供使用接口,彼此之间互不影响,每个模块都是实现某一特定的功能。模块化开发的基础就是函数
4、为什么模块很重要
因为有了模块,我们就可以更方便地使用别人的代码,想要什么功能,就加载什么模块。 但是,这样做有一个前提,那就是大家必须以同样的方式编写模块,否则你有你的写法,我有我的写法,岂不是乱了套
5、模块化发展历程
全局函数模式将不同的功能封装成不同的全局函数,使用的时候,直接调用就行了。这种做法的缺点很明显:"污染"了全局变量,无法保证不与其他模块发生变量名冲突,而且模块成员之间看不出直接关系。
function func1() {
// ...
}
function func2() {
// ...
}
使用对象封装 把模块写成一个对象,所有的模块成员都放到这个对象里面。减少了全局变量,解决命名冲突,缺点是变量可以被外面随意改变而导致数据不安全
var obj = {
age: 0,
func1: function () {
// ...
},
func2: function () {
// ...
}
}
IIFE模式: 匿名函数自调用(闭包)使用"立即执行函数"(Immediately-Invoked FunctionExpression,IIFE),可以达到不暴露私有成员的目的。这个也是闭包处理的一种方式。
var obj = (function () {
var age = 0
var func1 = function () {
// ...
};
var func2 = function () {
//...
};
return {
m1: func1,
m2: func2
};
})();
// 使用 上面的写法,外部代码无法读取内部的age变量
console.info(obj.age); // undefined
放大模式如果一个模块很大,必须分成几个部分,或者一个模块需要继承另一个模块,这时就有必要采用"放大模式"(augmentation)。在原有的基础上扩展更多的方法。下面的代码为obj模块添加了一个新方法func3 (),然后返回新的obj模块,方便方法连续调用
var obj = (function (mod) {
mod.func3 = function () {
// ...
}
return mod; // 方便方法连续调用
})(obj);
宽放大模式(Loose augmentation) 在浏览器环境中,模块的各个部分通常都是从网上获取的,有时无法知道哪个部分会先加载。如果采用上面的写法,第一个执行的部分有可能加载一个不存在空对象,这时就要采用"宽放大模式"
var obj = (function (mod) {
// ...
return mod;
})(window.obj||{}); // 确保对象不为空
输入全局变量
独立性是模块的重要特点,模块内部最好不与程序的其他部分直接交互。为了在模块内部调用全局变量,必须显式地将其他变量输入模块这是jQuery框架的源码,将window对象作为参数传入,这样做除了保证模块的独立性,还使得模块之间的依赖关系变得明显
(function (window, undefined) {
// ...
})(window);
目前流行的js模块化规范有CommonJS、AMD、CMD以及ES6的模块系统
6、前端模块化规范—CommonJS
CommonJS的出发点: JS没有完善的模块系统,标准库较少,缺少包管理工具。伴随着NodeJS的兴起,能让JS在任何地方运行,特别是服务端,也达到了具备开发大型项目的能力,所以CommonJS营运而生。
Node. js是commonJS规范的主要实践者,它有四个重要的环境变量为模块化的实现提供支持: module、exports、require、global。实际使用时,用module.exports定义当前模块对外输出的接口(不推荐直接用exports ) , 用require加载模块。
commonJS用同步的方式加载模块。在服务端,模块文件都存在本地磁盘,读取非常快,所以这样做不会有问题。但是在浏览器端,限于网络原因,更合理的方案是使用异步加载。
暴露模块: module.exports = value或exports.XXX = value
引入模块: require (xxx)
CommonJS规范
一个文件就是一个模块,拥有单独的作用域
普通方式定义的变量、函数、对象都属于该模块内
通过require来加载模块
通过exports和module.exports来暴露块中的内容
注意
1.当exports和module.exports同时存在时,module.exports会覆盖exports
2.当模块内全是exports时,就等同于module.exports
3.exports就是module.exports的子集
4.所有代码都运行在模块作用域,不会污染全局作用域
5.模块可以多次加载,但只会在第一次加载时候运行,然后运行结果就被缓存了,以后再加载,就直接读取缓存结果
6.模块加载顺序,按照代码出现的顺序同步加载
7.__dirname代表当前文件所在的文件夹路径
8.__ filename代表当前模 块文件所在的文件夹路径+文件名
7、前端模块化规范—ES6模块化
ES6在语言标准的层面上,实现了模块功能,而且实现得相当简单,旨在成为浏览器和服务器通用的模块解决方案。其模块功能主要由两个命令构成: export和
import。export 命令于规定模块的对外接口,import命令用于输入其他模块提供的功能。
其实ES6还提供了export default命令,为模块指定默认输出,对应的import语句不需要使用大括号。这也更趋近于AMD的引用写法。
ES6的模块不是对象,import命令会被JavaScript引擎静态分析,在编译时就引入模块代码,而不是在代码运行时加载,所以无法实现条件加载。也正因为这个,使得静态分析成为可能。
export
export可以导出的是一个对象中包含的多个属性、方法。export default只能导出一个可以不具名的函数。我们可以通过import进行引用。同时,我们也可以直接使用require使用,原因是webpack起了server相关。
import
- import {fn} from 'xxxxxx ( export导出方式的引用方式)
- import fn from ‘/xxx /xxx1’ ( export default导出方式的引用方式)
8、前端模块化规范—AMD
Asynchronous Module Definition,异步加载模块。它是一个在浏览器端模块化开发的规范,不是原生js的规范,使用AMD规范进行页面开发需要用到对应的函数库,RequireJS.
AMD规范采用异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。
使用requirejs实现AMD规范的模块化,用require.config()指定引用路径等用define()定义模块,用require ()加载模块。
// 定义模块
define('moduleName', ['a', 'b'], function (ma, mb) {
return someExportValue;
})
// 引入模块
require(['a', 'b'], function (ma, mb) {
// code
})
RequireJS主要解决的问题
文件可能有依赖关系,被依赖的文件需要早于依赖它的文件加载到浏览器
js加载的时候浏览器会停止页面渲染,加载文件愈多,页面响应事件就越长
异步前置加载
语法
define (id, dependencies, factory)
id可选参数,用来定义模块的标识,如果没有提供该参数,脚本文件名( 去掉拓展名)
dependencies 是一个当前模块用来的模块名称数组
factory工厂方法,模块初始化要执行的函数或对象,如果为函数,它应该只被执行一次,如果是对象,此对象应该为模块的输出值。
9、前端模块化规范—CMD
CMD是另一种js模块化方案,它与AMD很类似,不同点在于AMD推崇依赖前置、提前执行 , CMD推崇依赖就近、延迟执行。此规范其实是在seajs推广过程中产生的。
因为CMD推崇一个文件一 个模块,所以经常就用文件名作为模块id; CMD推祟依赖就近,所以一般不在define的参数中写依赖,而是在factory中写。
define(id, deps, factory)
factory有三个参数:function(require,exports,module) {}
require 是factory函数的第一个参数,require是-个方法,接受模块标识作为唯一参数,用来获取其他模块提供的接口;
exports 是一个对象,用来向外提供模块接口;module module是一个对象 ,上面存储了与当前模块相关联的一-些属性和方法。
//定义没有依赖的模块
define(function (require, exports, module) {
exports.xxx = vaule;
module.exports = value;
})
//定义有依赖的模块
define(function (require, exports, module) {
//同步引入模块
var module1 = require("./module1.js");
//异步引入模块
require.async("./module2.js", function (m2) {})
// 暴露接口
exports.xxx = value;
})
//引入模块
define(function (require) {
const m1 = require("./module1.js");
m1.show();
})
10、前端模块化规范—UMD
一种整合了CommonJS和AMD规范的方法,希望能解决跨平台模块方案
运行原理
UMD先判断是否支持Node.js的模块( exports)是否存在,存在则使用Nodejs模块模式再判断是否支持AMD ( define是否存在),存在则使用AMD方式加载模块
(function (window, factory) {
if (typeof exports === 'object') {
module.exports = factory();
} else if (typeof define === 'function' && define.amd) {
define(factory);
} else {
window.eventUtil = factory();
}
})(this, function () {
//module ...
});
11、前端模块化规范—总结
commonjs是同步加载的。主要是在nodejs也就是服务端应用的模块化机制,通过module.export导出声明,通过require(‘’) 加载。每个文件都是一个模块。他有自己的作用域,文件内的变量,属性函数等不能被外界访问。node会将模块缓存, 第二次加载会直接在缓存中获取。
AMD是异步加载的。主要应用在浏览器环境下。
requireJS是遵循AMD规范的模块化工具。他是通过define ()定义声明通过require(‘’, function(){})加载。
ES6的模块化加载时通过export 导出,用import导入可通过{}对导出的内容进行解构。
ES6的模块的运行机制与common不-样, js引擎对脚本静态分析的时候,遇到模块加载指令后会生成一个只读引用,等到脚本真正执行的时候才会通过引用去模块中获取值,在引用到执行的过程中模块中的值发生了变化,导入的这里也会跟着变, ES6模块是动态引用,并不会缓存值,模块里总是绑定其所在的模块。
12、什么是webpack? 解决了什么问题?
webpack是一个打包工具,他的宗旨是一切静态资源皆可打包。有人就会问为什么要webpack?webpack是现代前端技术的基石,常规的开发方式,比如jquery,html,css静态网页开发已经落后了。现在是MVVM的时代,数据驱动界面。webpack它做的事情是,分析你的项目结构,找到JavaScript模块以及其它的一些浏览器不能直接运行的拓展语言(Scss,TypeScript等),并将其打包为合适的格式以供浏览器使用
模块化工具的出现原因
模块化开发会划分出很多的模块文件,前端应用运行在浏览器中,所有文件都需要从服务器请求回来,因此必然会导致浏览器频繁发送网络请求,影响应用的工作效率。 前端项目中的 HTML CSS 这些资源文件也需要被模块化处理。
工具需要具备的能力
把 ES6 代码编译成浏览器能解析的 ES5 代码
把各种文件HTML、CSS、JS 图片等分别打包到一起,这样就不用频繁发送网络请求
模块化只在开发阶段需要,能更好地组织我们的代码
Webpack 解决的问题
在前端项目中高效地管理和维护项目中的每一个资源
14、Webpack原理(执行流程)
webpack启动后会在entry里配置的module开始递归解析entry所依赖的所有module,每找到一个module, 就会根据配置的loader去找相应的转换规则,对module进行转换后在解析当前module所依赖的module,这些模块会以entry为分组,一个entry和所有相依赖的module也就是一个chunk,最后webpack会把所有chunk转换成文件输出,在整个流程中webpack会在恰当的时机执行plugin的逻辑
1)初始化参数
从配置文件和Shell语句中读取与合并参数,得出最终的参数
2)开始编译
用上一步得到的参数初始化Compiler对象,加载所有配置的插件,执行对象的run方法开始执行编译
3)确定入口
根据配置中的entry找出所有的入口文件
4)编译模板
从入口文件出发,调用所有配置的Loader对模块进行翻译,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理
5)模块编译完成
在经过第4步使用Loader翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系;
6)输出资源
根据入口和模块之间的依赖关系,组装成一个个包含多个模块的Chunk ,再把每个Chunk转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会
7)输出完成
在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统
8)其它
整个过程中特定的时间点广播事件,插件可以进行监听和处理
15、什么是loader?解决了什么问题?
在webpack内部中,任何文件都是模块,不仅仅只是js文件,默认情况下,在遇到import或者load加载模块的时候,webpack只支持对js文件打包。像css、sass、png等这些类型的文件的时候,webpack则无能为力,这时候就需要配置对应的loader进行文件内容的解析
loader是文件加载器,能够加载资源文件,并对这些文件进行一些处理,诸如编译、压缩等,最终一起打包到指定的文件中
处理一个文件可以使用多个loader,loader的执行顺序和配置中的顺序是相反的,即最后一个loader最先执行,第一个loader最后执行
第一个执行的loader接收源文件内容作为参数,其它loader接收前一个执行的loader的返回值作为参数,最后执行的loader会返回此模块的JavaScript源码
16、常见的loader
1.file-loader:文件加载
2.url-loader:文件加载,可以设置阈值,小于时把文件base64编码
3.image-loader:加载并压缩图片
4.json-loader:webpack默认包含了
5.babel-loader:ES6+转成ES5
6.ts-loader:将ts转成js
7.awesome-typescript-loader:比上面那个性能好
8.css-loader:处理@import和url这样的外部资源
9.style-loader:在head创建style标签把样式插入
10.postcss-loader:扩展css语法,使用postcss各种插件autoprefixer , cssnext , cssnano
11.eslint-loadertslint-loader:通过这两种检查代码,tslint不再维护,用的eslint
12.vue-loader:加载vue单文件组件
13.i18n-loader:国际化
14.cache-loader:性能开销大的1oader前添加,将结果缓存到磁盘
15.svg-inline-loader:压缩后的svg注入代码;
16.source-map-loader:加载source Map文件,方便调试
17.expose-loader:暴露对象为全局变量
18.imports-loader、exports-loader等可以向模块注入变或者提供导出模块功能
19.raw-loader:可以将文件已字符串的形式返回
20.校验测试:mocha-loader、jshint-loader 、eslint-loader等
17、css-loader和style-loader的区别
css-loader处理css文件
style-loader把js中import导入的样式文件代码,打包到js文件中,运行js文件时,将样式自动插入到<style>
标签中
18、file-loader和url-loader的区别
file-loader返回的是图片的url
url-loader可以通过limit属性对图片分情况处理,当图片小于limit(单位: byte )大小时转base64 ,大于limit时调用file-loader对图片进行处理。
url-loader封装了file-loader,但url-loader 并不依赖于file-loader
19、什么是plugin?解决了什么问题?
plugin赋予其各种灵活的功能,例如打包优化、资源管理、环境变量注入等,在webpack运行的生命周期中会广播出许多事件,plugin可以监听这些事件,在合适的时机通过webpack提供的API改变输出结果。
20、常见的plugin
1.ignore-plugin:忽略文件
2.uglifyjs-webpack-plugin:不支持ES6压缩(Webpack4以前使用)
3.terser-webpack plugin:支持压缩ES6 (Webpack4)
4.webpack-parallel-uglify-plugin:多进程执行代码压缩,提升构建速度
5.mini-css-extract-plugin:分离样式文件, CSS提取为独立文件,支持按需加载
6.serviceworker-webpack-plugin:为网页应用增加离线缓存功能
7.clean-webpack-plugin:目录清理
8.speed-measure-webpack-plugin:可以看到每个Loader和Plugin执行耗时
9.webpack内置UglifyJsPlugin ,压缩和混淆代码。
10.webpack内置CommonsChunkPlugin,提高打包效率,将第三方库和业务代码分开打包。
11.ProvidePlugin:自动加载模块,代替require和import
12.html-webpack-plugin:可以根据模板自动生成html代码,并自动引用css和js文件
13.extract-tex-webpack-plugin:将js文件中引用的样式单独抽离成css文件
14.DefinePlugin:编译时配置全局变量,这对开发模式和发布模式的构建允许不同的行为非常有用。
15.HotModuleReplacementPlugin:热更新
16.DIPlugin和DIlReferencePluin相互配合,前者第三包的构建,只构建业务代码,同时能解决Externals多次引用问题。DIIRefrencePlugin引用DIPlugin配置生成的manifest.json文件,manifest.json包含了依赖模块和module id的映射关系
17.optimize-css-assets-webpack-plugin:不同组件中重复的css可以快速去重
18.Webpack-bundle-analyzer:一个webpack的bundle文件分析工具,将bundle文件以可交互缩放的treemap的形式展示。
19.compression-webpack-plugin:铲环境可采用gzip压缩JS和CSS
20.happypack:通过多进程模型,来加速代码构建
21、loader和plugin的区别
对于loader,它是一个转换器,将A文件进行编译形成B文件,这里操作的是文件,比如将A. scss转换为A. css,单纯的文件转换过程
plugin是一个扩展器,它丰富了webpack本身,针对是loader结束后,webpack打包的整个过程,它并不直接操作文件,而是基于事件机制工作,会监听webpack打包过程中的某些节点,执行广泛的任务
22、webpack编译生命周期钩子
entry-option:初始化 option
run:
compile:真正开始的编译,在创建 compilation 对象之前
compilation:生成好了 compilation 对象
make:从 entry 开始递归分析依赖,准备对每个模块进行 build
after-compile:编译 build 过程结束
emit:在将内存中 assets 内容写到磁盘文件夹之前
after-emit:在将内存中 assets 内容写到磁盘文件夹之后
done:完成所有的编译过程
failed:编译失败的时候
23、webpack热更新
1.通过webpack-dev-server创建两个服务器:提供静态资源的服务(express)和Socket服务
2.express server 负责直接提供静态资源的服务(打包后的资源直接被浏览器请求和解析)
3.socket server 是一个 websocket 的长连接,双方可以通信
4.当 socket server 监听到对应的模块发生变化时,会生成两个文件.json(manifest文件)和.js文件(update chunk)
5.通过长连接,socket server 可以直接将这两个文件主动发送给客户端(浏览器)
6.浏览器拿到两个新的文件后,通过HMR runtime机制,加载这两个文件,并且针对修改的模块进行更新
24、Webpack proxy工作原理?为何能实现跨域?
在开发阶段, webpack-dev-server 会启动一个本地开发服务器,所以我们的应用在开发阶段是独立运行在 localhost的一个端口上,而后端服务又是运行在另外一个地址上
所以在开发阶段中,由于浏览器同源策略的原因,当本地访问后端就会出现跨域请求的问题
通过设置webpack proxy实现代理请求后,相当于浏览器与服务端中添加一个代理者
当本地发送请求的时候,代理服务器响应该请求,并将请求转发到目标服务器,目标服务器响应数据后再将数据返回给代理服务器,最终再由代理服务器将数据响应给本地
在代理服务器传递数据给本地浏览器的过程中,两者同源,并不存在跨域行为,这时候浏览器就能正常接收数据
注意:服务器与服务器之间请求数据并不会存在跨域行为,跨域行为是浏览器安全策略限制
25、如何借助webpack来优化前端性能
关于webpack对前端性能的优化,可以通过文件体积大小入手,其次还可通过分包的形式、减少http请求次数等方式,实现对前端性能的优化
JS代码压缩
CSS代码压缩
Html文件代码压缩
文件大小压缩
图片压缩
Tree Shaking
代码分离
内联 chunk
26、如何提高webpack构建速度
优化webpack构建的方式有很多,主要可以从优化搜索时间、缩小文件搜索范围、减少不必要的编译等方面入手
优化 loader 配置在使用loader时,可以通过配置include、exclude、test属性来匹配文件,接触include、exclude规定哪些匹配应用loader
module.exports = {
module: {
rules: [{
//如果项目源码中只有js文件就不要写成/\.jsx?$/,提升正则表达式性能
test: /\.js$/,
// babel-loader 支持缓存转换出的结果,通过cacheDirectory 选项开启
use: ['babel-loader?cacheDirectory'],
//只对项目根目录下的src目录中的文件采用babel-loader
include: path.resolve(__dirname, 'src'),
}, ]
},
};
合理使用 resolve.extensions
在开发中我们会有各种各样的模块依赖,这些模块可能来自于自己编写的代码,也可能来自第三方库, resolve可以帮助webpack从每个 require/import 语句中,找到需要引入到合适的模块代码
通过resolve.extensions是解析到文件时自动添加拓展名
当我们引入文件的时候,若没有文件后缀名,则会根据数组内的值依次查找
当我们配置的时候,不要随便把所有后缀都写在里面,这会调用多次文件的查找,这样就会减慢打包速度
module.exports = {
extensions: [".warm", ".mjs", ".js", ".json"]
}
优化 resolve.modulesresolve.modules 用于配置 webpack 去哪些目录下寻找第三方模块。默认值为[‘node_modules’],所以默认会从node_modules中查找文件 当安装的第三方模块都放在项目根目录下的 ./node_modules目录下时,所以可以指明存放第三方模块的绝对路径,以减少寻找
module.exports = {
resolve: {
//使用绝对路径指明第三方模块存放的位置,以减少搜索步骤
//其中__dirname表示当前工作目录,也就是项目根目录
modules: [path.resolve(__dirname, ' node_ modules')]
},
};
优化 resolve.alias
alias给一些常用的路径起一个别名,特别当我们的项目目录结构比较深的时候,一个文件的路径可能是./…/…/的形式。通过配置alias以减少查找过程
module.exports = {
...
resolve: {
alias: {
"@": path.resolve(__dirname, ' ./src')
}
}
}
使用 DLLPlugin 插件
DLL全称是 动态链接库,是为软件在winodw种实现共享函数库的一种实现方式,而Webpack也内置了DLL的功能,为的就是可以共享,不经常改变的代码,抽成一个共享的库。这个库在之后的编译过程中,会被引入到其他项目的代码中
打包一个 DLL 库:webpack内置了一个DllPlugin可以帮助我们打包一个DLL的库文件
module.exports = {
...
new webpack.DllPlugin({
name: 'dll_[name]',
path: path.resolve(__dirname, "./dll/[name].mainfest.json")
})
}
引入 DLL 库:使用 webpack 自带的 DllReferencePlugin 插件对 mainfest.json 映射文件进行分析,获取要使用的DLL库,然后再通过AddAssetHtmlPlugin插件,将我们打包的DLL库引入到Html模块中
module.exports = {
...
new webpack.DllReferencePlugin({
context: path.resolve(__dirname, "./dll/dll_react.js"),
mainfest: path.resolve(__dirname, "./dll/react.mainfest.json")
}),
new AddAssetHtmlPlugin({
outputPath: "./auto",
filepath: path.resolve(__dirname, "./dll/dll react.js")
})
}
使用 cache-loader
在一些性能开销较大的 loader之前添加 cache-loader,以将结果缓存到磁盘里,显著提升二次构建速度保存和读取这些缓存文件会有一些时间开销,所以请只对性能开销较大的 loader 使用此loader
module.exports = {
module: {
rules: [{
test: /\.ext$/,
use: ['cache-loader', ...loaders],
include: path.resolve('src'),
}, ],
},
};
terser 启动多线程使用多进程并行运行来提高构建速度
module.exports = {
optimization: {
minimizer: [
new TerserPlugin({
parallel: true,
}),
],
},
};
合理使用
打包生成 sourceMap 的时候,如果信息越详细,打包速度就会越慢。对应属性取值如下所示:
设计模式
1、介绍一下单一职责原则和开放封闭原则
单一职责原则: 一个类只负责一个功能领域中的相应职责,或者可以定义为:就一个类而言,应该只有一个引起它变化的原因。
开放封闭原则: 核心的思想是软件实体(类、模块、函数等)是可扩展的、但不可修改的。也就是说,对扩展是开放的,而对修改是封闭的。
2、单例模式
单例模式即一个类只能构造出唯一实例,单例模式的意义在于共享、唯一,Redux/Vuex中的store、JQ的$或者业务场景中的购物车、登录框都是单例模式的应用
class SingletonLogin {
constructor(name, password) {
this.name = name
this.password = password
}
static getInstance(name, password) {
//判断对象是否已经被创建,若创建则返回旧对象
if (!this.instance) this.instance = new SingletonLogin(name, password)
return this.instance
}
}
let obj1 = SingletonLogin.getInstance('Demi', '123')
let obj2 = SingletonLogin.getInstance('Demi', '321')
console.log(obj1 === obj2) // true
console.log(obj1) // {name:Demi,password:123}
console.log(obj2) // 输出的依然是{name:Demi,password:123}
3、工厂模式
工厂模式即对创建对象逻辑的封装,或者可以简单理解为对new的封装,这种封装就像创建对象的工厂,故名工厂模式。工厂模式常见于大型项目,比如JQ的$对象,我们创建选择器对象时之所以没有new selector就是因为$()已经是一个工厂方法,其他例子例如React.createElement()、Vue.component()都是工厂模式的实现。工厂模式有多种:简单工厂模式、工厂方法模式、抽象工厂模式,这里只以简单工厂模式为例
class User {
constructor(name, auth) {
this.name = name
this.auth = auth
}
}
class UserFactory {
static createUser(name, auth) {
//工厂内部封装了创建对象的逻辑:
//权限为admin时,auth=1, 权限为user时, auth为2
//使用者在外部创建对象时,不需要知道各个权限对应哪个字段, 不需要知道赋权的逻辑,只需要知道创建了一个管理员和用户
if (auth === 'admin') new User(name, 1)
if (auth === 'user') new User(name, 2)
}
}
const admin = UserFactory.createUser('Demi', 'admin');
const user = UserFactory.createUser('Demi', 'user');
4、观察者模式
观察者模式算是前端最常用的设计模式了,观察者模式概念很简单:观察者监听被观察者的变化,被观察者发生改变时,通知所有的观察者。观察者模式被广泛用于监听事件的实现,有关观察者模式的详细应用
//观察者
class Observer {
constructor(fn) {
this.update = fn
}
}
//被观察者
class Subject {
constructor() {
this.observers = [] //观察者队列
}
addObserver(observer) {
this.observers.push(observer) //往观察者队列添加观察者
}
notify() { //通知所有观察者,实际上是把观察者的update()都执行了一遍
this.observers.forEach(observer => {
observer.update() //依次取出观察者,并执行观察者的update方法
})
}
}
var subject = new Subject() //被观察者
const update = () => {
console.log('被观察者发出通知')
} //收到广播时要执行的方法
var ob1 = new Observer(update) //观察者1
var ob2 = new Observer(update) //观察者2
subject.addObserver(ob1) //观察者1订阅subject的通知
subject.addObserver(ob2) //观察者2订阅subject的通知
subject.notify() //发出广播,执行所有观察者的update方法
5、装饰器模式
装饰器模式,可以理解为对类的一个包装,动态地拓展类的功能,ES7的装饰器语法以及React中的高阶组件(HoC)都是这一模式的实现。react-redux的connect()也运用了装饰器模式,这里以ES7的装饰器为例
function info(target) {
target.prototype.name = 'Demi'
target.prototype.age = 10
}
@info
class Man {}
let man = new Man()
man.name // Demi
6、适配器模式
适配器模式,将一个接口转换成客户希望的另一个接口,使接口不兼容的那些类可以一起工作。我们在生活中就常常有使用适配器的场景,例如出境旅游插头插座不匹配,这时我们就需要使用转换插头,也就是适配器来帮我们解决问题。
class Adaptee {
test() {
return '旧接口'
}
}
class Target {
constructor() {
this.adaptee = new Adaptee()
}
test() {
let info = this.adaptee.test()
return `适配${info}`
}
}
let target = new Target()
console.log(target.test())
7、代理模式
代理模式,为一个对象找一个替代对象,以便对原对象进行访问。即在访问者与目标对象之间加一层代理,通过代理做授权和控制。最常见的例子是经纪人代理明星业务,假设你作为一个投资者,想联系明星打广告,那么你就需要先经过代理经纪人,经纪人对你的资质进行考察,并通知你明星排期,替明星本人过滤不必要的信息。事件代理、JQuery的$.proxy、ES6的proxy都是这一模式的实现,下面以ES6的proxy为例
const idol = {
name: 'Demi',
phone: 10086,
price: 1000000 //报价
}
const agent = new Proxy(idol, {
get: function (target) {
//拦截明星电话的请求,只提供经纪人电话
return '经纪人电话:10010'
},
set: function (target, key, value) {
if (key === 'price') {
//经纪人过滤资质
if (value < target.price) throw new Error('报价过低')
target.price = value
}
}
})
agent.phone //经纪人电话:10010
agent.price = 100 //Uncaught Error: 报价过低
NPM
1、npm是什么?
npm是JavaScript世界的包管理工具,并且是Node.js平台的默认包管理工具。通过npm可以安装、共享、分发代码,管理项目依赖关系。
2、介绍下npm模块安装机制,为什么输入npm install 就可以自动安装对应模块?
1.发出npm install命令
2.查询node_ modules 目录之中是否已经存在指定模块
3.若存在,不再重新安装
4.若不存在,npm向registry查询模块压缩地址,下载压缩包,存放在根目录下的.npm里,解压压缩包到当前项目的node_ modules录
3、npm2和npm3区别
npm2所有项目依赖是嵌套关系,
npm3为了改进嵌套过多套路过深的情况,会将所有依赖放在第二层依赖中(所有依赖只嵌套一次 ,彼此平行,也就是平铺的结构)
npm2依赖安装的时候比较简单直接,直接按照包依赖的树形结构下载填充本地目录结构,也就是说每个包都会将该包的依赖组织到当前包所在的node. modules目录中。
npm3则对依赖安装进行了改造,采用"扁平结构"的思路来组织依赖包的目录结构。具体的就是npm install时:按照package.json里依赖的顺序依次解析,遇到新的包就把它放在第一级目录,后面如果遇到一级目录已经存在的包,会先判断版本,如果版本一样则忽略,否则会按照npm2的方式依次挂在依赖包目录下。
4、npm发包流程
1.在npm官网注册npm账号(已有可忽略)
2.新建文件夹,进入该文件夹,运行npm init,生成package.json文件
3.将需要发布的代码放入该文件夹
4.在终端执行npm login 命令登录npm,按照提示填写对应的内容(账号、密码、邮箱)
5.运行 npm publish 发布
5、npm和yarn的区别?
npm:
基于node.js的包管理工具;常用命令 npm install 包名;
缺点:因服务器在国外,所以下载包的速度超级慢,所以出现了cnpm和yarn
cnpm:
跟npm是一样的,这是淘宝出的下载工具,服务器在国内,所以下载速度比npm快很多,但是安装有点乱,并且容易出错;cnpm install 包名
安装cnpm
npm install -g cnpm --registry=https://registry.npm.taobao.org
yarn:
Yarn是由Facebook、Google、Exponent 和 Tilde 联合推出了一个新的 JS 包管理工具 ,正如官方文档中写的,Yarn 是为了弥补 npm 的一些缺陷而出现的
速度对比
耗时从少到多:
cnpm:cnpm install vue-cli --save
用时:41600ms,包数量:1508
yarn:yarn add vue-cli
用时:181200ms,包数量:742
npm:npm install vue-cli --save
用时:362400.5ms,包数量:727
结论:
cnpm虽然是最快的,不过有很多同行吐槽它的包文件过多和凌乱,包括其他一些问题(安装会出错等),因此国内有一些大的团队在内部并不使用cnpm。
npm的未来:npm5.0
有了yarn的压力之后,npm做了一些类似的改进。默认新增了类似yarn.lock的 package-lock.json;
1.git 依赖支持优化:这个特性在需要安装大量内部项目(例如在没有自建源的内网开发),或需要使用某些依赖的未发布版本时很有用。在这之前可能需要使用指定 commit_id 的方式来控制版本。
2.文件依赖优化:在之前的版本,如果将本地目录作为依赖来安装,将会把文件目录作为副本拷贝到 node_modules 中。而在 npm5 中,将改为使用创建 symlinks 的方式来实现(使用本地 tarball 包除外),而不再执行文件拷贝。这将会提升安装速度。目前yarn还不支持。
Git
1、图解git仓库
2、常用git命令
git add . 将工作区所有文件添加到暂存区
git add <file-name>
将指定文件添加到暂存区
git commit -m ‘备注’ 将暂存区的文件提交到本地仓库
git status 查看仓库当前的状态,文件是否有被修改过
git log 显示从最近到最远的提交日志历史记录
git reflog 查看所以操作记录(包括commit 和reset操作以及已经被删除的commit记录)
git reste -hard HEAD^ 回退到上一个版本(HEAD代码当前版本,HEAD代表上一个版本,HEAD代表上上个版本,HEAD100代表上100个版本)
git reset -hard <commit id>
回退到指定版本号的版本
git diff 查看工作区域内容和暂存区域内容之间的差异
git diff --cached 比较暂存区和本地仓库之间的差异
git diff HEAD 比较工作区和本地仓库最新版本的区别
git diff <commit id>
比较工作区和指定版本的之间的差异
git diff <commit id> <commit id>
比较两个版本之间的差异
git rm <file-name>
从版本库删除指定文件
git branch <branch-name>
创建新分支
git checkout <branch-name>
切换到指定分支
git checkout -b <branch-name>
创建并切换分支
git branch 列出所有分支,当前分支前面会标有一个*号
git merge <branch-name>
合并指定分支的内容到当前分支
git merge --no–ff <branch-name>
--no-ff 表示禁用fast forward合并,用普通模式进行分支合并,fast forward合并看不出曾经做过合并,普通模式可以。
git branch -d <branch-name>
删除合并后的分支
git branch -D <branch-name>
强制删除分支
git remote 查看远程仓库的信息
git remote -v 查看远程仓库的详细信息
git push origin <branch-name>
推送分支到远程仓库
git pull 拉取远程仓库的代码
git config --global alias.sta status 配置status的别名为sta
React
基本知识
1.什么是MVC
MVC全名是Model View Controller,是一种软件设计模式
其中可分为模型(model)-视图(view)-控制器(controller)三要素,
- 模型(model),模型的主要作用为数据的接受和发送设定规则。
- 视图(view),即用户接触的部分,用户看到并与之交互。
- 控制器(controller),控制器接受用户的输入并调用模型和视图去完成用户的需求,所以当单击Web页面中的超链接和发送HTML表单时,控制器本身不输出任何东西和做任何处理。它只是接收请求并决定调用哪个模型构件去处理请求,然后再确定用哪个视图来显示返回的数据。
即:
- 控制器(controller)监听视图(view).
- 用户与视图(view)交互,通知控制器(controller)调用模型(model)再请求服务器(server).
- 从而服务器(server)响应给模型(model),再返回数据给控制器(controller)再更新视图(view).
- 控制器(controller)回到监听状态.
2. MVC框架的主要问题是什么?
以下是MVC框架的一些主要问题:
- 对 DOM 操作的代价非常高
- 程序运行缓慢且效率低下
- 内存浪费严重
- 由于循环依赖性,组件模型需要围绕 models 和 views 进行创建
3.区分Real DOM和Virtual DOM
Real DOM | Virtual DOM |
---|---|
1. 更新缓慢。 | 1. 更新更快。 |
2. 可以直接更新 HTML。 | 2. 无法直接更新 HTML。 |
3. 如果元素更新,则创建新DOM。 | 3. 如果元素更新,则更新 JSX 。 |
4. DOM操作代价很高。 | 4. DOM 操作非常简单。 |
5. 消耗的内存较多。 | 5. 很少的内存消耗。 |
4.什么是React?
React是Facebook 开发的前端JavaScript库
V层:react并不是完整的MVC框架,而是MVC中的C层
虚拟DOM:react引入虚拟DOM,每当数据变化通过reactdiff运算,将上一次的虚拟DOM与本次渲染的DOM进行对比,仅仅只渲染更新的,有效减少了DOM操作
JSX语法:js+xml,是js的语法扩展,编译后转换成普通的js对象
组件化思想:将具有独立功能的UI模块封装为一个组件,而小的组件又可以通过不同的组合嵌套组成大的组件,最终完成整个项目的构建
单向数据流:指数据的流向只能由父级组件通过props讲数据传递给子组件,不能由子组件向父组件传递数据
要想实现数据的双向绑定只能由子组件接收父组件props传过来的方法去改变父组件数据,而不是直接将子组件数据传给父组件
生命周期:简单说一下生命周期:Mounting(挂载)、Updateing(更新)、Unmounting(卸载)
5.React有什么特点?
React的主要功能如下:
- 它使用**虚拟DOM **而不是真正的DOM。
- 它可以用服务器端渲染。
- 它遵循单向数据流或数据绑定。
6、为什么虚拟 DOM 会提高性能? 说下他的原理
虚拟 dom 相当于在 js 和真实 dom 中间加了一个缓存,利用 dom diff 算法避免了没有必要的 dom 操作,从而提高性能
Virtual DOM 工作过程有三个简单的步骤
1)每当底层数据发生改变时,整个 UI 都将在 Virtual DOM 描述中重新渲
2)然后计算之前 DOM 表示与新表示的之间的差异
3)完成计算后,将只用实际更改的内容更新 real DOM
7. 列出React的一些主要优点。
React的一些主要优点是:
- 它提高了应用的性能
- 可以方便地在客户端和服务器端使用
- 由于 JSX,代码的可读性很好
- React 很容易与 Meteor,Angular 等其他框架集成
- 使用React,编写UI测试用例变得非常容易
8. React有哪些限制?
React的限制如下:
- React 只是一个库,而不是一个完整的框架
- 它的库非常庞大,需要时间来理解
- 新手程序员可能很难理解
- 编码变得复杂,因为它使用内联模板和 JSX
9. 什么是JSX?
JSX 是J avaScript XML 的简写。是 React 使用的一种文件
它利用 JavaScript 的表现力和类似 HTML 的模板语法。
这使得 HTML 文件非常容易理解。此文件能使应用非常可靠,并能够提高其性能。下面是JSX的一个例子:
render(){
return(
<div>
<h1> Hello World from Edureka!!</h1>
</div>
);
}
10. 你了解 Virtual DOM 吗?解释一下它的工作原理。
Virtual DOM 是一个轻量级的 JavaScript 对象,它最初只是 real DOM 的副本。它是一个节点树,它将元素、它们的属性和内容作为对象及其属性。 React 的渲染函数从 React 组件中创建一个节点树。然后它响应数据模型中的变化来更新该树,该变化是由用户或系统完成的各种动作引起的。
Virtual DOM 工作过程有三个简单的步骤。
- 每当底层数据发生改变时,整个 UI 都将在 Virtual DOM 描述中重新渲染。
2.然后计算之前 DOM 表示与新表示的之间的差异。
3.完成计算后,将只用实际更改的内容更新 real DOM。
11. 为什么浏览器无法读取JSX?
浏览器只能处理 JavaScript 对象,而不能读取常规 JavaScript 对象中的 JSX。所以为了使浏览器能够读取 JSX,首先,需要用像 Babel 这样的 JSX 转换器将 JSX 文件转换为 JavaScript 对象,然后再将其传给浏览器。
12. 与 ES5 相比,React 的 ES6 语法有何不同?
以下语法是 ES5 与 ES6 中的区别:
1.require 与 import
// ES5
var React = require('react');
// ES6
import React from 'react';
2.export 与 exports
// ES5
module.exports = Component;
// ES6
export default Component;
3.component 和 function
// ES5
var MyComponent = React.createClass({
render: function() {
return
<h3>Hello Edureka!</h3>;
}
});
// ES6
class MyComponent extends React.Component {
render() {
return
<h3>Hello Edureka!</h3>;
}
}
4.props
// ES5
var App = React.createClass({
propTypes: { name: React.PropTypes.string },
render: function() {
return
<h3>Hello, {this.props.name}!</h3>;
}
});
// ES6
class App extends React.Component {
render() {
return
<h3>Hello, {this.props.name}!</h3>;
}
}
5.state
// ES5
var App = React.createClass({
getInitialState: function() {
return { name: 'world' };
},
render: function() {
return
<h3>Hello, {this.state.name}!</h3>;
}
});
// ES6
class App extends React.Component {
constructor() {
super();
this.state = { name: 'world' };
}
render() {
return
<h3>Hello, {this.state.name}!</h3>;
}
}
13. React与Angular有何不同?
react是Facebook出品,angular是Google
react只有MVC中的C,angular是MVC
react使用虚拟DOM,angular使用真实DOM
react是单项数据绑定,angular是双向数据绑定
主题 | React | Angular |
---|---|---|
1. 体系结构 | 只有 MVC 中的 View | 完整的 MVC |
2. 渲染 | 可以在服务器端渲染 | 客户端渲染 |
3. DOM | 使用 virtual DOM | 使用 real DOM |
4. 数据绑定 | 单向数据绑定 | 双向数据绑定 |
5. 调试 | 编译时调试 | 运行时调试 |
6. 作者 |
14、React 15和React 16的区别
react15的架构可以分为两层:
- Reconciler(协调器)— 找出需要更新的组件,以及标识出如何更新
- Renderer(渲染器)— 负责将变化后的组件渲染到页面上
react16的架构可以分为三层:
- Scheduler(调度器)— 调度任务的优先级,高级优先级的优先进入Reconciler阶段
- Reconciler(协调器)— 找出需要更新的组件,以及标识出如何更新
- Renderer(渲染器)— 负责将变化后的组件渲染到页面上
15和16区别
- react15的reconciler是stack-reconciler。即是采用递归形式工作的,是同步的,在生成虚拟dom树并diff过程中是无法中断的。这样在组件层级过深时,会造成js执行时间过长,浏览器无法布局和绘制,造成丢帧。
- react16的reconciler是fiber-reconciler。即采用的异步可中断更新代替react15的同步更新,react16的scheduler调度器会告诉reconciler,浏览器是否有空闲时间执行js脚本。这样就不会影响浏览器的绘制和布局工作。不会丢帧。
- 在react16中,原来的虚拟DOM,因其结构已不能满足异步可中断更新的需求,改而采用新的结构Fiber。虚拟dom节对应变为Fiber节点,虚拟dom树对应变为Fiber树。
React 组件
1. 你理解“在React中,一切都是组件”这句话。
组件是 React 应用 UI 的构建块。这些组件将整个 UI 分成小的独立并可重用的部分。每个组件彼此独立,而不会影响 UI 的其余部分。
2. 解释 React 中 render() 的目的。
每个React组件强制要求必须有一个 render()。它返回一个 React 元素,是原生 DOM 组件的表示。如果需要渲染多个 HTML 元素,则必须将它们组合在一个封闭标记内,例如 <form>
、<group>
、<div>
等。此函数必须保持纯净,即必须每次调用时都返回相同的结果。
3. 如何将两个或多个组件嵌入到一个组件中?
可以通过以下方式将组件嵌入到一个组件中:
class MyComponent extends React.Component{
render(){
return(
<div>
<h1>Hello</h1>
<Header/>
</div>
);
}
}
class Header extends React.Component{
render(){
return
<h1>Header Component</h1>
};
}
ReactDOM.render(
<MyComponent/>, document.getElementById('content')
);
4. 什么是 Props?
Props 是 React 中属性的简写。它们是只读组件,必须保持纯,即不可变。它们总是在整个应用中从父组件传递到子组件。子组件永远不能将 prop 送回父组件。这有助于维护单向数据流,通常用于呈现动态生成的数据。
5. React中的状态是什么?它是如何使用的?
状态是 React 组件的核心,是数据的来源,必须尽可能简单。基本上状态是确定组件呈现和行为的对象。与props 不同,它们是可变的,并创建动态和交互式组件。可以通过 this.state()
访问它们。
6. 区分状态和 props
条件 | State | Props |
---|---|---|
1. 从父组件中接收初始值 | Yes | Yes |
2. 父组件可以改变值 | No | Yes |
3. 在组件中设置默认值 | Yes | Yes |
4. 在组件的内部变化 | Yes | No |
5. 设置子组件的初始值 | Yes | Yes |
6. 在子组件的内部更改 | No | Yes |
7. 如何更新组件的状态?
可以用 this.setState()
更新组件的状态。
class MyComponent extends React.Component {
constructor() {
super();
this.state = {
name: 'Maxx',
id: '101'
}
}
render()
{
setTimeout(()=>{this.setState({name:'Jaeha', id:'222'})},2000)
return (
<div>
<h1>Hello {this.state.name}</h1>
<h2>Your Id is {this.state.id}</h2>
</div>
);
}
}
ReactDOM.render(
<MyComponent/>, document.getElementById('content')
);
8. React 中的箭头函数是什么?怎么用?
箭头函数(=>)是用于编写函数表达式的简短语法。这些函数允许正确绑定组件的上下文,因为在 ES6 中默认下不能使用自动绑定。使用高阶函数时,箭头函数非常有用。
//General way
render() {
return(
<MyInput onChange = {this.handleChange.bind(this) } />
);
}
//With Arrow Function
render() {
return(
<MyInput onChange = { (e)=>this.handleOnChange(e) } />
);
}
9. 区分有状态和无状态组件。
有状态组件主要用来定义交互逻辑和业务数据,使用{this.state.xxx}的表达式把业务数据挂载到容器组件的实例上(有状态组件也可以叫做容器组件,无状态组件也可以叫做展示组件),然后传递props到展示组件,展示组件接收到props,把props塞到模板里面
无状态组件主要用来定义模板,接收来自父组件props传递过来的数据,使用{props.xxx}的表达式把props塞到模板里面
有状态组件 | 无状态组件 |
---|---|
1. 在内存中存储有关组件状态变化的信息 | 1. 计算组件的内部的状态 |
2. 有权改变状态 | 2. 无权改变状态 |
3. 包含过去、现在和未来可能的状态变化情况 | 3. 不包含过去,现在和未来可能发生的状态变化情况 |
4. 接受无状态组件状态变化要求的通知,然后将 props 发送给他们。 | 4.从有状态组件接收 props 并将其视为回调函数。 |
10. React组件生命周期的阶段是什么?
React 组件的生命周期有三个不同的阶段:
- 初始渲染阶段: 这是组件即将开始其生命之旅并进入 DOM 的阶段。
- 更新阶段: 一旦组件被添加到 DOM,它只有在 prop 或状态发生变化时才可能更新和重新渲染。这些只发生在这个阶段。
- 卸载阶段: 这是组件生命周期的最后阶段,组件被销毁并从 DOM 中删除。
11. 详细解释 React 组件的生命周期方法。
一些最重要的生命周期方法是:
(1)Mounting挂载阶段
constructor()
componentWillMount()组件挂载到页面之前
render()创建虚拟DOM,进行diff运算,更新DOM树。不可进行setState()
componentDidMount()组件挂载到页面之后,可以在此请求数据
(2)Updateing更新阶段
componentWillReceiveProps()父级数据发生变化
shouldComponentUpdate()
性能优化:这个函数只返回true或false,表示接下来的组件是否需要更新(重新渲染)
返回true就是紧接着以下的生命周期函数,返回false表示组件不需要重新渲染,不再执行任何生命周期函数(包括render)
componentWillUpdate() 组件更新之前,不可进行setState() 会导致函数调用shouldComponentUpdate从而进入死循环
render()
componentDidUpdate()组件更新之后
(3)Unmounting卸载阶段
componentWillUnmount 组件卸载和销毁之前立刻停用
可以在此销毁定时器,取消网络请求,消除创建的相关DOM节点等
12、shouldComponentUpdate是做什么的,(react性能优化是哪个周期函数?)
shouldComponentUpdate 这个方法用来判断是否需要调用render方法重新绘制dom
因为DOM的描绘非常消耗性能,如果我们能在shouldComponentUpdate 方法中能够写出更优化的 dom diff 算法,可以极大的提高性能
13、为什么虚拟 DOM 会提高性能? 说下他的原理
虚拟 dom 相当于在 js 和真实 dom 中间加了一个缓存,利用 dom diff 算法避免了没有必要的 dom 操作,从而提高性能
Virtual DOM 工作过程有三个简单的步骤
1)每当底层数据发生改变时,整个 UI 都将在 Virtual DOM 描述中重新渲
2)然后计算之前 DOM 表示与新表示的之间的差异
3)完成计算后,将只用实际更改的内容更新 real DOM
14、调用setState之后发生了什么?
当调用setState后,新的 state 并没有马上生效渲染组件,而是,先看执行流中有没有在批量更新中
如果有,push到存入到dirtyeComponent中,如果没有,则遍历dirty中的component,调用updateComponent,进行state或props的更新
然后更新UI,react进行diff运算,与上一次的虚拟DOM相比较,进行有效的渲染更新组件
15、react diff 原理
diff(翻译差异):计算一棵树形结构转换成另一棵树形结构的最少操作
1)把树形结构按照层级分解,只比较同级元素
2)给列表结构的每个单元添加唯一的 key 属性,方便比较
3)React 只会匹配相同 class 的 component(这里面的 class 指的是组件的名字)
4)合并操作,调用 component 的 setState 方法的时候, React 将其标记为 dirty.到每一个事件循环结束, React 检查所有标记 dirty 的 component 重新绘制
5)选择性子树渲染。开发人员可以重写 shouldComponentUpdate 提高 diff 的性能
16、react渲染机制
1)当页面一打开,就会调用render构建一棵DOM树
2)当数据发生变化( state | props )时,就会再渲染出一棵DOM树
3)此时,进行diff运算,两棵DOM树进行差异化对比,找到更新的地方进行批量改动
17、React中的状态是什么?它是如何使用的?
状态是 React 组件的核心,是数据的来源,必须尽可能简单。基本上状态是确定组件呈现和行为的对象。与props 不同,它们是可变的,并创建动态和交互式组件。可以通过 this.state() 访问它们
18、组件的状态(state)和属性(props)之间有何不同
State 是一种数据结构,用于组件挂载时所需数据的默认值。State 可能会随着时间的推移而发生突变,但多数时候是作为用户事件行为的结果
Props(properties 的简写)则是组件的配置。props 由父组件传递给子组件,并且就子组件而言,props 是不可变的
组件不能改变自身的 props,但是可以把其子组件的 props 放在一起(统一管理)
19、setState 何时同步何时异步?
1)setState 只在合成事件(react为了解决跨平台,兼容性问题,自己封装了一套事件机制,代理了原生的事件,像在jsx中常见的onClick、onChange这些都是合成事件)和钩子函数(生命周期)中是“异步”的,在原生事件和 setTimeout 中都是同步的
2)setState的“异步”并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的,只是合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形式了所谓的“异步”,当然可以通过第二个参数 setState(partialState, callback) 中的callback拿到更新后的结果
3)setState 的批量更新优化也是建立在“异步”(合成事件、钩子函数)之上的,在原生事件和setTimeout 中不会批量更新,在“异步”中如果对同一个值进行多次 setState , setState 的批量更新策略会对其进行覆盖,取最后一次的执行,如果是同时 setState 多个不同的值,在更新时会对其进行合并批量更新
20、在哪些生命周期中可以修改组件的state
componentDidMount和componentDidUpdate
constructor、componentWillMount中setState会发生错误:setState只能在mounted或mounting组件中执行
componentWillUpdate中setState会导致死循环
21、在构造函数中调用 super(props) 的目的是什么
在 super() 被调用之前,子类是不能使用 this 的,在 ES2015 中,子类必须在 constructor 中调用 super()。传递 props 给 super() 的原因则是便于(在子类中)能在 constructor 访问 this.props
22、React的生命周期函数中,当props改变时 会引发的后续变化,rander()函数什么时候执行
componentWillUpdate(){}之后
render
componentDidupdate(){}之前
23、解释 React 中 render() 的目的
每个React组件强制要求必须有一个 render()。它返回一个 React 元素,是原生 DOM 组件的表示
如果需要渲染多个 HTML 元素,则必须将它们组合在一个封闭标记内,例如 <form>、<group>、<div>
等
此函数必须保持纯净,即必须每次调用时都返回相同的结果
24、为什么在componentDidMount()中请求数据
componentDidMount方法中的代码,是在组件已经完全挂载到网页上才会调用被执行,所以可以保证数据的加载
25、react父子组件之间如何通信,兄弟组件呢?
父级传递子级:把数据挂载子组件的属性上,子组件通过this.props来接收父组件的数据
子级传递父级:父级需要定义一个修改数据的方法,把修改数据的方法传给子组件,当子组件需要修改父级数据时,调用父级传过来的修改方法
兄弟组件传递:属于同一个父级,父组件分别和这两个组件传递。比如子组件A操作执行父组件方法,父组件进行修改,然后把信息传给子组件B
26、请列举定义react组件的中方法
1)函数式定义的无状态组件
2)es5原生的方式 React.createClass方式
3)es6中extends React.Component定义的组件
27. React中的事件是什么?
在 React 中,事件是对鼠标悬停、鼠标单击、按键等特定操作的触发反应。处理这些事件类似于处理 DOM 元素中的事件。但是有一些语法差异,如:
- 用驼峰命名法对事件命名而不是仅使用小写字母。
- 事件作为函数而不是字符串传递。
事件参数重包含一组特定于事件的属性。每个事件类型都包含自己的属性和行为,只能通过其事件处理程序访问。
28. 如何在React中创建一个事件?
class Display extends React.Component({
show(evt) {
// code
},
render() {
// Render the div with an onClick prop (value is a function)
return (
<div onClick={this.show}>Click Me!</div>
);
}
});
29. React中的合成事件是什么?
合成事件是围绕浏览器原生事件充当跨浏览器包装器的对象。它们将不同浏览器的行为合并为一个 API。这样做是为了确保事件在不同浏览器中显示一致的属性。
30. 你对 React 的 refs 有什么了解?
Refs 是 React 提供给我们的安全访问 DOM 元素或者某个组件实例的句柄
我们可以为元素添加 ref 属性然后在回调函数中接受该元素在 DOM 树中的句柄
该值会作为回调函数的第一个参数返回
class ReferenceDemo extends React.Component{
display() {
const name = this.inputDemo.value;
document.getElementById('disp').innerHTML = name;
}
render() {
return(
<div>
Name: <input type="text" ref={input => this.inputDemo = input} />
<button name="Click" onClick={this.display}>Click</button>
<h2>Hello <span id="disp"></span> </h2>
</div>
);
}
}
31. 列出一些应该使用 Refs 的情况。
以下是应该使用 refs 的情况:
- 需要管理焦点、选择文本或媒体播放时
- 触发式动画
- 与第三方 DOM 库集成
32. 如何模块化 React 中的代码?
可以使用 export 和 import 属性来模块化代码。它们有助于在不同的文件中单独编写组件。
//ChildComponent.jsx
export default class ChildComponent extends React.Component {
render() {
return(
<div>
<h1>This is a child component</h1>
</div>
);
}
}
//ParentComponent.jsx
import ChildComponent from './childcomponent.js';
class ParentComponent extends React.Component {
render() {
return(
<div>
<App />
</div>
);
}
}
33. 如何在 React 中创建表单
React 表单类似于 HTML 表单。但是在 React 中,状态包含在组件的 state 属性中,并且只能通过 setState()
更新。因此元素不能直接更新它们的状态,它们的提交是由 JavaScript 函数处理的。此函数可以完全访问用户输入到表单的数据。
handleSubmit(event) {
alert('A name was submitted: ' + this.state.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input type="text" value={this.state.value} onChange={this.handleSubmit} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
34. 你对受控组件和非受控组件了解多少?
受控组件 | 非受控组件 |
---|---|
1. 没有维持自己的状态 | 1. 保持着自己的状态 |
2.数据由父组件控制 | 2.数据由 DOM 控制 |
3. 通过 props 获取当前值,然后通过回调通知更改 | 3. Refs 用于获取其当前值 |
35. 什么是高阶组件(HOC)?
高阶组件是一个以组件为参数并返回一个新组件的函数。HOC 运行你重用代码、逻辑和引导抽象,最常见的可能是 Redux 的 connect 函数
除了简单分享工具库和简单的组合,HOC 最好的方式是共享 React 组件之间的行为
如果你发现你在不同的地方写了大量代码来做同一件事时,就应该考虑将代码重构为可重用的 HOC
36. 你能用HOC做什么?
HOC可用于许多任务,例如:
- 代码重用,逻辑和引导抽象
- 渲染劫持
- 状态抽象和控制
- Props 控制
37. 什么是纯组件?
纯(Pure) 组件是可以编写的最简单、最快的组件。它们可以替换任何只有 render() 的组件。这些组件增强了代码的简单性和应用的性能。
38. React 中 key 的重要性是什么?
key 用于识别唯一的 Virtual DOM 元素及其驱动 UI 的相应数据。它们通过回收 DOM 中当前所有的元素来帮助 React 优化渲染。这些 key 必须是唯一的数字或字符串,React 只是重新排序元素而不是重新渲染它们。这可以提高应用程序的性能。
Redux
1. 了解redux么,说一下redux
redux 是一个应用数据流框架,主要是解决了组件间状态共享的问题,原理是集中式管理,主要有三个核心方法,action,store,reducer
三大原则:
1)唯一数据源(整个应用的 state 被储存在一棵 object tree 中,并且这个 object tree 只存在于唯一一个 store 中)
2)reducer必须是纯函数(输入必须对应着唯一的输出)
3)State 是只读的, 想要更改必须经过派发action
redux的工作流程:
使用通过reducer创建出来的Store发起一个Action,reducer会执行相应的更新state的方法,当state更新之后,view会根据state做出相应的变化
1)提供getState()获取到state
2)通过dispatch(action)发起action更新state
3)通过subscribe()注册监听器
2. Redux遵循的三个原则是什么?
- 单一事实来源:整个应用的状态存储在单个 store 中的对象/状态树里。单一状态树可以更容易地跟踪随时间的变化,并调试或检查应用程序。
- 状态是只读的:改变状态的唯一方法是去触发一个动作。动作是描述变化的普通 JS 对象。就像 state 是数据的最小表示一样,该操作是对数据更改的最小表示。
- 使用纯函数进行更改:为了指定状态树如何通过操作进行转换,你需要纯函数。纯函数是那些返回值仅取决于其参数值的函数。
3. 你对“单一事实来源”有什么理解?
Redux 使用 “Store” 将程序的整个状态存储在同一个地方。因此所有组件的状态都存储在 Store 中,并且它们从 Store 本身接收更新。单一状态树可以更容易地跟踪随时间的变化,并调试或检查程序。
4. 列出 Redux 的组件。
Redux 由以下组件组成:
- Action – 这是一个用来描述发生了什么事情的对象。
- Reducer – 这是一个确定状态将如何变化的地方。
- Store – 整个程序的状态/对象树保存在Store中。
- View – 只显示 Store 提供的数据。
5. 数据如何通过 Redux 流动?
1)用户操作视图
2)发起一次dispatch。有异步:返回一个函数(使用thunk中间件),没有异步:return {}
3)进入reducer,通过对应的type去修改state,最后返回一个新的state
redux的工作流程
const redux = require('redux')
// redux三大块 store action reducer
const initState = {
count: 0,
}
// 1、action用来修改store
const action1 = { type: 'INCREMENT' }
const action2 = { type: 'DECREMENT' }
const action3 = { type: 'ADDNUM', num: 10 }
const action4 = { type: 'REDUCENUM', num: 20 }
// 2、reducer是连接store和action,返回新的state给store
const reducer = (state = initState,action) => {
switch (action.type) {
case "INCREMENT": return {...state,count: state.count + 1}
case "DECREMENT": return {...state,count: state.count - 1}
case "ADDNUM": return {...state,count: state.count + action.num }
case "REDUCENUM": return {...state,count: state.count - action.num }
}
}
// 3、store保存状态,创建一个对象即可
const store = redux.createStore(reducer);
// 5: 在派发之前监听store的变化
store.subscribe(() => {
console.log(`count:${store.getState().count}`);
})
// 4、派发action
store.dispatch(action1)
store.dispatch(action2)
store.dispatch(action3)
store.dispatch(action4)
6、connect()前两个参数是什么?
mapStateToProps(state, ownProps)
允许我们将store中的数据作为props绑定到组件中,只要store更新了就会调用mapStateToProps方法,mapStateToProps返回的结果必须是object对象,该对象中的值将会更新到组件中
mapDispatchToProps(dispatch, [ownProps])
允许我们将action作为props绑定到组件中,如果不传这个参数redux会把dispatch作为属性注入给组件,可以手动当做store.dispatch使用
mapDispatchToProps希望你返回包含对应action的object对象
7.redux的三大原则
1: 单一数据源
2: state是只读的,修改state唯一方法就是触发action
3: 使用纯函数 reducer来修改state,返回一个新的state给store
8. 解释 Reducer 的作用。
Reducers 是纯函数,它规定应用程序的状态怎样因响应 ACTION 而改变。Reducers 通过接受先前的状态和 action 来工作,然后它返回一个新的状态。它根据操作的类型确定需要执行哪种更新,然后返回新的值。如果不需要完成任务,它会返回原来的状态。
9、你怎么理解redux的state的
数据按照领域(Domain)分类,存储在不同的表中,不同的表中存储的列数据不能重复
表中每一列的数据都依赖于这张表的主键,表中除了主键以外的其他列,互相之间不能有直接依赖关系
把整个应用的状态按照领域(Domain)分成若干子State,子State之间不能保存重复的数据
State以键值对的结构存储数据,以记录的key/ID作为记录的索引,记录中的其他字段都依赖于索引
State中不能保存可以通过已有数据计算而来的数据,即State中的字段不互相依赖
10. Store 在 Redux 中的意义是什么?
Store 是一个 JavaScript 对象,它可以保存程序的状态,并提供一些方法来访问状态、调度操作和注册侦听器。应用程序的整个状态/对象树保存在单一存储中。因此,Redux 非常简单且是可预测的。我们可以将中间件传递到 store 来处理数据,并记录改变存储状态的各种操作。所有操作都通过 reducer 返回一个新状态。
11. Redux 有哪些优点?
Redux 的优点如下:
- 结果的可预测性 - 由于总是存在一个真实来源,即 store ,因此不存在如何将当前状态与动作和应用的其他部分同步的问题。
- 可维护性 - 代码变得更容易维护,具有可预测的结果和严格的结构。
- 服务器端渲染 - 你只需将服务器上创建的 store 传到客户端即可。这对初始渲染非常有用,并且可以优化应用性能,从而提供更好的用户体验。
- 开发人员工具 - 从操作到状态更改,开发人员可以实时跟踪应用中发生的所有事情。
- 社区和生态系统 - Redux 背后有一个巨大的社区,这使得它更加迷人。一个由才华横溢的人组成的大型社区为库的改进做出了贡献,并开发了各种应用。
- 易于测试 - Redux 的代码主要是小巧、纯粹和独立的功能。这使代码可测试且独立。
- 组织 - Redux 准确地说明了代码的组织方式,这使得代码在团队使用时更加一致和简单。
12、redux本身有什么不足?
1)向事件池中追加方法时,没有做去重处理
2)把绑定的方从在事件池中移除掉时,用的是arr.splice(index,1),这样可能会引起数组塌陷
3)reducer中state,每次返回都需要深克隆,可以在redux中获取状态信息时,深克隆,这样就不用在reducer里深克隆了
React 路由
1. 什么是React 路由?
React 路由是一个构建在 React 之上的强大的路由库,它有助于向应用程序添加新的屏幕和流。这使 URL 与网页上显示的数据保持同步。它负责维护标准化的结构和行为,并用于开发单页 Web 应用。 React 路由有一个简单的API。
2、react-router的原理
BrowserRouter或hashRouter用来渲染Router所代表的组件
Route用来匹配组件路径并且筛选需要渲染的组件
Switch用来筛选需要渲染的唯一组件
Link直接渲染某个页面组件
Redirect类似于Link,在没有Route匹配成功时触发
3. 为什么React Router v4中使用 switch 关键字 ?
虽然 <div>
用于封装 Router 中的多个路由,当你想要仅显示要在多个定义的路线中呈现的单个路线时,可以使用 “switch” 关键字。使用时,<switch>
标记会按顺序将已定义的 URL 与已定义的路由进行匹配。找到第一个匹配项后,它将渲染指定的路径。从而绕过其它路线。
4. 为什么需要 React 中的路由?
Router 用于定义多个路由,当用户定义特定的 URL 时,如果此 URL 与 Router 内定义的任何 “路由” 的路径匹配,则用户将重定向到该特定路由。所以基本上我们需要在自己的应用中添加一个 Router 库,允许创建多个路由,每个路由都会向我们提供一个独特的视图
<switch>
<route exact path=’/’ component={Home}/>
<route path=’/posts/:id’ component={Newpost}/>
<route path=’/posts’ component={Post}/>
</switch>
5. 列出 React Router 的优点。
几个优点是:
- 就像 React 基于组件一样,在 React Router v4 中,API 是 ‘All About Components’。可以将 Router 可视化为单个根组件(
<BrowserRouter>
),其中我们将特定的子路由(<route>
)包起来。 - 无需手动设置历史值:在 React Router v4 中,我们要做的就是将路由包装在
<BrowserRouter>
组件中。 - 包是分开的:共有三个包,分别用于 Web、Native 和 Core。这使我们应用更加紧凑。基于类似的编码风格很容易进行切换。
6. React Router与常规路由有何不同?
主题 | 常规路由 | React 路由 |
---|---|---|
参与的页面 | 每个视图对应一个新文件 | 只涉及单个HTML页面 |
URL 更改 | HTTP 请求被发送到服务器并且接收相应的 HTML 页面 | 仅更改历史记录属性 |
体验 | 用户实际在每个视图的不同页面切换 | 用户认为自己正在不同的页面间切换 |
Vue
1、对MVVM 的理解?
MVVM 是 Model-View-ViewModel 的缩写。MVVM 是一种设计思想。Model 层代表数据模型,可以在 Model 中定义数据修改和操作的业务逻辑;View 代表 UI 组件,它负责将数据模型转化成 UI 展现出来,ViewModel 是一个同步 View 和 Model 的对象(桥梁)
在 MVVM 架构下,View 和 Model 之间并没有直接的联系,而是通过 ViewModel 进行交互,Model 和 ViewModel 之间的交互是双向的, 因此 View 数据的变化会同步到 Model 中,而 Model 数据的变化也会立即反应到 View 上。
ViewModel 通过双向数据绑定把 View 层和 Model 层连接了起来,而 View 和 Model 之间的同步工作完全是自动的,无需人为干涉,因此开发者只需关注业务逻辑,不需要手动操作 DOM, 不需要关注数据状态的同步问题,复杂的数据状态维护完全由 MVVM 来统一管理。
2、MVC MVVM 的区别
MVC
MVC是应用最广泛的软件架构之一,一般MVC分为:Model(模型),View(视图),Controller(控制器)。 这主要是基于分层的目的,让彼此的职责分开。View一般用Controller来和Model进行联系。Controller是Model和View的协调者,View和Model不直接联系。基本都是单向联系。M和V指的意思和MVVM中的M和V意思一样。C即Controller指的是页面业务逻辑。MVC是单向通信。也就是View跟Model,必须通过Controller来承上启下。
MVVM
在MVVM框架下视图和模型是不能直接通信的,只能通过ViewModel进行交互,它能够监听到数据的变化,然后通知视图进行自动更新,而当用户操作视图时,VM也能监听到视图的变化,然后通知数据做相应改动,这实际上就实现了数据的双向绑定。并且V和VM可以进行通信。
区别
MVC和MVVM的区别并不是VM完全取代了C,ViewModel存在目的在于抽离Controller中展示的业务逻辑,而不是替代Controller,其它视图操作业务等还是应该放在Controller中实现。也就是说MVVM实现的是业务逻辑组件的重用。
MVC中Controller演变成MVVM中的ViewModel
MVVM通过数据来显示视图层而不是节点操作
MVVM主要解决了MVC中大量的dom操作使页面渲染性能降低,加载速度变慢,影响用户体验
3、说说你对vue的理解?
Vue 是一套用于构建用户界面的渐进式MVVM框架。那怎么理解渐进式呢?渐进式含义:强制主张最少。Vue.js包含了声明式渲染、组件化系统、客户端路由、大规模状态管理、构建工具、数据持久化、跨平台支持等,但在实际开发中,并没有强制要求开发者使用所有功能,而是根据需求逐渐扩展。
Vue.js的核心库只关心视图渲染,且由于渐进式的特性,Vue.js便于与第三方库或既有项目整合。
4、说说你对单页应用SPA的理解?有什么优缺点?
单页应用
单页应用又称 SPA(Single Page Application)指的是使用单个 HTML 完成多个页面切换和功能的应用。这些应用只有一个 html 文件作为入口,一开始只需加载一次 js,css 等相关资源。使用 js 完成页面的布局和渲染。页面展示和功能室根据路由完成的。单页应用跳转,就是切换相关组件,仅刷新局部资源。
多页应用
多页应用又称 MPA(Multi Page Application)指有多个独立的页面的应用,每个页面必须重复加载 js,css 等相关资源。多页应用跳转,需要整页资源刷新。
区别
单页应用优点:
具有桌面应用的即时性、网站的可移植性和可访问性
用户体验好、快,内容的改变不需要重新加载整个页面
良好的前后端分离,分工更明确
单页应用缺点:
1.首次渲染速度相对较慢:为实现单页 Web 应用功能及显示效果,需要在加载页面的时候将 JavaScript、CSS 统一加载,部分页面按需加载
2.前进后退路由管理:由于单页应用在一个页面中显示所有的内容,所以不能使用浏览器的前进后退功能,所有的页面切换需要自己建立堆栈管理
3.SEO 难度较大:由于所有的内容都在一个页面中动态替换显示,所以在 SEO 上其有着天然的弱势
5、Watch和computed的区别
watch、computed、methods作用机制
1.侦听属性watch和计算属性computed都是以Vue的依赖追踪机制为基础的,它们都试图处理这样一件事情:当某一个数据(称它为依赖数据)发生变化的时候,所有依赖这个数据的“相关”数据“自动”发生变化,也就是自动调用相关的函数去实现数据的变动。
2.对methods里面是用来定义函数的,很显然,它需要手动调用才能执行。而不像watch和computed那样,“自动执行”预先定义的函数
watch、computed区别
1.computed支持缓存,只有依赖数据发生改变,才会重新进行计算;而watch不支持缓存,数据变,直接会触发相应的操作。
2.computed不支持异步,当computed内有异步操作时无效,无法监听数据的变化;而watch支持异步。
3.computed属性值会默认走缓存,计算属性是基于它们的响应式依赖进行缓存的,也就是基于data中声明过或者父组件传递的props中的数据通过计算得到的值;而watch监听的函数接收两个参数,第一个参数是最新的值,第二个参数是输入之前的值。
computed使用场景
1.一个属性是由其它属性计算而来的,这个属性依赖其它属性,是一个多对一或者一对一,使用computed
2.当存在复杂逻辑、需要用到缓存时
watch使用场景
1.当一个属性发生变化时,需要执行对应的操作;一对多一般用watch
2.当需要在数据变化时执行异步或开销较大的操作时
6、v-show和v-if的区别?
vue中显隐方法常用两种,v-show和v-if,但这两种是有区别的。
实现本质方法区别
v-show本质就是设置元素display为none,控制隐藏
v-if是动态的向DOM树内添加或者删除DOM元素
编译的区别
v-show其实就是在控制css
v-if切换有一个局部编译/卸载的过程,切换过程中合适地销毁和重建内部的事件监听和子组件
编译的条件
v-show都会编译,初始值为false,只是将display设为none,但它也编译了
v-if初始值为false,就不会编译了
性能
v-show只编译一次,后面其实就是控制css,而v-if不停的销毁和创建,故v-show性能更好一点。
用法
v-if更灵活,还可以配合v-else一起使用
7、Vue实例挂载过程
1.new Vue的时候调用会调用_init方法
定义 $set、$get 、$delete、$watch 等方法
定义 $on、$off、$emit、$off等事件
定义 _update、$forceUpdate、$destroy生命周期
2.调用$mount进行页面的挂载
3.挂载的时候主要是通过mountComponent方法
4.定义updateComponent更新函数
5.执行render生成虚拟DOM
6._update将虚拟DOM生成真实DOM结构,并且渲染到页面中
8、$route和$router的区别?
$router是 VueRouter 的实例,包含了一些路由的跳转方法,钩子函数,想要导航到不同 URL,则使用 $router.push 方法
$route对象表示当前的路由信息,包含了当前url解析得到的信息,包含当前路由的 name、path、query、params等
9、vue的生命周期
vue实例从创建到销毁的整个过程就是生命周期。
beforeCreate:组件实例被创建之初
在实例初始化之后,数据观测 (data observer) 和 event/watcher 事件配置之前被调用。
执行时组件实例还未创建,通常用于插件开发中执行一些初始化任务
created:组件实例已经完全创建
在实例创建完成后被立即调用。在这一步,实例已完成以下的配置:数据观测 (data observer),属性和方法的运算,watch/event 事件回调。然而,挂载阶段还没开始,$el 属性目前不可见。
组件初始化完毕,各种数据可以使用,常用于异步数据获取
beforeMount:组件挂载之前
在挂载开始之前被调用:相关的 render 函数首次被调用。(该钩子在服务器端渲染期间不被调用)
未执行渲染、更新,dom未创建
mounted:组件挂载到实例上去之后
el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用该钩子。如果 root 实例挂载了一个文档内元素,当 mounted 被调用时 vm.$el 也在文档内。
初始化结束,dom已创建,可用于获取访问数据和dom元素
beforeUpdate:组件数据发生变化,更新之前
数据更新时调用,发生在虚拟 DOM 打补丁之前。这里适合在更新之前访问现有的 DOM,比如手动移除已添加的事件监听器。(该钩子在服务器端渲染期间不被调用,因为只有初次渲染会在服务端进行。)
更新前,可用于获取更新前各种状态
updated:组件数据更新之后
由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。
当这个钩子被调用时,组件 DOM 已经更新,所以你现在可以执行依赖于 DOM 的操作。然而在大多数情况下,应该避免在此期间更改状态。如果要相应状态改变,通常最好使用计算属性或 watcher 取而代之。
更新后,所有状态已是最新
beforeDestory:组件实例销毁之前
实例销毁之前调用。在这一步,实例仍然完全可用。(该钩子在服务器端渲染期间不被调用)
销毁前,可用于一些定时器或订阅的取消
destroyed:组件实例销毁之后
Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。(该钩子在服务器端渲染期间不被调用)
组件已销毁
10、v-if和v-for为什么不建议一起用?
永远不要把 v-if 和 v-for 同时用在同一个元素上,因为v-for优先级比v-if高,每次渲染都会先循环一遍所有的节点,再进行条件判断,带来性能方面的浪费。
为避免出现这种情况,可以在外层嵌套template(页面渲染不生成dom节点),在这一层进行v-if判断,然后在内部进行v-for循环
11、SPA首屏加速慢怎么解决
减小入口文件积
常用的手段是路由懒加载,把不同路由对应的组件分割成不同的代码块,待路由被请求的时候会单独打包路由,使得入口文件变小,加载速度大大增加
routes: [
path: 'Blogs',
name: 'ShowBlogs',
component: () => import('./components/ShowBlogs.vue')
]
静态资源本地缓存
后端返回资源采用HTTP缓存,设置Cache-Control,Last-Modified,Etag等响应头
前端合理利用localStorage
UI框架按需加载
在日常使用UI框架,例如element-UI,我们经常性直接饮用整个UI库,但实际上我用到的组件只有按钮,分页,表格,输入与警告 所以我们要按需引用
import { Button, Input, Pagination, Table, TableColumn, MessageBox } from "element-ui "
Vue.use(Button)
Vue.use(Input)
Vue.use(Pagination)
图片资源的压缩
图片资源虽然不在编码过程中,但它却是对页面性能影响最大的因素。对于所有的图片资源,我们可以进行适当的压缩。对页面上使用到的icon,可以使用在线字体图标,或者雪碧图,将众多小图标合并到同一张图上,用以减轻http请求压力
组件重复打包
假设A.js文件是一个常用的库,现在有多个路由使用了A.js文件,这就造成了重复下载
解决方案:在webpack的config文件中,修改CommonsChunkPlugin的配置。设置minChunks为3表示会把使用3次及以上的包抽离出来,放进公共依赖文件,避免了重复加载组件
minChunks: 3
开启GZip压缩
拆完包之后,我们再用gzip做一下压缩 安装compression-webpack-plugin
使用SSR
SSR(Server side ),也就是服务端渲染,组件或页面通过服务器生成html字符串,再发送到浏览器。从头搭建一个服务端渲染是很复杂的,vue应用建议使用Nuxt.js实现服务端渲染
12、data属性为何是函数而不是一个对象
根实例对象data可以是对象也可以是函数(根实例是单例),不会产生数据污染情况
组件实例对象data必须为函数,目的是为了防止多个组件实例对象之间共用一个data,产生数据污染。采用函数的形式,initData时会将其作为工厂函数都会返回全新data对象
13、Vue给对象添加新属性界面不刷新
原因是一开始data所有的属性通过Object.defineProperty设置成了响应式数据,如果是后面新增的属性,并没有通过Object.defineProperty设置成响应式数据,所有新增属性数据更新了,但是页面不刷新
解决方法
Vue.set():使用Vue.set( target, propertyName/index, value )给对象添加新属性,这个方法再次调用了Object.defineProperty方法,实现新增属性的的响应式
Object.assign():创建一个新的对象,合并原对象和混入对象的属性
$forceUpdated():强制刷新
14、Vue组件和插件的区别
组件
组件就是把图形、非图形的各种逻辑均抽象为一个统一的概念(组件)来实现开发的模式,在Vue中每一个.vue文件都可以视为一个组件
插件
插件通常用来为 Vue 添加全局功能。插件的功能范围没有严格的限制
编写形式不同
组件:编写一个组件,常见的就是vue单文件的这种格式,每一个.vue文件我们都可以看成是一个组件
插件:vue插件的实现应该暴露一个 install 方法。这个方法的第一个参数是 Vue 构造器,第二个参数是一个可选的选项对象
注册形式不同
组件: vue组件注册主要分为全局注册与局部注册。全局注册通过Vue.component方法,第一个参数为组件的名称,第二个参数为传入的配置项,局部注册只需在用到的地方通过components属性注册一个组件
插件:插件的注册通过Vue.use()的方式进行注册(安装),第一个参数为插件的名字,第二个参数是可选择的配置项
使用场景不同
组件:组件 (Component) 是用来构成 App 的业务模块,它的目标是 App.vue
插件:插件 (Plugin) 是用来增强你的技术栈的功能模块,它的目标是 Vue 本身,简单来说,插件就是指对Vue的功能的增强或补充
15、Vue组件通信方式
vue中8种通信方案
1.通过 props 传递
2.通过 $emit 触发自定义事件
3.使用 ref
4.EventBus
5. p a r e n t 或 parent 或 parent或root
6.attrs 与 listeners
7.Provide 与 Inject
8.Vuex
16、props传递数据
适用场景:父组件传递数据给子组件
子组件设置props属性,定义接收父组件传递过来的参数
父组件在使用子组件标签中通过字面量来传递值
$emit 触发自定义事件
适用场景:子组件传递数据给父组件
子组件通过 e m i t 触发自定义事件, emit触发自定义事件, emit触发自定义事件,emit第二个参数为传递的数值
父组件绑定监听器获取到子组件传递过来的参数
ref
父组件在使用子组件的时候设置ref
父组件通过设置子组件ref来获取子组件数据
EventBus
使用场景:兄弟组件传值
创建一个中央事件总线EventBus
兄弟组件通过 e m i t 触发自定义事件, emit触发自定义事件, emit触发自定义事件,emit第二个参数为传递的数值
另一个兄弟组件通过$on监听自定义事件
//创建一个中央事件总线类 Bus.js
class Bus {
constructor() {
this.callbacks = {}; // 存放事件的名字
}
$on(name, fn) {
this.callbacks[name] = this.callbacks[name] || [];
this.callbacks[name].push(fn);
}
$emit(name, args) {
if (this.callbacks[name]) {
this.callbacks[name].forEach((cb) => cb(args));
}
}
}
// main.js
Vue.prototype.$bus = new Bus() // 将$bus挂载到vue实例的原型上
//另一种方式
Vue.prototype.$bus = new Vue() // Vue已经实现了Bus的功能
// Children1.vue
this.$bus.$emit('foo')
// Children2.vue
this.$bus.$on('foo', this.handle)
$parent 或$ root
通过共同祖辈 p a r e n t 或者 parent或者 parent或者root搭建通信侨联
兄弟组件:this.$parent.on(‘add’,this.add)
另一个兄弟组件:this.$parent.emit(‘add’)
$attrs 与$ listeners
适用场景:祖先传递数据给子孙
设置批量向下传属性$attrs和 $listeners
包含了父级作用域中不作为 prop 被识别 (且获取) 的特性绑定 ( class 和 style 除外)。
可以通过 v-bind=“$attrs” 传⼊内部组件
provide 与 inject
在祖先组件定义provide属性,返回传递的值
在后代组件通过inject接收组件传递过来的值
vuex
适用场景: 复杂关系的组件数据传递
Vuex作用相当于一个用来存储共享变量的容器
小结
- 父子关系的组件数据传递选择 props 与 $emit进行传递,也可选择ref
- 兄弟关系的组件数据传递可选择 b u s ,其次可以选择 bus,其次可以选择 bus,其次可以选择parent进行传递
- 祖先与后代组件数据传递可选择attrs与listeners或者 Provide与 Inject
- 复杂关系的组件数据传递可以通过vuex存放共享的变量
17、Vuex介绍
Vuex是-个专为Vue.js应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态, 并以相应的规则保证状态以一种可预测的方式发生变化。vuex用于组件之间的传值。
核心流程中的主要功能
1.Vue Components是我们的vue组件,组件会触发( dispatch)一些事件或动作,也就是图中的Actions
2.我们在组件中发出的动作,肯定是想获取或者改变数据的,但是在vuex中,数据是集中管理的,我们不能直接去更改数据,所以会把这个动作提交( Commit )到Mutations中
3.然后Mutations就去改变 State中的数据
4.当State中的数据被改变之后,就会重新渲染( Render )到Vue Components中去,组件展示更新后的数据,完成一个流程
各模块在核心流程中的主要功能
1.Vue Components: Vue组件,HTML页面上,负责接收用户操作等交互行为,执行dispatch方法触发对应action进行回应
2.dispatch:操作行为触发方法,是唯一能执行action的方法
3.actions: 操作行为处理模块,负责处理VueComponents接收到的所有交互行为。包含同步/异步操作,支持多个同名方法,按照注册的顺序依次触发。向后台API请求的操作就在这个模块中进行,包括触发其他action以及提交mutation的操作。该模块提供了Promise的封装,以歧持action的链式触发
4.commit:状态改变提交操作方法。对mutation进行提交,是唯一能执行mutation的方法
5.mutations:状态改变操作方法。是Vuex修改state的唯一推荐方法,其他修改方式在严格模式下将会报错。该方法只能进行同步操作,且方法名只能全局唯一。操作之中会有一些hook暴露出来,以进行state的监控等
6.state:页面状态管理容器对象。集中存储Vuecomponents中data对象的零散数据,全局唯一, 以进行统一的状态管理。页面显示所需的数据从该对象中进行读取,利用Vue的细粒度数据响应机制来进行高效的状态更新
7.getters: state对象读取方法。图中没有单独列出该模块,应该被包含在了render中, Vue Components通过该方法读取全局state对象
store拆分
在项目比较复杂的时候,数据全部写在一个state 方法全部集中一个mutations中,将会使我们的文件显得太过于臃肿,而且不易维护,那怎么办呢?还是那句话办法总比问题多,vuex为我们提供了module这样一个模块的概念。我们可以利用它来根据我们个个组件或者页面所需要的数据一一分割成不同的模块
const moduleA = {
state: {},
mutations: {},
actions: {},
getters: {}
}
const moduleB = {
state: {},
mutations: {},
actions: {},
getters: {}
}
export default new Vuex.Store({
modules: {
a: moduleA,
b: moduleB
}
})
// 在各自的模块内部使用
state.price // 这种使用方式和单个使用方式-样,直接使用就行
// 在组件中使用
store.state.a.price // 先找到模块的名字,再去调用厢性
store.state.b.price // 先找到模块的名字,再去调用属性
vuex的原理
Vuex是通过全局注入store对象,来实现组件间的状态共享。store注入 vue的实例组件的方式,是通过vue的 mixin机制,借助vue组件的生命周期 钩子 beforeCreate 完成的。即 每个vue组件实例化过程中,会在 beforeCreate 钩子前调用 vuexInit 方法。
Vue.mixin({
beforeCreate() {
if (this.$options && this.$options.store) {
// 找到根组件main上面挂一个$store
this.$store = this.$options.store
// console.log(this.$store);
} else {
// 非根组件指向其父组件的$store
this.$store = this.$parent && this.$parent.$store
}
}
})
在大型复杂的项目中(多级组件嵌套),需要实现一个组件更改某个数据,多个组件自动获取更改后的数据进行业务逻辑处理,这时候使用vuex比较合适。假如只是多个组件间传递数据,使用vuex未免有点大材小用,其实只用使用组件间常用的通信方法即可。
18、vuex的action和mutation的区别
Mutation
在vuex的严格模式下, Mutaion是vuex中改变State的唯一途径
Mutation 中只能是同步操作
通过store.commit()调用Mutation
Action
一些对State的异步操作可放在Action中,并通过在Action中commit调用 Mutation变更状态
通过store.dispatch() 方法触发,或者通过mapActions辅助函数将vue组件的methods映射成store.dispatch()调用
总结
mutations 可以直接修改state ,但只能包含同步操作,同时,只能通过提交commit调用。actions是用来触发mutations的,它无法直接改变state ,它可以包含异步操作,它只能通过store.dispatch触发
19、双向绑定原理
vue是通过数据劫持结合发布者-订阅者模式的方式来实现数据双向绑定的,通过Object.defineProperty()方法来劫持对象属性的setter和getter,在数据变动时发布消息给订阅者,触发相应的监听回调。
viewModel主要职责
数据变化后更新视图
视图变化后更新数据
viewModel两个主要部分组成
监听器(Observer):对所有数据的属性进行监听
解析器(Compiler):对每个元素节点的指令进行扫描跟解析,根据指令模板替换数据,以及绑定相应的更新函数
具体步骤:
第一步:对需要observe的数据对象data进行递归遍历,包括子属性对象的属性,都加上 setter和getter,这样的话,给这个对象的某个值赋值,就会触发setter,那么就能监听到了数据变化
第二步:compile解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图
第三步:Watcher订阅者是Observer和Compile之间通信的桥梁,主要做的事情是:
1.在自身实例化时往属性订阅器(dep)里面添加自己
2.自身必须有一个update()方法
3.待属性变动dep.notice()通知时,能调用自身的update()方法,并触发Compile中绑定的回调,则功成身退。
第四步:MVVM作为数据绑定的入口,整合Observer、Compile和Watcher三者,通过Observer来监听自己的model数据变化,通过Compile来解析编译模板指令,最终利用Watcher搭起Observer和Compile之间的通信桥梁,达到数据变化 -> 视图更新;视图交互变化(input) -> 数据model变更的双向绑定效果。
总结
1.new Vue()首先执行初始化,对data执行响应化处理,这个过程发生Observe中
2.同时对模板执行编译,找到其中动态绑定的数据,从data中获取并初始化视图,这个过程发生在Compile中。同时定义⼀个更新函数和Watcher,将来对应数据变化时Watcher会调用更新函数
3.由于data的某个key在⼀个视图中可能出现多次,所以每个key都需要⼀个管家Dep来管理多个Watcher。将来data中数据⼀旦发生变化,会首先找到对应的Dep,通知所有Watcher执行更新函数
20、vue2. x中如何监测数组变化
通过Object.defineProperty()劫持数组为其设置getter和setter后,调用的数组的push、splice、pop等方法改变数组元素时并不会触发数组的setter,这就会造成使用上述方法改变数组后,页面上并不能及时体现这些变化,也就是数组数据变化不是响应式的,那么vue中是如何实现的呢?
vue重写了数组的push、pop、shift、unshift、splice、sort、reverse七种方法,重写方法在实现时除了将数组方法名对应的原始方法调用一遍并将执行结果返回外,还通过执行ob.dep.notify()将当前数组的变更通知给其订阅者,这样当使用重写后方法改变数组后,数组订阅者会将这边变化更新到页面中。
21、对nextTick的理解?
在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM
我们可以理解成,Vue 在更新 DOM 时是异步执行的。当数据发生变化,Vue将开启一个异步更新队列,视图需要等队列中所有数据变化完成之后,再统一进行更新,如果想要在修改数据后立刻得到更新后的DOM结构,可以使用Vue.nextTick()
22、对mixin混入的理解
mixin(混入),提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能
本质其实就是一个js对象,它可以包含我们组件中任意功能选项,如data、components、methods、created、computed等等。我们只要将共用的功能以对象的方式传入 mixins选项中,当组件使用 mixins对象时所有mixins对象的选项都将被混入该组件本身的选项中来
合并策略
替换型:同名的props、methods、inject、computed会被后来者代替
合并型:data,通过set方法进行合并和重新赋值
队列型:生命周期钩子和watch被合并为一个数组,然后正序遍历依次执行
叠加型:component、directives、filters,通过原型链进行层层的叠加
23、对slot的理解?
通俗易懂的讲,slot具有“占坑”的作用,在子组件占好了位置,那父组件使用该子组件标签时,新添加的DOM元素就会自动填到这个坑里面
默认插槽: 子组件用<slot>
标签来确定渲染的位置,父组件在使用的时候,直接在子组件的标签内写入内容即可
具名插槽: 给子组件占的每一个坑<slot name="xxx">
取名,将父组件添加的HTML元素添加到指定名字的坑,就实现了分发内容在不同位置显示
作用域插槽: 子组件在作用域上绑定属性来将子组件的信息传给父组件使用,这些属性会被挂在父组件v-slot接受的对象上
24、v-for为何要加key关键字
key是给每一个vnode的唯一id,也是diff的一种优化策略,可以根据key,更准确, 更快的找到对应的vnode节点
25、说说对keep-alive缓存组件的理解
keep-alive是vue中的内置组件,能在组件切换过程中将状态保留在内存中,防止重复渲染DOM, keep-alive 包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们,它自身不会渲染一个DOM元素,也不会出现在父组件链中
keep-alive可以设置以下props属性:
include : 字符串或正则表达式,只有名称匹配的组件会被缓存
Exclude: 字符串或正则表达式,任何名称匹配的组件都不会被缓存
max : 数字,最多可以缓存多少组件实例
activated和deactivated两个生命周期函数
activated:当组件激活时,钩子触发的顺序是created->mounted->activated
deactivated: 组件停用时会触发deactivated,当再次前进或者后退的时候只触发activated
26、Vue常用修饰符
表单修饰符
lazy:在我们填完信息,光标离开标签的时候,才会将值赋予给value,也就是在change事件之后再进行信息同步
trim: 自动过滤用户输入的首尾空格字符,而中间的空格不会过滤
number:自动将用户的输入值转为数值类型,但如果这个值无法被parseFloat解析,则会返回原来的值
事件修饰符
stop:阻止了事件冒泡,相当于调用了event.stopPropagation方法
prevent:阻止了事件的默认行为,相当于调用了event.preventDefault方法
self:只当在 event.target 是当前元素自身时触发处理函数
once:绑定了事件以后只能触发一次,第二次就不会触发
capture:使事件触发从包含这个元素的顶层开始往下触发
passive:在移动端,当我们在监听元素滚动事件的时候,会一直触发onscroll事件会让我们的网页变卡,因此我们使用这个修饰符的时候,相当于给onscroll事件整了一个.lazy修饰符
native:让组件变成像html内置标签那样监听根元素的原生事件,否则组件上使用 v-on 只会监听自定义事件
鼠标按键修饰符
left:左键点击
right:右键点击
middle:中键点击
键值修饰符
普通键:(enter、tab、delete、space、esc、up…)
系统修饰键:(ctrl、alt、meta、shift…)
v-bind修饰符
async:能对props进行一个双向绑定
prop:设置自定义标签属性,避免暴露数据,防止污染HTML结构
camel:将命名变为驼峰命名法,如将view-Box属性名转换为 viewBox
27、项目中有用过自定义指令吗?
项目中定义一个auth自定义指令来显示隐藏页面元素,从而实现按钮权限
// util/auth.js
import store from "@/store";
export default {
bind(el, binding, vnode) {
let auths = store.state.loginInfo.roleBtns; // 后台返回的按钮权限列表
let hasAuth = auths.includes(binding.value);
if (hasAuth) {
delete el.style.display;
} else {
el.style.display = "none";
}
}
}
全局注册
import Vue from 'vue'
import App from './App.vue'
import router from './router '
import store from './store'
import iview from 'iview'
import 'iview/dist/styles/iview.css'
import auth from '@/util/auth'; // 引入
Vue.config.productionTip = false
Vue.use(iview)
Vue.directive("auth", auth); // 注册
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
通过自定义指令v-auth绑定字段,当绑定的字段包含在后端返回的roleBtns列表里,那么该按钮会显示,否则隐藏
28、过滤器filter的应用场景
在Vue中使用过滤器(Filters)来渲染数据是一种很有趣的方式。过滤器不改变真正的data,而只是改变渲染的结果,并返回过滤后的版本。局部过滤器优先于全局过滤器被调用。一个表达式可以使用多个过滤器。过滤器之间需要用管道符“|”隔开。其执行顺序从左往右
定义
组件内定义局部过滤器
filters: {
capitalize: function (value) {
if (!value) return ''
value = value.toString()
return value.charAt(0).toUpperCase + value.slice(1)
}
}
vue全局过滤器
Vue.filter('capitalize', function (value) {
if (!value) return
value = value.toString()
return value.charAt(0).toUpperCase() + value.slice(1)
})
new Vue({
//...
})
使用
<!--在双花括号中-->
{{message| capitalize}}
<!--在`v-bind` 中-->
<div v-bind:id="rawId| formatId"></div>
应用场景
单位转换、数字打点、文本格式化、时间格式化之类的等
29、什么是虚拟dom,如何实现一个虚拟dom
定义
它只是一层对真实DOM的抽象,以JavaScript 对象 (VNode 节点) 作为基础的树,用对象的属性来描述节点,最终可以通过一系列操作使这棵树映射到真实环境上
在Javascript对象中,虚拟DOM 表现为一个 Object对象。并且最少包含标签名 (tag)、属性 (attrs) 和子元素对象 (children) 三个属性,不同框架对这三个属性的名命可能会有差别
创建虚拟DOM就是为了更好将虚拟的节点渲染到页面视图中,所以虚拟DOM对象的节点与真实DOM的属性一一照应
为何需要虚拟DOM
真正的 DOM 元素非常庞大,由此可以得知DOM是很慢的。而且操作它们的时候你要小心翼翼,轻微的触碰可能就会导致页面重排。浏览器里一遍又一遍的渲染DOM是非常非常消耗性能的,常常会出现页面卡死的情况,影响用户的体验
得益于V8引擎的出现,让JavaScript可以高效地运行,在性能上有了极大的提高。相对于 DOM 对象,原生的 JavaScript 对象处理起来更快,而且更简单。DOM 树上的结构、属性信息我们都可以很容易地用 JavaScript 对象表示出来,为Virtual DOM的产生提供了大前提。
虚拟DOM的优势
很多人认为虚拟 DOM 最大的优势是 diff 算法,减少 JavaScript 操作真实 DOM 的带来的性能消耗。虽然这一个虚拟 DOM 带来的一个优势,但并不是全部。虚拟 DOM 最大的优势在于抽象了原本的渲染过程,实现了跨平台的能力,而不仅仅局限于浏览器的 DOM,可以是安卓和 IOS 的原生组件,可以是近期很火热的小程序,也可以是各种GUI
30、你了解vue的diff算法吗
定义
diff 算法是一种通过同层的树节点进行比较的高效算法
diff整体策略为:深度优先,同层比较
两个特点
比较只会在同层级进行, 不会跨层级比较
在diff比较的过程中,循环从两边向中间比较
Vue中当数据发生改变时,订阅者watcher就会调用patch给真实的DOM打补丁
通过isSameVnode进行判断,相同则调用patchVnode方法
patchVnode做了以下操作:
找到对应的真实dom,称为el
如果都有都有文本节点且不相等,将el文本节点设置为Vnode的文本节点
如果oldVnode有子节点而VNode没有,则删除el子节点
如果oldVnode没有子节点而VNode有,则将VNode的子节点真实化后添加到el
如果两者都有子节点,则执行updateChildren函数比较子节点
updateChildren主要做了以下操作:
设置新旧VNode的头尾指针
新旧头尾指针进行比较,循环向中间靠拢,根据情况调用patchVnode进行patch重复流程、调用createElem创建一个新节点,从哈希表寻找 key一致的VNode 节点再分情况操作
31、Vue权限管理
接口权限
登录页面进行登录,登录完拿到token,将token存起来,通过axios请求拦截器进行拦截,每次请求的时候头部携带token,如果token失效,后端返回约定好的状态码,前端在axios响应拦截进行拦截,重新跳转到登录页进行登录
菜单权限
前端定义路由信息,路由的name字段都不为空,根据路由name字段与后端返回菜单列表做关联,后端返回的菜单信息中必须要包含name对应的字段才展示对应菜单项
32、Vue项目是如何解决跨域的
如果是通过vue-cli脚手架工具搭建项目,我们可以通过webpack为我们起一个本地服务器devServer作为请求的代理对象。通过该服务器转发请求至目标服务器,得到结果再转发给前端,但是最终发布上线时如果web应用和接口服务器不在一起仍会跨域在vue.config.js文件,新增以下代码
module.exports = {
devServer: {
host: '127.0.0.1',
port: 8084,
open: true, // vue项目启动时自动打开浏览器
proxy: {
'/api': { // '/api'是代理标识,用于告诉node, url前面是/api的就是使用代理的
target: "http://xxx.xxx.xxx:8080", // 目标地址,一般是指后台服务器地址
changeOrigin: true, // 是否跨域
pathRewrite: { // pathRewrite 的作用是把实际Request Url中的"/api" 用''代替
'^/api': ''
}
}
}
}
}
通过axios发送请求中,配置请求的根路径
axios.defaults.baseURL = '/api'
33、Vue3有了解吗?说说跟vue2的区别?
优化
利用新的语言特性(es6)
解决架构问题
更小
vue2采用面向对象编程的思想,vue3则采用函数式编程的思想。充分利用函数式编程组合大于继承的优势,采用函数式编程更利于逻辑功能的复用,webpack打包时更有利于tree-shaking,更利于代码的压缩,更利于返回值类型校验,压缩后的文件体积更小。
更快
vue2需要diff所有的虚拟dom节点,而vue3参考了SVELTE框架的思想,先分层次,然后找不变化的层,针对变化的层进行diff,更新速度不会再受template大小的影响,而是仅由可变的内容决定。经过尤雨溪自己的测试,大概有6倍的速度提升。
加强typescript支持
vue3的源码开始采用了ts进行编写,给开发者也提供了支持ts的开发模式。
Api一致性
vue3最开始的版本可以完美兼容vue2的api。
提高可维护能力
从源码的层面上提供了更多的可维护能力。
开放更多底层功能
把更多的底层功能开放出来,比如render、依赖收集功能,我们可以更好的进行自定义化开发,可以写更多的高阶组件。
数据双向绑定方面的区别
vue2 采用了defineProperty,而vue3则采用了proxy。
1.使用proxy不污染源对象,会返回一个新对象,defineProperty是注入型的,会破坏源对象
2.使用proxy只需要监听整个源对象的属性,不需要循环使用Object.defineProperty监听对象的属性
3.使用proxy可以获取到对象属性的更多参数,使用defineProperty只能获取到监听属性的新值newvalue
34、Vue3为什么用proxy代替defineProperty
defineProperty缺点
检测不到对象属性的添加和删除
数组API方法无法监听到
需要对每个属性进行遍历监听,如果嵌套对象,需要深层监听,造成性能问题
Proxy优点
1.Proxy直接可以劫持整个对象,并返回一个新对象,我们可以只操作新的对象达到响应式目的
2.Proxy可以直接监听数组的变化(push、shift、splice)
3.Proxy有多达13种拦截方法,不限于apply、ownKeys、deleteProperty、has等等,这是Object.defineProperty不具备的
35、Vue中错误处理
后端接口错误
通过axios的interceptor实现网络请求的response先进行一层拦截
代码逻辑错误设置全局错误处理函数,errorHandler为全局钩子,使用Vue.config.errorHandler配置,2.6版本后可捕捉v-on与promise链的错误,可用于统一错误处理与错误兜底。
Vue.config.errorHandler = function (err, Vm, info) {
// handle error
// `info` 是Vue 特定的错误信息,比如错误所在的生命周期钩子
//只在2.2.0+ 可用
}
设置生命周期钩子,errorCaptured是组件内部钩子,可捕捉本组件与子孙组件抛出的错误,接收error、vm、info三个参数,return false后可以阻止错误继续向上抛出。
errorCaptured(err, vm, info) {
console.log(`cat EC: ${err.toString()}\n info:${info}`);
return false;
}
36、react和vue有哪些不同,说说你对这两个框架的看法
相同点
都有组件化思想
都支持服务器端渲染
都有Virtual DOM(虚拟dom)
数据驱动视图
都支持props进行父子组件间数据通信
都有支持native的方案:Vue的weex、React的React native
都有自己的构建工具:Vue的vue-cli、React的Create React App
区别
1.数据流向的不同:react从诞生开始就推崇单向数据流,而Vue是双向数据流
2.组件写法不同:组件写法React推荐JSX,就是把HTML和css全都写进JavaScript中,Vue推荐是webpack+vue+loader单文件组件格式,就是HTML、css、JavaScript都写进一个文件
3.数据变化的实现原理不同:react使用的是不可变数据,而Vue使用的是可变的数据
4.组件化通信的不同:react中我们通过使用回调函数来进行通信的,而Vue中子组件向父组件传递消息有两种方式:事件和回调函数
5.diff算法不同:react主要使用diff队列保存需要更新哪些DOM,得到patch树,再统一操作批量更新DOM。Vue 使用双向指针,边对比,边更新DOM
37、父组件和子组件生命周期钩子执行顺序
加载渲染过程: 父beforeCreate -> 父created -> 父beforeMount -> 子beforeCreate -> 子created -> 子beforeMount -> 子mounted -> 父mounted
子组件更新过程: 父beforeUpdate -> 子beforeUpdate -> 子updated -> 父updated
父组件更新过程: 父beforeUpdate -> 父updated
销毁过程: 父beforeDestroy -> 子beforeDestroy -> 子destroyed -> 父destroyed
38、Router-link和a标签的区别
Router-link做了三件事:
1.有onclick那就执行onclick
2.click的时候阻止a标签默认事件(这样子点击123就不会跳转和刷新页面)
3.再取得跳转href(即是to),用history(前端路由两种方式之一,history & hash)跳转,此时只是链接变了,并没有刷新页面
39、Router路由
完整的导航Router路由
1.触发进入其它路由
2.调用要离开路由的组件守卫beforeRouteLeave
3.调用全局的前置守卫beforeEach
4.在重用的组件里调用 beforeRouteUpdate
5.在路由配置里调用 beforeEnter
6.解析异步路由组件
7.在将要进入的路由组件中调用beforeRouteEnter
8.调用全局的解析守卫beforeResolve
9.导航被确认
10.调用全局的后置钩子afterEach。
11.触发 DOM 更新mounted。
12.执行beforeRouteEnter守卫中传给 next的回调函数。
40、vue-router的两种模式histroy和hash的区别?
为什么要有hash 和history?
对于Vue这类渐进式前端开发框架,为了构建SPA(单页面应用),需要引入前端路由系统,这也就是Vue-Router存在的意义。前端路由的核心,就在于改变视图的同时不会向后端发出请求。
为了达到这一目的,浏览器当前提供了以下两种支持:
1.hash: 即地址栏URL中的 # 符号(此hash不是密码学里的散列运算)
比如这个URL:http://www.abc.com/#/hello
, hash的值为#/hello.它的特点在于hash虽然出现在URL中,但不会被包括在HTTP请求中,对后端完全没有影响,因此改变hash不会重新加载页面
2.history:利用了HTML5 History API中新增的pushState()和replaceState()方法。(需要特定浏览器支持)
这两个方法应用于浏览器的历史记录栈,在当前已有的back、forward、go的基础上,它们提供了对历史记录进行修改的功能。只是当它们执行修改时,虽然改变了当前的URL,但浏览器不会即向后端发送请求。因此可以说,hash模式和histoury模式都是属于浏览器自身的特性,Vue-Router只是利用了这两个特性(通过调用浏览器提供的接口)来实现前端路由
使用场景
一般情景下,hash和history都可以,除非你更在意颜值,#符号夹杂在URL里看起来确实有些不太美丽。如果不想要很丑的hash,我们可以用路由的history模式,这种模式充分利用history pushState API来完成URL跳转,无须重新加载页面。
调用history.pushState()相比于直接修改hash ,存在以下优势:
1.pushState()设置的新URL可以是与当前URL同源的任意URL;而hash只可修改#后面的部分,因此只能设置与当前URL同文档的URL;
2.pushState()设置的新URL可以与当前URL一模一样,这样也会把记录添加到栈中;而hash设置的新值必须与原来不一样才会触发动作将记录添加到栈中;
3.pushState()通过stateObject参数可以添加任意类型的数据到记录中;而hash只可添加短字符串;
4.pushState()可额外设置title属性供后续使用。
当然history也不是样样都好。SPA虽然在浏览器里游刃有余,单真要通过URL向后端发起HTTP请求时,两者的差异就来了。尤其在用户手动输入URL后回车,或者刷新(重启)浏览器的时候。
1.hash 模式下,仅hash符号之前的内容会被包含在请求中,如http://www.abc.com
,因此对于后端来说,即使没有做到对路由的全覆盖,也不会返回404错误。
2.history模式下,前端的URL必须和实际向后端发起请求的URL一致。如htttp://www.abc.com/book/id
。如果后端缺少对/book/id 的路由处理,将返回404错误
前端安全
1、什么是CSRF攻击?如何防御CSRF攻击?
CSRF攻击
CSRF即Cross-site request forgery(跨站请求伪造),是一种挟制用户在当前已登录的Web应用程序上执行非本意的操作的攻击方法。
假如黑客在自己的站点上放置了其他网站的外链,例如www.weibo.com/api
,默认情况下,浏览器会带着weibo.com的cookie访问这个网址,如果用户已登录过该网站且网站没有对CSRF攻击进行防御,那么服务器就会认为是用户本人在调用此接口并执行相关操作,致使账号被劫持。
如何防御CSRF攻击
验证Token:浏览器请求服务器时,服务器返回一个token,每个请求都需要同时带上token和cookie才会被认为是合法请求
验证Referer:通过验证请求头的Referer来验证来源站点,但请求头很容易伪造
设置SameSite:设置cookie的SameSite,可以让cookie不随跨域请求发出,但浏览器兼容不一
2、什么是XSS攻击?XSS攻击有哪些类型?如何防御XSS攻击?
xss攻击
XSS即Cross Site Scripting(跨站脚本攻击),指的是通过利用网页开发时留下的漏洞,注入恶意指令代码到网页,使用户加载并执行攻击者恶意制造的网页程序。常见的例如在评论区植入JS代码,用户进入评论页时代码被执行,造成页面被植入广告、账号信息被窃取
XSS攻击有哪些类型
存储型:即攻击被存储在服务端,常见的是在评论区插入攻击脚本,如果脚本被储存到服务端,那么所有看见对应评论的用户都会受到攻击。
反射型:攻击者将脚本混在URL里,服务端接收到URL将恶意代码当做参数取出并拼接在HTML里返回,浏览器解析此HTML后即执行恶意代码
DOM型:将攻击脚本写在URL中,诱导用户点击该URL,如果URL被解析,那么攻击脚本就会被运行。和前两者的差别主要在于DOM型攻击不经过服务端
如何防御XSS攻击
输入检查:对输入内容中的<script><iframe>
等标签进行转义或者过滤
设置httpOnly:很多XSS攻击目标都是窃取用户cookie伪造身份认证,设置此属性可防止JS获取cookie
开启CSP,即开启白名单,可阻止白名单以外的资源加载和运行
小程序
1、微信小程序架构
微信小程序视图层是WebView ,逻辑层是JS引擎。三端的脚本执行环境以及用于渲染非原生组件的环境是各不相同的
运行环境
Android
iOS
小程序开发者工具
逻辑层
V8
JavaScriptCore
NWJS
渲染层
Chromium定制内核
WKWebview
Chrome Webview
2、小程序打开页面数量
最多可以打开10层,但是可以通过类似的redirectTo方法来在最后一层进行无限的跳转,但是这样做会无法返回上一个页面,只能返回路由栈中的上一层级
3、组件生命周期
created创建
attached挂载
ready挂载完毕
moved移动
detached移除
error组件错误
4、页面生命周期
onLoad初始化注册执行-次
onShow前台展示的时候,即执行多次
onReady初次渲染完成,执行-次
onHide页面隐藏
onUnload页面销毁
5、App生命周期
onLaunch小程序注册,只会执行一 次
onShow小程序唤起的前台展示
onHide小程序前台隐藏
onError小程序出现错误
6、常用API
1.路由跳转
wx.switchTab: 只能跳转到导航页,并关闭其他的导航页
wx.reLaunch: 关闭所有页面,打开到应用内的某个页面
wx.redirectTo: 关闭当前页面,跳转到应用内的某个页面。但是不允许跳转到导航页面
wx.navigateTo: 只保留当前页面,跳转到应用内的某个页面。但是不能跳到tabbar页面(最多10层)
wx navigateBack: 返回上一页,可以返回多级页面
2.弹窗提示
wx.showToast
wx.showModal
wx .showLoading
3.分享
wx.showShareMenu
onShareAppMessage
4.数据缓存
wx.setStorageSync
wx.getStorageSync
7、简单描述下微信小程序的相关文件类型
WXML(WeiXin Markup Language) 是框架设计的一套标签语言,结合基础组件、事件系统,可以构建出页面的结构。内部主要是微信自己定义的一套组件。与html差不多。
WXSS (WeiXin Style Sheets)是一套样式语言,用于描述 WXML 的组件样式,与css差不多
js 逻辑处理,网络请求
json 小程序设置,如页面注册,页面标题及tabBar。
project.config.json 项目配置文件,用得最多的就是配置是否开启https校验;
App.js 设置一些全局的基础数据等;
App.json 底部tab, 标题栏和路由等设置;
App.wxss 公共样式,引入iconfont等;
8、有哪些参数传值的方法?
1.给HTML元素添加data-*属性来传递我们需要的值,然后通过e.currentTarget.dataset或onload的param参数获取。但data-名称不能有大写字母和不可以存放对象
2.设置id 的方法标识来传值通过e.currentTarget.id获取设置的id的值,然后通过设置全局对象的方式来传递数值
3.在navigator中添加参数传值(传的值的名称=所传的值在onLoad(option)用option来接收并获取)
9、使用过哪些方法来提高微信小程序的应用速度?
(1)提高页面加载速度
(2)用户行为预测
(3)减少默认data的大小
(4)组件化方案
10、你是怎么封装微信小程序的数据请求的?
(1)在根目录下创建utils目录及api.js文件和apiConfig.js文件;
(2)在apiConfig.js 封装基础的get, post 和 put, upload等请求方法,设置请求体,带上token和异常处理等;
(3)在api中引入apiConfig.js封装好的请求方法,根据页面数据请求的urls, 设置对应的方法并导出;
(4)在具体的页面中导入;
11、请谈谈小程序的双向绑定和vue的异同?
大体相同,但小程序直接this.data的属性是不可以同步到视图的,必须调用this.setData()方法
12、如何实现下拉刷新?
用view代替scroll-view,设置onPullDownRefresh函数实现
13、使用webview直接加载要注意哪些事项?
(1)必须要在小程序后台使用管理员添加业务域名;
(2)h5页面跳转至小程序的脚本必须是1.3.1以上;
(3)微信分享只可以都是小程序的主名称了,如果要自定义分享的内容,需小程序版本在1.7.1以上;
(4)h5的支付不可以是微信公众号的appid,必须是小程序的appid,而且用户的openid也必须是用户和小程序
14、小程序简单介绍下三种事件对象的属性列表?
基础事件(BaseEvent)
type: 事件类型
timeStamp:事件生成时的时间戳
target:触发事件的组件的属性值集合
currentTarget:当前组件的一些属性集合
自定义事件(CustomEvent)
detail
触摸事件(TouchEvent)
touches
changedTouches
15、小程序还有哪些功能?
客服功能,录音,视频,音频,地图,定位,拍照,动画,canvas
16、小程序对wx:if 和 hidden使用的理解?
wx:if 有更高的切换消耗
hidden 有更高的初始渲染消耗
因此,如果需要频繁切换的情景下用hidden更好,如果在运行时条件不大可能改变则 wx:if 较好
17、请谈谈原生开发小程序、wepy、mpvue 的对比?
如果是新项目,且没有旧的 h5 项目迁移,则考虑用小程序原生开发,好处是相比于第三方框架,坑少。 而如果有老的 h5 项目是 vue 开发 或者 也有 h5 项目也需要小程序开发,则比较适合 wepy 或者 mpvue 来做迁移或者开发,近期看wepy几乎不更新了,所以推荐美团的mpvue
需要该文章的文档请关注私信(发送:前端面试汇总)