SegmentFault Javascript 五十问最新的文章
2019-02-23T14:09:24+08:00
https://segmentfault.com/feeds/blogs
https://creativecommons.org/licenses/by-nc-nd/4.0/
JavaScript五十问——对比来说CSS的Grid与FlexBox(下篇)
https://segmentfault.com/a/1190000018258428
2019-02-23T14:09:24+08:00
2019-02-23T14:09:24+08:00
蔺相如如
https://segmentfault.com/u/alexruru
9
<h2>前言</h2>
<p>在上篇——<a href="https://segmentfault.com/a/1190000018182409">JavaScript五十问——对比来说CSS的Grid与FlexBox(上篇)</a>,我介绍了Flex的属性与使用,今天我们来总结一下Grid的具体使用方法,最后会结合Flex与Grid布局讲一讲二者的联系与不同。</p>
<p>需要注意得是,Grid布局与我们之前所熟悉的css布局思路有很大的不同,阅读这篇文章之前,需要把我们平时对css的刻板印象抛弃掉,准备接受知识的洗礼吧,少年!</p>
<h2>Grid</h2>
<p>与 Flex 相同,Grid 也分为容器与元素两个概念;在一个 html 标签中添加样式:<code>display:grid</code>或者<code>display:inline-grid</code>,即构建了一个 Grid 的容器,里面的 dom 元素即为 Grid 元素。同样,Grid 也分为两类属性,分别装载在容器与元素上,下面一一说明。</p>
<h4>HTML结构说明</h4>
<p>以下所有例子均基于或扩展于下面的HTML结构:</p>
<pre><code><style>
.container{
width:500px;
background-color:#999;
}
.item{
width: 50px;
background-color:#567;
color:#fff;
}
</style>
<div class="container">
<div class="item">1</div>
<div class="item">2</div>
<div class="item">3</div>
<div class="item">4</div>
<div class="item">5</div>
</div></code></pre>
<h3>Grid 基本概念介绍</h3>
<h4>网格</h4>
<p>Grid容器里面有网格一系列的概念;听着唬人,但是结合图很容易理解。</p>
<h5>网格线</h5>
<p>水平方向有垂直方向的线段即为<code>网格线</code><br><img src="/img/bVbovyA?w=666&h=398" alt="clipboard.png" title="clipboard.png"></p>
<h5>网格轨道</h5>
<p>两个相邻的平行网格线之间的区域就是<code>网格轨道</code><br><img src="/img/bVbovyF?w=922&h=394" alt="clipboard.png" title="clipboard.png"></p>
<h5>网格单元</h5>
<p>四个相邻边组成的区域就是网格单元。<br><img src="/img/bVbovyI?w=792&h=398" alt="clipboard.png" title="clipboard.png"></p>
<h4>fr</h4>
<p><code>fr</code>是Grid中特有表示<code>尺寸</code>的单位,是<code>分数——fraction</code>的缩写,假设我们现在有四个grid元素,每个元素的宽度都是1fr,那么每个元素的实际宽度就是总宽度的1/4。</p>
<p>fr也可以跟%,px 共同使用,他的计算规则就是刨去px与%的剩余空间作为fr分配空间,所有fr相加之和作为分母,分子为每个元素对应的fr的值。(当然,在Grid语境下,我们是不需要设置width属性的,这样说是为了让大家容易理解)。</p>
<p>这样说来好像fr仅仅是%的另外一种写法,随着我的介绍,你就会发现fr优于%的地方。</p>
<p><img src="/img/bVborRD?w=2038&h=956" alt="Grid属性脑图.png" title="Grid属性脑图.png"><br>接下来,我还是以脑图为思路介绍Grid的各个属性。</p>
<h3>容器属性</h3>
<h4>grid-template</h4>
<p>grid-tempalte是三个属性的简写,这三个属性都是描述整块区域即多个<code>网格单元</code>的属性。</p>
<h5>grid-template-rows</h5>
<p>grid-template-rows是描述<code>横向</code>的<code>单元轨道属性</code>的。可以试想一下,我们在一个Grid容器中,关于这个属性,我们关心的是什么呢?无非就是这个容器中有多少行,每行的高度;所以,这个属性就是让我们定义这些值得。先来看语法:<br><code>grid-template-rows: <track-size> | <line-name> <track-size> ...;</code><br>这个属性除了可以定义轨道尺寸和个数之外,还允许我们定义两个轨道之间的网格线的名称,至于他的作用,我们先按下不表,先来看这个属性是怎样定义每个轨道的尺寸和轨道个数的。<br>先来看一个例子:</p>
<pre><code>.container{
grid-template-rows:200px auto 1fr 1fr 20%;
grid-row-gap:10px;//定义轨道之间的间距
}</code></pre>
<p>grid-template-rows定义了五个值,表示Grid容器里面有五行,可以使用任意的定义尺寸的方式,效果如下</p>
<p><img src="/img/bVbovGS?w=1104&h=1118" alt="clipboard.png" title="clipboard.png"></p>
<p>其中<code>auto</code>值就表示元素的实际占用大小。<br>Grid分配空间首先计算除了fr对应轨道的尺寸,然后将剩余尺寸按照比例分配给fr加持的元素。<br>以上,我们在Grid容器里定义了五行容器轨道,当我们定义轨道过多时,可以使用repeat函数来减少我们的工作量,语法为:<br><code>grid-template-rows:repeat(n, size)</code><br>例子:</p>
<pre><code>.container{
grid-template-rows:repeat(5,1fr);
grid-row-gap:10px;
}</code></pre>
<p><img src="/img/bVbovJu?w=1114&h=1114" alt="clipboard.png" title="clipboard.png"><br>上面就定义了五个网格轨道,每个轨道的高度是Grid容器高度的1/5。</p>
<h5>grid-template-columns</h5>
<p>grid-template-columns 与 grid-template-rows使用方法是一致的,这两个属性共同作用于Grid容器,相当于把Grid容器分割为m*n个子区域。<br>例子:</p>
<pre><code>.container{
grid-template-rows:repeat(3,1fr);
grid-template-columns: repeat(2, 1fr)</code></pre>
<p>上面这个例子就会得到六个均等分的子区域。</p>
<p>通过上面两个属性,相信大家对Grid布局有一个基本的认识了,想必对Grid二维布局的模式也有一些概念了,接下来才是Grid精彩之处,震撼灵魂的地方!</p>
<h5>grid-template-areas</h5>
<p>上面两个属性分别设置Grid行属性和列属性,grid-template-areas是设置Grid区域的。所谓区域是由一个或多个行、列、单元组成的一篇区域。首先看一下语法:</p>
<pre><code>.container {
grid-template-areas:
"<grid-area-name> | . | none | ..."
"...";
}</code></pre>
<p>其中<br><code>grid-area-name</code>表示网格区域的名称<br><code>.</code>表示空的网格区域<br><code>none</code> 表示没有定义网格区域<br>在我们平时开发时,经常会出现上头下尾中间两栏布局的情况,下面我们使用grid-template-areas完成这样的布局。</p>
<pre><code><style>
.container{
display:grid;
grid-template-rows:60px auto 60px;
grid-template-columns:100px 1fr;
grid-template-areas:
"header header"
"left right"
"footer footer";
}
.container .item:first-child{
grid-area: header;
}
.container .item:nth-child(2){
grid-area: left;
}
.container .item:nth-child(3){
grid-area: right;
}
.container .item:nth-child(4){
grid-area: footer;
}
</style>
.container{
display:grid;
grid-template-rows:60px 1fr 60px;
grid-template-columns:100px 1fr;
grid-template-areas:
"header header"
"left right"
"footer footer";
}
.container .item:first-child{
grid-area: header;
}
.container .item:nth-child(2){
grid-area: left;
}
.container .item:nth-child(3){
grid-area: right;
}
.container .item:nth-child(4){
grid-area: footer;
}
.container{
display:grid;
grid-template-rows:60px 1fr 60px;
grid-template-columns:100px 1fr;
grid-template-areas:
"header header"
"left right"
"footer footer";
}
.container .item:first-child{
grid-area: header;
}
.container .item:nth-child(2){
grid-area: left;
}
.container .item:nth-child(3){
grid-area: right;
}
.container .item:nth-child(4){
grid-area: footer;
}
</style>
<div class="container">
<div class="item">1</div>
<div class="item">2</div>
<div class="item">3</div>
<div class="item">4</div>
</div>
</code></pre>
<p>在Grid容器中,我们定义了6个网格单元,使用<code>grid-tempalte-areas</code>划分了header footer left right 四片区域;而在<code>grid元素</code>中,每个元素使用<code>grid-area</code>来指定元素所对应的grid区域。因此,我们虽然划分了6个单元,但可以使用四个元素来表示。</p>
<p><img src="/img/bVboxm6?w=942&h=255" alt="clipboard.png" title="clipboard.png"></p>
<p>是不是很神奇呢,更神奇的是,grid-area-name是支持中文定义的。</p>
<p>以上grid-template的子属性就说完了,grid-template是以上那三个属性的简写方式,语法如下:</p>
<pre><code>grid-tempalte:<'grid-template-rows'> / <'grid-template-columns'>`
`grid-tempalte:[ <line-names>? <string> <track-size>? <line-names>? ]+
[ / <explicit-track-list> ]?</code></pre>
<p>例如上面的例子可以这样简写</p>
<pre><code>grid-template:
"header header" 60px
"left right" 1fr
"footer footer" 60px
/ 50px 1fr;</code></pre>
<h4>grid-gap</h4>
<p>grid-gap用来描述Grid 区域之间间隙的尺寸大小。语法如下:</p>
<pre><code>.container {
grid-gap: <grid-row-gap> <grid-column-gap>;
}</code></pre>
<p>grid-gap是简写属性,也可以分别定义grid行间隔和grid 列间隔。</p>
<pre><code>.container{
grid-row-gap:10px;
grid-column-gap:10px;
}</code></pre>
<p>grid-gap与margin与padding不同,它不占用当前元素的盒模型的位置。</p>
<p><img src="/img/bVbozmy?w=1836&h=496" alt="clipboard.png" title="clipboard.png"><br>上图显示的很清楚,3号元素的margin 与 padding 均为零。</p>
<h4>place-items</h4>
<p><code>place-items </code>是 <code>justify-items</code>和<code>align-items</code>的简写方式<br>这两个属性分别定义了<code>Grid元素</code>水平与垂直分布方式。<br>语法如下:</p>
<pre><code> justify-items: stretch | start | end | center;
align-items: stretch | start | end | center;</code></pre>
<p>对于这四个属性,默认stretch,相信读者在熟悉了Flex布局后都不会陌生,这里不多做解释,直接看例子,以align-items 为例:</p>
<pre><code>.container{
display:grid;
grid-template:
"header header" 160px
"footer footer" 160px
/ 160px 160px;
height:500px;
grid-row-gap:10px;
grid-column-gap: 10px;
}</code></pre>
<p>首先定义四个grid单元,每个单元的长宽均为160px<br>接下来我们更改align-items的值</p>
<p><strong>align-items:stretch</strong><br><img src="/img/bVbozsk?w=1106&h=1108" alt="clipboard.png" title="clipboard.png"></p>
<p><strong>align-items:center</strong></p>
<p><img src="/img/bVbozsN?w=1114&h=1154" alt="clipboard.png" title="clipboard.png"></p>
<p><strong>align-items:start</strong></p>
<p><img src="/img/bVboztm?w=1092&h=1112" alt="clipboard.png" title="clipboard.png"></p>
<p><strong>align-items: end</strong></p>
<p><img src="/img/bVboztt?w=1100&h=1104" alt="clipboard.png" title="clipboard.png"><br>为了方便大家理解,我用红框框出每个Grid单元所占用的空间。由此可以看出,place-items属性是用来表明一个元素在当前grid单元中的分布方式,这个元素的拉伸,居中等都是以grid单元作为参考的。</p>
<h4>place-content</h4>
<p>place-content同样是一个简写属性,它包括:justify-content 和 align-content,它表示grid元素在grid容器中的分布方式,只有当grid容器中有剩余空间的时候才起作用。<br>语法如下:</p>
<pre><code>justify-content: stretch | start | end | center | space-between | space-around | space-evenly;
align-content: stretch | start | end | center | space-between | space-around | space-evenly;</code></pre>
<p>属性值得含义同Flex;这里不再过多说明,读者可以自行验证。</p>
<h4>grid-auto-rows 与 grid-auto-columns</h4>
<p>grid-auto-rows 和 grid-auto-columns;用于当实际的Grid的元素多余划分的Grid元素时,定义多余Grid元素的长宽;<br>例如我们在HTML里面一定了五个Grid元素,但是在css中只定义了2*2的Grid单元,可以使用grid-auto来定义多出来的轨道的尺寸。</p>
<h4>grid-auto-flow</h4>
<p>grid-auto-flow的用法需要结合下面的元素属性来说明。</p>
<h3>元素属性</h3>
<h4>grid-column-start grid-column-end</h4>
<p>两个属性是用来定义Grid元素列方向上的起始与终止位置。<br>语法格式为:</p>
<pre><code> grid-column-start: <number> | <name> | span <number> | span <name> | auto</code></pre>
<p>其中:<br>number为起止第几条网格线<br>name 为网格线的名称<br>span <number>网格元素会跨越网格单元的数量<br>span <name> 当前的网格元素会在哪一个网格线上开始or终止</p>
<p>注意 使用span 如果是start,表示这个从开始的位置跨过的grid单元,如果是end 表示这个元素覆盖的grid单元。<br>grid-column是它们的简写方式,语法为:</p>
<pre><code>grid-column:grid-column-start / grid-column-end</code></pre>
<h4>grid-row-start grid-row-end</h4>
<p>grid-row属性与grid-column用法一致,这里不过多赘言,直接看例子:</p>
<pre><code>
.container{
display:grid;
grid-template-rows:[rone]1fr[rtwo]3fr[rthree]1fr[rfour];
grid-template-columns: [cone]1fr[ctwo]5fr[cthree]2fr[cfour];
height:500px;
}
.item:first-child{
grid-column-start:1;
grid-column-end:cfour;
grid-row-start:rone;
grid-row-end: 2;
}
.item:nth-child(2){
grid-column-start:1;
grid-column-end:span 1;
grid-row-start:rtwo;
grid-row-end: span cthree;
}
.item:nth-child(3){
grid-column-start: ctwo;
grid-column-end:4;
grid-row-start:rtwo;
grid-row-end: span cthree;
}
.item:nth-child(4){
grid-column-start:1;
grid-column-end:4;
grid-row-start:3;
grid-row-end: span 4;
} </code></pre>
<p>效果:</p>
<p><img src="/img/bVboLVU?w=1112&h=1108" alt="clipboard.png" title="clipboard.png"><br>首先在Grid容器中划分出9个grid单元,这九个单元被六个网格线所分割,并给这六个网格线命名。在四个Grid元素中定义横行的起始位置。<br>grid-row 与 grid-column结合使用,可以起到与Grid-template-areas同样的效果。</p>
<h4>grid-area</h4>
<p>grid-area我们在前面已经接触过一部分了,他与Grid容器中的grid-template-areas一起定义,也是grid-column与grid-row的简写属性,语法为:</p>
<pre><code> grid-area: <name> | <row-start> / <column-start> / <row-end> / <column-end>;</code></pre>
<p>name为grid区域的名称,与grid-tempalte-areas结合使用。</p>
<p>上面我们的例子就可以用grid-area表示:</p>
<pre><code>.item:first-child{
grid-area:1/rone/2/cfour;
}</code></pre>
<h4>place-self</h4>
<h5>justify-self</h5>
<p>justify-self定义Grid元素的水平布局方式的,例如,我们在Grid容器中定义justify-items的属性为默认属性,而在某一个Grid元素中定义justify-self为center,那么其他元素表现为拉伸,这个元素则单独表现为居中。也就是说,justify-self在Grid容器中对应的属性是justify-items。<br>语法为:</p>
<pre><code>.item {
justify-self: stretch | start | end | center;
}</code></pre>
<h5>algin-self</h5>
<p>align-self与justify-self一致,改变的是这个元素的垂直部署方式,与容器中align-items对应,<br>语法为:</p>
<pre><code>.container {
align-self: stretch | start | end | center;
}</code></pre>
<p>由于这四个属性值已经在我们的系列文章中出现多次,这里不再多说。</p>
<p>plac-self是以上两个属性的简写方式,语法为:</p>
<pre><code>.container {
place-items: <align-self> / <justify-self>;
}</code></pre>
<h3>再论fr</h3>
<p>Grid的所有属性已经介绍完毕了,在对Grid宇宙有了一个基本的认识后,我们再回头看一下Grid宇宙中出现的新尺寸单位——fr。<br>可能大家在刚刚结仇到fr这个单位时,都会认为它是%的一个别名;但是,我们来看最下面的例子:<br>我们在Grid容器中划分出四个Grid区域,并定义每个区域的宽度为25%,并定义每个Grid元素之间的gap宽70px;</p>
<pre><code>.container{
display:grid;
grid-template-columns: repeat(4, 25%);
grid-column-gap: 70px;
}</code></pre>
<p>效果如下:<br><img src="/img/bVboLZh?w=1582&h=1114" alt="clipboard.png" title="clipboard.png"><br>很明显,这里元素溢出了。这种情况是我们不想看到的。<br>下面,我们将25%替换为1fr看一下效果:</p>
<p><img src="/img/bVboLZy?w=1112&h=1114" alt="clipboard.png" title="clipboard.png"><br>效果对比很明显。<br>而造成两者显著区别的原因是二者的计算空间方式的不同。使用百分比它的分母是父元素的width或者height,而fr的分母是父元素中剩余空间的尺寸;css会首先计算使用%和px定义的元素尺寸,剩下的空间再由fr元素进行比例分配。这就是使用fr不会出现元素溢出的情况。当然我们也可以使用calc避免溢出的尴尬,但是两种解决方案孰优孰略已经一目了然了。</p>
<h2>Flex 与 Grid</h2>
<p>Flex布局与Grid布局有很多相似的地方,例如justify-content和justify-items的用法。但是更多的是不同,最重要的是Grid开拓了<code>二维</code>布局的方式,相比于传统的css布局方式(Flex、bootstrp 12列),Grid开创了网格的概念,用户可以从<code>横纵</code>两个方面部署元素。正是因为如此,在Grid宇宙中,传统的css布局、尺寸属性就显得格格不入了。而Grid布局的二维特性所带来的整体观,使Grid在<code>页面级别</code>样式上更加游刃有余。而Flex相比于Grid 更加适合小组件上的样式开发,二者并不冲突相信在Grid 与 Flex双双加持之下,一定会收获更好的开发效果!<br>Grid布局还是一个较新潮的概念,我也是一般看资料学习,一边分享,由于缺少实际的开发经验,对于很多属性的应用场景还没有很深入的理解,故而有的属性一笔带过;如果我有理解不正确的地方,欢迎大家指正!</p>
<h2>参考文献</h2>
<p><a href="https://link.segmentfault.com/?enc=bIJJ6j%2B9EZD2P8LNKMrIlQ%3D%3D.huugLjFnAQO8VUowXy6YfT4hicowZ66lXwYmxWcI7%2FCUvLNTguYKdfByqgy5Y%2FA52oetnuPoLrIL7%2B45gUxqxRLQl0IkHLVdmP65g%2Fluxo8%3D" rel="nofollow">MDN:Grid Layout</a><br><a href="https://link.segmentfault.com/?enc=NVTpaj9GmCul8q0OsGXkMg%3D%3D.OrfXB25pxiTBgAeWfzZm%2B8Q91tJhPOpKFaQx0v6%2FY%2BKEhfBf7LB%2BnenfSbIgUDq2FrelFi43BBxtBIF7TzaaJCa0a%2BWIqRurx0knfKTjf1E%3D" rel="nofollow">张鑫旭:写给自己看的display: grid布局教程</a><br><a href="https://link.segmentfault.com/?enc=1tokGfVBWD2JCkXZYBXV7g%3D%3D.pih2by%2BfdjYSiBkgL60t5pJW488pB99nTTqL7uguY47E%2Bi3iloyB3EnBmxyjHuj4" rel="nofollow">知乎:CSS 新的长度单位 fr 你知道么?</a></p>
<h2>What's more:</h2>
<p><a href="https://segmentfault.com/a/1190000018182409">JavaScript五十问——对比来说CSS的Grid与FlexBox(上篇)</a></p>
JavaScript五十问——对比来说CSS的Grid与FlexBox(上篇)
https://segmentfault.com/a/1190000018182409
2019-02-17T21:15:44+08:00
2019-02-17T21:15:44+08:00
蔺相如如
https://segmentfault.com/u/alexruru
8
<h2>前言</h2>
<p>春节假期有幸拜读了张鑫旭大大的关于Flex与Grid的两篇文章(见参考文献),很有收获;自己在开发的过程中,很多时候都会采用Flex布局,而Float与inline-box这种方式已经很少使用了;这次在春假期间学习了Grid,深感Grid的好用与便利。趁着这次机会总结一下Grid与Flex的使用。</p>
<h2>浏览器支持</h2>
<ul><li>Flex 浏览器支持情况</li></ul>
<p><img src="/img/bVborQ6?w=2652&h=506" alt="Flex浏览器支持情况.png" title="Flex浏览器支持情况.png"></p>
<ul><li>Grid浏览器支持情况</li></ul>
<p><img src="/img/bVborQW?w=2628&h=522" alt="Grid浏览器支持情况.png" title="Grid浏览器支持情况.png"></p>
<p>可以看出来,相对于Grid来说,Flex无论实在PC端还是移动端都得到了很好的支持,而Grid,在移动端的支持还是差强人意。</p>
<h2>FlexBox</h2>
<p>在flex布局中,有两个概念需要谨记:容器与元素。在一个html标签中声明样式:<code>display:flex</code>or<code>display:inline-flex</code>即声明了一个flex的容器,在这个容器里面的元素即为flex元素。而flex所有的样式属性分为两类:<code>容器属性</code>与<code>元素属性</code>,他们均作用于<code>flex元素</code>,只不过<code>flex容器</code>中声明的属性统领flex<code>所有元素</code>整体显示与排布方式,而<code>flex元素</code>的属性表示<code>单一元素</code>的排布方式。</p>
<p><img src="/img/bVborUX?w=2380&h=998" alt="Flex属性脑图.png" title="Flex属性脑图.png"></p>
<p>下面,根据上面脑图的思路,依次介绍flex的属性。<br><code>声明:</code><br>下面师范的所有元素都遵从以下HTML结构 和基本样式</p>
<pre><code><style>
.container{
height:200px;
background-color:#999;
}
.container >.item{
background-color:#456;
width:80px;
font-size:30px;
color:white;
text-align:center;
}
</style>
<div class="container">
<div class="item">1</div>
<div class="item">2</div>
<div class="item">3</div>
<div class="item">4</div>
...
</div>
</code></pre>
<h3>容器属性</h3>
<h4><strong>flex-direction</strong></h4>
<p>flex-direction顾名思义,是控制flex元素的整体布局<code>方向</code>的,它包括四个属性:</p>
<pre><code>1.row //从左到右 默认
2.row-reverse //从右到左
3.column //从上到下
4.column-reverse //从下到上</code></pre>
<p>1、flex-direction:row<br><img src="/img/bVborXH?w=1414&h=450" alt="row.png" title="row.png"></p>
<p>2.flex-direction:row-reverse<br><img src="/img/bVborXF?w=1414&h=470" alt="row-reverse.png" title="row-reverse.png"></p>
<p>3.flex-direction:column<br><img src="/img/bVborXM?w=192&h=448" alt="column.png" title="column.png"><br>4.flex-direction: column-reverse<br><img src="/img/bVborXO?w=222&h=470" alt="columu-reverse.png" title="columu-reverse.png"></p>
<p>上面四个图是分别在容器上(.container)设置flex-direction四个属性得到的效果。</p>
<h4><strong>flex-wrap</strong></h4>
<p>flex-wrap是控制元素是换行显示还是单行显示,它共有三个属性</p>
<pre><code>1.no-wrap //不换行 默认
2.wrap // 换行
3.wrap-reverse //换行反序</code></pre>
<p>为了更加明显的看出区别,我特别添加了一个难看的边框。<br>在容器属性上添加flex-wrap,分别设置三个wrap属性<br>1.flex-wrap: no-wrap</p>
<p><img src="/img/bVborYO?w=1254&h=438" alt="no-wrap.png" title="no-wrap.png"></p>
<p>2.flex-wrap: wrap<br><img src="/img/bVborYv?w=1186&h=452" alt="wrap.png" title="wrap.png"></p>
<p>3.flex-wrap: wrap-reverse<br><img src="/img/bVborYG?w=1202&h=448" alt="wrap-reverse.png" title="wrap-reverse.png"><br>在这里需要注意no-wrap属性:<br>如果容器元素设置了最小宽度,而且元素的最小宽度之和>父容器的宽度,则显示内容溢出。<br>如果容器元素没有设置最小宽度或者最小宽度之和<父容器的宽度,则容器元素收缩并将父元素填满,在上面no-wrap例子中,就没有设置子元素的最小宽度,读者可以自行添加验证。</p>
<p>设置wrap-reverse属性后,我们可以看到它的换行结果与wrap的换行结果在<code>行</code>这一维度上是相反的。</p>
<p>读者可以试试将<code>flex-direction</code>设置为<code>column</code>或者其他非默认值,再查看在不同的flex-wrap值下的显示,会有不同的结果哟。</p>
<h4><strong>flex-flow</strong></h4>
<p>flex-flow是 flex-direction与flex-wrap的统写,语法是<br><code>flex-flow:<‘flex-direction’> || <‘flex-wrap’></code></p>
<h4><strong>justify-content</strong></h4>
<p>justify-content控制flex元素水平方向的对齐方式,它共有 个属性:</p>
<p>1.flex-start // 默认值 默认情况下左对齐<br>2.flex-end // 右对齐<br>3.center // 居中<br><strong>下面以space开头的属性都是描述<code>剩余空间</code>的排布方式的</strong><br>4.space-around //剩余空间围绕在<code>每个</code>子元素的周围<br>5.space-between //剩余空间只分布在<code>子元素之间</code>,两端元素左右没有剩余空间<br>6.space-evenly // 剩余空间<code>均匀</code>分布在元素两端、之间</p>
<p>下面看例子:<br>1.flex-start</p>
<p><img src="/img/bVbor1f?w=1518&h=452" alt="flex-start.png" title="flex-start.png"></p>
<p>2.flex-end</p>
<p><img src="/img/bVbor1g?w=1534&h=436" alt="flex-end.png" title="flex-end.png"></p>
<p>3.center</p>
<p><img src="/img/bVbor1j?w=1520&h=468" alt="center.png" title="center.png"></p>
<p>4.space-between</p>
<p><img src="/img/bVbor1x?w=1534&h=444" alt="space-between.png" title="space-between.png"></p>
<p>5.space-around</p>
<p><img src="/img/bVbor1D?w=1522&h=440" alt="space-around.png" title="space-around.png"></p>
<p>6.space-evenly<br><img src="/img/bVbor09?w=1520&h=450" alt="clipboard.png" title="clipboard.png"><br>为了更加明显的看出不同space下的额外空间分布特点,我使用红框框出每一属性下的剩余元素。几个属性分布不同不言而喻了。</p>
<h4><strong>align-content</strong></h4>
<p>align-content与justify-content对应,代表元素垂直方向上的分布。而这种分布方式只有在<code>多行</code>的情况下才能够凸显出来,单行情况下设置此属性无效。<br>相对于justify-content,它多出来一个stretch的属性,代表拉伸,默认属性。</p>
<p>1.stretch</p>
<p><img src="/img/bVbor2x?w=1184&h=434" alt="clipboard.png" title="clipboard.png"></p>
<p>2.flex-start</p>
<p><img src="/img/bVbor2u?w=1186&h=444" alt="clipboard.png" title="clipboard.png"></p>
<p>3.flex-end</p>
<p><img src="/img/bVbor2y?w=1192&h=444" alt="clipboard.png" title="clipboard.png"></p>
<p>4.space-between</p>
<p><img src="/img/bVbor2F?w=1190&h=440" alt="clipboard.png" title="clipboard.png"></p>
<p>5.space-around</p>
<p><img src="/img/bVbor2J?w=1184&h=438" alt="clipboard.png" title="clipboard.png"></p>
<p>6.space-evenly</p>
<p><img src="/img/bVbor2N?w=1186&h=442" alt="clipboard.png" title="clipboard.png"></p>
<p>可以看出,它的排布方式与justify-content的逻辑是一致的,只不过方向不同而已。</p>
<h4><strong>align-items</strong></h4>
<p>既然有了多行情况下控制元素排布方式,就会有单行情况下元素排布方式。align-items就是在单行情况下,控制元素排布方式的。共有5个属性:<br>1.stretch 默认属性,会将元素的高度拉伸至父容器的高度</p>
<p><img src="/img/bVbor6F?w=1410&h=440" alt="clipboard.png" title="clipboard.png"></p>
<p>2.flex-start 顶部对齐</p>
<p><img src="/img/bVbor6H?w=1402&h=440" alt="clipboard.png" title="clipboard.png"></p>
<p>3.flex-end 底部对齐</p>
<p><img src="/img/bVbor6L?w=1404&h=440" alt="clipboard.png" title="clipboard.png"></p>
<p>4.baseline 基线对齐</p>
<p><img src="/img/bVbor7q?w=920&h=456" alt="clipboard.png" title="clipboard.png"></p>
<p>5.center 居中</p>
<p><img src="/img/bVbor63?w=932&h=436" alt="clipboard.png" title="clipboard.png"></p>
<p><strong><code>注意:</code></strong>以上所有的例子展示都是在其他属性默认情况的显示情况,如果更改其他属性(如:<br><code>flex-direction</code> 与 <code>flex-wrap</code>)值,会有不同的显示效果。</p>
<h3>元素属性</h3>
<h4>order</h4>
<p>order属性可以控制flex元素的排布顺序,flex元素会根据order从小到大依次排布,order的默认值为0,因此如果想要某个元素排在前面,只需要将他的order值设为比0小的值即可。</p>
<h4>flex-grow</h4>
<p>flex-grow控制元素所占宽度,默认值为0,当容器内有剩余空间时,flex-grow不为0的元素将会按照以下规则扩展:</p>
<ul><li>容器中只有一个元素设置了flex-grow<br> 1、flex-grow 值>=1 那么这个元素会填充容器的剩余空间</li></ul>
<p><img src="/img/bVbor9S?w=1408&h=454" alt="clipboard.png" title="clipboard.png"></p>
<pre><code>.container .item:first-child{
order:2;
flex-grow:1;
}</code></pre>
<pre><code>2、flex-grow 在0-1之间,那么这个元素会多占用空间为剩余空间乘以这个flex-grow的值。
</code></pre>
<p><img src="/img/bVbor9g?w=1402&h=444" alt="clipboard.png" title="clipboard.png"></p>
<pre><code>.container .item:first-child{
order:2;
flex-grow:0.5;
}</code></pre>
<p><img src="/img/bVbor9s?w=1414&h=460" alt="clipboard.png" title="clipboard.png"></p>
<pre><code>.container{
display:flex;
justify-content:space-around;//如果子元素的flex-grow<1,此属性依然有效
}
.container .item:first-child{
order:2;
flex-grow:0.5;
}</code></pre>
<ul><li>容器中有多个元素设置了flex-grow<br>1、所有元素的flex-grow的值之和>1<br>则占用全部的剩余空间,多占用的剩余空间比例即为各个元素所设置flex-grow的比例。<br>2、所有元素的flex-grow的值之和<1<br>所占用的剩余空间的比例即为各个元素的felx-grow的值的比例。</li></ul>
<h4>flex-shrink</h4>
<p>flex-shrink的属性与flex-grow相反,指的是当空间不足的时候,元素的收缩比例,默认值为1;其核心思路与grow一致,这里不再赘述,读者可以自行检验。</p>
<h4>flex-basis</h4>
<p>flex-basis定义了在分配剩余空间之前,每个元素的默认宽度,默认为auto即元素的宽度;当flex-basis的值不为auto时,其显示的优先级是高于元素的width的。</p>
<h4>flex</h4>
<p>flex属性为以上三个属性的统称,语法为:<br><code>flex: none | auto | [ <'flex-grow'> <'flex-shrink'>? || <'flex-basis'> ]</code><br>flex翻译为中文就是弹性的,所以这个属性就是说明当有空间过大or空间不足时,每个元素如何分布。</p>
<h4>align-self</h4>
<p>align-self顾名思义,就是确定单个元素垂直分布状态;其在父容器对应的属性是algin-items;<br>其属性值有align-items一致,只不过多了一个auto默认属性,表示与父元素的auto-items值一致。<br>来看例子:</p>
<p><img src="/img/bVboscQ?w=1422&h=484" alt="clipboard.png" title="clipboard.png"><br>对应css代码:</p>
<pre><code>.container{
display:flex;
align-items:center;
}
.container .item:nth-child(2n+1){
order:2;
flex-grow:2;
align-self:flex-start;
}
</code></pre>
<p>我在父元素上添加align-items属性,使flex元素居中对齐,在子元素上添加align-self属性,使奇数子元素向上对齐。</p>
<p><img src="/img/bVbosc1?w=1428&h=460" alt="clipboard.png" title="clipboard.png"><br>当有多行的情况下,但是align-content未设置,我们可以看到,每一个设置了align-self属性的子元素会在当前的行按照align-self属性显示,但是当align-content属性设置后,其align-self属性失效(见下图)。</p>
<p><img src="/img/bVbosdq?w=1422&h=440" alt="clipboard.png" title="clipboard.png"><br>对应代码:</p>
<pre><code>.container{
display:flex;
align-content:center;
flex-wrap:wrap;
}
.container .item:nth-child(2n+1){
order:2;
flex-grow:2;
align-self:flex-start;
}
</code></pre>
<h2>总结</h2>
<p>flex布局就总结到这里,里面有些类似属性没有举例子,还需要读者自己去验证。相对于其他解决方案,flex布局更加简洁,也更加具有语义性;最显而易见的,可以很方便的解决我们平时面试过程中遇到的多栏布局,垂直居中问题。<br>其实细细品读flex,相比于Grid而言,他还是更加线性化,就是把所有的元素都看成一条线,确定这条线的方向、居中垂直方式,当这条线超过父元素的范围,我们怎样处理。所以,虽然flex看似有许多属性,但是合理的理解后,还是非常简单的。</p>
<p>这篇是glex与Grid的上篇,主要介绍了flex,而Grid的相关属性与应用,我们下篇见。</p>
<h2>参考文献</h2>
<p><a href="https://link.segmentfault.com/?enc=%2Fg2k3Ne4yEM5A5VzN9KQdw%3D%3D.v0dCcjmuRRi9zkwVloKzARz4%2B8NIE91pBT3hGc1cQd92q%2BJBgR4TjKBIq%2Bk3gfFPc4DPTypglIEYrxc%2B7RUHVBZIWbk8KdU1ULDGdJma2eE%3D" rel="nofollow">张鑫旭:写给自己看的display: flex布局教程</a><br><a href="https://link.segmentfault.com/?enc=CwFW%2BRaPypbphsb5Bc6Ixw%3D%3D.v0IRFf4iu0krLlHKSJdT8%2Ft5Bs%2BihUVqJetbHUJaQ56Ek5KLLDBlPFLMwyEa0t7J%2BDVahTsCMlEMiF2%2Bl0ZFsA%3D%3D" rel="nofollow">阮一峰:Flex 布局教程</a></p>
Javascript五十问——从源头细说Webpack与Gulp
https://segmentfault.com/a/1190000017933455
2019-01-17T21:49:16+08:00
2019-01-17T21:49:16+08:00
蔺相如如
https://segmentfault.com/u/alexruru
24
<blockquote>前言:Webpack 与 gulp是目前圈子内比较活跃的前端构建工具。网上有很多二者比较的文章,面试中也会经常遇到<code>gulp,Webpack的区别</code>这样的问题。对于初学者来说,对这二者往往容易认识不清,今天,就从事件的源头,说清楚Webpack与gulp。</blockquote>
<h2>Gulp</h2>
<p>那是2014年,虽然JQuery风光多年,但是前端却暗流涌动;MVVM刚刚提出不久,Angular快速成长,而React和Vue也刚刚开源不到一年,尚属于冷门小语种。那个时候,前端工作者面临的主要矛盾在于<code>日益增长的业务复杂化的需求</code>同<code>落后低效率的前端部署</code>。开发工作者为了发布一个网站,往往会重复的进行一些与开发无关的工作,手动完成这些工作会带来很大的挫败感。这个时候,自动化构建工具及应运而生,gulp就是在大浪淘沙中的胜利者。</p>
<p>Gulp是基于流的前端构建工具,nodejs的stream操作来读取和操作数据;可以实现文件的转换,压缩,合并,监听,自动部署等功能。gulp拥有强大的插件库,基本上满足开发需求,而且开发人员也可以根据自己的需求开发自定义插件。难得是,gulp只有五个api,容易上手。</p>
<pre><code>const gulp = require('gulp');
const sass = require("gulp-sass")
gulp.task("sassStyle",function() {
gulp.src("style/*.scss")
.pipe(sass())
.pipe(gulp.dest("style"))
})
</code></pre>
<p>上面就是一个基本的<code>gulpfile</code>配置文件,实现了scss文件到css文件的转换;在终端输入<code>gulp sassStyle</code>就能够进行文件处理了。<br>对于gulp而言,会有一个<code>task</code>,这个<code>task</code>只会做一件事,比如将sass格式的文档转换成css文件;对于一个<code>task</code>而言,会有一个入口文件,即<code>gulp.src</code>,最会有一个目标文件,即<code>gulp.dest</code>;一入一出,可以将gulp理解为 <code>一元函数</code>,输入一个<code>x</code>,根据<code>funcion</code>产出一个<code>y</code>。</p>
<p>Gulp简单,快速,自动化的构建方案,收获了很多开发者的喜爱。但是怎样的机遇,让webpack占据了前端工程化的半壁江山呢?</p>
<h2>Webpack</h2>
<p>解决方案永远是紧跟需求的脚步的。随着React与Vue份额越来越大,spa开发模式应用在越来越多的领域中,而<code>ES6 Module</code>语法的提出与大规模应用,<code>模块化</code>开发方式越来越受人们的青睐。致使前端文件之间的依赖性越来越高,这时候就需要一个工具能够解析这些依赖,并且将它们有条理的打包起来,优化请求,最好顺便能够解析成浏览器可以识别的语言——这正是webpack所承担的工作;而很多开发者,也是从react或者vue的项目入手webpack的。</p>
<p><img src="/img/bVbnoxk?w=1053&h=453" alt="webpack打包示意.png" title="webpack打包示意.png"><br>图片来源于互联网,侵删</p>
<p>Webpack 是前端资源<code>模块化</code> <code>管理</code>和<code>打包</code>工具。它可以将许多松散的模块按照依赖和规则打包成符合生产环境部署的前端资源。还可以将按需加载的模块进行代码分割,等到实际需要的时候再异步加载——来自Webpack官方网站。<br>所以Webpack只完成两件事:按需加载,打包。</p>
<pre><code>module.exports = {
// 入口文件,是模块构建的起点,同时每一个入口文件对应最后生成的一个 chunk。
entry: {
bundle: [
'webpack/hot/dev-server',
'webpack-dev-server/client?http://localhost:8080',
path.resolve(__dirname, 'app/app.js')
]
},
// 文件路径指向(可加快打包过程)。
resolve: {
alias: {
'react': pathToReact
}
},
// 生成文件,是模块构建的终点,包括输出文件与输出路径。
output: {
path: path.resolve(__dirname, 'build'),
filename: '[name].js'
},
// 这里配置了处理各模块的 loader ,包括 css 预处理 loader ,es6 编译 loader,图片处理 loader。
module: {
loaders: [
{
test: /\.js$/,
loader: 'babel',
query: {
presets: ['es2015', 'react']
}
}
],
noParse: [pathToReact]
},
// webpack 各插件对象,在 webpack 的事件流中执行对应的方法。
plugins: [
new webpack.HotModuleReplacementPlugin()
]
};</code></pre>
<p>上面是比较简单的webpack配置文件 webpack.config.js,如果说gulp是一个一元函数,那么,webpack就是一个多元函数或者是加工厂;webpack从入口文件开始,递归找出所有依赖的代码块,再将代码块按照loader解析成新内容,而在webpack会在各个特定的时期广播对应事件,插件会监听这些事件,在某个事件中进行特定的操作。通俗一点来说,webpack本身来递归找到各个文件之间的依赖关系,在这个过程中,使用loaders对文件进行解析,最后,在各个不同的事件阶段,插件可以对文件进行一个统一的处理。</p>
<p>webpack.config文件会包括以下几部分:</p>
<blockquote>1.entry:入口,webpack问此文件入手迭代。<br>2.output: 打包后形成的文件出口。<br>3.module: 模块,在webpack中一个模块对应一个文件。webpack会从entry开始,递归找出所有依赖的模块<br>4.loaders:文件解析的各种转换器<br>5.plugin:拓展插件</blockquote>
<p>webpack的配置文件和构建方式比较复杂,这里不再赘述,感兴趣的同学可以参考我列出来的参考文献第三篇文章,或者可以关注我的专栏,后期我会出一篇关于webpack的学习笔记。</p>
<h2>比较</h2>
<p>所以,我们可以看出来,虽然Webpack与gulp都是前端工程化的管理工具,但是二者的侧重点不同——gulp更加关注的是自动化的构建工具,你把代码写好了,gulp会帮你编译、压缩、解析。而Webpack关注的是在<code>模块化背景下</code>的打包工作;它侧重的还是如何将依赖的文件合理的组织起来,并且实现按需加载。</p>
<h2>总结</h2>
<p>总的来说,虽然webpack以打包起家,但是gulp能够实现的功能,Webpack也能做;那么,是不是我们以后都要唯<code>webpack</code>马首是瞻呢?非也,非也!webpack功能强大,但是它的缺点也来自于此;webpack并非一个轻量级的工具,学习曲线也非gulp那般平缓。曾经,gulp为了弥补js打包方面的不足,也有gulp-webpack插件的出现;但是webpack强大如斯,如果仅仅只是解析es6文件,未免有大马拉小车之感。<br>根据我的项目实践经验,如果你要构建一个复杂的项目,项目使用<code>vue</code>或者<code>react</code>,模块化引领,那么请选择Webpack,Webpack天生<code>模块化</code>,更加适合于<code>SPA</code>的应用场景,而gulp在SPA下明显后力不足。如果你只是开发一个工具,请选择gulp,至于js打包这种工作,有更加专一的<code>rollup</code>。毕竟,如果只是写一个年会抽奖工具活跃气氛,就不需要webpack火种送碳了。</p>
<p>总结下来:gulp与Webapck是各有所长,并不存在东风压倒西风,而在前端工程化的大旗下,并非只有Webpack与gulp,我们还能看到<code>rollup</code>与<code>browserify</code>的一席之地。因此,在真正的工作中,还是要结合项目本身特点,切忌<code>人云亦云</code>。</p>
<h2>参考文献</h2>
<p>1、<a href="https://link.segmentfault.com/?enc=bB%2FuRk%2BQrO7Dq1dUbPaZOQ%3D%3D.ngpS4%2Bevy65ta%2FHf%2FxgfgaE9wjcxWONqK8sBr%2F639cioWPY2icjrtNL%2BfQwIzr1PYk3zod2EsaM9HYxDg%2B4xsQ%3D%3D" rel="nofollow">JavaScript开发者的工具箱 非常实用</a><br>2、<a href="https://link.segmentfault.com/?enc=gjpvqrv%2Bis352fs7wr3kfA%3D%3D.Lox%2FYNZ0Oec%2B4pCg0%2BWulziq3qMwFxavaEy47GuPMACSEuZeQgxhn7lVSMPLC9EJ" rel="nofollow">Gulp官网</a><br>3、<a href="https://segmentfault.com/a/1190000017890529">超级详细的Webpack解读</a>—五星推荐<br>4、<a href="https://link.segmentfault.com/?enc=%2B6cUv7T5HhGImGmhRPOj1g%3D%3D.I%2BjpUPqalbsAmWfchVB24zg4WQCTUsRharckx%2Bv0yqcxNfN0kawRR%2BbUbfqwsBHy" rel="nofollow">端构建工具之争——Webpack vs Gulp 谁会被拍死在沙滩上</a>—五星推荐</p>
JavaScript五十问——浅入深出,自己实现一个 ES 6 Promise
https://segmentfault.com/a/1190000017865637
2019-01-12T13:36:50+08:00
2019-01-12T13:36:50+08:00
蔺相如如
https://segmentfault.com/u/alexruru
23
<h2>前言</h2>
<p>说到 ES6,Promise 是绕不过的问题;如果说 ES6 的 Class 是基于 Javascript 原型继承的<code>封装</code>,那么 Promise 则是对 callback 回调机制的<code>改进</code>。这篇文章,不谈 Promise 的实际应用;聊一下 Promise 的实现原理,从最简单的解决方案入手,一步一步的自己实现一个 SimplePromise。</p>
<h2>正文</h2>
<h3>入门</h3>
<p>从最简单的 Promise 初始化和使用入手:</p>
<pre><code>const pro = new Promise ((res, rej) => {})
pro.then(data => {}, err => {})</code></pre>
<p>Promise 的构造函数如上,需要传递一个函数作为参数,这个函数有两个变量: resolve, reject。而 Promise 有不同的执行状态,分三种情况:Resolve, Reject, Pending。根据以上的信息,写出最基本的 SimplePromise 的类结构:</p>
<pre><code>class SimplePromise{
constructor(handler){
this._status = "PENDING"
handler(this._resolve.bind(this), this._reject.bind(this))//参数函数的作用域指向Class
}
_resolve(){}
_reject(){}
}</code></pre>
<p>接下来思考一下<code>_resolve</code> 与<code>_reject</code>两个函数的作用。我们知道,Promise 根据 then 方法来执行回调,而 then 是根据<code>状态</code>判断要执行的回调函数。不难推导出,<code>_resolve</code> 与<code>_reject</code>正是根据<code>handler</code>的执行来进行状态变更的,而状态只能由<code>Pending</code>向<code>Reslove</code>或<code>Rejected</code>转换,所以有:</p>
<pre><code>class SimplePromise{
constructor(handler){
...
}
_resolve(val){//异步返回的数据
if(this._status === "PENDING"){//保证状态的不可逆性
this._status = "RESOLVED"
this._value = val
}
}
_reject(val){
if(this._status === "PENDING"){
this._status = "REJECTED"
this._value = val
}
}
}</code></pre>
<h3>then的调用逻辑</h3>
<p>下面分析 then 函数的逻辑,从调用入手:</p>
<pre><code>pro.then(data => {}, err => {})</code></pre>
<p>then 接收两个参数,第一个是执行成功调用的函数,第二个是执行失败调用的函数。</p>
<pre><code>class SimplePromise{
constructor(handler){
...
}
_resolve(val){
...
}
_reject(val){
...
}
then(success, fail){
switch (this._status){
case "PENDING":
break;
case "RESOLVED":
success(this._value)
break;
case "REJECTED":
fail(this._value)
break;
}
}
}</code></pre>
<p>以上实现了最简单的一个 Promise<br>测试代码:</p>
<pre><code>const pro = new SimplePromise(function(res, rej) {
let random = Math.random() * 10
if(random > 5){
res("success")
}
else{
rej("fail")
}
})
pro.then(function(data) {
console.log(data)
}, function(err) {
console.log(err)
})
</code></pre>
<p>当然,这不能算是一个 Promise,目前仅仅实现了根据状态调用不同的回调函数。还没有实现异步。<br>那如何实现异步呢?关键在于 then 函数,当判断<code>_status</code>为<code>PENDING</code>时,如何延后调用 <code>success</code>与<code>fail</code>函数,等待状态改变后再调用?</p>
<h3>支持异步</h3>
<p>这里采用数组来存储 fail 与 success 函数:</p>
<pre><code>class SimplePromise{
constructor(handler){
this.status = "PENDING"
this._onSuccess = []//存储fail 与 success 函数
this._onFail = []
handler(this._resolve.bind(this), this._reject.bind(this))
}
_resolve(val){
if(this.status === "PENDING"){
...
let temp
while(this._onSuccess.length > 0){//依次执行onSuccess中的回调函数
temp = this._onSuccess.shift()
temp(val)
}
}
}
_reject(val){
if(this.status === "PENDING"){
...
let temp
while(this._onFail.length > 0){
temp = this._onFail.shift()
temp(val)
}
}
}
then (success, fail){
switch (this.status){
case "PENDING":
this._onSuccess.push(success)
this._onFail.push(fail)
break;
...
}
}
}</code></pre>
<p>使用 <code>onSuccess</code> 和<code> onFail </code>来存储回调函数,当处理状态为<code> PENDING </code>时,将回调函数 <code>push </code>到相应的数组里,当状态变更后,依次执行数组里的回调函数。</p>
<p>测试代码:</p>
<pre><code>const pro = new SimplePromise(function(res, rej) {
setTimeout(function(){
let random = Math.random() * 10
if(random > 5){
res("success")
}
else{
rej("fail")
}
}, 2000)
})
pro.then(function(data) {
console.log(data)
}, function(err) {
console.log(err)
})</code></pre>
<p>两秒后,会执行相应的回调。</p>
<p>到目前为止,最最最简单的一个 Promise 骨架已经基本完成了。但是还有很多功能待完成。现在可以稍微休息一下,喝个咖啡打个鸡血,回来我们会继续让这个 Promise 骨架更加丰满起来。</p>
<p><b>. . . . . .</b></p>
<h3>完善Promise</h3>
<p>欢迎回来,下面我们继续完善我们的 Promise。<br>上面完成了一个最基础的 Promise,然而还远远不够。首先,Promise 需要实现链式调用,其次 Promise 还需要实现 <code>all race resolve reject </code>等静态函数。</p>
<p>首先,如何实现 then 的链式调用呢?需要 then 返回的也是一个 Promise。<br>于是有</p>
<pre><code>class SimplePromise{
...
then(success, fail){
return new SimplePromise((nextSuccess, nextFail) => {
const onFullfil = function(val){
const res = success(val)
nextSuccess(res)
}
const onReject = function(val){
const res = fail(val)
nextSuccess(res) ;
}
switch (this._status){
case "PENDING":
this._onSuccess.push(onFullfil)
this._onFail.push(onReject)
break;
case "RESOLVED":
onFullfil(this._value)
break;
case "REJECTED":
onReject(this._value)
break;
}
})
}
}</code></pre>
<p>测试代码:</p>
<pre><code>const sp = new SimplePromise(function (res, rej){
setTimeout(function(){
let random = Math.random() * 10
random > 5 ? res(random) : rej(random)
}, 1000)
})
sp.then(data => {
console.log("more than 5 " + data)
return data
}, err =>{
console.log("less than 5 " + err)
return err
}).then((data) => {
console.log(data)
})</code></pre>
<h3>then的参数限制</h3>
<p>完成了链式调用,then 方法还有许多其他限制:<br>下面思考 以下问题:代码中四个使用 promise 的语句之间的不同点在哪儿?<br>假设 doSomething 也 doSomethingElse 都返回 Promise</p>
<pre><code>doSomething().then(function () {
return doSomethingElse();
}).then(finalHandler);
doSomething().then(function () {
doSomethingElse();
}).then(finalHandler);;
doSomething().then(doSomethingElse()).then(finalHandler);;
doSomething().then(doSomethingElse).then(finalHandler);;</code></pre>
<p>答案 一会儿再揭晓,我们先来梳理以下then 方法对传入不同类型参数的处理机制:<br>直接上代码:</p>
<pre><code>class SimplePromise{
...
then(success, fail){
return new SimplePromise((nextSuccess, nextFail) => {
const onFullfil = function(val){
if(typeof success !== "function"){
nextSuccess(val)
}
else{
const res = success(val)//success 的返回值
if(res instanceof SimplePromise){//如果success 返回一个promise 对象
res.then(nextSuccess, nextFail)
}
else{
nextSuccess(res)
}
}
}
if(fail){
const onReject = function(val){
if(typeof fail !== "function"){
nextSuccess(val)
}
else{
const res = fail(val)
if(res instanceof SimplePromise){
res.then(nextSuccess, nextFail)
}
else{
nextSuccess(res)
}
}
}
}
else{
onReject = function(){}
}
switch (this._status){
case "PENDING":
this._onSuccess.push(onFullfil)
this._onFail.push(onReject)
break;
case "RESOLVED":
onFullfil(this._value)
break;
case "REJECTED":
onReject(this._value)
break;
}
})
}
}</code></pre>
<p>对于传入 then 方法的参数,首先判断其是否为 function,判断为否,直接执行 下一个 then 的 success 函数;判断为是,接着判断函数的返回值 res 类型是否为 Promise,如果为否,直接执行下一个 then 的 success 函数,如果为是,通过 then 调用接下来的函数。<br>所以,上面的问题就不难得到答案了。</p>
<pre><code><!-- 1 -->
doSomething().then(function () {
return doSomethingElse();//返回值为Promise
}).then(finalHandler);
RETURN:
doSomething
--->doSomethingElse(undefined)
---> final(doSomethingElseResult)</code></pre>
<pre><code><!-- 2 -->
doSomething().then(function () {
doSomethingElse();//返回值为 undefined
}).then(finalHandler);
RETURN:
doSomething
--->doSomethingElse(undefined)
---> final(undefined)
</code></pre>
<pre><code><!-- 3 -->
doSomething().then(doSomethingElse())//参数 typeof != function
.then(finalHandler);
RETURN:
doSomething
doSomethingElse(undefined)
---> final(doSomethingResult)
</code></pre>
<pre><code><!-- 4 -->
doSomething().then(doSomethingElse)//与1的调用方式是不同的
.then(finalHandler);
RETURN:
doSomething
--->doSomethingElse(doSomethingResult)
---> final(doSomethingElseResult)</code></pre>
<p>好,then 方法已经完善好了。</p>
<h3>静态函数</h3>
<p>接下来是 Promise 的各种静态函数</p>
<pre><code>class SimplePromise(){
...
static all(){}
static race(){}
static resolve(){}
static reject(){}
}</code></pre>
<h4><b>all</b></h4>
<pre><code> static all(promiselist){
if(Array.isArray(promiselist)){
const len = promiselist.length;
const count = 0
const arr = []
return new SimplePromise((res, rej) => {
for(let i = 0; i<len; i++){
this.resolve(promiselist[i]).then(data => {
arr[i] = data
count ++
if(count === len){//每一个Promise都执行完毕后返回
res(arr)
}
}, err => {
rej(err)
})
}
})
}
}</code></pre>
<h4><b> race </b></h4>
<pre><code> static race(promiselist){
if(Array.isArray(promiselist)){
const len = promiselist.length
return new SimplePromise((res, rej) =>{
promiselist.forEach(item =>{
this.resolve(item).then(data => {
res(data)
}, err =>{
rej(err)
})
})
})
}
}</code></pre>
<h4><b>resolve</b></h4>
<pre><code> static resolve(obj){
if(obj instanceof SimplePromise){
return obj
}
else {
return new SimplePromise((res) =>{
res(obj)
})
}
}</code></pre>
<h4><b>reject</b></h4>
<pre><code> static reject(obj){
if(obj instanceof SimplePromise){
return obj
}
else {
return new SimplePromise((res, rej) =>{
rej(obj)
})
}
}
</code></pre>
<h2>总结</h2>
<p>现在,一个完整的 Promise 对象就完成了。现在来总结一下 callback 回调和 Promise 的异同吧。<br>其实,不管是 callback 还是 Promise,这二者都是将<code>需要滞后</code>执行方法而<code>提前声明</code>的方式,只不过 callback 的处理方式比较粗犷,将 cb 函数放到异步执行的结尾;而 Promise 优于 cb 的是通过定义了不同的执行状态,更加细致的进行结果处理,提供了很好的 catch 机制,这是其一;其二,then 的链式调用解决了 cb 的回调地狱;但是 then 的链式调用也不是很好的解决方案,如果封装不好,then里面套用大量的代码的话也会引起代码的不美观和阅读上的困难,这一方面的终极解决方法还是 es7 的 async/await。</p>
<h2>后记</h2>
<p>这篇文章的代码是几个星期以前写的,参考的是思否上的一篇关于promise的文章;总结的是我对promise的理解和思考,如果有不准确或错误的地方还希望各位不吝赐教!希望大家积极反馈,一起交流!</p>
<h2>参考文档</h2>
<p>谈一谈使用 Promise 的反模式<<a href="https://link.segmentfault.com/?enc=PFRkJ5KMzbhgvqRT0tTKyg%3D%3D.P8fN9ZCBdZ5Uvin0MtCheIVXYeCY5Gtg06bqI59mS7FTyJLiu48DaHCxA6llMPhumlo3JzSb79tjbq8pkUDy5w%3D%3D" rel="nofollow">https://blog.csdn.net/kingppy...</a></p>
<p>写这篇文章的时候,我是参考我两周前的代码写的,当时的代码思路来源于思否上的谋篇博客,等我找到会贴上来</p>
JavaScript 五十问——认真聊一聊去抖与节流
https://segmentfault.com/a/1190000017847839
2019-01-10T20:06:07+08:00
2019-01-10T20:06:07+08:00
蔺相如如
https://segmentfault.com/u/alexruru
1
<h4>前言</h4>
<p>无论是面试还是在讨论浏览器优化过程中,都会涉及到去抖动和节流的问题。<br>总的来说,这二者是一种限制事件触发频率的方式。不同的是,节流会指定事件触发的时间间隔;而去抖动会指定事件不触发的时间间隔。从结果上来看,节流降低了时间处理的敏感度;而去抖对从触发事件先存储起来,等到超过指定事件间隔后,一起发送。<br>越来越晕,直接上代码:<br><b>HTML</b></p>
<pre><code><input type="text" oninput="fatch()"></code></pre>
<p>这里有一个供用户搜索使用的input标签,有一个input事件会触发的处理函数fatch,这个fatch会根据input的value值向后台去请求联想词。<br>上面代码思路是没有问题的,但是如果不做触发限制的话,可能会产生大量的http请求,而这些请求里面很多可能意义不大,为我们的优化提供了空间;下面,我就采用节流和去抖两种思路来解决这个问题。(一般针对input这种情况,使用去抖解决;这里只是方便做代码说明)</p>
<h4>节流</h4>
<pre><code>function jieliu (func, time){//func 执行函数, time 时间间隔
let lastRun = null
return function(){
const now = new Date()
if(now - lastRun > time){
func(...arguments)
lastRun = now
}
}
}
const listener = jieliu(function(value){//监听函数,指定间隔时间
console.log(value)
}, 1000)
const input = document.querySelector("input")
//调用方法
input.addEventListener("input", function(event){
listener(event.target.value)
})</code></pre>
<p>以上是比较简单的节流实现以及基本的调用方式;使用闭包是为了保存每一次执行的lastRun。基本实现了限制请求频率的需求,但忽略了最后一个的触发。<br>改进如下:</p>
<pre><code>function jieliu (func, time){// 触发时间间隔>time 发送请求
let lastRun = null
let timeout = undefined
return function(){
const self = this;
const now = new Date()
if(now - lastRun > time){
if(timeout){
clearTimeout(timeout)
timeout = undefined
}
func.apply(self, arguments)
lastRun = now
}
else{
if(!timeout){
timeout = setTimeout(func.apply(self, arguments), time)
}
}
}
}</code></pre>
<p>加入timeout,判断是否是最后一次请求。</p>
<h4>去抖动</h4>
<pre><code>function qudou(func, time){
let timeout = undefined
return function(){
const argu = arguments
const self = this
if(timeout){
clearTimeout(timeout)
timeout = undefined
}else{
timeout = setTimeout(func.apply(this, arguments), time)
}
}
}</code></pre>
<p>以上简单实现去抖动,同样,最后一次事件不能够触发处理函数。</p>
<p>改进如下:</p>
<pre><code>function qudou(func, time){//判断连续time时间内不触发,发送func请求
let timeout = undefined;
let lastRun = null
return function(){
const self = this
const now = new Date()
if(now - lastRun > time){
func.apply(self, arguments)
}
else {
if(!timeout){
timeout = setTimeout(func.apply(self, arguments), time)
}
else {
clearTimeout(timeout)
timeout = undefined
}
}
lastRun = new Date()
}
}
</code></pre>
<h4>总结</h4>
<p>通篇写下来,节流主要的实现方式还是通过对比“now”与“lastRun”的时间差,进而减少处理函数的调用次数;而防抖还是通过settimeout来延缓处理函数的调用时机,进而把多次触发的结果汇总一起调用处理函数。</p>
<h4>后记</h4>
<p>节流与去抖动两种方案还是有很大不同的,很多人包括我都很容易搞混。如果大家有更好的解决方案或者需要讨论的地方,欢迎在踊跃留言!</p>
JavaScript 五十问——从源码分析 ES6 Class 的实现机制
https://segmentfault.com/a/1190000017842257
2019-01-10T14:35:09+08:00
2019-01-10T14:35:09+08:00
蔺相如如
https://segmentfault.com/u/alexruru
5
<p>Class是ES6中新加入的继承机制,实际是Javascript关于原型继承机制的语法糖,本质上是对原型继承的封装。本文将会讨论:<br>1、ES6 class的实现细<br>2、相关Object API盘点<br>3、Javascript中的继承实现方案盘点</p>
<h3>正文</h3>
<h4>1、Class 实现细节</h4>
<pre><code>class Person{
constructor(name, age){
this.name = name
this.age = age
}
static type = 'being'
sayName (){
return this.name
}
static intro(){
console.log("")
}
}
class Men extends Person{
constructor(name, age){
super()
this.gender = 'male'
}
}
</code></pre>
<p>const men = new Men()</p>
<p>以上代码是ES6 class的基本使用方式,通过babel解析后,主要代码结构如下:</p>
<pre><code>'use strict';
var _createClass = function () {...}();// 给类添加方法
function _possibleConstructorReturn(self, call) { ...}//实现super
function _inherits(subClass, superClass) {...}// 实现继承
function _classCallCheck(instance, Constructor) {...} // 防止以函数的方式调用class
var Person = function () {
function Person(name, age) {
_classCallCheck(this, Person);
this.name = name;
this.age = age;
}
_createClass(Person, [{
key: 'sayName',
value: function sayName() {
return this.name;
}
}], [{
key: 'intro',
value: function intro() {
console.log("");
}
}]);
return Person;
}();
Person.type = 'being'; //静态变量
var Men = function (_Person) {
_inherits(Men, _Person);
function Men(name, age) {
_classCallCheck(this, Men);
var _this = _possibleConstructorReturn(this, (Men.__proto__ || Object.getPrototypeOf(Men)).call(this));
_this.gender = 'male';
return _this;
}
return Men;
}(Person);
var men = new Men();
</code></pre>
<p>为什么说es6的class 是基于原型继承的封装呢? 开始省略的四个函数又有什么作用呢?<br>下面,我们就从最开始的四个函数入手,详细的解释es6的class 是如何封装的。</p>
<p><b>第一:<code>_classCallCheck</code>函数, 检验构造函数的调用方式:</b><br>代码</p>
<pre><code>function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}</code></pre>
<p>我们知道,在javascript中 person = new Person() ,通常完成以下几件事:<br>1、创建一个新的对象 Object.create()<br>2、将 新对象的 this 指向 构造函数的原型对象<br>3、新对象的__proto__ 指向 构造函数<br>4、执行构造函数<br>而普通函数调用,this通常指向全局<br>因此,<code>_classCallCheck</code>函数是用来检测类的调用方式。防止类的构造函数以普通函数的方式调用。</p>
<p><b>第二: <code>_createClass</code> 给类添加方法</b></p>
<pre><code>var _createClass = function () {
function defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ("value" in descriptor)
descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}
return function (Constructor, protoProps, staticProps) {
if (protoProps) defineProperties(Constructor.prototype, protoProps); //非静态函数 -> 原型
if (staticProps) defineProperties(Constructor, staticProps); return Constructor; // 静态函数 -> 构造函数
};
}();</code></pre>
<p><code>_createClass</code>是一个闭包+立即执行函数,以这种方式模拟一个作用域,将<code>defineProperties</code>私有化。<br>这个函数的主要作用是通过<code>Object.defineProperty</code>给类添加方法,其中将静态方法添加到构造函数上,将非静态的方法添加到构造函数的原型对象上。</p>
<p><b>第三: <code>_inherits</code> 实现继承</b></p>
<pre><code>function _inherits(subClass, superClass) {
if (typeof superClass !== "function" && superClass !== null) {
throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
}
subClass.prototype = Object.create(superClass && superClass.prototype, // 子类的原型的__proto__指向父类的原型
//给子类添加 constructor属性 subclass.prototype.constructor === subclass
{ constructor:
{
value: subClass,
enumerable: false,
writable: true,
configurable: true
}
}
);
if (superClass)
//子类__proto__ 指向父类
Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
}</code></pre>
<p>从这个函数就能够很明显的看出来,class实现继承的机制了。</p>
<p><b>第四: <code> _possibleConstructorReturn</code> super()</b></p>
<pre><code>function _possibleConstructorReturn(self, call) {
if (!self) {
throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); //保证子类构造函数中 显式调用 super()
}
return call && (typeof call === "object" || typeof call === "function") ? call : self;
}</code></pre>
<p>要想理解这个函数的作用,需要结合他的调用场景</p>
<pre><code>var _this = _possibleConstructorReturn(this, (Men.__proto__ || Object.getPrototypeOf(Men)).call(this));// function Men(){}</code></pre>
<p>此时已经执行完<code>_inherits</code>函数,Men.__proto__ === Person<br>相当于:</p>
<pre><code>var _this = _possibleConstructorReturn(this, Person.call(this));</code></pre>
<p>很明显,就是将子类的this 指向父类。</p>
<h4>API 总结</h4>
<p>根据以上的分析,es6 class 的实现机制也可以总结出来了:<br>毫无疑问的,class机制还是在prototype的基础之上进行封装的<br>——contructor 执行构造函数相关赋值<br>——使用 Object.defineProperty()方法 将方法添加的构造函数的原型上或构造函数上<br>——使用 Object.create() 和 Object.setPrototypeOf 实现类之间的继承 子类原型__proto__指向父类原型 子类构造函数__proto__指向父类构造函数<br>——通过变更子类的this 作用域实现super()</p>
<h4>盘点JavaScript中的继承方式</h4>
<p>1.原型链继承<br>2.构造函数继承<br>3.组合继承<br>4.ES6 extends 继承</p>
<p>详细内容可以参考 聊一聊 JavaScript的继承方式<a href="https://segmentfault.com/a/1190000017834614">https://segmentfault.com/a/11...</a></p>
<h4>后记</h4>
<p>终于写完了,在没有网络辅助的情况下写博客真是太难了!绝知此事要躬行呀!<br>原来觉得写一篇关于class的博客还不简单吗,就是原型链继承那一套呗,现在总结下来,还是有很多地方需要注意的;学习到了很多!嗯 不说了, 我还有好几个坑要填呢~<br>如果这篇文章对你有帮助的话,欢迎点赞收藏!<br>如果你有疑问的话,希望积极留言,共同讨论,共同进步!</p>
<h4>参考文档</h4>
<p>ES6—类的实现原理 <a href="https://segmentfault.com/a/1190000008390268">https://segmentfault.com/a/11...</a></p>
<p>JavaScript 红宝书</p>
Javascript 五十问——实现的继承多种方式
https://segmentfault.com/a/1190000017834614
2019-01-09T22:08:08+08:00
2019-01-09T22:08:08+08:00
蔺相如如
https://segmentfault.com/u/alexruru
0
<p>谈到继承,或者更广义上的:一个对象可以使用另外一个对象的属性或方法。实现起来无外乎有两种方式:<br>apply or call 改变this的作用域<br>原型继承 改变__proto__指向,添加作用域链</p>
<p>而JavaScript所有的继承实现,都是围绕以上两点展开的。<br>1.原型链继承<br>2.构造函数继承<br>3.组合继承<br>4.ES6 extends 继承</p>
<h5>原型链继承</h5>
<pre><code>function Father(){}
function Son(){}
Son.prototype = new Father()</code></pre>
<p>缺点很明显:<br>子类构造函数不能传递参数<br>子类只是拷贝父类的引用,父类的引用类型的属性会被所有的子类共享</p>
<h5>构造函数继承</h5>
<pre><code>function Father(){}
function Son(){
Father.apply(this, arguments)
}</code></pre>
<p>解决了参数和引用共享问题,但是父类方法不能够共享。</p>
<h5>组合继承</h5>
<pre><code>function Father(){}
function Son(){
Father.apply(this, arguments)
}
Son.prototype = new Father()</code></pre>
<p>实现了属性分离,方法共享;es5下的<code>完美</code>继承方案</p>
<h5>ES6继承</h5>
<p>我们的主角,ES6 extends,就是对组合继承的改进。不同的是在子类中,子类作用域和父类作用域谁先谁后的问题。</p>
<p>在ES5中,首先声明子类的 作用域,然后在将子类的作用域指向父类</p>
<p>在ES6中,是首先将子类的作用域指向父类,然后在此基础上增强子类的作用域。这也是为什么在子类构造函数中一定要显示调用super()的原因。<br>参考babel转换后的代码:</p>
<pre><code>var Son = function (_Father) {
_inherits(Son, _Person);
function Son() {
_classCallCheck(this, Son);
//为了方便阅读,简略了代码
var _this = _possibleConstructorReturn(this,Father.call(this));
_this.gender = "male";
return _this;//返回的是 指向父类的作用域 _this
}
return Son;
}(Father);</code></pre>
<p>关于更详细的ES6 Class的实现机制,可以参考我的另外一篇文章:聊一聊ES6 CLASS 实现原理<></p>
<h4>后记</h4>
<p>第一次在<code>sifou</code>上发布文章,添加了一个专题——Javascript五十问——里面会细致聊一些关于JavaScript原生和ES6的内容;算是我自己在开发过程中的一点积累;如果哪位发现错误,希望不吝赐教,共同进步!</p>
<h4>参考文档</h4>
<p>一篇文章理解JS继承 <a href="https://segmentfault.com/a/1190000015727237">https://segmentfault.com/a/11...</a><br>Javascript 红宝书<br>阮一峰 ES6标准入门</p>