在编程中,语义指的是一段代码的含义,这个HTML元素有什么作用,扮演了什么样的角色。简单的概括为:在适当的位置使用适当的标签,用正确的标签做正确的事情
Header nav article section aside footer main stong em small
两者虽然在网页中显示效果一样,但实际目的不同。
这个标签对应bold ,即文本加粗,其目的仅仅是为了加粗显示文本,是一种样式/风格需求
这个标签意思是加强,表示该文本比较重要,提醒读者/终端注意。为了达到这个目的,浏览器等终端将其加粗显示;
为了加粗而加粗,
为了标明重点而加粗。
最重要的区别的就是样式标签与语义化标签的区别。最容易理解的场景就是盲人朋友使用阅读设备阅读网页时: 会重读,
不会
iframe会阻塞主页面的Onload事件;搜索引擎的检索程序无法解读这种页面,不利于SEO;
iframe和主页面共享连接池,而浏览器对相同域的连接有限制,所以会影响页面的并行加载;
使用iframe之前需要考虑这两个缺点。如果需要使用iframe,最好是通过javascript动态给iframe添加src属性值,这样可以绕开以上两个问题。
1.缓存dom对象:首先不管在什么场景下。操作Dom一般首先会去访问Dom ,尤其是像循环遍历这种时间复杂度可能会比较高的操作。那么可以在循环之前就将主节点,不必循环的Dom节点先获取到,那么在循环里就可以直接引用,而不必去重新查询
// 不好的做法
for (let i = 0; i < 10; i++) {
document.getElementById("temp").innerHTML = ""
document.getElementById("temp").innerHTML += "temp
";
}
// 改进的做法
let temp = document.getElementById(temp);
for (let i = 0; i < 10; i++) {
temp.innerHTML = "";
temp.innerHTML += "temp
";
}
//再改进
let temp = document.getElementById("temp");
let fragments = ""
for (let i = 0; i < 10; i++) {
fragments += "temp
";
}
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
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
1.文档类型声明
DOCTYPE html PUBLIC "-/ /W3C/ /DTD XHTML 1.0 Transitional/ /EN" "http://mw.w3.org/TR/xhtn11/DTD/xhtml transitional.dtd">
DOCTYPE html>
2.结构语义
Html:没有体现结构语义化的标签,通常都是这样来命名的 , 这样表示网站的头部
Html5:在语义上却有很大的优势,提供了一些新的HTML5标签比如: article、 footer、header、nav、 section , 这些通俗易懂
1.突出重要内容
合理的设计title 、description 和keywords
标题:只强调重点即可,尽量做到每个页面的
标题中不要设置相同的内容。
标签:关键词,列举出几个页面的重要关键字即可,切记过分堆砌。
标签:网页描述,需要高度概括网页内容,切记不能太长、 过分堆砌关键词,每个页面也要有所不同。
2.语义化书写HTML代码,符合W3C标准
尽量让代码语义化,在适当的位置使用适当的标签,用正确的标签做正确的事。让阅读源码者和”蜘蛛”都一目了然。比如: h1-h6是用于标题类的, 标签是用来设置页面主导航,列表形式的代码使用ul或ol ,重要的文字使用strong等。
页内链接,要加title 属性加以说明,让访客和”蜘蛛”知道。而外部链接,链接到其他网站的,则需要加上rel=“nofollow” 属性,告诉"蜘蛛” 不要爬,因为一旦"蜘蛛”爬了外部链接之后,就不会再回来了。
4.正文标题要用h1标签
h1标签自带权重,"蜘蛛”认为它最重要,一个页面有且最多只能有一个H1标签,放在该页面最重要的标题上面,如首页的logo.上可以加H1标签。副标题用 标签,而其它地方不应该随便乱用h标题标签。
5.应使用"alt"属性加以说明
当网络速度很慢,或者图片地址失效的时候,就可以体现出alt属性的作用,他可以让用户在图片没有显示的时候知道这个图片的作用。同时为图片设置高度和宽度,可提高页面的加载速度。
6.表格应该使用
表格标题标签
caption元素定义表格标题。caption 标签必须紧随table标签之后
7. 、
标签
需要强调是使用标签在搜索引擎中能够得到高度的重视,它能突出关键词,表现重要的内容,
标签强调效果仅次于
标签;
、
标签:只是用于显示效果时使用,在SEO中不会起任何效果。
8.重要内容不要用JS输出
因为”蜘蛛" 不会读取JS里的内容,所以重要内容必须放在HTML里。前端框架针对SEO的缺陷,可通过服务端渲染弥补
9.尽少使用iframe框架
因为"蜘蛛”一般不会读取其中的内容。
10.搜索引擎会过滤掉display:none其中的内容
11.蜘蛛只能抓取a标签中href
测试
最好后面不要带参数测试
如果带上参数蜘蛛不会考虑的。这样的话,就需要用到URL写了。
12.蜘蛛不会执行JavaScript
换句话说如果在a标签中使用了onclick蜘蛛是不会抓到的。
13.蜘蛛只能抓到get请求的页面,不会抓到post请求的页面
14.我们希望网页的前台页面全部被蜘蛛抓到
但是不希望后台页面被蜘蛛抓到,蜘蛛可没有那么智能,知道你的网站哪个是前台页面,哪个是后台页面。这里就需要创建一个名为"robots.txt” (注意robots.txt是一个协议,不是命令, -般最好要遵守的robots.txt是搜索引擎搜索该网站时的第一个文件。
两者共同点都是使脚本异步加载
defer 属性在文档解析完成后再执行这个脚本文件,这样的话就能使页面的渲染不被阻塞。多个设置了 defer 属性的脚本是顺序执行的
async 属性当脚本加载完成后立即执行js脚本,这个时候如果文档没有解析完成的话同样会阻塞。多个 async 属性的脚本的执行顺序是不可预测的,一般不会按照代码的顺序依次执行。
当一个script标签内同时包含defer与async属性时,只会触发async ,不会触发defer ,除非浏览器不兼容async。
src 用于替换当前元素,href 用于在当前文档和引用资源之间确立联系。
src 是 source 的缩写,指向外部资源的位置,指向的内容将会嵌入到文档中当前标签所在位置;在请求 src 资源时会将其指向的资源下载并应用到文档内,例如 js 脚本,img 图片和 frame 等元素。当浏览器解析到该元素时,会暂停其他资源的下载和处理,直到将该资源加载、编译、执行完毕,图片和框架等元素也如此,类似于将所指向资源嵌入当前标签内。这也是为什么将 js 脚本放在底部而不是头部。
href 是 Hypertext Reference 的缩写,指向网络资源所在位置,建立和当前元素(锚点)或当前文档(链接)之间的链接,如果我们在文档中添加那么浏览器会识别该文档为 css 文件,就会并行下载资源并且不会停止对当前文档的处理。这也是为什么建议使用 link 方式来加载 css,而不是使用@import 方式。
DOCTYPE是document type (文档类型) 的缩写。声明位于文档的最前面,处于标签之前,它不是html标签。主要作用是告诉浏览器的解析器使用哪种HTML规范或者XHTML规范来解析页面。
严格模式和混杂模式都是浏览器的呈现模式,浏览器究竟使用混杂模式还是严格模式呈现页面与网页中的DTD(文件类型定义)有关,DTD里面包含了文档的规则。
严格模式:又称标准模式,是指浏览器按照W3C标准来解析代码,呈现页面
混杂模式:又称为怪异模式或者兼容模式,是指浏览器按照自己的方式来解析代码,使用一种比较宽松的向后兼容的方式来显示页面。
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配合使用。
内容优化
(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)使用来代替@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)图标尽量使用矢量图标
渐进增强:
针对低版本浏览器进行构建页面,保证最基本的功能,然后再针对高级浏览器进行效果、交互等改进和追加功能达到更好的用户体验。
优雅降级:
一开始就构建完整的功能,然后再针对低版本浏览器进行兼容。
区别:
1.优雅降级是从复杂的现状开始,并试图减少用户体验的供给
2.渐进增强则是从一个非常基础的,能够起作用的版本开始,并不断扩充,以适应未来环境的需要
3.降级(功能衰减)意味着往回看;而渐进增强则意味着朝前看,同时保证其根基处于安全地带
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;
}
什么是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重叠(塌陷)
自适应多栏布局
清除内部浮动
display:none
隐藏后的元素不占据任何空间
给子元素设置display:none 不会显示出来
修改后会引起回流
会影响计数器的计数(ol)
visibility:hidden
隐藏后的元素空间依旧保留
visibility具有继承性,给父元素设置visibility:hidden;子元素也会继承这个属性。但是如果重新给子元素设置visibility: visible,则子元素又会显示出来
修改后不会引起回流
不会影响计数器的计数(ol)
设置父元素高度
设置父元素的高度解决的是元素浮动产生的父元素高度塌陷问题,其内部元素浮动影响并未彻底清除,且需要进行计算然后再设置, 比较固定,一旦元素的高度发生改变,父元素的高度也需要再次计算设置,不够灵活。除非内容高度固定一成不变,否则不推荐使用。
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即可
盒模型是css中的一种基础设计模式, web页面中的所有元素都可以当做一个盒模型,每一个盒模型都是由content, margin , padding和border等属性组合所构成的
CSS中的盒模型有两种: W3C标准模型和IE的传统模型。不同之处在于两者的计算方式不同。给元素设置宽度width和高度height,在w3c盒子模型中,width和height只是content部分,在IE盒模型中,这个width和height包括了 content、padding和border三个部分
选择器
标签名选择器
类选择器
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
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%;
行内元素会在一条直线上排列(默认宽度只与内容有关) , 都是同一行的,水平方向排列。
块级元素各占据一行(默认宽度是它本身父容器的100% (和父元素的宽度一致 , 与内容无关) ,垂直方向排列。块级元素从新行开始,结束接着一个断行。
块级元素可以包含行内元素和块级元素。行内元素不能包含块级元素,只能包含文本或者其它行内元素。
行内元素与块级元素属性的不同,主要是盒模型属性上,行内元素设置width无效, height无效(可以设置line-height) , margin上下无效,padding上下无效
link是XHTML标签,除了加载CSS外,还可以定义RSS等其他事务; @import属于CSS范畴,只能加载CSS。
link引用CSS时,在页面载入时同时加载; @import需要页面网页完全载入以后加载。所以会出现一开始没有css样式,闪烁一下出现样式后的页面(网速慢的情况下)
link是XHTML标签,无兼容问题;@import是在CSS2.1提出的,低版本的浏览器不支持。
link支持使用Javascript控制DOM去改变样式;而@import不支持。
响应式开发一套界面,通过检测视口分辨率,针对不同客户端在客户端做代码处理,来展现不同的布局和内容;
自适应需要开发多套界面,通过检测视口分辨率,来判断当前访问的设备是pc端、平板、手机,从而请求服务层,返回不同的页面。
媒体查询
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
什么是重绘重排
当我们改变了一个元素的尺寸位置属性时,会重新进行样式计算(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 ,合并操作;
防抖节流控制频率;
避免频繁读取会引发回流/重绘的属性
对具有复杂动画的元素使用绝对定位
CSS预处理器
为CSS增加编程特性的拓展语言,可以使用变量、简单逻辑判断、函数等基本编程技巧;CSS预处理器编译输出还是标准的CSS样式
Less、 Sass都是是动态的样式语言,是CSS预处理器,CSS上的一种抽象层。他们是一种特殊的语法/语言而编译成CSS。less变量符号是@ , Sass变量符号是$;
解决的问题
CSS语法不够强大,因为无法嵌套导致有很多重复的选择器
没有变量和合理的样式复用机制,导致逻辑上相关的属性值只能以字面量的形式重复输出,难以维护
常用规范
变量、嵌套语法、混入、@import. 运算、函数、继承
CSS预处理器带来的好处
CSS代码更加整洁,更易维护,代码量更少
修改更快,基础颜色使用变量,一处动处处动.
常用代码使用代码块,节省大量代码
CSS嵌套减少了大量的重复选择器,避免一些低级错误
变量、混入大大提升了样式的复用性
额外的工具类似颜色函数( lighten,darken ,transparentize等等) , mixins , loops ,这些方法使css更像一个真正的编程语言,让开发者能够有能力生成更加复杂的css样式。
px:表示像素,所谓像素就是呈现在我们显示器上的一个个小点,每个像素点都是大小等同的,所以像素为计量单位被分在了绝对长度单位中
em:是相对长度单位。相对于当前对象内文本的字体尺寸。如当前对行内文本的字体尺寸未被人为设置,则相对于浏览器的默认字体尺寸(1em = 16px)
rem:相对单位,相对的只是HTML根元素font-size的值。同理,如果想要简化font-size的转化,我们可以在根元素html中加入font-size: 62.5%
vw:就是根据窗口的宽度,分成100等份,100vw就表示满宽,50vw就表示一半宽。(vw 始终是针对窗口的宽),同理,vh则为窗口的高度
浮动+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;
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
Grid 布局即网格布局,是一个二维的网格布局方式,由纵横相交的两组网格线形成的框架性布局结构,能够同时处理行与列。擅长将一个页面划分为几个主要区域,以及定义这些区域的大小、位置、层次等关系
单行溢出
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;
}
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);
}
block:块对象的默认值。用该值为对象之后添加新行
none:隐藏对象。与visibility属性的hidden值不同,其不为被隐藏的对象保留其物理空间
inline:内联对象的默认值。用该值将从对象中删除行
compact:分配对象为块对象或基于内容之上的内联对象
marker:指定内容在容器对象之前或之后。要使用此参数,对象必须和:after及:before 伪元素一起使用
inline-table:将表格显示为无前后换行的内联对象或内联容器
list-item:将块对象指定为列表项目。并可以添加可选项目标志
run-in:分配对象为块对象或基于内容之上的内联对象
table:将对象作为块元素级的表格显示
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 属性的元素在标准流中不占位置。
新增选择器 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;
基本数据类型:js 一共有六种基本数据类型,分别是 Undefined、Null、Boolean、Number、String,还有在 ES6 中新增的 Symbol 和 ES10 中新增的 BigInt 类型。Symbol 代表创建后独一无二且不可变的数据类型,它的出现我认为主要是为了解决可能出现的全局变量冲突的问题。BigInt 是一种数字类型的数据,它可以表示任意精度格式的整数,使用 BigInt 可以安全地存储和操作大整数,即使这个数已经超出了 Number 能够表示的安全整数范围。
复杂数据类型:指的是 Object 类型,所有其他的如 Array、Date 等数据类型都可以理解为 Object 类型的子类。
存储差别
原始数据类型直接存储在栈(stack)中的简单数据段,占据空间小、大小固定,属于被频繁使用数据,所以放入栈中存储。
引用数据类型存储在堆(heap)中的对象,占据空间大、大小不固定。如果存储在栈中,将会影响程序运行的性能;引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。
原生对象:独立于宿主环境的 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 对象都是宿主对象
增:push()、unshift()、splice()、concat()
删:pop()、shift()、splice()、slice()
改:splice()
查:includes()、indexOf()、find()
排序:reverse()、sort()
转换:join()
迭代:some() every() forEach() filter() map()
增: +、${}、concat
删: slice()、substr()、substring()
改:trim()、trimLeft()、trimRight()、repeat()
查:chatAt()、indexOf()、includes()、startWith()
转换:toLowerCase()、toUpperCase()、split()
迭代:some()、every()、forEach()、filter()、map()
等于操作符用两个等于号( == )表示,如果操作数相等,则会返回 true
在JavaScript中存在隐式转换。等于操作符(==)在比较中会先进行类型转换,再确定操作数是否相等
全等操作符由 3 个等于号( === )表示,只有两个操作数在不转换的前提下相等才返回 true。即类型相同,值也需相同
浅拷贝—浅拷贝是指复制对象的时候,只对第一层键值对进行独立的复制,如果对象内还有对象,则只能复制嵌套对象的地址
深拷贝—深拷贝是指复制对象的时候完全的拷贝一份对象,即使嵌套了对象,两者也相互分离,修改一个对象的属性,也不会影响另一个。其实只要递归下去,把那些属性的值仍然是对象的再次进入对象内部一 一进行复制即可。
浅拷贝
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'}}
定义:当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行。
三个特性:
函数嵌套函数
函数内部可以引用外部的参数和变量
参数和变量不会被垃圾回收机制回收
作用:使用闭包主要是为了设计私有的方法和变量,闭包的优点是可以避免全局变量的污染,缺点是闭包会常驻内存,会增大内存使用量,使用不当很容易造成内存泄露。
在 js 中我们是使用构造函数来新建一个对象的,每一个构造函数的内部都有一个 prototype 属性值,这个属性值是一个对象,这个对象包含了可以由该构造函数所有实例共享的属性和方法。当我们使用构造函数新建一个实例后,在这个实例的内部将包含一个指针指向构造函数的 prototype 属性对应的值,在 ES5 中这个指针被称为对象的原型。
当我们访问一个对象的属性时,如果这个对象内部不存在这个属性,那么它就会去它的原型对象里找这个属性,这个原型对象又会有自己的原型,于是就这样一直找下去,也就是原型链的概念。原型链的终点是null
获取原型的方法
p._proto_
p.constructor.prototype
Object.getPrototypeOf§
作用域,即变量和函数生效(也就是能被访问)的区域或集合
作用域分类
全局作用域:任何不在函数中或是大括号中声明的变量,都是在全局作用域下,全局作用域下声明的变量可以在程序的任意位置访问
函数作用域:函数作用域也叫局部作用域,如果一个变量是在函数内部声明的它就在一个函数作用域下面。这些变量只能在函数内部访问,不能在函数以外去访问
块级作用域:ES6引入了let和const关键字,和var关键字不同,在大括号中使用let和const声明的变量存在于块级作用域中。在大括号之外不能访问这些变量
作用域链
当在Javascript中使用一个变量的时候,首先Javascript引擎会尝试在当前作用域下去寻找该变量,如果没找到,再到它的上层作用域寻找,以此类推直到找到该变量或是已经到了全局作用域。如果在全局作用域里仍然找不到该变量,它就会在全局范围内隐式声明该变量或是直接报错
原型链继承
原型链继承的原理很简单,直接让子类的原型对象指向父类实例,当子类实例找不到对应的属性和方法时,就会往它的原型对象,也就是父类实例上找,从而实现对父类的属性和方法的继承
// 父类
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']
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}
每一个方法或函数都会有一个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。
执行上下文
执行上下文就是当前 JavaScript 代码被解析和执行时所在环境的抽象概念, JavaScript 中运行任何的代码都是在执行上下文中运行。
执行上下文分类
全局执行上下文:这是默认的、最基础的执行上下文。不在任何函数中的代码都位于全局执行上下文中。它做了两件事:1. 创建一个全局对象,在浏览器中这个全局对象就是 window 对象。2. 将this指针指向这个全局对象。一个程序中只能存在一个全局执行上下文
函数执行上下文:每次调用函数时,都会为该函数创建一个新的执行上下文。每个函数都拥有自己的执行上下文,但是只有在函数被调用的时候才会被创建。一个程序中可以存在任意数量的函数执行上下文。每当一个新的执行上下文被创建,它都会按照特定的顺序执行一系列步骤,具体过程将在本文后面讨论。
Eval 函数执行上下文: 指的是运行在 eval 函数中的代码,很少用而且不建议使用
执行栈
执行栈,在其他编程语言中也被叫做调用栈,具有 LIFO(后进先出)结构,用于存储在代码执行期间创建的所有执行上下文。
当Javascript引擎开始执行你第一行脚本代码的时候,它就会创建一个全局执行上下文然后将它压到执行栈中
每当引擎碰到一个函数的时候,它就会创建一个函数执行上下文,然后将这个执行上下文压到执行栈中
引擎会执行位于执行栈栈顶的执行上下文(一般是函数执行上下文),当该函数执行结束后,对应的执行上下文就会被弹出,然后控制流程到达执行栈的下一个执行上下文
创建一个新的对象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}
事件是用户操作网页时发生的交互动作或者网页本身的一些操作,现代浏览器一共有三种事件模型。
事件流三个阶段
事件捕获阶段(capture phase)
事件目标阶段(target phase)
事件冒泡阶段(bubbling phase)
事件模型
原始事件模型(DOM0级)
IE事件模型(基本不用)
标准事件模型(DOM2级)
1.第一种事件模型是最早的 DOM0 级模型,这种模型不会传播,所以没有事件流的概念,但是现在有的浏览器支持以冒泡的方式实现,它可以在网页中直接定义监听函数,也可以通过 js 属性来指定监听函数。这种方式是所有浏览器都兼容的。
2.第二种事件模型是 IE 事件模型,在该事件模型中,一次事件共有两个过程,事件处理阶段,和事件冒泡阶段。事件处理阶段会首先执行目标元素绑定的监听事件。然后是事件冒泡阶段,冒泡指的是事件从目标元素冒泡到 document,依次检查经过的节点是否绑定了事件监听函数,如果有则执行。这种模型通过 attachEvent 来添加监听函数,可以添加多个监听函数,会按顺序依次执行。
3.第三种是 DOM2 级事件模型,在该事件模型中,一次事件共有三个过程,第一个过程是事件捕获阶段。捕获指的是事件从 document 一直向下传播到目标元素,依次检查经过的节点是否绑定了事件监听函数,如果有则执行。后面两个阶段和 IE 事件模型的两个阶段相同。这种事件模型,事件绑定的函数是 addEventListener,其中第三个参数可以指定事件是否在捕获阶段执行。
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 类型以外,其他的也无法判断
事件代理(Event Delegation),又称之为事件委托。“事件代理”即是把原本需要绑定在子元素的响应事件(click、keydown…)委托给父元素,让父元素担当事件监听的职务。事件代理的原理是DOM元素的事件冒泡。
应用场景
如果我们有一个列表,列表之中有大量的列表项,我们需要在点击列表项的时候响应一个事件,如果给每个列表项一一都绑定一个函数,那对于内存消耗是非常大的。这时候就可以事件委托,把点击事件绑定在父级元素ul上面,然后执行事件的时候再去匹配目标元素li
如果用户能够随时动态的增加或者去除列表项元素,那么在每一次改变的时候都需要重新给新增的元素绑定事件,给即将删去的元素解绑事件。如果用了事件委托就没有这种麻烦了,因为事件是绑定在父层的,和目标元素的增减是没有关系的,执行到目标元素是在真正响应执行事件函数的过程中去匹配的
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()
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模式,所以这个框架基本的流程是:对象初始化、发送请求、服务器接收、服务器返回、客户端接收、修改客户端页面内容。只不过这个过程是异步的。
同步是阻塞模式,异步是非阻塞模式。
同步就是指一个进程在执行某个请求的时候,若该请求需要一段时间才能返回信息,那么这个进程将会一直等待下去,直到收到返回信息才继续执行下去;
异步是指进程不需要一直等下去,而是继续执行下面的操作,不管其他进程的状态。当有消息返回时系统会通知进程进行处理,这样可以提高执行的效率。
状态0(请求未初始化):XMLHttpRequest)对象已经创建或已被abort()方法重置,但还没有调用open()方法
状态1(载入服务器连接已建立):已经调用open() 方法,但是send()方法未调用,尚未发送请求
状态2(载入完成,请求已接收):send()方法已调用,HTTP请求已发送到web服务器,请求已经发送完成,未接收到响应
状态3(交互,请求处理中):所有响应头部都已经接收到。响应体开始接收但未完成,即可以接收到部分响应数据
状态4(请求完成,且响应已就绪):已经接收到了全部数据,并且连接已经关闭
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。
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
JavaScript是一门单线程的语言,意味着同一时间内只能做一件事,而实现单线程非阻塞的方法就是事件循环。在代码执行的时候,通过将不同函数的执行上下文压入执行栈中来保证代码的有序执行。
在JavaScript中,所有的任务都可以分为同步任务和异步任务。同步任务也就是立即执行的任务,同步任务一般会直接进入到主线程中执行。异步任务是异步执行的任务,比如ajax网络请求,setTimeout定时函数等
在执行同步任务的时候,如果遇到了异步事件,js 引擎并不会一直等待其返回结果,而是会将这个事件挂起,继续执行执行栈中的其他任务。当异步事件执行完毕后,再将异步事件对应的回调加入到与当前执行栈中不同的另一个任务队列中等待执行。任务队列可以分为宏任务队列和微任务队列,当当前执行栈中的事件执行完毕后,js 引擎首先会判断微任务队列中是否有任务可以执行,如果有就将微任务队首的事件压入栈中执行。当微任务对列中的任务都执行完成后再去判断宏任务对列中的任务。
微任务包括了 promise 的回调、node 中的 process.nextTick 、对 Dom 变化监听的 MutationObserver。
宏任务包括了 script 脚本的执行、setTimeout ,setInterval ,setImmediate 一类的定时事件,还有如 I/O 操作、UI 渲染等。
文档对象模型 (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把自己删掉
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:获取历史记录数
内存泄漏(Memory leak)是在计算机科学中,由于疏忽或错误造成程序未能释放已经不再使用的内存
常见的内存泄漏
解决内存泄漏
1.使用严格模式,可以避免意外的全局变量
2.清理dom元素, dom = null
3.setTimeout第一个参数为函数
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
优点:
1.极高的扩展性和可用性
2.通过编程方式,控制保存在cookie中的session对象的大小
3.通过加密和SSL(安全传输技术),减少cookie被破解的可能性
4.只在cookie中存放不敏感数据,被盗也不会有重大的损失
5.控制cookie的生命周期,可用让其永远有效,偷盗者就有可能拿到一个过期的cookie
缺点:
1.cookie数量和长度的限制,每个最多只能有20条的cookie,长度不能超过4kb,超过就会被截取
2.安全性问题,被人拦截,就可以得到cookie的所有信息,只要原样转发就可以达到目的
函数式编程中的函数这个术语不是指计算机中的函数,而是指数学中的函数,即自变量的映射。也就是说一个函数的值仅决定于函数参数的值,不依赖其他状态。
函数式编程就是一种抽象程度很高的编程范式,纯粹的函数式编程语言编写的函数没有变量,因此,任意一个函数,只要输入是确定的,输出就是确定的,这种纯函数我们称之为没有副作用。而允许使用变量的程序设计语言,由于函数内部的变量状态不确定,同样的输入,可能得到不同的输出,因此,这种函数是有副作用的。
函数式编程的一个特点就是,允许把函数本身作为参数传入另一个函数,还允许返回一个函数!
当计算机计算 0.1+0.2 的时候,实际上计算的是这两个数字在计算机里所存储的二进制,0.1 和 0.2 在转换为二进制表示的时候会出现位数无限循环的情况。js 中是以 64 位双精度格式来存储数字的,只有 53 位的有效数字,超过这个长度的位数会被截取掉这样就造成了精度丢失的问题。这是第一个会造成精度丢失的地方。
在对两个以 64 位双精度格式的数据进行计算的时候,首先会进行对阶的处理,对阶指的是将阶码对齐,也就是将小数点的位置对齐后,再进行计算,一般是小阶向大阶对齐,因此小阶的数在对齐的过程中,有效数字会向右移动,移动后超过有效位数的位会被截取掉,这是第二个可能会出现精度丢失的地方。
当两个数据阶码对齐后,进行相加运算后,得到的结果可能会超过 53 位有效数字,因此超过的位数也会被截取掉,这是可能发生精度丢失的第三个地方。
解决
1.对于这样的情况,我们可以将其转换为整数后再进行运算,运算后再转换为对应的小数,以这种方式来解决这个问题。
2.使用bigInt()进行计算
面向过程: 分析出解决问题所需要的步骤,强调的是解决问题的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了;
面向对象: 是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。
优缺点比较:
面向过程优点:
1.流程化使得编程任务明确,在开发之前基本考虑了实现方式和最终结果,具体步骤清楚,便于节点分析。
2.效率高,面向过程强调代码的短小精悍,善于结合数据结构来开发高效率的程序。
面向过程缺点:
1.需要深入的思考,耗费精力,代码重用性低,扩展能力差,后期维护难度比较大。
面向对象优点:
1.结构清晰,程序是模块化和结构化,更加符合人类的思维方式;
2.易扩展,代码重用率高,可继承,可覆盖,可以设计出低耦合的系统;
3.易维护,系统低耦合的特点有利于减少程序的后期维护工作量。
面向对象缺点:
1.开销大,当要修改对象内部时,对象的属性不允许外部直接存取,所以要增 加许多没有其他意义、只负责读或写的行为。这会为编程工作增加负担,增加运行开销,并且使程序显得臃肿。
2.性能低,由于面向更高的逻辑抽象层,使得面向对象在实现的时候,不得不做出性能上面的牺牲,计算时间和空间存储大小都开销很大。
得出结论
面向过程:用函数来定义解决问题的步骤
面向对象:用类和对象的方法来定义解决问题的行为或者说功能
面向过程性能比面向对象高。因为类调用时需要实例化,开销比较大,比较消耗资源,所以当性能是最重要的考量因素的时候,比如单片机、嵌入式开发、Linux/Unix 等一般采用面向过程开发。但是,面向过程没有面向对象易维护、易复用、易扩展。
函数缓存,就是将函数运算过的结果进行缓存,本质上就是用空间(缓存存储)换时间(计算过程)。实现函数缓存主要依靠闭包、柯里化、高阶函数
函数防抖是指在事件被触发 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)
}
}
}
变量提升
var声明的变量存在变量提升,即变量可以在声明之前调用,值为undefined
let和const不存在变量提升,即它们所声明的变量一定要在声明后使用,否则报错
暂时性死区
var不存在暂时性死区
let和const存在暂时性死区,只有等到声明变量的那一行代码出现,才可以获取和使用该变量
块级作用域
var不存在块级作用域
let和const存在块级作用域
重复声明
var允许重复声明变量
let和const在同一作用域不允许重复声明变量
修改声明的变量
var和let可以
const声明一个只读的常量。一旦声明,常量的值就不能改变
使用
能用const的情况尽量使用const,其他情况下大多数使用let,避免使用var
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
首先 Undefined 和 Null 都是基本数据类型,这两个基本数据类型分别都只有一个值,就是 undefined 和 null。
undefined 代表的含义是未定义,null 代表的含义是空对象。一般变量声明了但还没有定义的时候会返回undefined,null主要用于赋值给一些可能会返回对象的变量,作为初始化。
undefined 在 js 中不是一个保留字,这意味着我们可以使用 undefined 来作为一个变量名,这样的做法是非常危险的,它会影响我们对 undefined 值的判断。但是我们可以通过一些方法获得安全的 undefined 值,比如说 void 0。
当我们对两种类型使用 typeof 进行判断的时候,Null 类型化会返回 “object”,这是一个历史遗留的问题。当我们使用双等号对两种类型的值进行比较时会返回 true,使用三个等号时会返回 false。
说到 ES6 代码转成 ES5 代码,我们肯定会想到 Babel。那么 Babel 是如何把 ES6 转成 ES5 呢,其大致分为三步:
1.将代码字符串解析成抽象语法树,即所谓的 AST
2.对 AST 进行处理,在这个阶段可以对 ES6 代码进行相应转换,即转成 ES5 代码
3.根据处理后的 AST 再生成代码字符串
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')
}
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
})
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')
在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);
})
语法更加简洁、清晰
箭头函数没有 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]
装箱操作: 把基本数据类型转换为对应的引用类型的操作
拆箱操作: 把引用类型转换为基本数据类型的操
基本类型: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]"
语法
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。
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
同步和异步的差别就在于这条流水线上各个流程的执行顺序不同。
同步任务: 指的是在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务
异步任务: 指的是不进入主线程、而进入"任务队列"(task queue)的任务,只有等主线程任务执行完毕,"任务队列"开始通知主线程,请求执行任务,该任务才会进入主线程执行。
先将img标签的src链接设为同一张图片(比如空白图片),然后给img标签设置自定义属性(比如 data-src),然后将真正的图片地址存储在data-src中,当JS监听到该图片元素进入可视窗口时,将自定义属性中的地址存储到src属性中。达到懒加载的效果。
1.DNS缓存:短时间内多次访问某个网站,在限定时间内,不用多次访问DNS服务器。
2.CDN缓存:内容分发网络(人们可以在就近的代售点取火车票了,不用非得到火车站去排队)
3.浏览器缓存:浏览器在用户磁盘上,对最新请求过的文档进行了存储。
4.服务器缓存:将需要频繁访问的Web页面和对象保存在离用户更近的系统中,当再次访问这些对象的时候加快了速度。
1.类型检测:在Typescript中为变量指定具体类型时,IDE会做出类型检测,这个特性减少在开发阶段犯错几率。
2.语法提示:在IDE里编写Typescript代码时, IDE会根据你当前的上下文,把你能用的类、变量、方法和关键字都给你提示出来。直接选择,这个特性提高开发效率。
3.便于重构:重构是说你可以很方便的去修改你的变量或者方法的名字或者是文件的名字,当你做出这些修改的时候, IDE会帮你自动引用这个变量或者调用这个方法地方的代码自动帮你修改掉
4.活跃社区:Typescript拥抱es6的规范,也支持部分ESNext草案规范,大部分的第三方库提供Typescript类型定义的文件
TypeScript的最大特点是静态类型,不同于javascript的动态类型,静态类型有以下优势:
1.其一,静态类型检查可以做到early fail ,即你编写的代码即使没有被执行到,一旦你编写代码时发生类型不匹配,语言在编译阶段(解释执行也一样,可以在运行前)即可发现。
2.其二,静态类型对阅读代码是友好的,针对大型应用,方法众多,调用关系复杂,不可能每个函数都有人编写细致的文档,所以静态类型就是非常重要的提示和约束。此外TS还实现了类,接口,枚举,泛型,方法重载等语法糖,方便了前端开发;
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插件做出来的效果很炫,并且可以根据自己需要去改写和封装插件,简单实用。
定义
Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中
特点
支持从浏览器中创建 XMLHttpRequests
支持从 node.js 创建 http 请求
支持 Promise API
能拦截请求和响应
能转换请求数据和响应数据
能取消请求
自动转换 JSON 数据
客户端支持防御 CSRF
为什么要封装
axios 的 API 很友好,你完全可以很轻松地在项目中直接使用。
不过随着项目规模增大,如果每发起一次HTTP请求,就要把这些比如设置超时时间、设置请求头、根据项目环境判断使用哪个请求地址、错误处理等等操作,都需要写一遍,这种重复劳动不仅浪费时间,而且让代码变得冗余不堪,难以维护。为了提高我们的代码质量,我们应该在项目中二次封装一下 axios 再使用
如何封装
封装的同时,需要和后端协商好一些约定,请求头,状态码,请求超时时间…
设置接口请求前缀:根据开发、测试、生产环境的不同,前缀需要加以区分
请求头:来实现一些具体的业务,必须携带一些参数才可以请求(例如:会员业务)
状态码:根据接口返回的不同status ,来执行不同的业务,这块需要和后端约定好
请求方法:根据get、post等方法进行一个再次封装,使用起来更为方便
请求拦截器:根据请求的请求头设定,来决定哪些请求可以访问
响应拦截器:这块就是根据 后端返回来的状态码判定执行不同业务
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支持各种编码。
开发模式
请求方式
前后端分离优势
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并回车
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页面。
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显示到屏幕上
重排:当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。
什么是浏览器缓存
浏览器的缓存机制指的是通过在一段时间内保留已接收到的 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,继续使用缓存
预解析:检查语法错误但不生成AST
生成AST:经过词法/语法分析,生成抽象语法树
生成字节码:基线编译器(Ignition)将AST转换成字节码
生成机器码:优化编译器(Turbofan)将字节码转换成优化过的机器码,此外在逐行执行字节码的过程中,如果一段代码经常被执行,那么V8会将这段代码直接转换成机器码保存起来,下一次执行就不必经过字节码,优化了执行速度
引用计数:给一个变量赋值引用类型,则该对象的引用次数+1,如果这个变量变成了其他值,那么该对象的引用次数-1,垃圾回收器会回收引用次数为0的对象。但是当对象循环引用时,会导致引用次数永远无法归零,造成内存无法释放。
标记清除:垃圾收集器先给内存中所有对象加上标记,然后从根节点开始遍历,去掉被引用的对象和运行环境中对象的标记,剩下的被标记的对象就是无法访问的等待回收的对象。
栈内存调用栈上下文切换后就被回收,比较简单
V8的堆内存分为新生代内存和老生代内存,新生代内存是临时分配的内存,存在时间短,老生代内存存在时间长
新生代内存回收机制
新生代内存容量小,64位系统下仅有32M。新生代内存分为From、To两部分,进行垃圾回收时,先扫描From,将非存活对象回收,将存活对象顺序复制到To中,之后调换From/To,等待下一次回收
老生代内存回收机制
晋升:如果新生代的变量经过多次回收依然存在,那么就会被放入老生代内存中
标记清除:老生代内存会先遍历所有对象并打上标记,然后对正在使用或被强引用的对象取消标记,回收被标记的对象
整理内存碎片:把对象挪到内存的一端
减少 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 节点很多时。
定义
浏览器最重要或者说核心的部分是“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双内核
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;
}
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");
}
同源策略
同源策略(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请求。
核心思想:网页通过添加一个元素,向服务器请求 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
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:服务不可用,服务器目前无法使用(处于超载或停机维护状态)。通常是暂时状态。
请求报文
一个HTTP请求报文由请求行(request line)、请求头(header)、空行和请求体4个部分组成
响应报文
一个HTTP响应报文主要由状态行、响应头部、空行和响应正文4部分组成
长连接: 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)。
多路复用: 即多个请求都通过一个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的传输,又减小了需要传输的大小。
HTTP/1.0: 一次请求/响应,建立一个tcp连接,用完关闭,每一个请求都要建立一个tcp连接,也就意味着每个请求都要进行三次握手,这会造成资源的浪费
HTTP/1.1长连接: HTTP/1.1支持长连接和请求的流水线,在一个TCP连接上可以传送多个HTTP请求,避免了因为多次建立TCP连接的时间消耗和延时。
HTTP/1.1管道: 解决方式为,若干个请求排队串行化单线程处理,后面的请求等待前面请求的返回才能获得执行机会,一旦有某请求超时等,后续请求只能被阻塞,无办法,也就是人们常说的线头阻塞
HTTP/2多路复用: 多个请求可同时在一个连接上并行执行。某个请求任务耗时严重,不会影响到其它连接的正常执行;
TCP慢启动: TCP连接建立后,会经历一个先慢后快的发送过程,就像汽车启动一般,如果我们的网页文件(HTML/JS/CSS/icon)都经过一次慢启动,对性能是不小的损耗。另外慢启动是TCP为了减少网络拥塞的一种策略,我们是没有办法改变的。
多条TCP连接竞争带宽: 如果同时建立多条TCP连接,当带宽不足时就会竞争带宽,影响关键资源的下载。
HTTP/1.1队头阻塞: 尽管HTTP/1.1长链接可以通过一个TCP连接传输多个请求,但同一时刻只能处理一个请求,当前请求未结束前,其他请求只能处于阻塞状态。
为了解决以上几个问题,HTTP/2一个域名只使用一个TCP连接来传输数据,而且请求直接是并行的、非阻塞的,这就是多路复用
实现原理: HTTP/2引入了一个二进制分帧层,客户端和服务端进行传输时,数据会先经过二进制分帧层处理,转化为一个个带有请求ID的帧,这些帧在传输完成后根据ID组合成对应的数据。
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)乙方得到加密后的信息,用私钥解密。
公钥加密的信息只有私钥解得开,那么只要私钥不泄漏,通信就是安全的
TCP 协议是面向连接的通信协议,即在传输数据前先在发送端和接收端建立逻辑连接,然后再传输数据,它提供了两台计算机之间可靠无差错的数据传输。
在 TCP 连接中必须要明确客户端与服务器端,由客户端向服务端发出连接请求,每次连接的创建都需要经过“三次握手”
第一次握手,客户端向服务器端发送一个带 SYN 标志的数据包,等待服务器确认
第二次握手,服务器端向客户端回传一个带有 SYN/ACK 标志的数据包,通知客户端收到了连接请求
第三次握手,客户端再次向服务器端回传一个带 ACK 标志的数据包,确认连接,“握手”结束。
上述每一次握手的作用如下:
第一次握手:客户端发送网络包,服务端收到了,这样服务端就能得出结论:客户端的发送能力、服务端的接收能力是正常的。
第二次握手:服务端发包,客户端收到了 这样客户端就能得出结论:服务端的接收、发送能力,客户端的接收、发送能力是正常的。不过此时服务器并不能确认客户端的接收能力是否正常
第三次握手:客户端发包,服务端收到了。 这样服务端就能得出结论:客户端的接收、发送能力正常,服务器自己的发送、接收能力也正常
通过三次握手,就能确定双方的接收和发送能力是正常的。之后就可以正常通信了
为什么不是两次握手?
如果是两次握手,发送端可以确定自己发送的信息能对方能收到,也能确定对方发的包自己能收到,但接收端只能确定对方发的包自己能收到 无法确定自己发的包对方能收到
并且两次握手的话, 客户端有可能因为网络阻塞等原因会发送多个请求报文,延时到达的请求又会与服务器建立连接,浪费掉许多服务器的资源
1、客户端向服务器发送一个断开连接的请求(不早了,我该走了);
2、服务器接到请求后发送确认收到请求的信号(知道了);
3、服务器向客户端发送断开通知(我也该走了);
4、客户端接到断开通知后断开连接并反馈一个确认信号(嗯,好的),服务器收到确认信号后断开连接;
第一次挥手:主动关闭方发送一个 FIN,用来关闭主动方到被动关闭方的数据传送,也就是主动关闭方告诉被动关闭方:我已经不会再给你发数据了(当然,在 fin 包之前发送出去的数据,如果没有收到对应的 ack 确认报文,主动关闭方依然会重发这些数据),但是,此时主动关闭方还可 以接受数据。
第二次挥手:被动关闭方收到 FIN 包后,发送一个 ACK 给对方,确认序号为收到序号+1(与 SYN 相同,一个 FIN 占用一个序号)。
第三次挥手:被动关闭方发送一个 FIN,用来关闭被动关闭方到主动关闭方的数据传送,也就是告诉主动关闭方,我的数据也发送完了,不会再给你发数据了。
第四次挥手:主动关闭方收到 FIN 后,发送一个 ACK 给被动关闭方,确认序号为收到序号+1,至此,完成四次挥手。
四次挥手的原因
服务端在收到客户端断开连接Fin报文后,并不会立即关闭连接,而是先发送一个ACK包先告诉客户端收到关闭连接的请求,只有当服务器的所有报文发送完毕之后,才发送FIN报文断开连接,因此需要四次挥手
4.每一条TCP连接只能是点到点的,UDP支持一对一, 一对多,多对一和多对多的交互通信
应用场景
TCP 应用场景适用于对效率要求低,对准确性要求高或者要求有链接的场景,而UDP 适用场景为对效率要求高,对准确性要求低的场景
原因是浏览器会自动带上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验证不会通过,所以不会进行扣款操作。
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通道
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中
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>
什么是工程化?
前端工程本质上是软件工程的一种。软件工程化关注的是性能、稳定性、可用性、可维护性等方面,注重基本的开发效率、运行效率的同时,思考维护效率。一切以这些为目标的工作都是“前端工程化”。工程化是一种思想而不是某种技术。
可用性: 可用性指的是:产品是否容易上手,用户能否完成任务,效率如何,以及这过程中用户的主观感受可好,是从用户的角度来看产品的质量。可用性好意味着产品质量高,是企业的核心竞争力。
可维护性: 可维护性一般包含两个层次,一是当系统出现问题时,快速定位并解决问题的成本,成本低则可维护性好。二是代码是否容易被人理解,是否容易修改和增强功能。可维护性和可复用性、可扩展性等有交叉的地方。构建可维护性好的代码,对企业的长期发展非常重要。
可访问性: Web内容对于残障用户的可阅读和可理解性。提高可访问性也能让普通用户更容易理解Web内容。具体而言,要考虑以下两方面:
1.无论用户是否残障,都得通过用户代理(User Agent)来访问Web内容。因此要提高可访问性,首先得考虑各种用户代理 :桌面浏览器、语音浏览器、移动电话、车载个人电脑等等。在Google, 专门聘请了一些残障雇员,来帮助提高产品的可访问性。
2.还得考虑用户访问Web内容时的环境限制 。比如:嘈杂的环境、过暗或过亮的房间、或者是免提等各种情况。这是更高一层次的可访问性要求,做到了,能造就产品在特定领域的核心竞争力。
所谓的模块化开发就是封装细节,提供使用接口,彼此之间互不影响,每个模块都是实现某一特定的功能。模块化开发的基础就是函数
因为有了模块,我们就可以更方便地使用别人的代码,想要什么功能,就加载什么模块。 但是,这样做有一个前提,那就是大家必须以同样的方式编写模块,否则你有你的写法,我有我的写法,岂不是乱了套
全局函数模式将不同的功能封装成不同的全局函数,使用的时候,直接调用就行了。这种做法的缺点很明显:"污染"了全局变量,无法保证不与其他模块发生变量名冲突,而且模块成员之间看不出直接关系。
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的模块系统
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代表当前模 块文件所在的文件夹路径+文件名
ES6在语言标准的层面上,实现了模块功能,而且实现得相当简单,旨在成为浏览器和服务器通用的模块解决方案。其模块功能主要由两个命令构成: export和
import。export 命令于规定模块的对外接口,import命令用于输入其他模块提供的功能。
其实ES6还提供了export default命令,为模块指定默认输出,对应的import语句不需要使用大括号。这也更趋近于AMD的引用写法。
ES6的模块不是对象,import命令会被JavaScript引擎静态分析,在编译时就引入模块代码,而不是在代码运行时加载,所以无法实现条件加载。也正因为这个,使得静态分析成为可能。
export
export可以导出的是一个对象中包含的多个属性、方法。export default只能导出一个可以不具名的函数。我们可以通过import进行引用。同时,我们也可以直接使用require使用,原因是webpack起了server相关。
import
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工厂方法,模块初始化要执行的函数或对象,如果为函数,它应该只被执行一次,如果是对象,此对象应该为模块的输出值。
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();
})
一种整合了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 ...
});
commonjs是同步加载的。主要是在nodejs也就是服务端应用的模块化机制,通过module.export导出声明,通过require(‘’) 加载。每个文件都是一个模块。他有自己的作用域,文件内的变量,属性函数等不能被外界访问。node会将模块缓存, 第二次加载会直接在缓存中获取。
AMD是异步加载的。主要应用在浏览器环境下。
requireJS是遵循AMD规范的模块化工具。他是通过define ()定义声明通过require(‘’, function(){})加载。
ES6的模块化加载时通过export 导出,用import导入可通过{}对导出的内容进行解构。
ES6的模块的运行机制与common不-样, js引擎对脚本静态分析的时候,遇到模块加载指令后会生成一个只读引用,等到脚本真正执行的时候才会通过引用去模块中获取值,在引用到执行的过程中模块中的值发生了变化,导入的这里也会跟着变, ES6模块是动态引用,并不会缓存值,模块里总是绑定其所在的模块。
webpack是一个打包工具,他的宗旨是一切静态资源皆可打包。有人就会问为什么要webpack?webpack是现代前端技术的基石,常规的开发方式,比如jquery,html,css静态网页开发已经落后了。现在是MVVM的时代,数据驱动界面。webpack它做的事情是,分析你的项目结构,找到JavaScript模块以及其它的一些浏览器不能直接运行的拓展语言(Scss,TypeScript等),并将其打包为合适的格式以供浏览器使用
模块化工具的出现原因
模块化开发会划分出很多的模块文件,前端应用运行在浏览器中,所有文件都需要从服务器请求回来,因此必然会导致浏览器频繁发送网络请求,影响应用的工作效率。 前端项目中的 HTML CSS 这些资源文件也需要被模块化处理。
工具需要具备的能力
把 ES6 代码编译成浏览器能解析的 ES5 代码
把各种文件HTML、CSS、JS 图片等分别打包到一起,这样就不用频繁发送网络请求
模块化只在开发阶段需要,能更好地组织我们的代码
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)其它
整个过程中特定的时间点广播事件,插件可以进行监听和处理
在webpack内部中,任何文件都是模块,不仅仅只是js文件,默认情况下,在遇到import或者load加载模块的时候,webpack只支持对js文件打包。像css、sass、png等这些类型的文件的时候,webpack则无能为力,这时候就需要配置对应的loader进行文件内容的解析
loader是文件加载器,能够加载资源文件,并对这些文件进行一些处理,诸如编译、压缩等,最终一起打包到指定的文件中
处理一个文件可以使用多个loader,loader的执行顺序和配置中的顺序是相反的,即最后一个loader最先执行,第一个loader最后执行
第一个执行的loader接收源文件内容作为参数,其它loader接收前一个执行的loader的返回值作为参数,最后执行的loader会返回此模块的JavaScript源码
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等
css-loader处理css文件
style-loader把js中import导入的样式文件代码,打包到js文件中,运行js文件时,将样式自动插入到 标签中
file-loader返回的是图片的url
url-loader可以通过limit属性对图片分情况处理,当图片小于limit(单位: byte )大小时转base64 ,大于limit时调用file-loader对图片进行处理。
url-loader封装了file-loader,但url-loader 并不依赖于file-loader
plugin赋予其各种灵活的功能,例如打包优化、资源管理、环境变量注入等,在webpack运行的生命周期中会广播出许多事件,plugin可以监听这些事件,在合适的时机通过webpack提供的API改变输出结果。
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:通过多进程模型,来加速代码构建
对于loader,它是一个转换器,将A文件进行编译形成B文件,这里操作的是文件,比如将A. scss转换为A. css,单纯的文件转换过程
plugin是一个扩展器,它丰富了webpack本身,针对是loader结束后,webpack打包的整个过程,它并不直接操作文件,而是基于事件机制工作,会监听webpack打包过程中的某些节点,执行广泛的任务
entry-option:初始化 option
run:
compile:真正开始的编译,在创建 compilation 对象之前
compilation:生成好了 compilation 对象
make:从 entry 开始递归分析依赖,准备对每个模块进行 build
after-compile:编译 build 过程结束
emit:在将内存中 assets 内容写到磁盘文件夹之前
after-emit:在将内存中 assets 内容写到磁盘文件夹之后
done:完成所有的编译过程
failed:编译失败的时候
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机制,加载这两个文件,并且针对修改的模块进行更新
在开发阶段, webpack-dev-server 会启动一个本地开发服务器,所以我们的应用在开发阶段是独立运行在 localhost的一个端口上,而后端服务又是运行在另外一个地址上
所以在开发阶段中,由于浏览器同源策略的原因,当本地访问后端就会出现跨域请求的问题
通过设置webpack proxy实现代理请求后,相当于浏览器与服务端中添加一个代理者
当本地发送请求的时候,代理服务器响应该请求,并将请求转发到目标服务器,目标服务器响应数据后再将数据返回给代理服务器,最终再由代理服务器将数据响应给本地
在代理服务器传递数据给本地浏览器的过程中,两者同源,并不存在跨域行为,这时候浏览器就能正常接收数据
注意:服务器与服务器之间请求数据并不会存在跨域行为,跨域行为是浏览器安全策略限制
关于webpack对前端性能的优化,可以通过文件体积大小入手,其次还可通过分包的形式、减少http请求次数等方式,实现对前端性能的优化
JS代码压缩
CSS代码压缩
Html文件代码压缩
文件大小压缩
图片压缩
Tree Shaking
代码分离
内联 chunk
优化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 的时候,如果信息越详细,打包速度就会越慢。对应属性取值如下所示:
单一职责原则: 一个类只负责一个功能领域中的相应职责,或者可以定义为:就一个类而言,应该只有一个引起它变化的原因。
开放封闭原则: 核心的思想是软件实体(类、模块、函数等)是可扩展的、但不可修改的。也就是说,对扩展是开放的,而对修改是封闭的。
单例模式即一个类只能构造出唯一实例,单例模式的意义在于共享、唯一,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}
工厂模式即对创建对象逻辑的封装,或者可以简单理解为对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');
观察者模式算是前端最常用的设计模式了,观察者模式概念很简单:观察者监听被观察者的变化,被观察者发生改变时,通知所有的观察者。观察者模式被广泛用于监听事件的实现,有关观察者模式的详细应用
//观察者
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方法
装饰器模式,可以理解为对类的一个包装,动态地拓展类的功能,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
适配器模式,将一个接口转换成客户希望的另一个接口,使接口不兼容的那些类可以一起工作。我们在生活中就常常有使用适配器的场景,例如出境旅游插头插座不匹配,这时我们就需要使用转换插头,也就是适配器来帮我们解决问题。
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())
代理模式,为一个对象找一个替代对象,以便对原对象进行访问。即在访问者与目标对象之间加一层代理,通过代理做授权和控制。最常见的例子是经纪人代理明星业务,假设你作为一个投资者,想联系明星打广告,那么你就需要先经过代理经纪人,经纪人对你的资质进行考察,并通知你明星排期,替明星本人过滤不必要的信息。事件代理、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是JavaScript世界的包管理工具,并且是Node.js平台的默认包管理工具。通过npm可以安装、共享、分发代码,管理项目依赖关系。
1.发出npm install命令
2.查询node_ modules 目录之中是否已经存在指定模块
3.若存在,不再重新安装
4.若不存在,npm向registry查询模块压缩地址,下载压缩包,存放在根目录下的.npm里,解压压缩包到当前项目的node_ modules录
npm2所有项目依赖是嵌套关系,
npm3为了改进嵌套过多套路过深的情况,会将所有依赖放在第二层依赖中(所有依赖只嵌套一次 ,彼此平行,也就是平铺的结构)
npm2依赖安装的时候比较简单直接,直接按照包依赖的树形结构下载填充本地目录结构,也就是说每个包都会将该包的依赖组织到当前包所在的node. modules目录中。
npm3则对依赖安装进行了改造,采用"扁平结构"的思路来组织依赖包的目录结构。具体的就是npm install时:按照package.json里依赖的顺序依次解析,遇到新的包就把它放在第一级目录,后面如果遇到一级目录已经存在的包,会先判断版本,如果版本一样则忽略,否则会按照npm2的方式依次挂在依赖包目录下。
1.在npm官网注册npm账号(已有可忽略)
2.新建文件夹,进入该文件夹,运行npm init,生成package.json文件
3.将需要发布的代码放入该文件夹
4.在终端执行npm login 命令登录npm,按照提示填写对应的内容(账号、密码、邮箱)
5.运行 npm publish 发布
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 add . 将工作区所有文件添加到暂存区
git add
将指定文件添加到暂存区
git commit -m ‘备注’ 将暂存区的文件提交到本地仓库
git status 查看仓库当前的状态,文件是否有被修改过
git log 显示从最近到最远的提交日志历史记录
git reflog 查看所以操作记录(包括commit 和reset操作以及已经被删除的commit记录)
git reste -hard HEAD^ 回退到上一个版本(HEAD代码当前版本,HEAD代表上一个版本,HEAD代表上上个版本,HEAD100代表上100个版本)
git reset -hard
回退到指定版本号的版本
git diff 查看工作区域内容和暂存区域内容之间的差异
git diff --cached 比较暂存区和本地仓库之间的差异
git diff HEAD 比较工作区和本地仓库最新版本的区别
git diff
比较工作区和指定版本的之间的差异
git diff
比较两个版本之间的差异
git rm
从版本库删除指定文件
git branch
创建新分支
git checkout
切换到指定分支
git checkout -b
创建并切换分支
git branch 列出所有分支,当前分支前面会标有一个*号
git merge
合并指定分支的内容到当前分支
git merge --no–ff
--no-ff 表示禁用fast forward合并,用普通模式进行分支合并,fast forward合并看不出曾经做过合并,普通模式可以。
git branch -d
删除合并后的分支
git branch -D
强制删除分支
git remote 查看远程仓库的信息
git remote -v 查看远程仓库的详细信息
git push origin
推送分支到远程仓库
git pull 拉取远程仓库的代码
git config --global alias.sta status 配置status的别名为sta
MVC全名是Model View Controller,是一种软件设计模式
其中可分为模型(model)-视图(view)-控制器(controller)三要素,
即:
以下是MVC框架的一些主要问题:
Real DOM | Virtual DOM |
---|---|
1. 更新缓慢。 | 1. 更新更快。 |
2. 可以直接更新 HTML。 | 2. 无法直接更新 HTML。 |
3. 如果元素更新,则创建新DOM。 | 3. 如果元素更新,则更新 JSX 。 |
4. DOM操作代价很高。 | 4. DOM 操作非常简单。 |
5. 消耗的内存较多。 | 5. 很少的内存消耗。 |
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(卸载)
React的主要功能如下:
虚拟 dom 相当于在 js 和真实 dom 中间加了一个缓存,利用 dom diff 算法避免了没有必要的 dom 操作,从而提高性能
Virtual DOM 工作过程有三个简单的步骤
1)每当底层数据发生改变时,整个 UI 都将在 Virtual DOM 描述中重新渲
2)然后计算之前 DOM 表示与新表示的之间的差异
3)完成计算后,将只用实际更改的内容更新 real DOM
React的一些主要优点是:
React的限制如下:
JSX 是J avaScript XML 的简写。是 React 使用的一种文件
它利用 JavaScript 的表现力和类似 HTML 的模板语法。
这使得 HTML 文件非常容易理解。此文件能使应用非常可靠,并能够提高其性能。下面是JSX的一个例子:
render(){
return(
<div>
<h1> Hello World from Edureka!!</h1>
</div>
);
}
Virtual DOM 是一个轻量级的 JavaScript 对象,它最初只是 real DOM 的副本。它是一个节点树,它将元素、它们的属性和内容作为对象及其属性。 React 的渲染函数从 React 组件中创建一个节点树。然后它响应数据模型中的变化来更新该树,该变化是由用户或系统完成的各种动作引起的。
Virtual DOM 工作过程有三个简单的步骤。
2.然后计算之前 DOM 表示与新表示的之间的差异。
3.完成计算后,将只用实际更改的内容更新 real DOM。
浏览器只能处理 JavaScript 对象,而不能读取常规 JavaScript 对象中的 JSX。所以为了使浏览器能够读取 JSX,首先,需要用像 Babel 这样的 JSX 转换器将 JSX 文件转换为 JavaScript 对象,然后再将其传给浏览器。
以下语法是 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>;
}
}
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. 作者 |
react15的架构可以分为两层:
react16的架构可以分为三层:
15和16区别
组件是 React 应用 UI 的构建块。这些组件将整个 UI 分成小的独立并可重用的部分。每个组件彼此独立,而不会影响 UI 的其余部分。
每个React组件强制要求必须有一个 render()。它返回一个 React 元素,是原生 DOM 组件的表示。如果需要渲染多个 HTML 元素,则必须将它们组合在一个封闭标记内,例如 可以通过以下方式将组件嵌入到一个组件中: Props 是 React 中属性的简写。它们是只读组件,必须保持纯,即不可变。它们总是在整个应用中从父组件传递到子组件。子组件永远不能将 prop 送回父组件。这有助于维护单向数据流,通常用于呈现动态生成的数据。 状态是 React 组件的核心,是数据的来源,必须尽可能简单。基本上状态是确定组件呈现和行为的对象。与props 不同,它们是可变的,并创建动态和交互式组件。可以通过 可以用 箭头函数(=>)是用于编写函数表达式的简短语法。这些函数允许正确绑定组件的上下文,因为在 ES6 中默认下不能使用自动绑定。使用高阶函数时,箭头函数非常有用。 有状态组件主要用来定义交互逻辑和业务数据,使用{this.state.xxx}的表达式把业务数据挂载到容器组件的实例上(有状态组件也可以叫做容器组件,无状态组件也可以叫做展示组件),然后传递props到展示组件,展示组件接收到props,把props塞到模板里面 无状态组件主要用来定义模板,接收来自父组件props传递过来的数据,使用{props.xxx}的表达式把props塞到模板里面 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节点等 shouldComponentUpdate 这个方法用来判断是否需要调用render方法重新绘制dom 因为DOM的描绘非常消耗性能,如果我们能在shouldComponentUpdate 方法中能够写出更优化的 dom diff 算法,可以极大的提高性能 虚拟 dom 相当于在 js 和真实 dom 中间加了一个缓存,利用 dom diff 算法避免了没有必要的 dom 操作,从而提高性能 Virtual DOM 工作过程有三个简单的步骤 1)每当底层数据发生改变时,整个 UI 都将在 Virtual DOM 描述中重新渲 2)然后计算之前 DOM 表示与新表示的之间的差异 3)完成计算后,将只用实际更改的内容更新 real DOM 当调用setState后,新的 state 并没有马上生效渲染组件,而是,先看执行流中有没有在批量更新中 如果有,push到存入到dirtyeComponent中,如果没有,则遍历dirty中的component,调用updateComponent,进行state或props的更新 然后更新UI,react进行diff运算,与上一次的虚拟DOM相比较,进行有效的渲染更新组件 diff(翻译差异):计算一棵树形结构转换成另一棵树形结构的最少操作 1)把树形结构按照层级分解,只比较同级元素 2)给列表结构的每个单元添加唯一的 key 属性,方便比较 3)React 只会匹配相同 class 的 component(这里面的 class 指的是组件的名字) 4)合并操作,调用 component 的 setState 方法的时候, React 将其标记为 dirty.到每一个事件循环结束, React 检查所有标记 dirty 的 component 重新绘制 5)选择性子树渲染。开发人员可以重写 shouldComponentUpdate 提高 diff 的性能 1)当页面一打开,就会调用render构建一棵DOM树 2)当数据发生变化( state | props )时,就会再渲染出一棵DOM树 3)此时,进行diff运算,两棵DOM树进行差异化对比,找到更新的地方进行批量改动 状态是 React 组件的核心,是数据的来源,必须尽可能简单。基本上状态是确定组件呈现和行为的对象。与props 不同,它们是可变的,并创建动态和交互式组件。可以通过 this.state() 访问它们 State 是一种数据结构,用于组件挂载时所需数据的默认值。State 可能会随着时间的推移而发生突变,但多数时候是作为用户事件行为的结果 Props(properties 的简写)则是组件的配置。props 由父组件传递给子组件,并且就子组件而言,props 是不可变的 组件不能改变自身的 props,但是可以把其子组件的 props 放在一起(统一管理) 1)setState 只在合成事件(react为了解决跨平台,兼容性问题,自己封装了一套事件机制,代理了原生的事件,像在jsx中常见的onClick、onChange这些都是合成事件)和钩子函数(生命周期)中是“异步”的,在原生事件和 setTimeout 中都是同步的 2)setState的“异步”并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的,只是合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形式了所谓的“异步”,当然可以通过第二个参数 setState(partialState, callback) 中的callback拿到更新后的结果 3)setState 的批量更新优化也是建立在“异步”(合成事件、钩子函数)之上的,在原生事件和setTimeout 中不会批量更新,在“异步”中如果对同一个值进行多次 setState , setState 的批量更新策略会对其进行覆盖,取最后一次的执行,如果是同时 setState 多个不同的值,在更新时会对其进行合并批量更新 componentDidMount和componentDidUpdate constructor、componentWillMount中setState会发生错误:setState只能在mounted或mounting组件中执行 componentWillUpdate中setState会导致死循环 在 super() 被调用之前,子类是不能使用 this 的,在 ES2015 中,子类必须在 constructor 中调用 super()。传递 props 给 super() 的原因则是便于(在子类中)能在 constructor 访问 this.props componentWillUpdate(){}之后 render componentDidupdate(){}之前 每个React组件强制要求必须有一个 render()。它返回一个 React 元素,是原生 DOM 组件的表示 如果需要渲染多个 HTML 元素,则必须将它们组合在一个封闭标记内,例如 、
、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?
5. React中的状态是什么?它是如何使用的?
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 中的箭头函数是什么?怎么用?
//General way
render() {
return(
<MyInput onChange = {this.handleChange.bind(this) } />
);
}
//With Arrow Function
render() {
return(
<MyInput onChange = { (e)=>this.handleOnChange(e) } />
);
}
9. 区分有状态和无状态组件。
有状态组件
无状态组件
1. 在内存中存储有关组件状态变化的信息
1. 计算组件的内部的状态
2. 有权改变状态
2. 无权改变状态
3. 包含过去、现在和未来可能的状态变化情况
3. 不包含过去,现在和未来可能发生的状态变化情况
4. 接受无状态组件状态变化要求的通知,然后将 props 发送给他们。
4.从有状态组件接收 props 并将其视为回调函数。
10. React组件生命周期的阶段是什么?
11. 详细解释 React 组件的生命周期方法。
12、shouldComponentUpdate是做什么的,(react性能优化是哪个周期函数?)
13、为什么虚拟 DOM 会提高性能? 说下他的原理
14、调用setState之后发生了什么?
15、react diff 原理
16、react渲染机制
17、React中的状态是什么?它是如何使用的?
18、组件的状态(state)和属性(props)之间有何不同
19、setState 何时同步何时异步?
20、在哪些生命周期中可以修改组件的state
21、在构造函数中调用 super(props) 的目的是什么
22、React的生命周期函数中,当props改变时 会引发的后续变化,rander()函数什么时候执行
23、解释 React 中 render() 的目的