这个来自以前作的培训,删减了一些业务相关的,参考了不少资料( 参考资料列表),谢谢前辈们,么么哒 😘
前端有三个基本构成:结构层HTML、表现层CSS和行为层Javascript。
他们分别成熟的版本是HTML五、CSS3和ECMAScript 6+。
这里咱们主要了解现代前端三层结构的演进历程以及如何在三层结构的基础之上进行高效开发。javascript
HTML(超文本标记语言——HyperText Markup Language)是构成 Web 世界的基石。css
<!DOCTYPE>
声明不是 HTML 标签;它是指示 web 浏览器关于页面使用哪一个 HTML 版本进行编写的指令。若是 DOCTYPE 不存在或者格式不正确,则会致使文档以兼容模式呈现,这时浏览器会使用较低的浏览器标准模式来解析整个HTML文本。html
HTML 5:前端
<!DOCTYPE html>复制代码
HTML5中的doctype是不区分大小写的。java
HTML 4.01 Strict:git
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">复制代码
HTML语义化能让页面内容更具结构化且更加清晰,便于浏览器和搜索引擎进行解析,所以要尽可能使用带有语义化结构标签。程序员
通常状况下,具备良好Web语义化的页面结构在没有样式文件的状况下也是可以阅读的,例如列表会以列表的样式展示,标题文字会加粗,而不是所有内容都以无层次的文本内容形式呈现。es6
CSS规范规定,每一个标签都是有 display 属性的。因此根据标签元素的display属性特色,能够将HTML标签分为如下几类:github
<a>、 <b>、<span>、<img>、<input>、<button>、<select>、<strong>
等标签元素,其默认宽度是由内容宽度决定的。<div>、<ul>、<ol>、<li>、<dl>、<dt>、<dd>、<h1>、<h2>、<h3>、<h4>、 <h5>、 <h6>、<p>、<table>
等标签元素,其默认宽度为父元素的100%。<br>、<hr>、 <link>、<meta>、<area>、 <base>、<col> 、<command>、<embed>、 <keygen>、 <param>、<source>、<track>
等不能显示内容,甚至不会在页面中出现,可是对页面的解析有着其余重要做用的元素。有时候使用语义化的标签可能会形成一些兼容性问题或者性能问题,好比页面中使用 <table> 这个语义化标签是会致使内容渲染较慢,由于<table>里面的内容渲染是等表格内容所有解析完生成渲染树后一次性渲染到页面上的,若是表格内容较多,就可能产生渲染过程较慢的问题,所以咱们有时可能须要经过其余的方式来模拟<table>元素,例如使用无序列表来模拟表格。web
咱们在书写标签的时候,还要注意加上必要的属性,好比:<img> 标签,须要加上 alt 和 title 属性(注意alt属性和title 属性是有区别的,alt 属性通常表示图片加载失败时提示的文字内容,title 属性则指鼠标放到元素上时显示的提示文字)。加上这些属性有助于搜索引擎优化。
看下面的代码:jsfiddle.net/humtd6v1/
不知道你有没有想过,为何这么简单的标签订义能生成这样两个较复杂的选择输入界面呢?
Shadow DOM是HTML的一个规范,它容许浏览器开发者封装本身的HTML标签、CSS样式和特定的JavaScript 代码,同时也可让开发人员建立相似<video>这样的自定义一级标签,建立这些新标签内容和相关的API被称为Web Component。
Shadow root是Shadow DOM的根节点,它和它的后代元素,都将对用户隐藏,可是它们是实际存在的;Shadow tree为这个Shadow DOM包含的节点子树结构,例如<div> 和<input>等; Shadow host则称为Shadow DOM的容器元素,也就是宿主元素,即上面的标签<input>。
新版本的浏览器提供了建立Shadow DOM的API,指定一个元素,而后可使用document.createShadowRoot() 方法建立一个Shadow root,在Shadow root上能够任意经过DOM的基本操做API添加任意的Shadow tree,同时指定样式和处理的逻辑,并将本身的API暴露出来。完成建立后须要经过document.registerElement()在文档中注册元素,这样Shadow DOM的建立就完成了。
使用 Shadow DOM 有什么好处呢?
现行的组件都是开放式的,即最终生成的 HTML DOM 结构难以与组件外部的 DOM 进行有效结构区分,样式容易互相混淆。Shadow-dom 的 封装隐藏性为咱们提供了解决这些问题的方法。在 Web 组件化的规范中也能够看到 Shadow-dom 的身影,使用具备良好密封性的 Shadow-dom 开发下一代 Web 组件将会是一种趋势。
CSS (Cascading Style Sheets)是随着前端表现分离的提出而产生的,由于最先网页内容的样式都是经过center、strike等标签或fontColor等属性内容来体现的,而CSS提出使用样式描述语言来表达页面内容,而不是用HTML的标签来表达。
继CSS1后,W3C在1998年发布了CSS2规范,CSS2的出现主要是为了解决早期网页开发过程当中排版时表现分离的问题,后来随着页面表现的内容愈来愈复杂,浏览器平台厂商继续推进W3C组织对CSS规范进行更多的改进和完善,添加了例如 border-radius、 text-shadow、ransform、animation等更灵活的表现层特性,逐渐造成了一套全新的W3C标准,即CSS3。CSS3能够认为是在CSS2规范的基础上进行补充和加强造成的,让CSS体系更能适应现代浏览器的须要,拥有更强的表现能力,尤为对于移动端浏览器。
目前CSS4的草案也在制定中,CSS4 中更强大的选择器、伪类和伪元素特性已经被曝光出来,但具体发布时间仍不肯定。
从形式上来讲,CSS3 标准自身已经不存在了。每一个模块都被独立的标准化。
有些 CSS 模块已经十分稳定,其状态为 CSSWG 规定的三个推荐品级之一:Candidate Recommendation(候选推荐), Proposed Recommendation(建议推荐)或 Recommendation(推荐)。代表这些模块已经十分稳定,使用时也没必要添加前缀。处于改善阶段(refining phase)的规范已基本稳定。虽然还有可能被修改,但不会和当前的实现产生冲突。处于修正阶段的模块没处于改善阶段的模块稳定。它们的语法通常还须要详细审查,可能还会有些大变化,还有可能不兼容以前的规范。
下面列出一些经常使用的模块:
增长 opacity 属性,还有 hsl(), hsla(), rgba() 和 rgb() 函数来建立 <color> 值。
增长:
将以前的媒体类型 ( print, screen,……) 扩充为完整的语言, 容许使用相似 only screen 和 (color) 来实现 设备媒体能力查询功能。
增长:
增长:
为 CSS display 属性增长了 flexbox layout(伸缩盒布局) 及多个新 CSS 属性来控制它:flex,flex-align,flex-direction,flex-flow,flex-item-align,flex-line-pack,flex-order,flex-pack 和 flex-wrap。
增长:
经过增长 CSS transition,transition-delay,transition-duration, transition-property,和 transition-timing-function 属性来支持定义两个属性值间的 transitions effects(过渡效果)。
容许定义动画效果, 借助于新增的 CSS animation, animation-delay, animation-direction, animation-duration, animation-fill-mode, animation-iteration-count, animation-name, animation-play-state, 和 animation-timing-function 属性, 以及 @keyframes @ 规则。
增长:
目前访问Web网站应用时,用户使用的浏览器版本较多,因为浏览器间内核实现的差别性,不一样浏览器可能对同一元素标签样式的默认设置是不一样的,若是不对CSS样式进行统一化处理,可能会出现同一个网页在不一样浏览器下打开时显示不一样或样式不一致的问题。要处理这一问题,目前主要有三种实现思路:reset、normalize 和neat。
reset的思路是将不一样浏览器中标签元素的默认样式所有清除,消除不一样浏览器下默认样式的差别性。典型的reset默认样式的代码以下:
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}复制代码
这种方式能够将不一样浏览器上大多数标签的内外边距清除。消除默认样式后从新定义元素样式时,经常须要针对具体的元素标签重写样式来覆盖reset中的默认规则,因此这种状况下咱们经常须要去重写样式来对元素添加各自的样式规则。
Normalize.css主要是指:necolas.github.io/normalize.c… 这个库。它是一种CSS reset的替代方案。相比reset,normalize.css 有以下特色:
neat能够认为是对上面两种实现的综合,由于咱们一般不能保证网站界面上的全部元素的内外边距都是肯定的,又不想将全部样式都清除后再进行覆盖重写。neat至关于一个折中的方案,任何前端项目均可以根据本身的标准写出本身的neat来。
一个neat的实现:thx.github.io/cube/doc/ne…
现阶段国内大部分团队使用的是reset,国外大部分使用normalize,我我的偏向使用normalize。
CSS 自诞生以来,基本语法和核心机制一直没有本质上的变化,它的发展几乎全是表现力层面上的提高。现在网站的复杂度已经不可同日而语,原生 CSS 已经让开发者力不从心。
当一门语言的能力不足而用户的运行环境又不支持其它选择的时候,这门语言就会沦为 “编译目标” 语言。开发者将选择另外一门更高级的语言来进行开发,而后编译到底层语言以便实际运行。因而,CSS 预处理器应运而生。
简单来讲,CSS 预处理器为咱们带来了几项重要的能力:
Sass 和 Less 是两种 CSS 预处理器,扩展了 CSS 语法,目的都是为了让 CSS 更容易维护。
Sass 有两种语法,最经常使用是的 SCSS(Sassy CSS),是 CSS3 的超集。另外一个语法是 SASS(老的,缩进语法,类 Python)。
两个处理器都很强大,相比较 Sass 功能更多,Less 更好上手。对于CSS复杂的项目,建议用 Sass。
PostCSS 是一个用 JavaScript 工具和插件转换 CSS 代码的工具。
PostCSS 拥有很是多的插件,诸如自动为CSS添加浏览器前缀的插件autoprefixer、当前移动端最经常使用的px转rem插件px2rem,还有支持还没有成为CSS标准但特定可用的插件cssnext,让CSS兼容旧版IE的CSSGrace,还有不少不少。著名的Bootstrap在下一个版本Bootstrap 5也将使用PostCSS做为样式的基础。
如今更多的使用 PostCSS 的方式是对现有预处理器的补充,好比先经过Sass编译,再加上autoprefixer自动补齐浏览器前缀。
前端实现动画的方式有不少种。好比一个方块元素从左到右移动:
JavaScript直接实现动画的方式在前端早期使用较多,其主要思想是经过JavaScript 的setInterval方法或setTimeout方法的回调函数来持续调用改变某个元素的CSS样式以达到元素样式持续变化的结果,例如:jsfiddle.net/cm2vdbzt/1/
核心代码:
let timer = setInterval(() => {
if (left < window.innerWidth - 200) {
element.style.marginLeft = left + 'px';
left++;
} else {
clearInterval(timer);
}
}, 16);复制代码
JavaScript直接实现动画也就是不断执行setInterval 的回调改变元素的marginLeft样式属性达动画的效果,例如jQuery 的animate()方法就属于这种实现方式。不过要注意的是,经过JavaScript实现动画一般会致使页面频繁性重排重绘,很消耗性能,若是是稍微复杂的动画,在性能较差的浏览器上,就会明显感受到卡顿,因此咱们尽可能避免使用它。
咱们设置setInterval 的时间间隔是16ms,为何呢?通常认为人眼能辨识的流畅动画为每秒60帧,这里16ms比1000ms/60帧略小一点,因此这种状况下能够认为动画是流畅的。在不少移动端动画性能优化时,通常使用16ms来进行节流处理连续触发的浏览器事件,例如对touchmove、 scroll 事件进行节流等。咱们经过这种方式来减小持续性事件的触发频率,能够大大提高动画的流畅性。
SVG又称可伸缩矢量图形,原生支持一些动画效果,经过组合能够生成较复杂的动画,并且不须要使用JavaScript 参与控制。SVG动画由SVG元素内部的元素属性控制,一般经过 <set>、 <animate>、<animateColor>、<animateTransform>、<animateMotion> 这几个元素来实现。<set>能够用于控制动画延时,例如一段时间后设置SVG中元素的位置,就可使用<set>在动画中设置延时;<animate>能够对属性的连续改变进行控制,例如实现左右移动动画效果等;<animateColor> 表示颜色的变化,不过如今用<animate>就能够控制了,因此用的基本很少;<animateTransform>能够控制如缩放、旋转等几何变化;<animateMotion>则用于控制SVG内元素的移动路径。
<svg id="box" width="800" height="400" version="1.1" xmIns="http://www.w3.org/2000/svg">
<rect width="100" height="100" style="fill :rgb(255,0,0) ;">
<set attributeName="x" attributeType="XML" to="100" begin="4s" />
<animate attributeName="x" attributeType="XML" begin="0s" dur="4s" from="O" to="300" />
<animate attributeName="y" attributeType="XML" begin="Os" dur="4s" from="O" to="O" />
<animateTransform attributeName="transform" begin="Os" dur="4s" type="scale"
from="1" to="2" repeatCount="1" />
<animateMotion path="M10,80 q100, 120 120,20 q140,-50 160,0" begin="Os" dur="4s" repeatCount="1" />
</rect>
</svg>复制代码
须要注意的是,SVG 内部元素的动画只能在元素内进行,超出<svg>标签元素,就能够认为是超出了动画边界。经过理解上面的代码能够看出,在网页中<svg>元素内部定义了一个边长100像素的正方形,而且在4秒时间延时后开始向右移动,通过4秒时间向右移动300像素。相对于JavaScript 直接控制动画的方式,使用SVG的一个很大优点是含有较丰富的动画功能,原生能够绘制各类图形、滤镜和动画,绘制的动画为矢量图,并且实现动画的原生元素依然是可被JavaScript调用的。然而另外一方面,元素较多且复杂的动画使用SVG渲染会比较慢,并且SVG格式的动画绘制方式必须让内容嵌入到HTML中使用。之前这种动画实现的场景相对比较多,但随着CSS3的出现,这种动画实现方式相对使用得愈来愈少了。
CSS3出现后,增长了两种CSS3实现动画的方式:transition 和 animation。
<style>
* {
margin: 0;
padding: 0;
}
div {
width: 200px;
height: 200px;
background-color: red;
margin-left: 0;
transition: all 3s ease-in-out 0s;
}
.right {
margin-left: 400px;
background-color: blue;
}
</style>
</head>
<body>
<div id="box"></div>
<script>
let timer = setTimeout(function() {
let element = document.getElementById('box');
element.setAttribute('class', 'right');
}, 500);
</script>复制代码
咱们通常经过改变元素的起始状态,让元素的属性自动进行平滑过渡产生动画,固然也能够设置元素的任意属性进行过渡变化。transition 应用于处理元素属性改变时的过渡动画,而不能应用于处理元素独立动画的状况,不然就须要不断改变元素的属性值来持续触发动画过程了。
在移动端开发中,直接使用transition 动画会让页面变慢甚至变卡顿,因此咱们一般经过添加 transform: translate3D(0, 0, 0)
或 transform: translateZ(0)
来开启移动端动画的GPU加速,让动画过程更加流畅。
CSS3 animation的动画则能够认为是真正意义上页面内容的CSS3动画,经过对关键帧和循环次数的控制,页面标签元素会根据设定好的样式改变进行平滑过渡,并且关键帧状态的控制通常是经过百分比来控制的,这样咱们就能够在这个过程当中实现不少动画的动做了。定义动画的keyframes中from值和0%的意义是相同的,表示动画的开始关键帧。to和100%的意义相同,表示动画的结束关键帧。
<style>
div {
width: 200px;
height: 200px;
background-color: red;
margin-left: 0;
animation: move 4s infinite;
}
@keyframes move {
from {
margin-left: 0;
}
50% {
margin-left: 400px;
}
to {
margin-left: 0;
}
}
</style>复制代码
CSS3实现动画的最大优点是脱离JavaScript 的控制,并且能用到硬件加速,能够用来实现较复杂的动画效果。
<canvas>做为HTML5的新增元素,也能够借助Web API实现页面动画。Canvas 动画的实现思路和SVG的思路有点相似,都是借助元素标签来达到页面动画的效果,都须要借助对应的一套API来实现,不过SVG的API能够认为主要是经过SVG元素内部的配置规则来实现的,而Canvas则是经过JavaScript API来实现的。须要注意的是,和SVG动画同样,Canvas动画的进行只能在<canvas>元素内部,超出<canvas>元素边界将不被显示。
<canvas id="canvas" width="700" height="550">
浏览器不支持canvas
</canvas>
<script>
let canvas = document.getElementById('canvas');
let ctx = canvas.getContext('2d');
let left = 0;
let timer = setInterval(function() {
// 不断清空画布
ctx.clearRect(0, 0, 700, 550);
ctx.beginPath();
//将颜色块填充为红色
ctx.fillStyle = '#f00';
//持续在新的位置上绘制矩形
ctx.fillRect(left, 0, 100, 100);
ctx.stroke();
if (left > 700)
clearInterval(timer);
left += 1;
}, 16);
</script>复制代码
元素DOM对象经过调用getContext ()能够获取元素的绘制对象,而后经过clearRect不断清空画布并在新的位置上使用fillStyle绘制新矩形内容来实现页面动画效果。使用Canvas的主要优点是能够应对页面中多个动画元素渲染较慢的状况,彻底经过JavaScript 来渲染控制动画的执行,这就避免了DOM性能较慢的问题,可用于实现较复杂的动画。
requestAnimationFrame是前端表现层实现动画的另外一种API实现,它的原理和setTimeout及setInterval 相似,都是经过JavaScript 持续循环的方法调用来触发动画动做的,可是requestAnimationFrame是浏览器针对动画专门优化而造成的API,在实现动画方面性能比setTimeout及setInterval要好,能够将动画每一步的操做方法传入到requestAnimationFrame中,在每一次执行完后进行异步回调来连续触发动画效果。
<script>
//获取requestAnimationFrame API对象
window.requestAnimationFrame = window.requestAnimationFrame;
let element = document.getElementById('box');
let left = 0;
//自动执行持续性回调
requestAnimationFrame(step);
// 持续改变元素位置
function step() {
if (left < window.innerWidth - 200)
left += 1;
element.style.marginLeft = left + 'px';
requestAnimationFrame(step);
}
</script>复制代码
能够看出,和setInterval方法相似,requestAnimationFrame 只是将回调的方法传入到自身的参数中处理执行,而不是经过setInterval 调用,其余的实现过程则基本同样。
考虑到兼容性的问题,在项目实践中,通常咱们在桌面浏览器端仍然推荐使用JavaScript直接实现动画的方式或SVG动画的实现方式,移动端则能够考虑使用CSS3 transition、CSS3 animation、canvas 或requestAnimationFrame。
一般认为,响应式设计是指根据不一样设备浏览器尺寸或分辨率来展现不一样页面结构层、行为层、表现层内容的设计方式。
谈到响应式设计网站,目前比较主流的实现方法有两种:
两种方式各有利弊:
第一种方案:
Pros:能够根据不一样的设备加载相应的网页资源,针对移动端的浏览器能够请求加载更加优化后的执行脚本或更小的静态资源。移动端和PC端页面差别比较大也无所谓。
Cons:须要开发并维护至少两个站点;多了一次跳转。
第二种方案:
Pros:桌面浏览器和移动端浏览器使用同一个站点域名来加载内容,只须要开发维护一个站点就能够了。适用于访问量较小、性能要求不高或PC端和移动端差异不大的应用场景。
Cons:移动端可能会加载到冗余或体积较大的资源;只实现了内容布局显示的适应,可是要作更多差别性的功能比较难。
响应式页面设计一直是一个很难完美解决的问题,由于多多少少都存在这些问题:
经过合理的开发方式和网站访问架构设计,再加上适当的取舍,能够解决上述的大部分问题。
结构层响应式设计能够理解成HTML内容的自适应渲染实现方式,即根据不一样的设备浏览器渲染不一样的页面内容结构,而不是直接进行页面跳转。这里页面中结构层渲染的方式可能不一样,包括前端渲染数据和后端渲染数据,这样主要就有两种不一样的设计思路:一是页面内容是在前端渲染,二是页面内容在后端渲染。
如今不少网站使用了先后分离,前端渲染页面,为了保证咱们使用移动端打开的页面加载到相对最优的页面资源内容,咱们可使用异步的方式来加载CSS文件和JS文件,这样就能够作到根据移动端页面和桌面端页面加载到不一样的资源内容了。
除了前端数据渲染的方式,目前还有一部分网站的内容生成使用了后端渲染的实现方式。这种状况的处理方式其实能够作到更优化,只要尽量将桌面端和移动的业务层模板分开维护就能够了。在模板选择判断时还是能够经过userAgent甚至URL参数来进行的。
响应式布局是根据浏览器宽度、分辨率、横屏、竖屏等状况来自动改变页面元素展现的一种布局方式,通常可使用栅格方式来实现,实现思路有两种:一种是桌面端浏览器优先,扩展到移动端浏览器适配;另外一种则是以移动端浏览器优先,扩展到桌面端浏览器适配。因为移动端的网络和计算资源相对较少,因此通常比较推荐从移动端扩展到桌面端的方式进行适配,这样就避免了在移动端加载冗余的桌面端CSS样式内容。
屏幕适配布局则是主要针对移动端的,因为目前移动端设备屏幕大小各不相同,屏幕适配布局是为了实现网页内容根据移动端设备屏幕大小等比例缩放所提出的一种布局计算方式。
表现层的响应式,主要是经过响应式布局和屏幕适配布局,来完成网页针对不一样设备的适配。通常包含以下技术点和设计原则:
元视口代码会指示浏览器如何对网页尺寸和缩放比例进行控制。
<meta name="viewport" content="width=device-width, initial-scale=1.0">复制代码
为了提供最佳体验,移动浏览器会以桌面设备的屏幕宽度(一般大约为 980 像素,但不一样设备可能会有所不一样)来呈现网页,而后再增长字体大小并将内容调整为适合屏幕的大小,从而改善内容的呈现效果。对用户来讲,这就意味着,字体大小可能会不一致,他们必须点按两次或张合手指进行缩放,才能查看内容并与之互动。
使用元视口值 width=device-width 指示网页与屏幕宽度(以设备无关像素为单位)进行匹配。这样一来,网页即可以重排内容,使之适合不一样的屏幕大小。
媒体查询是实现响应式的最主要依据。经过媒体查询语法,咱们能够建立可根据设备特色应用的规则。
@media (query) {
/* CSS Rules used when query matches */
}复制代码
尽管咱们能够查询多个不一样的项目,但自适应网页设计最常使用的项目为:min-width、max-width、min-height 和 max-height。好比:
<link rel="stylesheet" media="(max-width: 640px)" href="max-640px.css">
<link rel="stylesheet" media="(min-width: 640px)" href="min-640px.css">
<link rel="stylesheet" media="(orientation: portrait)" href="portrait.css">
<link rel="stylesheet" media="(orientation: landscape)" href="landscape.css">
<style>
@media (min-width: 500px) and (max-width: 600px) {
h1 {
color: fuchsia;
}
.desc:after {
content:" In fact, it's between 500px and 600px wide.";
}
}
</style>复制代码
与固定宽度的版式相比,自适应设计的主要概念基础是流畅性和比例可调节性。使用相对衡量单位有助于简化版式,并防止无心间建立对视口来讲过大的组件。
经常使用的相对单位有:
因为em计算比较复杂,有不少不肯定性,如今基本上不怎么使用了。
以从小屏幕开始、不断扩展的方式选择主要断点,尽可能根据内容建立断点,而不要根据具体设备、产品或品牌来建立。
通常来讲,常选取的端点能够参考Bootstrap:
栅格化布局(Grid Layout)一般会把屏幕宽度分红多个固定的栅格,好比12个,它有助于内容的呈现和实现响应式布局,好比使用Bootstrap框架,栅格就会根据不一样设备自适应排列。
根据统计,目前主要网站60%以上的流量数据来自图片,因此如何在保证用户访问网页体验不下降的前提下尽量地下降网站图片的输出流量具备很重要的意义。
一般在咱们手机访问网页时,请求的图片可能仍是加载了与桌面端浏览器相同的大图,件体积大,消耗流量多,请求延时长。媒体响应式要解决的一个关键问题就是让浏览器上的展现媒体内容尺寸根据屏幕宽度或屏幕分辨率进行自适应调节。咱们须要根据浏览器设备屏幕宽度和屏幕的分辨率来加载不一样大小尺寸的图片,避免在移动端上加载体积过大的资源。通常有以下方式来处理图片:
由于 CSS 容许内容溢出其容器,所以通常须要使用 max-width: 100% 来保证图像及其余内容不会溢出。
img, embed, object, video {
max-width: 100%;
}复制代码
<img src="lighthouse-200.jpg" sizes="50vw"
srcset="lighthouse-100.jpg 100w, lighthouse-200.jpg 200w, lighthouse-400.jpg 400w, lighthouse-800.jpg 800w, lighthouse-1000.jpg 1000w, lighthouse-1400.jpg 1400w, lighthouse-1800.jpg 1800w" alt="a lighthouse">复制代码
在不支持 srcset 的浏览器上,浏览器只需使用 src 属性指定的默认图像文件。
picture 元素定义了一个声明性解决办法,可根据设备大小、设备分辨率、屏幕方向等不一样特性来提供一个图像的多个版本。
<picture>
<source media="(min-width: 800px)" srcset="head.jpg, head-2x.jpg 2x">
<source media="(min-width: 450px)" srcset="head-small.jpg, head-small-2x.jpg 2x">
<img src="head-fb.jpg" srcset="head-fb-2x.jpg 2x" alt="a head carved out of wood">
</picture>复制代码
.example {
height: 400px;
background-image: url(small.png);
background-repeat: no-repeat;
background-size: contain;
background-position-x: center;
}
@media (min-width: 500px) {
body {
background-image: url(body.png);
}
.example {
background-image: url(large.png);
}
}复制代码
媒体查询不只影响页面布局,还能够用于有条件地加载图像。
媒体查询可根据设备像素比建立规则,能够针对 2x 和 1x 显示屏分别指定不一样的图像。
.sample {
width: 128px;
height: 128px;
background-image: url(icon1x.png);
}
@media (min-resolution: 2dppx), /* Standard syntax */
(-webkit-min-device-pixel-ratio: 2) /* Safari & Android Browser */
{
.sample {
background-size: contain;
background-image: url(icon2x.png);
}
}复制代码
尽量使用 SVG 图标,某些状况下,可使用 unicode 字符。好比:
You're a super ★复制代码
You're a super ★
选择正确的图像格式:
尽可能将图片放在CDN。
在能够接受的状况下,尽量的压缩图片到最小。tinypng.com/
使用 image sprites,将许多图像合并到一个“精灵表”图像中。 而后,经过指定元素背景图像(精灵表)以及指定用于显示正确部分的位移,可使用各个图像。
在主要内容加载和渲染完成以后加载图像。或者内容可见后才加载。
若是能够,不要使用图像,而是使用浏览器的原生功能实现相同或相似的效果。好比CSS效果:
<style>
div#noImage {
color: white;
border-radius: 5px;
box-shadow: 5px 5px 4px 0 rgba(9,130,154,0.2);
background: linear-gradient(rgba(9, 130, 154, 1), rgba(9, 130, 154, 0.5));
}
</style>复制代码
目前CSS的成熟标准版本是CSS3, 并且在移动端使用较多。CSS4的规范仍在制定中,CSS4的处境将会比较尴尬,相似于如今的ES6,发布后不能兼容仍须要转译。
就目前来看,CSS4新添加的特性优点并不明显(最主要的实用的是一些新的选择器,好比 not),不少特性暂时来讲实用性不强,并且不如现有的预处理语法。因此只能看它后面的发展状况了。
JavaScript 由于互联网而生,紧随着浏览器的出现而问世。
1994年12月,Navigator发布了1.0版,市场份额一举超过90%。Netscape 公司很快发现,Navigator浏览器须要一种能够嵌入网页的脚本语言,用来控制浏览器行为。好比,若是用户忘记填写“用户名”,就点了“发送”按钮,到服务器再发现这一点就有点太晚了,最好能在用户发出数据以前,就告诉用户“请填写用户名”。这就须要在网页中嵌入小程序,让浏览器检查每一栏是否都填写了。
1995年,Netscape公司雇佣了程序员Brendan Eich开发这种网页脚本语言。Brendan Eich只用了10天,就设计完成了这种语言的初版。
1996年8月,微软模仿JavaScript开发了一种相近的语言,取名为JScript,Netscape公司面临丧失浏览器脚本语言的主导权的局面。Netscape公司决定将JavaScript提交给国际标准化组织ECMA(European Computer Manufacturers Association),但愿JavaScript可以成为国际标准,以此抵抗微软。
1997年7月,ECMA组织发布262号标准文件(ECMA-262)的初版,规定了浏览器脚本语言的标准,并将这种语言称为ECMAScript。这个版本就是ECMAScript 1.0版。所以,ECMAScript和JavaScript的关系是,前者是后者的规格,后者是前者的一种实现。在平常场合,这两个词是能够互换的。
1999年12月,ECMAScript 3.0版发布,成为JavaScript的通行标准,获得了普遍支持。
2009年12月,ECMAScript 5.0版正式发布(ECMAScript 4.0争议太大被废弃,ECMAScript 3.1更名为ECMAScript 5)。
2011年6月,ECMAscript 5.1版发布,而且成为ISO国际标准(ISO/IEC 16262:2011)。到了2012年末,全部主要浏览器都支持ECMAScript 5.1版的所有功能。
2015年6月,ECMAScript 6正式发布,而且改名为“ECMAScript 2015”。
2017年6月,ECMAScript 2017 标准发布,正式引入了 async 函数。
2017年11月,全部主流浏览器所有支持 WebAssembly,这意味着任何语言均可以编译成 JavaScript,在浏览器运行。
<div data-type="alignment" data-value="center" style="text-align:center">
<div data-type="p">
<a target="_blank" rel="noopener noreferrer nofollow" href="http://es6katas.org/" class="bi-link">http://es6katas.org/</a>复制代码
</div>
</div>
ES6 主要新增了以下特性:
以前JS的做用域很是的奇怪,只有全局做用域和函数做用域,没有块级做用域。好比:var 命令会发生”变量提高“现象,即变量能够在声明以前使用,值为undefined。var 还能够重复声明。
ES6 的let实际上为 JavaScript 新增了块级做用域。
{
let a = 10;
var b = 1;
}
a // ReferenceError: a is not defined.
b // 1复制代码
ES5 只有两种声明变量的方法:var命令和function命令。ES6 除了添加let和const命令,还有另外两种声明变量的方法:import命令和class命令。因此,ES6 一共有 6 种声明变量的方法。
字符串模板设计主要来自其余语言和前端模板的设计思想,即当有字符串内容和变量混合链接时,可使用字符串模板进行更高效的代码书写并保持代码的格式和整洁性。若是没有字符串模板,咱们依然须要像之前同样借助“字符串+操做符”拼接或数组join()方法来链接多个字符串变量。
// ES5
$('#result').append(
'There are <b>' + basket.count + '</b> ' +
'items in your basket, ' +
'<em>' + basket.onSale +
'</em> are on sale!'
);
// ES6
$('#result').append(`
There are <b>${basket.count}</b> items
in your basket, <em>${basket.onSale}</em>
are on sale!
`);复制代码
ES6 容许按照必定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)。
let a = 1;
let b = 2;
let c = 3;
let [a, b, c] = [1, 2, 3]; // ES6
let [head, ...tail] = [1, 2, 3, 4];
head // 1
tail // [2, 3, 4]
let { bar, foo } = { foo: "aaa", bar: "bbb" };
foo // "aaa"
bar // "bbb"复制代码
一道前端面试题:怎样用一行代码把数组中的元素去重?
let newArr = [...new Set(sourceArr)];复制代码
以前JS的Array大概有以下这些方法:
ES6又增长了不少实用的方法:
Array.from('hello'); // ['h', 'e', 'l', 'l', 'o']
Array.of(3, 11, 8); // [3,11,8]
[1, 4, -5, 10].find((n) => n < 0); // -5
[1, 5, 10, 15].findIndex((value) => value > 9); // 2
['a', 'b', 'c'].fill(7); // [7, 7, 7]
for (let index of ['a', 'b'].keys()) {
console.log(index);
}
// 0
// 1
[1, 2, 3].includes(2); // true
[1, 2, [3, 4]].flat(); // [1, 2, 3, 4]
[2, 3, 4].flatMap((x) => [x, x * 2]); // [2, 4, 3, 6, 4, 8]
复制代码
// 参数默认值
function log(x, y = 'World') {
console.log(x, y);
}
// 箭头函数
var sum = (num1, num2) => num1 + num2;
// 双冒号运算符
foo::bar;
// 等同于
bar.bind(foo);复制代码
箭头函数有几个使用注意点。
函数绑定运算符是并排的两个冒号(::),双冒号左边是一个对象,右边是一个函数。该运算符会自动将左边的对象,做为上下文环境(即this对象),绑定到右边的函数上面。
// 属性的简洁表示法
function f(x, y) {
return { x, y };
}
// 等同于
function f(x, y) {
return { x: x, y: y };
}
// 属性名表达式
obj['a' + 'bc'] = 123;
// Object.is() 比较两个值是否严格相等
Object.is(NaN, NaN) // true
// Object.assign() 对象合并,后面的属性会覆盖前面的属性
Object.assign({ a: 1 }, { b: 2 }, { c: 3 });
// Object.keys()
var obj = { foo: 'bar', baz: 42 };
Object.keys(obj)
// ["foo", "baz"]复制代码
ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,做为对象的模板。经过class关键字,能够定义类。
ES6 的class能够看做只是一个语法糖,他的内部实现和 Java 之类的语言差异很大。传统的类被实例化时,它的行为会被复制到实例中。类被继承时,行为也会被复制到子类中。多态看起来彷佛是从子类引用父类,可是本质上引用的实际上是复制的结果。
javascript 中的类机制有一个核心区别,就是不会进行复制,对象之间是经过内部的 [[Prototype]] 链关联的。
new 操做符在 JavaScript 当中自己就是一个充满歧义的东西,只是贴合程序员习惯而已。
执行new fn()会进行如下简化过程:
//定义一个函数,正常函数会具备__call__, __construct__
//同时Parent.__proto__指向Function.prototype
function Parent() {
this.sayAge = function() {
console.log('age is: ' + this.age);
}
}
//原型上添加一个方法
Parent.prototype.sayParent = function() {
console.log('this is Parent Method');
}
//定义另外一个函数
function Child(firstname) {
//这里就是调用Parent的__call__, 而且传入this
//而这里的this,是Child接受new时候生成的对象
//所以,这一步会给生成的Child生成的实例添加一个sayAge属性
Parent.call(this);
this.fname = firstname;
this.age = 40;
this.saySomething = function() {
console.log(this.fname);
this.sayAge();
}
}
//这一步就是new的调用,按原理分步来看
//1. 新建了个对象,记做o
//2. o.__proto__ = Parent.prototype, 所以o.sayParent会访问到o.__proto__.sayParent(原型链查找机制)
//3. Parent.call(o), 所以o也会有个sayAge属性(o.sayAge)
//4. Child.prototype = o, 所以 Child.prototype 经过o.__proto__ 这个原型链具备了o.sayParent属性,同时经过o.sayAge 具备了sayAge属性(也就是说Child.prototype上具备sayAge属性,但没有sayParent属性,可是经过原型链,也能够访问到sayParent属性)
Child.prototype = new Parent();
//这也是一步new调用
//1. 新建对象,记做s
//2. s.__proto__ = Child.prototype, 此时s会具备sayAge属性以及sayParent这个原型链上的属性
//3. Child.call(s), 执行后, 增长了fname, age, saySomething属性, 同时因为跑了Parent.call(s), s还具备sayAge属性, 这个属性是s身上的, 上面那个sayAge是Child.prototype上的, 即s.__proto__上的。
//4. child = s
var child = new Child('张')
//child自己属性就有,执行
child.saySomething();
//child自己属性没有, 去原型链上看, child.__proto__ = s.__proto__ = Child.prototype = o, 这里没找到sayParent, 继续往上找, o.__proto__ = Parent.prototype, 这里找到了, 执行(第二层原型链找到)
child.sayParent();
复制代码
以前的写法:
function Point(x, y) {
this.x = x;
this.y = y;
}
Point.prototype.toString = function () {
return '(' + this.x + ', ' + this.y + ')';
};
var p = new Point(1, 2);复制代码
ES6的写法:
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return '(' + this.x + ', ' + this.y + ')';
}
}复制代码
事实上,类的全部方法都定义在类的prototype属性上面。
class Point {
constructor() {
// ...
}
toString() {
// ...
}
toValue() {
// ...
}
}
// 等同于
Point.prototype = {
constructor() {},
toString() {},
toValue() {},
};复制代码
因为类的方法都定义在prototype对象上面,因此类的新方法能够添加在prototype对象上面。也就是说类的方法能够随时增长。
class Point {
constructor(){
// ...
}
}
Object.assign(Point.prototype, {
toString(){},
toValue(){}
});复制代码
constructor方法是类的默认方法,经过new命令生成对象实例时,自动调用该方法。一个类必须有constructor方法,若是没有显式定义,一个空的constructor方法会被默认添加。
class Point {
}
// 等同于
class Point {
constructor() {}
}复制代码
Class 能够经过extends关键字实现继承:
class Point {
}
class ColorPoint extends Point {
}复制代码
子类必须在constructor方法中调用super方法,不然新建实例时会报错。ES5 的继承,实质是先创造子类的实例对象this,而后再将父类的方法添加到this上面(Parent.apply(this))。ES6 的继承机制彻底不一样,实质是先将父类实例对象的属性和方法,加到this上面(因此必须先调用super方法),而后再用子类的构造函数修改this。
class ColorPoint extends Point {
constructor(x, y, color) {
super(x, y); // 调用父类的constructor(x, y)
this.color = color;
}
toString() {
return this.color + ' ' + super.toString(); // 调用父类的toString()
}
}复制代码
ES5 的对象属性名都是字符串,这容易形成属性名的冲突。若是有一种机制,保证每一个属性的名字都是独一无二的就行了,这样就从根本上防止属性名的冲突。这就是 ES6 引入Symbol的缘由。
ES6 引入了一种新的原始数据类型Symbol,表示独一无二的值。它是 JavaScript 语言的第七种数据类型,前六种是:undefined、null、布尔值(Boolean)、字符串(String)、数值(Number)、对象(Object)。
let s1 = Symbol('foo');
let s2 = Symbol('bar');
s1 // Symbol(foo)
s2 // Symbol(bar)
s1.toString() // "Symbol(foo)"
s2.toString() // "Symbol(bar)"复制代码
因为每个 Symbol 值都是不相等的,这意味着 Symbol 值能够做为标识符,用于对象的属性名,就能保证不会出现同名的属性。这对于一个对象由多个模块构成的状况很是有用,能防止某一个键被不当心改写或覆盖。
let mySymbol = Symbol();
// 第一种写法
let a = {};
a[mySymbol] = 'Hello!';
// 第二种写法
let a = {
[mySymbol]: 'Hello!'
};
// 第三种写法
let a = {};
Object.defineProperty(a, mySymbol, { value: 'Hello!' });
// 以上写法都获得一样结果
a[mySymbol] // "Hello!"复制代码
也许不少人会疑惑,既然数组和对象能够存储任何类型的值,为何还须要Map和Set呢?考虑几个问题:一是对象的键名通常只能是字符串,而不能是另外一个对象;二是对象没有直接获取属性个数等这些方便操做的方法;三是咱们对于对象的任何操做都须要进入对象的内部数据中完成,例如查找、删除某个值必须循环遍历对象内部的全部键值对来完成。总之咱们使用简单对象的方式仍然显得很低效,没有一个高效的方法集来管理对象数据。
所以ECMAScript 6增长了Map、Set、WeakMap、WeakSet, 试图弥补这些不足。这样咱们就可使用它们提供的has. add、delete、 clear 等方法来管理和操做数据集合,而不用具体进入到对象内部去操做了,这种状况下Map和Set就相似一个可用于存储数据的黑盒,咱们只管向里面高效存取数据,而不用知道它里面的结构是怎样的。咱们甚至能够这样理解:集合类型是对对象的加强类型,是一类使数据管理操做更加高效的对象类型。
Set 相似于数组,可是成员的值都是惟一的,没有重复的值。
const set = new Set([1, 2, 3, 4, 4]);
[...set]
// [1, 2, 3, 4]复制代码
WeakSet 的成员只能是对象,而不能是其余类型的值。WeakSet 中的对象都是弱引用,即垃圾回收机制不考虑 WeakSet 对该对象的引用,也就是说,若是其余对象都再也不引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象还存在于 WeakSet 之中。
一道笔试题:用一行代码实现数组去掉重复元素、从小到大排序、去掉全部偶数。
let arr = [13, 4, 8, 14, 1, 12, 17, 2, 7, 8, 13, 9, 6, 4, 9, 3, 2, 1, 17, 19, 12, 4, 14];
let arr2 = [...new Set(arr)].filter(v => v % 2 !== 0).sort((a, b) => a - b);
console.log(arr2); // [ 1, 3, 7, 9, 13, 17, 19 ]复制代码
Object 结构提供了“字符串—值”的对应,Map 结构提供了“值—值”的对应,是一种更完善的 Hash 结构实现。若是须要“键值对”的数据结构,Map 比 Object 更合适。
const m = new Map();
const o = {p: 'Hello World'};
m.set(o, 'content')
m.get(o) // "content"
m.has(o) // true
m.delete(o) // true
m.has(o) // false复制代码
WeakMap只接受对象做为键名(null除外),不接受其余类型的值做为键名。WeakMap的键名所指向的对象,不计入垃圾回收机制。
WeakSet 和 WeakMap 结构主要有助于防止内存泄漏。
历史上,JavaScript 一直没有模块(module)体系,没法将一个大程序拆分红互相依赖的小文件,再用简单的方法拼装起来。其余语言基本上都有这项功能,这对开发大型的、复杂的项目造成了巨大障碍。
在 ES6 以前,社区制定了一些模块加载方案,最主要的有 CommonJS 和 AMD 两种。前者用于服务器,后者用于浏览器。ES6 在语言标准的层面上,实现了模块功能,并且实现得至关简单,彻底能够取代 CommonJS 和 AMD 规范。
// profile.js
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;
export {firstName, lastName, year};
// main.js
import {firstName, lastName, year} from './profile.js';
function setName(element) {
element.textContent = firstName + ' ' + lastName;
}复制代码
异步编程对 JavaScript 语言很重要。Javascript 语言的执行环境是“单线程”的,若是没有异步编程,根本无法用,非卡死不可。ES6 诞生之前,异步编程的方法,大概有下面四种。
所谓回调函数,就是把任务的第二段单独写在一个函数里面,等到从新执行这个任务的时候,就直接调用这个函数。
fs.readFile('/etc/passwd', 'utf-8', function (err, data) {
if (err) throw err;
console.log(data);
});复制代码
Callback Hell:使用大量回调函数时,代码阅读起来晦涩难懂,并不直观。
Promise 是异步编程的一种解决方案,比传统的解决方案“回调函数和事件”更合理和更强大。它由社区最先提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。所谓Promise,简单说就是一个容器,里面保存着某个将来才会结束的事件(一般是一个异步操做)的结果。
const promise = new Promise(function(resolve, reject) {
// ... some code
if (/* 异步操做成功 */){
resolve(value);
} else {
reject(error);
}
});
promise
.then(function(value) { console.log(value) })
.catch(function(error) { console.log(error) });复制代码
resolve函数的做用是,将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操做成功时调用,并将异步操做的结果,做为参数传递出去;reject函数的做用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操做失败时调用,并将异步操做报出的错误,做为参数传递出去。
Promise实例生成之后,能够用then和catch方法分别指定resolved状态和rejected状态的回调函数。
举个例子,咱们能够把老的Ajax GET调用方式封装成Promise:
function get(url) {
return new Promise(function(resolve, reject) {
var req = new XMLHttpRequest();
req.open('GET', url);
req.onload = function() {
if (req.status == 200) {
resolve(req.response);
}
else {
reject(Error(req.statusText));
}
};
req.onerror = function() {
reject(Error("Network Error"));
};
req.send();
});
}复制代码
而后就能够这样使用:
get('story.json')
.then(function(response) {
console.log("Success!", response);
})
.catch(function(error) {
console.error("Failed!", error);
})复制代码
异步是JS的核心,几乎全部前端面试都会涉及到Promise的内容。
迭代器(Iterator)是一种接口,为各类不一样的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就能够完成遍历操做(即依次处理该数据结构的全部成员)。
迭代器其实就是维护一个当前的指针,这个指针能够指向当前的元素,能够返回当前所指向的元素,能够移到下一个元素的位置,经过这个指针能够遍历容器的全部元素。
Iterator 的做用有三个:一是为各类数据结构,提供一个统一的、简便的访问接口;二是使得数据结构的成员可以按某种次序排列;三是 ES6 创造了一种新的遍历命令for...of循环,Iterator 接口主要供for...of消费。
let arr = ['a', 'b', 'c'];
let iter = arr[Symbol.iterator]();
iter.next() // { value: 'a', done: false }
iter.next() // { value: 'b', done: false }
iter.next() // { value: 'c', done: false }
iter.next() // { value: undefined, done: true }复制代码
当循环迭代中每次单步循环操做都不同时,使用Interator就颇有用了。
若是对Iterator理解较深的话,那么你会发现生成器Generator和Interator的流程是有点相似的。可是,Generator 不是针对对象上内容的遍历控制,而是针对函数内代码块的执行控制,若是将一个特殊函数的代码使用yield关键字来分割成多个不一样的代码段,那么每次Generator调用next()都只会执行yield关键字之间的一段代码。
Generator能够认为是一个可中断执行的特殊函数,声明方法是在函数名后面加上*来与普通函数区分。
function* helloWorldGenerator() {
yield 'hello';
yield 'world';
return 'ending';
}
var hw = helloWorldGenerator();
hw.next(); // { value: 'hello', done: false }
hw.next(); // { value: 'world', done: false }
hw.next(); // { value: 'ending', done: true }
hw.next(); // { value: undefined, done: true }复制代码
调用 Generator 函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象。下一步,必须调用遍历器对象的next方法,使得指针移向下一个状态。也就是说,每次调用next方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield表达式(或return语句)为止。换言之,Generator 函数是分段执行的,yield表达式是暂停执行的标记,而next方法能够恢复执行。
回到以前说过的异步。
对于其余编程语言,早有异步编程的解决方案(实际上是多任务的解决方案)。其中有一种叫作"协程"(coroutine),意思是多个线程互相协做,完成异步任务。它的运行流程大体以下。
上面流程的协程A,就是异步任务,由于它分红两段(或多段)执行。好比你打电话就是A,吃蛋糕就是B,讲一句电话,吃一口蛋糕。
举例来讲,读取文件的协程写法以下。
function* asyncJob() {
// ...其余代码
var f = yield readFile(fileA);
// ...其余代码
}复制代码
上面代码的函数asyncJob是一个协程,它的奥妙就在其中的yield命令。它表示执行到此处,执行权将交给其余协程。也就是说,yield命令是异步两个阶段的分界线。
协程遇到yield命令就暂停,等到执行权返回,再从暂停的地方继续日后执行。它的最大优势,就是代码的写法很是像同步操做,若是去除yield命令,简直如出一辙。
Generator 函数是协程在 ES6 的实现,最大特色就是能够交出函数的执行权(即暂停执行)。整个 Generator 函数就是一个封装的异步任务,或者说是异步任务的容器。异步操做须要暂停的地方,都用yield语句注明。
function* gen(){
var url = 'https://api.github.com/users/github';
var result = yield fetch(url);
console.log(result.bio);
}
var g = gen();
var result = g.next();
result.value.then(function(data){
return data.json();
}).then(function(data){
g.next(data);
});复制代码
上面代码中,首先执行 Generator 函数,获取遍历器对象,而后使用next方法(第二行),执行异步任务的第一阶段。因为Fetch模块返回的是一个 Promise 对象,所以要用then方法调用下一个next方法。
以前异步部分咱们说过Promise和Generator,ES2017 标准引入了 async 函数,使得异步操做变得更加方便。
async 函数是什么?一句话,它就是 Generator 函数的语法糖。
Generator 函数,依次读取两个文件。
const fs = require('fs');
const readFile = function (fileName) {
return new Promise(function (resolve, reject) {
fs.readFile(fileName, function(error, data) {
if (error) return reject(error);
resolve(data);
});
});
};
const gen = function* () {
const f1 = yield readFile('/etc/fstab');
const f2 = yield readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};复制代码
写成async函数,就是下面这样。
const asyncReadFile = async function () {
const f1 = await readFile('/etc/fstab');
const f2 = await readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};复制代码
async函数有更好的语义,更广的适用性,能够直接执行,并且返回值是 Promise。
await命令后面的Promise对象,运行结果多是rejected,因此最好把await命令放在try...catch代码块中。
async function myFunction() {
try {
await somethingThatReturnsAPromise();
} catch (err) {
console.log(err);
}
}
// 另外一种写法
async function myFunction() {
await somethingThatReturnsAPromise()
.catch(function (err) {
console.log(err);
});
}复制代码
多个await命令后面的异步操做,若是不存在继发关系,最好让它们同时触发。
let foo = await getFoo();
let bar = await getBar();
// 写法一
let [foo, bar] = await Promise.all([getFoo(), getBar()]);
// 写法二
let fooPromise = getFoo();
let barPromise = getBar();
let foo = await fooPromise;
let bar = await barPromise;复制代码
Proxy 用于修改某些操做的默认行为,等同于在语言层面作出修改,因此属于一种“元编程”(meta programming),即对编程语言进行编程。
Proxy 能够理解成,在目标对象以前架设一层“拦截”,外界对该对象的访问,都必须先经过这层拦截,所以提供了一种机制,能够对外界的访问进行过滤和改写。
下面是一个拦截读取属性行为的例子。
var proxy = new Proxy({}, {
get: function(target, property) {
return 35;
}
});
proxy.time // 35
proxy.name // 35
proxy.title // 35复制代码
如今不少前端框架都实现了双向绑定(演示:scrimba.com/p/pXKqta/c9…),目前业界分为两个大的流派,一个是以React为首的单向数据绑定,另外一个是以Angular、Vue为主的双向数据绑定。能够实现双向绑定的方法有不少,好比Angular基于脏检查,Vue基于数据劫持等。双向绑定的思想很重要,我在面试的时候基本上都会问到Vue双向绑定的实现原理。
常见的基于数据劫持的双向绑定有两种实现,一个是目前Vue在用的Object.defineProperty,另外一个就是Proxy。
数据劫持比较好理解,一般咱们利用Object.defineProperty劫持对象的访问器,在属性值发生变化时咱们能够获取变化,从而进行进一步操做。
// 这是将要被劫持的对象
const data = {
name: '',
};
function say(name) {
if (name === '古天乐') {
console.log('给你们推荐一款超好玩的游戏');
} else if (name === '渣渣辉') {
console.log('戏我演过不少,可游戏我只玩贪玩懒月');
} else {
console.log('来作个人兄弟');
}
}
// 遍历对象,对其属性值进行劫持
Object.keys(data).forEach(function(key) {
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get: function() {
console.log('get');
},
set: function(newVal) {
// 当属性值发生变化时咱们能够进行额外操做
console.log(`你们好,我系${newVal}`);
say(newVal);
},
});
});
data.name = '渣渣辉';
//你们好,我系渣渣辉
//戏我演过不少,可游戏我只玩贪玩懒月复制代码
咱们要实现一个完整的双向绑定须要如下几个要点:
使用Proxy相比Object.defineProperty,有以下优点:
因为Proxy的这么多优点,Vue的下一个版本3.0宣称会用Proxy改写。
Reflect对象与Proxy对象同样,也是 ES6 为了操做对象而提供的新 API。Reflect对象的设计目的有这样几个。
修改某些Object方法的返回结果,让其变得更合理。好比,Object.defineProperty(obj, name, desc)在没法定义属性时,会抛出一个错误,而Reflect.defineProperty(obj, name, desc)则会返回false。
// 老写法
try {
Object.defineProperty(target, property, attributes);
// success
} catch (e) {
// failure
}
// 新写法
if (Reflect.defineProperty(target, property, attributes)) {
// success
} else {
// failure
}复制代码
让Object操做都变成函数行为。某些Object操做是命令式,好比name in obj和delete obj[name],而Reflect.has(obj, name)和Reflect.deleteProperty(obj, name)让它们变成了函数行为。
// 老写法
'assign' in Object // true
// 新写法
Reflect.has(Object, 'assign') // true复制代码
Reflect对象的方法与Proxy对象的方法一一对应,只要是Proxy对象的方法,就能在Reflect对象上找到对应的方法。也就是说,无论Proxy怎么修改默认行为,你总能够在Reflect上获取默认行为。
var loggedObj = new Proxy(obj, {
get(target, name) {
console.log('get', target, name);
return Reflect.get(target, name);
},
deleteProperty(target, name) {
console.log('delete' + name);
return Reflect.deleteProperty(target, name);
},
has(target, name) {
console.log('has' + name);
return Reflect.has(target, name);
}
});复制代码
TypeScript 是2012年微软发布的一种开源语言,和与之结合的开源编辑器VS code ( Visual Studio Code)一块儿推出供开发者使用。 到今天,TypeScript 已经发生了比较大的变化,就语言特性来讲,TypeScript 基本和ECMAScript 6的语法保持一致,能够认为是ECMAScript6的超集,基本包含了ECMAScript 6和ECMAScript6中部分未实现的内容,例如async/await,但仍有一些少数的差别性特征。
TypeScript 可使用 JavaScript 中的全部代码和编码概念,TypeScript 是为了使 JavaScript 的开发变得更加容易而建立的。
TypeScript 相比于 JavaScript 的优点:
从此,JS从语言层还会不断的完善,ECMAScript 每一年都会有更新,还有不少好的特性在审查中:kangax.github.io/compat-tabl…