ACChe

ACChe 查看完整档案

广州编辑  |  填写毕业院校  |  填写所在公司/组织填写个人主网站
编辑

举头思算法,低头写代码...

个人动态

ACChe 赞了回答 · 2018-11-20

解决小程序里,如何让组件最外层的view的高度自适应?

  • component.wxml
<view class="container" style="height:{{componentHeight}}">
    <view class="v1" style="height:20%"> </view>
    <view class="v1" style="height:30%"> </view>
    <view class="v1" style="height:30%"> </view>
    <view class="v1" style="height:20%"> </view>
</view>
  • component.js
Component({
    properties: {
        componentHeight: {
            type: String,
            value: '120rpx'
        }
    }
});
  • index.wxml
<view>
    <componentView component-height="120rpx"></componentView>
</view>

小程序的组件还是不熟啊,建议多看看文档
component文档

关注 2 回答 1

ACChe 提出了问题 · 2018-11-19

解决小程序里,如何让组件最外层的view的高度自适应?

在小程序的项目里,想做一个重用的view,用组件去实现,里面的高度想在用的时候指定,应该怎么写?

相关代码

// 请把代码文本粘贴到下方(请勿用图片代替代码)
component.wxml

<view class="container">
    <view class="v1" style="height:20%"> </view>
    <view class="v1" style="height:30%"> </view>
    <view class="v1" style="height:30%"> </view>
    <view class="v1" style="height:20%"> </view>
</view>

index.wxml

<view>
    <componentView style="height: 120rpx">
        
    </componentView>
</view>

你期待的结果是什么?实际看到的错误信息又是什么?

结果不是高度为0, 就是子view的比例出不来。

求解?!

关注 2 回答 1

ACChe 赞了文章 · 2018-09-12

CSS 边框 轮廓 阴影

概览

边框本应该在盒模型一文中一并介绍,只因 为避免篇幅较长特独立此文与轮廓和阴影一并介绍。

之所以将边框,轮廓和阴影一并介绍,看下图就明白了:

轮廓outline)是绘制于元素周围的一条线,位于边框边缘的外围,可起到突出元素的作用。

边框 (border) 是围绕元素内容和内边距的一条或多条线。

阴影box-shadow)是CSS 3新增的属性,用来向元素框添加阴影。

三者可以单独存在,也可以同时存在。下面将分开对其进行简单介绍。

边框 border

HTML 元素的边框有四个,每个边框有 3 个方面:宽度样式、以及颜色

/* border: 宽度 样式 颜色; */
border: 1px solid red;

/* 等价于 */
border-width: 1px;
border-style: solid;
border-color: red;
边框上边框右边框下边框左边框
borderborder-topborder-rightborder-bottomborder-left
border-widthborder-top-widthborder-right-widthborder-bottom-widthborder-left-width
border-styleborder-top-styleborder-right-styleborder-bottom-styleborder-left-style
border-colorborder-top-colorborder-right-colorborder-bottom-colorborder-left-color

上表对边框的属性进行了分组以方便记忆。第一行属性为简写属性,分别对应其下的三个具体属性。现仅对其中一组具体属性(第一列吧)进行介绍。

宽度 width

边框的宽度有两种值可选,一种是指定长度值,比如 2px0.1em,另一种是使用 3 个关键字之一,它们分别是 thinmedium(默认值) 和 thick

注释:CSS 没有定义 3 个关键字的具体宽度,所以一个用户代理可能把 thinmediumthick 分别设置为等于 5px、3px 和 2px,而另一个用户代理则分别设置为 3px、2px 和 1px。

p {border-style: solid; border-width: 5px;}
p {border-style: solid; border-width: thick;}

由于边框有四个方位,所以border-width有四个值可填,如若部分省略,同样遵循 值复制 原则。

p {border-style: solid; border-width: 5px;} /* 等价于 5px 5px 5px 5px */
p {border-style: solid; border-width: 5px 3px;} /* 等价于 5px 3px 5px 3px */
p {border-style: solid; border-width: 5px 3px 2px;} /* 等价于 5px 3px 2px 3px */

其他具体方位边框宽度(border-top-width,border-left-width等)只能填一个值。

样式 style

样式是边框最重要的一个方面,因为如果没有样式,就没有边框,换句话就是说:宽度和颜色都可以没有,但不能没有样式,样式默认为 none.

CSS 中定义了十种边框样式。

描述
none定义无边框。
hiddennone 相同。
dotted定义点状边框。
dashed定义虚线。
solid定义实线。
double定义双线。双线的宽度等于 border-width 的值。
groove定义 3D 凹槽边框。
ridge定义 3D 垄状边框。
inset定义 3D inset 边框。
outset定义 3D outset 边框。
inherit规定应该从父元素继承边框样式。

还是看一下效果图吧

和宽度一样,样式也可以分别作用在四个方位,并且遵循着相同的值复制规则。

border-style: dotted solid double dashed; 
border-style: dotted solid double;
border-style: dotted solid;
border-style: dotted;

颜色 color

设置边框颜色非常简单。

可以使用任何类型的颜色值,例如可以是命名颜色(red,blue等),也可以是十六进制(#ff0000)和 RGB 值:

p {
    border-style: solid;
    border-color: blue rgb(25%,35%,45%) #909090 red;
}

除了上面的三种值可选外,还有一个 transparent 透明边框可选,不是太常用。

另外,当我们不指定边框颜色的时候,只指定边框样式,边框也是有颜色和宽度的。它将与元素的文本颜色相同。另一方面,如果元素没有任何文本,假设它是一个表格,其中只包含图像,那么该表的边框颜色就是其父元素的文本颜色(因为 color 可以继承)。

注意:在 IE7 之前,没有提供对 transparent 的支持。在以前的版本,IE 会根据元素的 color 值来设置边框颜色。

CSS 3 中对边框进行了丰富,增加了 imageradius 两个属性。

图像 image

边框图像稍微有点复杂,先看一个例子来体会其简单用法:

使用的图像尺寸为: 81 x 81px

对一个 div 元素进行测试

<div class="demo"></div>

相应样式

div.demo {
    width:150px;
    height:80px;
    margin:50px auto;

    border-style:solid;
    border-width:20px;
    border-image:url('border.png') 27 fill/27px/30px repeat;
}

得到下面的样式

上例我们使用了border-image简写属性,其等价于下面的拆写属性:

border-image-source: url('border.png');
border-image-slice: 27 fill;
border-image-width: 27px;
border-image-outset: 30px;
border-image-repeat: repeat;

下面将对拆分属性及其值进行概要介绍。

border-image-source

这个很好理解,用来指定使用的图像。

border-image-slice

这个属性用来控制图像的切分。给定一个边框图像都会经过四次切分,你可以形象的按下图理解其切分流程。

经过四次切分后,得到9个区域(俗称“九宫格”)

border-image-slice 属性及值就是控制切分的偏移量的,类似border-width,它也有四个方位,并且遵循 值复制 规则,不同的是:截取的尺寸不需要单位,尺寸后可以添加fill关键字

border-image-slice: 27; /*等价于 27 27 27 27*/
border-image-slice: 27 20 fill; /*等价于 27 20 27 20 fill*/
border-image-slice: 27 20 22 fill; /*等价于 27 20 22 20 fill*/

关键字fill标示中间区域将出现(出现归出现,至于能否看见,要看你截取的中间区域部分是否有可见图像),如下图:

border-image-width

边框图像宽度属性用来设置边框图像的宽度,边框图像宽度和边框宽度不是一码事,但有部分关联。

如果没有边框图像宽度定义,则默认边框图像宽度等于边框宽度,如果有边框图像宽度定义,则以边框图像宽度为准。

下图为没有定义边框图像宽度的情形,边框图像宽度等于边框宽度。

下图为设置了边框图像宽度的情形:

边框图像宽度大于边框宽度,图像向边框内溢出。

边框图像宽度也类似与边框宽度的定义,也是四个方位值,也遵循值复制规则。

border-image-outset

此属性用来设置边框图像向边框外偏移的量。

在上面的图中我们看到图像向边框内溢出了,如果担心向内溢出遮挡内容,我们可以通过此属性让其向外溢出一定尺寸。

border-image-outset: 30px 10px;

此属性的值设置也是四个方位,同样遵循值复制规则。

border-image-repeat

此属性控制着边框图像复制延伸的方式。有三个值可选:

stretch

拉伸图像来填充区域,比较好理解。默认属性值

repeat

平铺(重复)图像来填充区域,从中间向两边复制

round

类似 repeat 值。如果无法完整平铺所有图像,则对图像进行缩放以适应区域。

使用 round 最明显的好处是保证截取区域的完整性,而不像 repeat 会出现一半的情况,round 可能会进行少量的缩放。

下图为 上下 round 左右 repeat 可以对比一下区别。

另外,此属性虽说也有四个方位,但最多只能设置两个值:上下一致,左右一致

border-image-repeat: round repeat;

border-image

边框图像的简写属性,可以将上述具体属性集中到此属性中,知道其语法格式即可,上面已经有过例子了。

border-image: border-image-source border-image-slice/border-image-width/border-image-outset border-image-repeat;

圆角 radius

CSS 3 中新增了边框圆角的样式。

圆角相对比较简单,只有一个简写属性(border-radius)和四个具体方位属性,对于圆角来说,四个方位不再是上下左右了,而是:左上角(border-top-left-radius右上角(border-top-right-radius右下角(border-bottom-right-radius左下角(border-bottom-left-radius。属性值同样遵循 值复制 规则。

border-radius: 15px; /*等价于 15px 15px 15px 15px*/
border-radius: 10% 10px; /*等价于 10% 10px 10% 10px*/

由于每个角都涉及两个方位(如:左上,关联 top 和 left),所以每个角可以设置两个值分别对应角上的两个方位,两个值使用 / 分隔,前面的表示上下的值,后面的表示左右的值。如果两个值相同,只写一个即可。

border-top-left-radius: 15px/15px; /*等价于 15px*/

通过下图你会对圆角有更加形象的认识。

轮廓 outline

outline 轮廓是绘制于元素周围的线,位于边框边缘的外围,可起到突出元素的作用。

注释:轮廓线不会占据空间。

轮廓的使用同边框,但没有边框那么复杂。轮廓只有:outline(简写属性,集中样式,尺寸,颜色的设置),outline-style,outline-width,outline-color,4个属性可选,没有像边框似的对四个方位的具体设置相关的属性。

由于其使用及相应的属性值跟边框相同,故不再赘述。

阴影 box-shadow

box-shadow 阴影属性用来向框添加一个或多个阴影。

div {
    box-shadow: 10px 10px 5px #888888;
}

语法

box-shadow: h-shadow v-shadow blur spread color inset;

box-shadow 向框添加一个或多个阴影。该属性是由逗号分隔的阴影列表,每个阴影由 2-4 个长度值、可选的颜色值以及可选的 inset 关键词来规定。省略长度的值是 0。

描述
h-shadow必需。水平阴影的位置。允许负值。
v-shadow必需。垂直阴影的位置。允许负值。
blur可选。模糊距离。
spread可选。阴影扩散的尺寸。
color可选。阴影的颜色。
inset可选。将外部阴影 (默认为外部阴影) 改为内部阴影。

水平和垂直阴影位置是必选项,其值可以为负值。

画过素描的同学会很容易理解阴影(没画过的也很容易理解的)。

阴影跟光源有关,光源的位置不同,阴影也不同,光源的数量多少也决定着阴影的效果,物体的形状也影响阴影。

想象上图是一个桶,所成的阴影是我们从桶的上面去看,光线方向大致在桶的左上方所致。

代码实现为:

div {
    width:100px;
    height:100px;
    margin: 100px auto;
    background-color:#ff8888;
    border:1px solid #000;
    border-radius: 50%;
    box-shadow: 10px 10px 5px #888888,
        10px 10px 5px #888 inset;
}

小结

边框和轮廓都是由样式,宽度和颜色进行定义,还可以给边框添加圆角,使其更加美观,阴影的使用可以使元素更加符合生活场景。

查看原文

赞 12 收藏 45 评论 2

ACChe 赞了文章 · 2018-09-12

CSS 边框 轮廓 阴影

概览

边框本应该在盒模型一文中一并介绍,只因 为避免篇幅较长特独立此文与轮廓和阴影一并介绍。

之所以将边框,轮廓和阴影一并介绍,看下图就明白了:

轮廓outline)是绘制于元素周围的一条线,位于边框边缘的外围,可起到突出元素的作用。

边框 (border) 是围绕元素内容和内边距的一条或多条线。

阴影box-shadow)是CSS 3新增的属性,用来向元素框添加阴影。

三者可以单独存在,也可以同时存在。下面将分开对其进行简单介绍。

边框 border

HTML 元素的边框有四个,每个边框有 3 个方面:宽度样式、以及颜色

/* border: 宽度 样式 颜色; */
border: 1px solid red;

/* 等价于 */
border-width: 1px;
border-style: solid;
border-color: red;
边框上边框右边框下边框左边框
borderborder-topborder-rightborder-bottomborder-left
border-widthborder-top-widthborder-right-widthborder-bottom-widthborder-left-width
border-styleborder-top-styleborder-right-styleborder-bottom-styleborder-left-style
border-colorborder-top-colorborder-right-colorborder-bottom-colorborder-left-color

上表对边框的属性进行了分组以方便记忆。第一行属性为简写属性,分别对应其下的三个具体属性。现仅对其中一组具体属性(第一列吧)进行介绍。

宽度 width

边框的宽度有两种值可选,一种是指定长度值,比如 2px0.1em,另一种是使用 3 个关键字之一,它们分别是 thinmedium(默认值) 和 thick

注释:CSS 没有定义 3 个关键字的具体宽度,所以一个用户代理可能把 thinmediumthick 分别设置为等于 5px、3px 和 2px,而另一个用户代理则分别设置为 3px、2px 和 1px。

p {border-style: solid; border-width: 5px;}
p {border-style: solid; border-width: thick;}

由于边框有四个方位,所以border-width有四个值可填,如若部分省略,同样遵循 值复制 原则。

p {border-style: solid; border-width: 5px;} /* 等价于 5px 5px 5px 5px */
p {border-style: solid; border-width: 5px 3px;} /* 等价于 5px 3px 5px 3px */
p {border-style: solid; border-width: 5px 3px 2px;} /* 等价于 5px 3px 2px 3px */

其他具体方位边框宽度(border-top-width,border-left-width等)只能填一个值。

样式 style

样式是边框最重要的一个方面,因为如果没有样式,就没有边框,换句话就是说:宽度和颜色都可以没有,但不能没有样式,样式默认为 none.

CSS 中定义了十种边框样式。

描述
none定义无边框。
hiddennone 相同。
dotted定义点状边框。
dashed定义虚线。
solid定义实线。
double定义双线。双线的宽度等于 border-width 的值。
groove定义 3D 凹槽边框。
ridge定义 3D 垄状边框。
inset定义 3D inset 边框。
outset定义 3D outset 边框。
inherit规定应该从父元素继承边框样式。

还是看一下效果图吧

和宽度一样,样式也可以分别作用在四个方位,并且遵循着相同的值复制规则。

border-style: dotted solid double dashed; 
border-style: dotted solid double;
border-style: dotted solid;
border-style: dotted;

颜色 color

设置边框颜色非常简单。

可以使用任何类型的颜色值,例如可以是命名颜色(red,blue等),也可以是十六进制(#ff0000)和 RGB 值:

p {
    border-style: solid;
    border-color: blue rgb(25%,35%,45%) #909090 red;
}

除了上面的三种值可选外,还有一个 transparent 透明边框可选,不是太常用。

另外,当我们不指定边框颜色的时候,只指定边框样式,边框也是有颜色和宽度的。它将与元素的文本颜色相同。另一方面,如果元素没有任何文本,假设它是一个表格,其中只包含图像,那么该表的边框颜色就是其父元素的文本颜色(因为 color 可以继承)。

注意:在 IE7 之前,没有提供对 transparent 的支持。在以前的版本,IE 会根据元素的 color 值来设置边框颜色。

CSS 3 中对边框进行了丰富,增加了 imageradius 两个属性。

图像 image

边框图像稍微有点复杂,先看一个例子来体会其简单用法:

使用的图像尺寸为: 81 x 81px

对一个 div 元素进行测试

<div class="demo"></div>

相应样式

div.demo {
    width:150px;
    height:80px;
    margin:50px auto;

    border-style:solid;
    border-width:20px;
    border-image:url('border.png') 27 fill/27px/30px repeat;
}

得到下面的样式

上例我们使用了border-image简写属性,其等价于下面的拆写属性:

border-image-source: url('border.png');
border-image-slice: 27 fill;
border-image-width: 27px;
border-image-outset: 30px;
border-image-repeat: repeat;

下面将对拆分属性及其值进行概要介绍。

border-image-source

这个很好理解,用来指定使用的图像。

border-image-slice

这个属性用来控制图像的切分。给定一个边框图像都会经过四次切分,你可以形象的按下图理解其切分流程。

经过四次切分后,得到9个区域(俗称“九宫格”)

border-image-slice 属性及值就是控制切分的偏移量的,类似border-width,它也有四个方位,并且遵循 值复制 规则,不同的是:截取的尺寸不需要单位,尺寸后可以添加fill关键字

border-image-slice: 27; /*等价于 27 27 27 27*/
border-image-slice: 27 20 fill; /*等价于 27 20 27 20 fill*/
border-image-slice: 27 20 22 fill; /*等价于 27 20 22 20 fill*/

关键字fill标示中间区域将出现(出现归出现,至于能否看见,要看你截取的中间区域部分是否有可见图像),如下图:

border-image-width

边框图像宽度属性用来设置边框图像的宽度,边框图像宽度和边框宽度不是一码事,但有部分关联。

如果没有边框图像宽度定义,则默认边框图像宽度等于边框宽度,如果有边框图像宽度定义,则以边框图像宽度为准。

下图为没有定义边框图像宽度的情形,边框图像宽度等于边框宽度。

下图为设置了边框图像宽度的情形:

边框图像宽度大于边框宽度,图像向边框内溢出。

边框图像宽度也类似与边框宽度的定义,也是四个方位值,也遵循值复制规则。

border-image-outset

此属性用来设置边框图像向边框外偏移的量。

在上面的图中我们看到图像向边框内溢出了,如果担心向内溢出遮挡内容,我们可以通过此属性让其向外溢出一定尺寸。

border-image-outset: 30px 10px;

此属性的值设置也是四个方位,同样遵循值复制规则。

border-image-repeat

此属性控制着边框图像复制延伸的方式。有三个值可选:

stretch

拉伸图像来填充区域,比较好理解。默认属性值

repeat

平铺(重复)图像来填充区域,从中间向两边复制

round

类似 repeat 值。如果无法完整平铺所有图像,则对图像进行缩放以适应区域。

使用 round 最明显的好处是保证截取区域的完整性,而不像 repeat 会出现一半的情况,round 可能会进行少量的缩放。

下图为 上下 round 左右 repeat 可以对比一下区别。

另外,此属性虽说也有四个方位,但最多只能设置两个值:上下一致,左右一致

border-image-repeat: round repeat;

border-image

边框图像的简写属性,可以将上述具体属性集中到此属性中,知道其语法格式即可,上面已经有过例子了。

border-image: border-image-source border-image-slice/border-image-width/border-image-outset border-image-repeat;

圆角 radius

CSS 3 中新增了边框圆角的样式。

圆角相对比较简单,只有一个简写属性(border-radius)和四个具体方位属性,对于圆角来说,四个方位不再是上下左右了,而是:左上角(border-top-left-radius右上角(border-top-right-radius右下角(border-bottom-right-radius左下角(border-bottom-left-radius。属性值同样遵循 值复制 规则。

border-radius: 15px; /*等价于 15px 15px 15px 15px*/
border-radius: 10% 10px; /*等价于 10% 10px 10% 10px*/

由于每个角都涉及两个方位(如:左上,关联 top 和 left),所以每个角可以设置两个值分别对应角上的两个方位,两个值使用 / 分隔,前面的表示上下的值,后面的表示左右的值。如果两个值相同,只写一个即可。

border-top-left-radius: 15px/15px; /*等价于 15px*/

通过下图你会对圆角有更加形象的认识。

轮廓 outline

outline 轮廓是绘制于元素周围的线,位于边框边缘的外围,可起到突出元素的作用。

注释:轮廓线不会占据空间。

轮廓的使用同边框,但没有边框那么复杂。轮廓只有:outline(简写属性,集中样式,尺寸,颜色的设置),outline-style,outline-width,outline-color,4个属性可选,没有像边框似的对四个方位的具体设置相关的属性。

由于其使用及相应的属性值跟边框相同,故不再赘述。

阴影 box-shadow

box-shadow 阴影属性用来向框添加一个或多个阴影。

div {
    box-shadow: 10px 10px 5px #888888;
}

语法

box-shadow: h-shadow v-shadow blur spread color inset;

box-shadow 向框添加一个或多个阴影。该属性是由逗号分隔的阴影列表,每个阴影由 2-4 个长度值、可选的颜色值以及可选的 inset 关键词来规定。省略长度的值是 0。

描述
h-shadow必需。水平阴影的位置。允许负值。
v-shadow必需。垂直阴影的位置。允许负值。
blur可选。模糊距离。
spread可选。阴影扩散的尺寸。
color可选。阴影的颜色。
inset可选。将外部阴影 (默认为外部阴影) 改为内部阴影。

水平和垂直阴影位置是必选项,其值可以为负值。

画过素描的同学会很容易理解阴影(没画过的也很容易理解的)。

阴影跟光源有关,光源的位置不同,阴影也不同,光源的数量多少也决定着阴影的效果,物体的形状也影响阴影。

想象上图是一个桶,所成的阴影是我们从桶的上面去看,光线方向大致在桶的左上方所致。

代码实现为:

div {
    width:100px;
    height:100px;
    margin: 100px auto;
    background-color:#ff8888;
    border:1px solid #000;
    border-radius: 50%;
    box-shadow: 10px 10px 5px #888888,
        10px 10px 5px #888 inset;
}

小结

边框和轮廓都是由样式,宽度和颜色进行定义,还可以给边框添加圆角,使其更加美观,阴影的使用可以使元素更加符合生活场景。

查看原文

赞 12 收藏 45 评论 2

ACChe 赞了问题 · 2018-09-10

解决微信小程序中遮罩层的滚动穿透问题

图片描述

使用小程序的modal组件实现遮罩层效果时,会出现滚动穿透的问题,即遮罩层后面的页面依旧可以滚动,这个问题有解决办法吗?

关注 17 回答 10

ACChe 赞了回答 · 2018-09-10

解决微信小程序中遮罩层的滚动穿透问题

如果弹出层没有滚动事件,就直接在蒙板上加catchtouchmove="move" move:function(){};

如果弹出层有滚动事件,那么在弹出层出现的时候给底部的containerView加上一个class 消失的时候移除。

<view class="{{showSearchView?'tripList_root':''}}">

.tripList_root{

    top:0px;

    left: 0px;

    width: 100%;

    height: 100%;

    overflow: hidden;

    position: fixed;

    z-index: 0;

}

  1. 亲测有效。

关注 17 回答 10

ACChe 赞了回答 · 2018-08-15

为什么vue组件的属性,有的需要加冒号“:”,有的不用?

加冒号,两点:

  • 例如

export default {
    data(){
        return {
            item: {
                src: 'xxxxx'
            }
        }
    }
}

 <img :data-original='item.src'>,说明 img标签的src属性是动态的,根据你的data里面的数据而来

 - 另一种,例如你这个tab是一个component,表示你把tab组件的line-width属性赋值了2,通过你的tab组件的props:['line-width']

关注 10 回答 9

ACChe 收藏了文章 · 2018-08-10

Axios源码深度剖析 - AJAX新王者

Axios源码分析 - XHR篇

文章源码托管在github上,欢迎fork指正!

axios 是一个基于 Promise 的http请求库,可以用在浏览器和node.js中,目前在github上有 42K 的star数

备注:

  1. 每一小节都会从两个方面介绍:如何使用 -> 源码分析
  2. [工具方法简单介绍]一节可先跳过,后面用到了再过来查看
  3. axios最核心的技术点是如何拦截请求响应并修改请求参数修改响应数据axios是如何用promise搭起基于xhr的异步桥梁的

axios项目目录结构


├── /dist/                     # 项目输出目录
├── /lib/                      # 项目源码目录
│ ├── /cancel/                 # 定义取消功能
│ ├── /core/                   # 一些核心功能
│ │ ├── Axios.js               # axios的核心主类
│ │ ├── dispatchRequest.js     # 用来调用http请求适配器方法发送请求
│ │ ├── InterceptorManager.js  # 拦截器构造函数
│ │ └── settle.js              # 根据http响应状态,改变Promise的状态
│ ├── /helpers/                # 一些辅助方法
│ ├── /adapters/               # 定义请求的适配器 xhr、http
│ │ ├── http.js                # 实现http适配器
│ │ └── xhr.js                 # 实现xhr适配器
│ ├── axios.js                 # 对外暴露接口
│ ├── defaults.js              # 默认配置 
│ └── utils.js                 # 公用工具
├── package.json               # 项目信息
├── index.d.ts                 # 配置TypeScript的声明文件
└── index.js                   # 入口文件

注:因为我们需要要看的代码都是/lib/目录下的文件,所以以下所有涉及到文件路径的地方,
我们都会在/lib/下进行查找

名词解释

  • 拦截器 interceptors

    (如果你熟悉中间件,那么就很好理解了,因为它起到的就是基于promise的中间件的作用)

    拦截器分为请求拦截器和响应拦截器,顾名思义:
    请求拦截器(interceptors.request)是指可以拦截住每次或指定http请求,并可修改配置项
    响应拦截器(interceptors.response)可以在每次http请求后拦截住每次或指定http请求,并可修改返回结果项。

    这里先简单说明,后面会做详细的介绍如何拦截请求响应并修改请求参数修改响应数据

  • 数据转换器 (其实就是对数据进行转换,比如将对象转换为JSON字符串)

    数据转换器分为请求转换器和响应转换器,顾名思义:
    请求转换器(transformRequest)是指在请求前对数据进行转换,
    响应转换器(transformResponse)主要对请求响应后的响应体做数据转换。

  • http请求适配器(其实就是一个方法)

    在axios项目里,http请求适配器主要指两种:XHR、http。
    XHR的核心是浏览器端的XMLHttpRequest对象,
    http核心是node的http[s].request方法

    当然,axios也留给了用户通过config自行配置适配器的接口的,
    不过,一般情况下,这两种适配器就能够满足从浏览器端向服务端发请求或者从node的http客户端向服务端发请求的需求。

    本次分享主要围绕XHR。

  • config配置项 (其实就是一个对象)

    此处我们说的config,在项目内不是真的都叫config这个变量名,这个名字是我根据它的用途起的一个名字,方便大家理解。

    在axios项目中的,设置读取config时,
    有的地方叫它defaults(/lib/defaults.js),这儿是默认配置项,
    有的地方叫它config,如Axios.prototype.request的参数,再如xhrAdapterhttp请求适配器方法的参数。

    config在axios项目里的是非常重要的一条链,是用户跟axios项目内部“通信”的主要桥梁。

axios内部的运作流程图

图片描述

工具方法简单介绍

(注:本节可先跳过,后面用到了再过来查看)

有一些方法在项目中多处使用,简单介绍下这些方法

1.bind: 给某个函数指定上下文,也就是this指向


bind(fn, context); 

实现效果同Function.prototype.bind方法: fn.bind(context)

2.forEach:遍历数组或对象


var utils = require('./utils');
var forEach = utils.forEach;

// 数组
utils.forEach([], (value, index, array) => {})

// 对象
utils.forEach({}, (value, key, object) => {})

3.merge:深度合并多个对象为一个对象


var utils = require('./utils');
var merge = utils.merge;

var obj1 = {
  a: 1,
  b: {
    bb: 11,
    bbb: 111,
  }
};
var obj2 = {
  a: 2,
  b: {
    bb: 22,
  }
};
var mergedObj = merge(obj1, obj2); 

mergedObj对象是:


{ 
  a: 2, 
  b: { 
    bb: 22, 
    bbb: 111 
  } 
}

4.extend:将一个对象的方法和属性扩展到另外一个对象上,并指定上下文


var utils = require('./utils');
var extend = utils.extend;

var context = {
  a: 4,
};
var target = {
  k: 'k1',
  fn(){
    console.log(this.a + 1)
  }
};
var source = {
  k: 'k2',
  fn(){
    console.log(this.a - 1)
  }
};
let extendObj = extend(target, source, context);

extendObj对象是:


{
  k: 'k2',
  fn: source.fn.bind(context),
}

执行extendObj.fn(), 打印3

axios为何会有多种使用方式

如何使用


// 首先将axios包引进来
import axios from 'axios'

第1种使用方式:axios(option)


axios({
  url,
  method,
  headers,
})

第2种使用方式:axios(url[, option])


axios(url, {
  method,
  headers,
})

第3种使用方式(对于get、delete等方法):axios[method](url[, option])


axios.get(url, {
  headers,
})

第4种使用方式(对于post、put等方法):axios[method](url[, data[, option]])


axios.post(url, data, {
  headers,
})

第5种使用方式:axios.request(option)


axios.request({
  url,
  method,
  headers,
})

源码分析

作为axios项目的入口文件,我们先来看下axios.js的源码
能够实现axios的多种使用方式的核心是createInstance方法:


// /lib/axios.js
function createInstance(defaultConfig) {
  // 创建一个Axios实例
  var context = new Axios(defaultConfig);

  // 以下代码也可以这样实现:var instance = Axios.prototype.request.bind(context);
  // 这样instance就指向了request方法,且上下文指向context,所以可以直接以 instance(option) 方式调用 
  // Axios.prototype.request 内对第一个参数的数据类型判断,使我们能够以 instance(url, option) 方式调用
  var instance = bind(Axios.prototype.request, context);

  // 把Axios.prototype上的方法扩展到instance对象上,
  // 这样 instance 就有了 get、post、put等方法
  // 并指定上下文为context,这样执行Axios原型链上的方法时,this会指向context
  utils.extend(instance, Axios.prototype, context);

  // 把context对象上的自身属性和方法扩展到instance上
  // 注:因为extend内部使用的forEach方法对对象做for in 遍历时,只遍历对象本身的属性,而不会遍历原型链上的属性
  // 这样,instance 就有了  defaults、interceptors 属性。(这两个属性后面我们会介绍)
  utils.extend(instance, context);

  return instance;
}

// 接收默认配置项作为参数(后面会介绍配置项),创建一个Axios实例,最终会被作为对象导出
var axios = createInstance(defaults);

以上代码看上去很绕,其实createInstance最终是希望拿到一个Function,这个Function指向Axios.prototype.request,这个Function还会有Axios.prototype上的每个方法作为静态方法,且这些方法的上下文都是指向同一个对象。

那么在来看看Axios、Axios.prototype.request的源码是怎样的?

Axios是axios包的核心,一个Axios实例就是一个axios应用,其他方法都是对Axios内容的扩展
Axios构造函数的核心方法是request方法,各种axios的调用方式最终都是通过request方法发请求的


// /lib/core/Axios.js
function Axios(instanceConfig) {
  this.defaults = instanceConfig;
  this.interceptors = {
    request: new InterceptorManager(),
    response: new InterceptorManager()
  };
}

Axios.prototype.request = function request(config) {
  // ...省略代码
};

// 为支持的请求方法提供别名
utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) {
  Axios.prototype[method] = function(url, config) {
    return this.request(utils.merge(config || {}, {
      method: method,
      url: url
    }));
  };
});
utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
  Axios.prototype[method] = function(url, data, config) {
    return this.request(utils.merge(config || {}, {
      method: method,
      url: url,
      data: data
    }));
  };
});

通过以上代码,我们就可以以多种方式发起http请求了: axios()、axios.get()、axios.post()

一般情况,项目使用默认导出的axios实例就可以满足需求了,
如果不满足需求需要创建新的axios实例,axios包也预留了接口,
看下面的代码:


// /lib/axios.js  -  31行
axios.Axios = Axios;
axios.create = function create(instanceConfig) {
  return createInstance(utils.merge(defaults, instanceConfig));
};

说完axios为什么会有这么多种使用方式,可能你心中会有一个疑问:
使用axios时,无论get方法还是post方法,最终都是调用的Axios.prototype.request方法,那么这个方法是怎么根据我们的config配置发请求的呢?

在开始说Axios.prototype.request之前,我们先来捋一捋在axios项目中,用户配置的config是怎么起作用的?

用户配置的config是怎么起作用的

这里说的config,指的是贯穿整个项目的配置项对象,
通过这个对象,可以设置:

`http请求适配器、请求地址、请求方法、请求头header、
请求数据、请求或响应数据的转换、请求进度、http状态码验证规则、超时、取消请求等`

可以发现,几乎axios所有的功能都是通过这个对象进行配置和传递的,
既是axios项目内部的沟通桥梁,也是用户跟axios进行沟通的桥梁。

首先我们看看,用户能以什么方式定义配置项:


import axios from 'axios'

// 第1种:直接修改Axios实例上defaults属性,主要用来设置通用配置
axios.defaults[configName] = value;

// 第2种:发起请求时最终会调用Axios.prototype.request方法,然后传入配置项,主要用来设置“个例”配置
axios({
  url,
  method,
  headers,
})

// 第3种:新建一个Axios实例,传入配置项,此处设置的是通用配置
let newAxiosInstance = axios.create({
  [configName]: value,
})

看下 Axios.prototype.request 方法里的一行代码: (/lib/core/Axios.js - 第35行)


config = utils.merge(defaults, {method: 'get'}, this.defaults, config);

可以发现此处将默认配置对象defaults/lib/defaults.js)、Axios实例属性this.defaultsrequest请求的参数config进行了合并。

由此得出,多处配置的优先级由低到高是:
—> 默认配置对象defaults/lib/defaults.js)
—> { method: 'get' }
—> Axios实例属性this.defaults
—> request请求的参数config

留给大家思考一个问题: defaultsthis.defaults 什么时候配置是相同的,什么时候是不同的?

至此,我们已经得到了将多处merge后的config对象,那么这个对象在项目中又是怎样传递的呢?


Axios.prototype.request = function request(config) {
  // ...
  config = utils.merge(defaults, {method: 'get'}, this.defaults, config);

  var chain = [dispatchRequest, undefined];
  // 将config对象当作参数传给Primise.resolve方法
  var promise = Promise.resolve(config);

  // ...省略代码
  
  while (chain.length) {
    // config会按序通过 请求拦截器 - dispatchRequest方法 - 响应拦截器
    // 关于拦截器 和 dispatchRequest方法,下面会作为一个专门的小节来介绍。
    promise = promise.then(chain.shift(), chain.shift());
  }

  return promise;
};

至此,config走完了它传奇的一生 -_-
下一节就要说到重头戏了: Axios.prototype.request

axios.prototype.request

这里面的代码比较复杂,一些方法需要追根溯源才能搞清楚,
所以只需对chain数组有个简单的了解就好,涉及到的拦截器、[dispatchRequest]后面都会详细介绍

chain数组是用来盛放拦截器方法和dispatchRequest方法的,
通过promise从chain数组里按序取出回调函数逐一执行,最后将处理后的新的promise在Axios.prototype.request方法里返回出去,
并将response或error传送出去,这就是Axios.prototype.request的使命了。

查看源码:


// /lib/core/Axios.js
Axios.prototype.request = function request(config) {
  // ...
  var chain = [dispatchRequest, undefined];
  var promise = Promise.resolve(config);

  this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
    chain.unshift(interceptor.fulfilled, interceptor.rejected);
  });
  this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
    chain.push(interceptor.fulfilled, interceptor.rejected);
  });

  while (chain.length) {
    promise = promise.then(chain.shift(), chain.shift());
  }

  return promise;
};

此时,你一定对拦截器充满了好奇,这个拦截器到底是个什么家伙,下一节就让我们一探究竟吧

如何拦截请求响应并修改请求参数修改响应数据

如何使用


// 添加请求拦截器
const myRequestInterceptor = axios.interceptors.request.use(config => {
    // 在发送http请求之前做些什么
    return config; // 有且必须有一个config对象被返回
}, error => {
    // 对请求错误做些什么
    return Promise.reject(error);
});

// 添加响应拦截器
axios.interceptors.response.use(response => {
  // 对响应数据做点什么
  return response; // 有且必须有一个response对象被返回
}, error => {
  // 对响应错误做点什么
  return Promise.reject(error);
});

// 移除某次拦截器
axios.interceptors.request.eject(myRequestInterceptor);

思考

  1. 是否可以直接 return error?

axios.interceptors.request.use(config => config, error => {
  // 是否可以直接 return error ?
  return Promise.reject(error); 
});
  1. 如何实现promise的链式调用

new People('whr').sleep(3000).eat('apple').sleep(5000).eat('durian');

// 打印结果
// (等待3s)--> 'whr eat apple' -(等待5s)--> 'whr eat durian'

源码分析

关于拦截器,名词解释一节已经做过简单说明。

每个axios实例都有一个interceptors实例属性,
interceptors对象上有两个属性requestresponse


function Axios(instanceConfig) {
  // ...
  this.interceptors = {
    request: new InterceptorManager(),
    response: new InterceptorManager()
  };
}

这两个属性都是一个InterceptorManager实例,而这个InterceptorManager构造函数就是用来管理拦截器的。

我们先来看看InterceptorManager构造函数:

InterceptorManager构造函数就是用来实现拦截器的,这个构造函数原型上有3个方法:use、eject、forEach。
关于源码,其实是比较简单的,都是用来操作该构造函数的handlers实例属性的。


// /lib/core/InterceptorManager.js

function InterceptorManager() {
  this.handlers = []; // 存放拦截器方法,数组内每一项都是有两个属性的对象,两个属性分别对应成功和失败后执行的函数。
}

// 往拦截器里添加拦截方法
InterceptorManager.prototype.use = function use(fulfilled, rejected) {
  this.handlers.push({
    fulfilled: fulfilled,
    rejected: rejected
  });
  return this.handlers.length - 1;
};

// 用来注销指定的拦截器
InterceptorManager.prototype.eject = function eject(id) {
  if (this.handlers[id]) {
    this.handlers[id] = null;
  }
};

// 遍历this.handlers,并将this.handlers里的每一项作为参数传给fn执行
InterceptorManager.prototype.forEach = function forEach(fn) {
  utils.forEach(this.handlers, function forEachHandler(h) {
    if (h !== null) {
      fn(h);
    }
  });
};

那么当我们通过axios.interceptors.request.use添加拦截器后,
axios内部又是怎么让这些拦截器能够在请求前、请求后拿到我们想要的数据的呢?

先看下代码:


// /lib/core/Axios.js
Axios.prototype.request = function request(config) {
  // ...
  var chain = [dispatchRequest, undefined];

  // 初始化一个promise对象,状态微resolved,接收到的参数微config对象
  var promise = Promise.resolve(config);

  // 注意:interceptor.fulfilled 或 interceptor.rejected 是可能为undefined
  this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
    chain.unshift(interceptor.fulfilled, interceptor.rejected);
  });

  this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
    chain.push(interceptor.fulfilled, interceptor.rejected);
  });

  // 添加了拦截器后的chain数组大概会是这样的:
  // [
  //   requestFulfilledFn, requestRejectedFn, ..., 
  //   dispatchRequest, undefined,
  //   responseFulfilledFn, responseRejectedFn, ....,
  // ]

  // 只要chain数组长度不为0,就一直执行while循环
  while (chain.length) {
    // 数组的 shift() 方法用于把数组的第一个元素从其中删除,并返回第一个元素的值。
    // 每次执行while循环,从chain数组里按序取出两项,并分别作为promise.then方法的第一个和第二个参数

    // 按照我们使用InterceptorManager.prototype.use添加拦截器的规则,正好每次添加的就是我们通过InterceptorManager.prototype.use方法添加的成功和失败回调

    // 通过InterceptorManager.prototype.use往拦截器数组里添加拦截器时使用的数组的push方法,
    // 对于请求拦截器,从拦截器数组按序读到后是通过unshift方法往chain数组数里添加的,又通过shift方法从chain数组里取出的,所以得出结论:对于请求拦截器,先添加的拦截器会后执行
    // 对于响应拦截器,从拦截器数组按序读到后是通过push方法往chain数组里添加的,又通过shift方法从chain数组里取出的,所以得出结论:对于响应拦截器,添加的拦截器先执行

    // 第一个请求拦截器的fulfilled函数会接收到promise对象初始化时传入的config对象,而请求拦截器又规定用户写的fulfilled函数必须返回一个config对象,所以通过promise实现链式调用时,每个请求拦截器的fulfilled函数都会接收到一个config对象

    // 第一个响应拦截器的fulfilled函数会接受到dispatchRequest(也就是我们的请求方法)请求到的数据(也就是response对象),而响应拦截器又规定用户写的fulfilled函数必须返回一个response对象,所以通过promise实现链式调用时,每个响应拦截器的fulfilled函数都会接收到一个response对象

    // 任何一个拦截器的抛出的错误,都会被下一个拦截器的rejected函数收到,所以dispatchRequest抛出的错误才会被响应拦截器接收到。

    // 因为axios是通过promise实现的链式调用,所以我们可以在拦截器里进行异步操作,而拦截器的执行顺序还是会按照我们上面说的顺序执行,也就是 dispatchRequest 方法一定会等待所有的请求拦截器执行完后再开始执行,响应拦截器一定会等待 dispatchRequest 执行完后再开始执行。

    promise = promise.then(chain.shift(), chain.shift());

  }

  return promise;
};

现在,你应该已经清楚了拦截器是怎么回事,以及拦截器是如何在Axios.prototype.request方法里发挥作用的了,
那么处于"中游位置"的dispatchRequest是如何发送http请求的呢?

dispatchrequest都做了哪些事

dispatchRequest主要做了3件事:
1,拿到config对象,对config进行传给http请求适配器前的最后处理;
2,http请求适配器根据config配置,发起请求
3,http请求适配器请求完成后,如果成功则根据header、data、和config.transformResponse(关于transformResponse,下面的数据转换器会进行讲解)拿到数据转换后的response,并return。


// /lib/core/dispatchRequest.js
module.exports = function dispatchRequest(config) {
  throwIfCancellationRequested(config);

  // Support baseURL config
  if (config.baseURL && !isAbsoluteURL(config.url)) {
    config.url = combineURLs(config.baseURL, config.url);
  }

  // Ensure headers exist
  config.headers = config.headers || {};

  // 对请求data进行转换
  config.data = transformData(
    config.data,
    config.headers,
    config.transformRequest
  );

  // 对header进行合并处理
  config.headers = utils.merge(
    config.headers.common || {},
    config.headers[config.method] || {},
    config.headers || {}
  );

  // 删除header属性里无用的属性
  utils.forEach(
    ['delete', 'get', 'head', 'post', 'put', 'patch', 'common'],
    function cleanHeaderConfig(method) {
      delete config.headers[method];
    }
  );

  // http请求适配器会优先使用config上自定义的适配器,没有配置时才会使用默认的XHR或http适配器,不过大部分时候,axios提供的默认适配器是能够满足我们的
  var adapter = config.adapter || defaults.adapter;

  return adapter(config).then(/**/);
};

好了,看到这里,我们是时候梳理一下:axios是如何用promise搭起基于xhr的异步桥梁的?

axios是如何用promise搭起基于xhr的异步桥梁的

axios是如何通过Promise进行异步处理的?

如何使用


import axios from 'axios'

axios.get(/**/)
.then(data => {
  // 此处可以拿到向服务端请求回的数据
})
.catch(error => {
  // 此处可以拿到请求失败或取消或其他处理失败的错误对象
})

源码分析

先来一个图简单的了解下axios项目里,http请求完成后到达用户的顺序流:

图片描述

通过axios为何会有多种使用方式我们知道,
用户无论以什么方式调用axios,最终都是调用的Axios.prototype.request方法,
这个方法最终返回的是一个Promise对象。


Axios.prototype.request = function request(config) {
  // ...
  var chain = [dispatchRequest, undefined];
  // 将config对象当作参数传给Primise.resolve方法
  var promise = Promise.resolve(config);

  while (chain.length) {
    promise = promise.then(chain.shift(), chain.shift());
  }

  return promise;
};

Axios.prototype.request方法会调用dispatchRequest方法,而dispatchRequest方法会调用xhrAdapter方法,xhrAdapter方法返回的是还一个Promise对象


// /lib/adapters/xhr.js
function xhrAdapter(config) {
  return new Promise(function dispatchXhrRequest(resolve, reject) {
    // ... 省略代码
  });
};

xhrAdapter内的XHR发送请求成功后会执行这个Promise对象的resolve方法,并将请求的数据传出去,
反之则执行reject方法,并将错误信息作为参数传出去。


// /lib/adapters/xhr.js
var request = new XMLHttpRequest();
var loadEvent = 'onreadystatechange';

request[loadEvent] = function handleLoad() {
  // ...
  // 往下走有settle的源码
  settle(resolve, reject, response);
  // ...
};
request.onerror = function handleError() {
  reject(/**/);
  request = null;
};
request.ontimeout = function handleTimeout() {
  reject(/**/);
  request = null;
};

验证服务端的返回结果是否通过验证:


// /lib/core/settle.js
function settle(resolve, reject, response) {
  var validateStatus = response.config.validateStatus;
  if (!response.status || !validateStatus || validateStatus(response.status)) {
    resolve(response);
  } else {
    reject(/**/);
  }
};

回到dispatchRequest方法内,首先得到xhrAdapter方法返回的Promise对象,
然后通过.then方法,对xhrAdapter返回的Promise对象的成功或失败结果再次加工,
成功的话,则将处理后的response返回,
失败的话,则返回一个状态为rejected的Promise对象,


  return adapter(config).then(function onAdapterResolution(response) {
    // ...
    return response;
  }, function onAdapterRejection(reason) {
    // ...
    return Promise.reject(reason);
  });
};

那么至此,用户调用axios()方法时,就可以直接调用Promise的.then.catch进行业务处理了。

回过头来,我们在介绍dispatchRequest一节时说到的数据转换,而axios官方也将数据转换专门作为一个亮点来介绍的,那么数据转换到底能在使用axios发挥什么功效呢?

数据转换器-转换请求与响应数据

如何使用

  1. 修改全局的转换器

import axios from 'axios'

// 往现有的请求转换器里增加转换方法
axios.defaults.transformRequest.push((data, headers) => {
  // ...处理data
  return data;
});

// 重写请求转换器
axios.defaults.transformRequest = [(data, headers) => {
  // ...处理data
  return data;
}];

// 往现有的响应转换器里增加转换方法
axios.defaults.transformResponse.push((data, headers) => {
  // ...处理data
  return data;
});

// 重写响应转换器
axios.defaults.transformResponse = [(data, headers) => {
  // ...处理data
  return data;
}];
  1. 修改某次axios请求的转换器

import axios from 'axios'

// 往已经存在的转换器里增加转换方法
axios.get(url, {
  // ...
  transformRequest: [
    ...axios.defaults.transformRequest, // 去掉这行代码就等于重写请求转换器了
    (data, headers) => {
      // ...处理data
      return data;
    }
  ],
  transformResponse: [
    ...axios.defaults.transformResponse, // 去掉这行代码就等于重写响应转换器了
    (data, headers) => {
      // ...处理data
      return data;
    }
  ],
})

源码分析

默认的defaults配置项里已经自定义了一个请求转换器和一个响应转换器,
看下源码:


// /lib/defaults.js
var defaults = {

  transformRequest: [function transformRequest(data, headers) {
    normalizeHeaderName(headers, 'Content-Type');
    // ...
    if (utils.isArrayBufferView(data)) {
      return data.buffer;
    }
    if (utils.isURLSearchParams(data)) {
      setContentTypeIfUnset(headers, 'application/x-www-form-urlencoded;charset=utf-8');
      return data.toString();
    }
    if (utils.isObject(data)) {
      setContentTypeIfUnset(headers, 'application/json;charset=utf-8');
      return JSON.stringify(data);
    }
    return data;
  }],

  transformResponse: [function transformResponse(data) {
    if (typeof data === 'string') {
      try {
        data = JSON.parse(data);
      } catch (e) { /* Ignore */ }
    }
    return data;
  }],
  
};

那么在axios项目里,是在什么地方使用了转换器呢?

请求转换器的使用地方是http请求前,使用请求转换器对请求数据做处理,
然后传给http请求适配器使用。


// /lib/core/dispatchRequest.js
function dispatchRequest(config) {
  
  config.data = transformData(
    config.data,
    config.headers,
    config.transformRequest
  );

  return adapter(config).then(/* ... */);
};

看下transformData方法的代码,
主要遍历转换器数组,分别执行每一个转换器,根据data和headers参数,返回新的data。


// /lib/core/transformData.js
function transformData(data, headers, fns) {
  utils.forEach(fns, function transform(fn) {
    data = fn(data, headers);
  });
  return data;
};

响应转换器的使用地方是在http请求完成后,根据http请求适配器的返回值做数据转换处理:


// /lib/core/dispatchRequest.js
return adapter(config).then(function onAdapterResolution(response) {
    // ...
    response.data = transformData(
      response.data,
      response.headers,
      config.transformResponse
    );

    return response;
  }, function onAdapterRejection(reason) {
    if (!isCancel(reason)) {
      // ...
      if (reason && reason.response) {
        reason.response.data = transformData(
          reason.response.data,
          reason.response.headers,
          config.transformResponse
        );
      }
    }

    return Promise.reject(reason);
  });

转换器和拦截器的关系?

拦截器同样可以实现转换请求和响应数据的需求,但根据作者的设计和综合代码可以看出,
在请求时,拦截器主要负责修改config配置项,数据转换器主要负责转换请求体,比如转换对象为字符串
在请求响应后,拦截器可以拿到response,数据转换器主要负责处理响应体,比如转换字符串为对象。

axios官方是将"自动转换为JSON数据"作为一个独立的亮点来介绍的,那么数据转换器是如何完成这个功能的呢?
其实非常简单,我们一起看下吧。

自动转换json数据

在默认情况下,axios将会自动的将传入的data对象序列化为JSON字符串,将响应数据中的JSON字符串转换为JavaScript对象

源码分析


// 请求时,将data数据转换为JSON 字符串
// /lib/defaults.js 
transformRequest: [function transformRequest(data, headers) {
  // ...
    if (utils.isObject(data)) {
      setContentTypeIfUnset(headers, 'application/json;charset=utf-8');
      return JSON.stringify(data);
    }
    return data;
}]

// 得到响应后,将请求到的数据转换为JSON对象
// /lib/defaults.js
transformResponse: [function transformResponse(data) {
    if (typeof data === 'string') {
      try {
        data = JSON.parse(data);
      } catch (e) { /* Ignore */ }
    }
    return data;
}]

至此,axios项目的运作流程已经介绍完毕,是不是已经打通了任督二脉了呢
接下来我们一起看下axios还带给了我们哪些好用的技能点吧。

header设置

如何使用


import axios from 'axios'

// 设置通用header
axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; // xhr标识

// 设置某种请求的header
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=utf-8';

// 设置某次请求的header
axios.get(url, {
  headers: {
    'Authorization': 'whr1',
  },
})

源码分析


// /lib/core/dispatchRequest.js  -  44行

  config.headers = utils.merge(
    config.headers.common || {},
    config.headers[config.method] || {},
    config.headers || {}
  );

如何取消已经发送的请求

如何使用


import axios from 'axios'

// 第一种取消方法
axios.get(url, {
  cancelToken: new axios.CancelToken(cancel => {
    if (/* 取消条件 */) {
      cancel('取消日志');
    }
  })
});

// 第二种取消方法
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
axios.get(url, {
  cancelToken: source.token
});
source.cancel('取消日志');

源码分析


// /cancel/CancelToken.js  -  11行
function CancelToken(executor) {
 
  var resolvePromise;
  this.promise = new Promise(function promiseExecutor(resolve) {
    resolvePromise = resolve;
  });
  var token = this;
  executor(function cancel(message) {
    if (token.reason) {
      return;
    }
    token.reason = new Cancel(message);
    resolvePromise(token.reason);
  });
}

// /lib/adapters/xhr.js  -  159行
if (config.cancelToken) {
    config.cancelToken.promise.then(function onCanceled(cancel) {
        if (!request) {
            return;
        }
        request.abort();
        reject(cancel);
        request = null;
    });
}

取消功能的核心是通过CancelToken内的this.promise = new Promise(resolve => resolvePromise = resolve)
得到实例属性promise,此时该promise的状态为pending
通过这个属性,在/lib/adapters/xhr.js文件中继续给这个promise实例添加.then方法
xhr.js文件的159行config.cancelToken.promise.then(message => request.abort()));

CancelToken外界,通过executor参数拿到对cancel方法的控制权,
这样当执行cancel方法时就可以改变实例的promise属性的状态为rejected
从而执行request.abort()方法达到取消请求的目的。

上面第二种写法可以看作是对第一种写法的完善,
因为很多是时候我们取消请求的方法是用在本次请求方法外,
例如,发送A、B两个请求,当B请求成功后,取消A请求。


// 第1种写法:
let source;
axios.get(Aurl, {
  cancelToken: new axios.CancelToken(cancel => {
    source = cancel;
  })
});
axios.get(Burl)
.then(() => source('B请求成功了'));

// 第2种写法:
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
axios.get(Aurl, {
  cancelToken: source.token
});
axios.get(Burl)
.then(() => source.cancel('B请求成功了'));

相对来说,我更推崇第1种写法,因为第2种写法太隐蔽了,不如第一种直观好理解。

发现的问题
  1. /lib/adapters/xhr.js文件中,onCanceled方法的参数不应该叫message么,为什么叫cancel?
  2. /lib/adapters/xhr.js文件中,onCanceled方法里,reject里应该将config信息也传出来

跨域携带cookie

如何使用


import axios from 'axios'

axios.defaults.withCredentials = true;

源码分析

我们在用户配置的config是怎么起作用的一节已经介绍了config在axios项目里的传递过程,
由此得出,我们通过axios.defaults.withCredentials = true做的配置,
/lib/adapters/xhr.js里是可以取到的,然后通过以下代码配置到xhr对象项。


var request = new XMLHttpRequest();

// /lib/adapters/xhr.js
if (config.withCredentials) {
  request.withCredentials = true;
}

超时配置及处理

如何使用


import axios from 'axios'

axios.defaults.timeout = 3000;

源码分析


// /adapters/xhr.js
request.timeout = config.timeout;

// /adapters/xhr.js
// 通过createError方法,将错误信息合为一个字符串
request.ontimeout = function handleTimeout() {
  reject(createError('timeout of ' + config.timeout + 'ms exceeded', 
    config, 'ECONNABORTED', request));
};
  • axios库外如何添加超时后的处理

axios().catch(error => {
  const { message } = error;
  if (message.indexOf('timeout') > -1){
    // 超时处理
  }
})

改写验证成功或失败的规则validatestatus

自定义http状态码的成功、失败范围

如何使用


import axios from 'axios'

axios.defaults.validateStatus = status => status >= 200 && status < 300;

源码分析

在默认配置中,定义了默认的http状态码验证规则,
所以自定义validateStatus其实是对此处方法的重写


// `/lib/defaults.js`
var defaults = {
  // ...
  validateStatus: function validateStatus(status) {
    return status >= 200 && status < 300;
  },
  // ...
}

axios是何时开始验证http状态码的?


// /lib/adapters/xhr.js
var request = new XMLHttpRequest();
var loadEvent = 'onreadystatechange';

// /lib/adapters/xhr.js
// 每当 readyState 改变时,就会触发 onreadystatechange 事件
request[loadEvent] = function handleLoad() {
  if (!request || (request.readyState !== 4 && !xDomain)) {
    return;
  }
  // ...省略代码
  var response = {
      // ...
      // IE sends 1223 instead of 204 (https://github.com/axios/axios/issues/201)
      status: request.status === 1223 ? 204 : request.status,
      config: config,
  };
  settle(resolve, reject, response);
  // ...省略代码
}

// /lib/core/settle.js
function settle(resolve, reject, response) {
  // 如果我们往上捣一捣就会发现,config对象的validateStatus就是我们自定义的validateStatus方法或默认的validateStatus方法
  var validateStatus = response.config.validateStatus;
  // validateStatus验证通过,就会触发resolve方法
  if (!response.status || !validateStatus || validateStatus(response.status)) {
    resolve(response);
  } else {
    reject(createError(
      'Request failed with status code ' + response.status,
      response.config,
      null,
      response.request,
      response
    ));
  }
};

总结

axios这个项目里,有很多对JS使用很巧妙的地方,比如对promise的串联操作(当然你也可以说这块是借鉴很多异步中间件的处理方式),让我们可以很方便对请求前后的各种处理方法的流程进行控制;很多实用的小优化,比如请求前后的数据处理,省了程序员一遍一遍去写JSON.xxx了;同时支持了浏览器和node两种环境,对使用node的项目来说无疑是极好的。

总之,这个能够在github斩获42K+(截止2018.05.27)的star,实力绝不是盖的,值得好好交交心!

查看原文

ACChe 赞了文章 · 2017-12-27

年终盘点 | 最全面的2017物联网安全事件盘点

2017年随着物联网的高速发展,人们生活中与物联网设备的接触越来越频繁,人与物联网之间的联系更加紧密。IPv6通讯协议、5G通信的推广,网络传输和响应的速度越来越快,万物互联时代即将来临。根据研究机构IDC的报告,2020年全球物联网市场规模将达到17000亿美元,物联网设备将有200亿台。在2025年,这一数字更将达到754亿台。

同时,由于物联网设备的防护难度或对安全性的忽视,使得物联网设备异常脆弱,很容易被攻击者发现漏洞并利用。据统计,物联网设备在全球范围内已暴露7100多万台,包含路由器、摄像头、防火墙、打印机、VPN等设备类型,物联网设备的安全需求日益凸显。

小编将为大家盘点2017年发生的十二大物联网安全事件。

undefined

一、智能玩具泄露200万父母与儿童语音信息

今年3月,Spiral Toys旗下的CloudPets系列动物填充玩具遭遇数据泄露,敏感客户数据库受到恶意入侵。此次事故泄露信息包括玩具录音、MongoDB泄露的数据、220万账户语音信息、数据库勒索信息等。这些数据被保存在一套未经密码保护的公开数据库当中。
Spiral Toys公司将客户数据库保存在可公开访问的位置之外,还利用一款未经任何验证机制保护的Amazon托管服务存储客户的个人资料、儿童姓名及其与父母、亲属及朋友间的关系信息。只需要了解文件的所处位置,任何人都能够轻松获取到该数据。

2015年11月,香港玩具制造商VTech就曾遭遇入侵,近500万名成年用户的姓名、电子邮箱地址、密码、住址以及超过20万儿童的姓名、性别与生日不慎外泄。就在一个月后,一位研究人员又发现美泰公司生产的联网型芭比娃娃中存在的漏洞可能允许黑客拦截用户的实时对话。

家长应该提高忧患意识,给小孩买任何可联网智能玩具之前,三思而后行。

图片描述

二、基带漏洞可攻击数百万部华为手机

今年4月,安全公司Comsecuris的一名安全研究员发现,未公开的基带漏洞MIAMI影响了华为智能手机、笔记本WWAN模块以及loT(物联网)组件。

基带是蜂窝调制解调器制造商使用的固件,用于智能手机连接到蜂窝网络,发送和接收数据,并进行语音通话。攻击者可通过基带漏洞监听手机通信,拨打电话,发送短信,或者进行大量隐蔽,不为人知的通信。

该漏洞是HiSliconBalong芯片组中的4G LTE调制解调器(俗称猫)引发的。Hisilion科技是华为的一个子公司,同时Balong应用处理器叫做:Kirin。

这些有漏洞的固件存在于华为荣耀系列手机中。研究人员无法具体确定有多少设备受到了这个漏洞的影响。他们估计有数千万的华为智能手机可能收到攻击。仅在2016年第三季度销售的3300万元的智能手机中,其中就有50%使用了这个芯片。

图片描述

三、三星Tizen操作系统存在严重安全漏洞

今年4月,三星Tizen操作系统被发现存在40多个安全漏洞,Tizen操作系统被应用在三星智能电视、智能手表、Z系列手机上,全球有不少用户正在使用。

这些漏洞可能让黑客更容易从远程攻击与控制设备,且三星在过去8个月以来一直没有修复这些三星在产品测试中编码错误所引起的漏洞。安全专家狠批其程序代码早已过时,黑客可以利用这些漏洞自远程完全地控制这些物联网装置。

值得一提的是三星目前大约有3000万台电视搭载了Tizen系统,而且三星更是计划到今年年底之前有1000万部手机运行该系统,并希望藉此减少对Android系统的依赖,但很显然Tizen现在仍不安全。

图片描述

四、无人机多次入侵成都双流国际机场

今年4月,成都双流连续发生多起无人机(无人飞行器)黑飞事件,导致百余架次航班被迫备降或返航,超过万名旅客受阻滞留机场,经济损失以千万元计,旅客的生命安全和损失更是遭到了巨大的威胁。

无人机已经进入人们的工作和生活。不仅在国防、救援、勘探等领域发挥着越来越重的作用,更成为物流、拍摄、旅游等商业服务的新模式。一台无人机由通信系统、传感器、动力系统、储能装置、任务载荷系统、控制电路和机体等多个模块组成。与我们平常使用的智能手机、平板电脑一样,在系统、信号、应用上面临各类安全威胁。

在今年11月份的一次安全会议上,阿里巴巴安全研究人员做了远程劫持无人机的演示,一个专业人员无需软件漏洞就能Root(获得管理员权限)无人机。而就在1年前的2016年黑帽安全亚洲峰会上,IBM安全专家也演示了远程遥控两公里内的无人机起飞的案例,攻击者只需要多掌握一点无线电通信的基础知识就能够完成劫持操作。

针对越来越多的安全威胁,为了保障无人机的运行安全,顶象技术联手迅蚁网络推出国内首个无人机安全解决方案——“无人机全链路防护体系”。在设备端:通过虚机源码保护技术对核心算法、系统源代码进行保护,以防范系统的代码泄露、被破解,保障无人机的系统安全。在通讯端:配置链路保护技术,防范在数据传输中被窃听、篡改、劫持,保障通信指令的可信有效和操控安全。在服务器端:部署云安全防护,保障服务器、网络和各个应用的安全,并提供实施风险决策和智能分析。

图片描述

五、Avanti Markets自动售货机泄露用户数据

今年七月,美国自动售货机供应商 Avanti Markets遭遇黑客入侵内网。攻击者在终端支付设备中植入恶意软件,并窃取了用户信用卡账户以及生物特征识别数据等个人信息。该公司的售货机大多分布在各大休息室,售卖饮料、零食等副食品,顾客可以用信用卡支付、指纹扫描支付或现金支付的方式买单。Avanti Markets的用户多达160万。

根据某位匿名者提供的消息,Avanti 没有采取任何安全措施保护数据安全,连基本的 P2P 加密都没有做到。

事实上,售货终端以及支付终端等IoT设备遭遇入侵在近几年似乎已成为家常便饭。支付卡机器以及POS终端之所以备受黑客欢迎,主要是因为从这里窃取到的数据很容易变现。遗憾的是,POS终端厂商总是生产一批批不安全的产品,而且只在产品上市发布之后才考虑到安全问题。

图片描述

六、17.5 万个安防摄像头被曝漏洞

今年八月,深圳某公司制造的17.5万个物联网安防摄像头被爆可能遭受黑客攻击,这些安防摄像头可以提供监控和多项安全解决方案,包括网络摄像头、传感器和警报器等。

安全专家在该公司制造的两个型号的安防摄像头中找到了多个缓冲区溢出漏洞。这些安防摄像头都是通用即插即用(UPnP)设备,它们能自动在路由器防火墙上打开端口接受来自互联网的访问。

安全专家注意到,两款安防摄像头可能会遭受两种不同的网络攻击,一种攻击会影响摄像头的网络服务器服务,另一种则会波及 RSTP(实时串流协议)服务器。

研究人员称这两款安防摄像头的漏洞很容易就会被黑客利用,只需使用默认凭证登陆,任何人都能访问摄像头的转播画面。同时,摄像头存在的缓冲区溢出漏洞还使黑客能对其进行远程控制。

图片描述

七、超1700对台IoT设备Telnet密码列表遭泄露

今年八月,安全研究人员 Ankit Anubhav 在 Twitter 上分享了一则消息,声称超 1700 台 IoT 设备的有效 Telnet 密码列表遭泄露,这些密码可以被黑客用来扩大僵尸网络进行 DDoS 攻击的动力来源。

这份列表中包含了33138 个IP地址、设备名称和telnet密码,列表中大部分的用户名密码组合都是”admin:admin”或者”root:root”等。这整份列表中包含143种密码组合,其中60种密码组合都来自于Mirai Telnet扫描器。GDI 研究人员在分析了上述列表后确认它由 8200 个独特 IP 地址组成,大约每 2.174 个 IP 地址是通过远程登录凭证进行访问的。然而,该列表中的 61% IP 地址位于中国。

该列表最初于今年 6 月在 Pastebin 平台出现,早期名单的泄露者与此前发布有效登录凭据转储、散发僵尸网络源代码的黑客是同一人。当天正值七夕,简直就是国内黑客们的七夕礼物。

图片描述

八、蓝牙协议爆严重安全漏洞,影响53亿设备

物联网安全研究公司Armis在蓝牙协议中发现了8个零日漏洞,这些漏洞将影响超过53亿设备——从Android、iOS、Windows以及Linux系统设备到使用短距离无线通信技术的物联网设备,利用这些蓝牙协议漏洞,Armis构建了一组攻击向量(attack vector)“BlueBorne”,演示中攻击者完全接管支持蓝牙的设备,传播恶意软件,甚至建立一个“中间人”(MITM)连接。

研究人员表示,想要成功实施攻击,必备的因素是:受害者设备中的蓝牙处于“开启”状态,以及很明显的一点,要尽可能地靠近攻击者的设备。此外,需要注意的是,成功的漏洞利用甚至不需要将脆弱设备与攻击者的设备进行配对。

BlueBorne可以服务于任何恶意目的,例如网络间谍、数据窃取、勒索攻击,甚至利用物联网设备创建大型僵尸网络(如Mirai僵尸网络),或是利用移动设备创建僵尸网络(如最近的WireX僵尸网络)。BlueBorne攻击向量可以穿透安全的‘气隙’网络(将电脑与互联网以及任何连接到互联网上的电脑进行隔离),这一点是其他大多数攻击向量所不具备的能力。

图片描述

九、WPA2爆严重安全漏洞,黑客可任意读取信息

今年10月,有安全专家表示WiFi的WPA2(WPA2是一种保护无线网络安全的加密协议)存在重大漏洞,导致黑客可任意读取通过WAP2保护的任何无线网络的所有信息。

据发现该漏洞的比利时鲁汶大学计算机安全学者马蒂·凡赫尔夫(Mathy Vanhoef)称:“我们发现了WPA2的严重漏洞,这是一种如今使用最广泛的WiFi网络保护协议。黑客可以使用这种新颖的攻击技术来读取以前假定为安全加密的信息,如信用卡号、密码、聊天信息、电子邮件、照片等等。”

据悉,该漏洞名叫“KRACK”,存在于所有应用WPA2协议的产品或服务中。其中,Android和Linux最为脆弱,Windows、OpenBSD、iOS、macOS、联发科技、Linksys等无线产品都受影响。

“KRACK”漏洞利用有一定局限性,比如,需要在正常WiFi信号辐射到范围内。另外,该漏洞可以让中间人窃取无线通信中的数据,而不是直接破解WiFi的密码。

图片描述

十、智能家居设备存在漏洞,吸尘器秒变监视器

今年11月,Check Point研究人员表示LG智能家居设备存在漏洞,黑客可以利用该漏洞完全控制一个用户账户,然后远程劫持LG SmartThinQ家用电器,包括冰箱,干衣机,洗碗机,微波炉以及吸尘机器人。

LG智能家居的移动端应用程序允许用户远程控制其设备(包括打开和关闭它们)。例如,用户可以在回家前启动烤箱和空调,在进超市前检查智能冰箱中还有多少库存,或者检查洗衣机何时完成一个洗衣循环。当用户离开时,无论设备是开启的还是关闭的,网络犯罪分子都可以得到一个完美的入侵机会,并将它们转换为实时监控设备。

研究人员演示了黑客通过控制安装在设备内的集成摄像头将LG Hom-Bot变成一个间谍。他们分析了Hom-Bot并找到了通用异步收发传输器(UART)的连接,当连接被找到时,研究人员就可以操纵它来访问文件系统,一旦主进程被调试,他们就可以找到启动Hom-Bot与SmartThinQ移动端应用程序之间用于通信的代码了。

迄今为止LG已售出超过100万台Hom-Bot吸尘器,但并非所有型号都具有HomeGuard安全监控功能。

十一、美国交通指示牌被攻击,播放反特朗普语言

今年12月,位于达拉斯北中央高速公路附近的一个电子交通指示牌遭到了不明黑客的攻击。标志牌的显示内容遭到了篡改,被用于显示针对美国现任总统唐纳德·特朗普(Donald Trump)以及其支持者的侮辱性言语。

事件发生在周五晚上,这些信息被持续不间断地循环播放,并一直持续到周六早上。如此一块指示牌不仅震惊了人们,还造成了交通拥堵,因为大多数司机决定停下来“拍照留念”。值得注意的是,这并不是美国首次遭遇电子交通指示牌被黑客攻击事件。在2015年12月,特朗普的一位支持者在位于加利福尼亚州科罗纳市的一个高速公路牌上留下了“为唐纳德·特朗普投票(Vote Donald Trump)”的消息。

安全专家表示,攻击电子交通指示牌是很简单的。因为,它们的控制后台总是采用默认密码,并提供有关如何打开控制台电源、关闭标志显示、关闭快速消息以及创建自定义消息的说明。

图片描述

十二、1000余台利盟(Lexmark)打印机在线暴露,涉及众多国家政府办公室

NewSky Security的安全研究人员最近发现超过1000台利盟(Lexmark)打印机因人为配置错误而在线暴露,任何能够连接到互联网的人都可以轻松地对其进行访问。

研究人员确定的易受攻击打印机具体数量为1123台,它们均没有设置密码保护。这意味着,只要潜在攻击者能够找到这些打印机,就可以执行多种不同类型的活动。比如,添加后门、劫持打印作业、使打印机脱机,甚至可以发送大量打印垃圾内容的作业指令造成打印机的物理损坏。

这些打印机来自全球多个国家,涉及企业、大学,甚至还包括某些国家政府办公室。NewSky Security的研究人员在使用Shodan引擎进行搜索时,就发现了一台在线暴露的打印机似乎属于美国拉斐特市政府办公室。

图片描述

总结

上述曝光的物联网安全事件,仅仅是呈现在大家眼前的冰山一角,隐藏在背后的物联网安全威胁层出不穷,2018的安全形势将会更加严峻。随着物联网逐渐走入千家万户的生活当中,物联网设备将成为黑客们新的战场,而且黑客攻击日益组织化、产业化,攻击对象的广度及深度,将有大幅度的变化。

查看原文

赞 5 收藏 3 评论 0

ACChe 赞了文章 · 2017-12-27

iOS-去除图片中指定范围内颜色的三种方式

实际项目场景:去除图片的纯白色背景图,获得一张透明底图片用于拼图功能

介绍两种途径的三种处理方式(不知道为啥想起了孔乙己),具体性能鶸并未对比,如果有大佬能告知,不胜感激。

  • Core Image
  • Core Graphics/Quarz 2D

Core Image

Core Image是一个很强大的框架。它可以让你简单地应用各种滤镜来处理图像,比如修改鲜艳程度,色泽,或者曝光。 它利用GPU(或者CPU)来非常快速、甚至实时地处理图像数据和视频的帧。并且隐藏了底层图形处理的所有细节,通过提供的API就能简单的使用了,无须关心OpenGL或者OpenGL ES是如何充分利用GPU的能力的,也不需要你知道GCD在其中发挥了怎样的作用,Core Image处理了全部的细节。

Chroma Key Filter

在苹果官方文档Core Image Programming Guide中,提到了Chroma Key Filter Recipe对于处理背景的范例

其中使用了HSV颜色模型,因为HSV模型,对于颜色范围的表示,相比RGB更加友好。

大致过程处理过程:

  1. 创建一个映射希望移除颜色值范围的立方体贴图cubeMap,将目标颜色的Alpha置为0.0f
  2. 使用CIColorCube滤镜和cubeMap对源图像进行颜色处理
  3. 获取到经过CIColorCube处理的Core Image对象CIImage,转换为Core Graphics中的CGImageRef对象,通过imageWithCGImage:获取结果图片

注意:第三步中,不可以直接使用imageWithCIImage:,因为得到的并不是一个标准的UIImage,如果直接拿来用,会出现不显示的情况。

- (UIImage *)removeColorWithMinHueAngle:(float)minHueAngle maxHueAngle:(float)maxHueAngle image:(UIImage *)originalImage{
    CIImage *image = [CIImage imageWithCGImage:originalImage.CGImage];
    CIContext *context = [CIContext contextWithOptions:nil];// kCIContextUseSoftwareRenderer : CPURender
    /** 注意
     *  UIImage 通过CIimage初始化,得到的并不是一个通过类似CGImage的标准UIImage
     *  所以如果不用context进行渲染处理,是没办法正常显示的
     */
    CIImage *renderBgImage = [self outputImageWithOriginalCIImage:image minHueAngle:minHueAngle maxHueAngle:maxHueAngle];
    CGImageRef renderImg = [context createCGImage:renderBgImage fromRect:image.extent];
    UIImage *renderImage = [UIImage imageWithCGImage:renderImg];
    return renderImage;
}

struct CubeMap {
    int length;
    float dimension;
    float *data;
};

- (CIImage *)outputImageWithOriginalCIImage:(CIImage *)originalImage minHueAngle:(float)minHueAngle maxHueAngle:(float)maxHueAngle{
    
    struct CubeMap map = createCubeMap(minHueAngle, maxHueAngle);
    const unsigned int size = 64;
    // Create memory with the cube data
    NSData *data = [NSData dataWithBytesNoCopy:map.data
                                        length:map.length
                                  freeWhenDone:YES];
    CIFilter *colorCube = [CIFilter filterWithName:@"CIColorCube"];
    [colorCube setValue:@(size) forKey:@"inputCubeDimension"];
    // Set data for cube
    [colorCube setValue:data forKey:@"inputCubeData"];
    
    [colorCube setValue:originalImage forKey:kCIInputImageKey];
    CIImage *result = [colorCube valueForKey:kCIOutputImageKey];
    
    return result;
}

struct CubeMap createCubeMap(float minHueAngle, float maxHueAngle) {
    const unsigned int size = 64;
    struct CubeMap map;
    map.length = size * size * size * sizeof (float) * 4;
    map.dimension = size;
    float *cubeData = (float *)malloc (map.length);
    float rgb[3], hsv[3], *c = cubeData;
    
    for (int z = 0; z < size; z++){
        rgb[2] = ((double)z)/(size-1); // Blue value
        for (int y = 0; y < size; y++){
            rgb[1] = ((double)y)/(size-1); // Green value
            for (int x = 0; x < size; x ++){
                rgb[0] = ((double)x)/(size-1); // Red value
                rgbToHSV(rgb,hsv);
                // Use the hue value to determine which to make transparent
                // The minimum and maximum hue angle depends on
                // the color you want to remove
                float alpha = (hsv[0] > minHueAngle && hsv[0] < maxHueAngle) ? 0.0f: 1.0f;
                // Calculate premultiplied alpha values for the cube
                c[0] = rgb[0] * alpha;
                c[1] = rgb[1] * alpha;
                c[2] = rgb[2] * alpha;
                c[3] = alpha;
                c += 4; // advance our pointer into memory for the next color value
            }
        }
    }
    map.data = cubeData;
    return map;
}

rgbToHSV在官方文档中并没有提及,笔者在下文中提到的大佬的博客中找到了相关转换处理。感谢

void rgbToHSV(float *rgb, float *hsv) {
    float min, max, delta;
    float r = rgb[0], g = rgb[1], b = rgb[2];
    float *h = hsv, *s = hsv + 1, *v = hsv + 2;
    
    min = fmin(fmin(r, g), b );
    max = fmax(fmax(r, g), b );
    *v = max;
    delta = max - min;
    if( max != 0 )
        *s = delta / max;
    else {
        *s = 0;
        *h = -1;
        return;
    }
    if( r == max )
        *h = ( g - b ) / delta;
    else if( g == max )
        *h = 2 + ( b - r ) / delta;
    else
        *h = 4 + ( r - g ) / delta;
    *h *= 60;
    if( *h < 0 )
        *h += 360;
}

接下来我们试一下,去除绿色背景的效果如何

我们可以通过使用HSV工具,确定绿色HUE值的大概范围为50-170

调用一下方法试一下

[[SPImageChromaFilterManager sharedManager] removeColorWithMinHueAngle:50 maxHueAngle:170 image:[UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"nb" ofType:@"jpeg"]]]

效果

效果还可以的样子。

如果认真观察HSV模型的同学也许会发现,我们通过指定色调角度(Hue)的方式,对于指定灰白黑显得无能为力。我们不得不去用饱和度(Saturation)和明度(Value)去共同判断,感兴趣的同学可以在代码中判断Alphafloat alpha = (hsv[0] > minHueAngle && hsv[0] < maxHueAngle) ? 0.0f: 1.0f;那里试一下效果。(至于代码中为啥RGB和HSV这么转换,请百度他们的转换,因为鶸笔者也不懂。哎,鶸不聊生)

对于Core Image感兴趣的同学,请移步大佬的系列文章

iOS8 Core Image In Swift:自动改善图像以及内置滤镜的使用
iOS8 Core Image In Swift:更复杂的滤镜
iOS8 Core Image In Swift:人脸检测以及马赛克
iOS8 Core Image In Swift:视频实时滤镜

Core Graphics/Quarz 2D

上文中提到的基于OpenGlCore Image显然功能十分强大,作为视图另一基石的Core Graphics同样强大。对他的探究,让鶸笔者更多的了解到图片的相关知识。所以在此处总结,供日后查阅。

如果对探究不感兴趣的同学,请直接跳到文章最后 Masking an Image with Color 部分

Bitmap

侵删
Quarz 2D官方文档中,对于BitMap有如下描述

A bitmap image (or sampled image) is an array of pixels (or samples). Each pixel represents a single point in the image. JPEG, TIFF, and PNG graphics files are examples of bitmap images.

32-bit and 16-bit pixel formats for CMYK and RGB color spaces in Quartz 2D
32-bit and 16-bit pixel formats for CMYK and RGB color spaces in Quartz 2D

回到我们的需求,对于去除图片中的指定颜色,如果我们能够读取到每个像素上的RGBA信息,分别判断他们的值,如果符合目标范围,我们将他的Alpha值改为0,然后输出成新的图片,那么我们就实现了类似上文中cubeMap的处理方式。

强大的Quarz 2D为我们提供了实现这种操作的能力,下面请看代码示例:

- (UIImage *)removeColorWithMaxR:(float)maxR minR:(float)minR maxG:(float)maxG minG:(float)minG maxB:(float)maxB minB:(float)minB image:(UIImage *)image{
    // 分配内存
    const int imageWidth = image.size.width;
    const int imageHeight = image.size.height;
    size_t bytesPerRow = imageWidth * 4;
    uint32_t* rgbImageBuf = (uint32_t*)malloc(bytesPerRow * imageHeight);
    
    // 创建context
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();// 色彩范围的容器
    CGContextRef context = CGBitmapContextCreate(rgbImageBuf, imageWidth, imageHeight, 8, bytesPerRow, colorSpace,kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipLast);
    CGContextDrawImage(context, CGRectMake(0, 0, imageWidth, imageHeight), image.CGImage);
    
    
    // 遍历像素
    int pixelNum = imageWidth * imageHeight;
    uint32_t* pCurPtr = rgbImageBuf;
    for (int i = 0; i < pixelNum; i++, pCurPtr++)
    {
        uint8_t* ptr = (uint8_t*)pCurPtr;
        if (ptr[3] >= minR && ptr[3] <= maxR &&
            ptr[2] >= minG && ptr[2] <= maxG &&
            ptr[1] >= minB && ptr[1] <= maxB) {
            ptr[0] = 0;
        }else{
            printf("\n---->ptr0:%d ptr1:%d ptr2:%d ptr3:%d<----\n",ptr[0],ptr[1],ptr[2],ptr[3]);
        }
    }
    // 将内存转成image
    CGDataProviderRef dataProvider =CGDataProviderCreateWithData(NULL, rgbImageBuf, bytesPerRow * imageHeight, nil);
    CGImageRef imageRef = CGImageCreate(imageWidth, imageHeight,8, 32, bytesPerRow, colorSpace,kCGImageAlphaLast |kCGBitmapByteOrder32Little, dataProvider,NULL,true,kCGRenderingIntentDefault);
    CGDataProviderRelease(dataProvider);
    UIImage* resultUIImage = [UIImage imageWithCGImage:imageRef];
    
    // 释放
    CGImageRelease(imageRef);
    CGContextRelease(context);
    CGColorSpaceRelease(colorSpace);
    return resultUIImage;
}

还记得我们在Core Image中提到的HSV模式的弊端吗?那么Quarz 2D则是直接利用RGBA的信息进行处理,很好的规避了对黑白色不友好的问题,我们只需要设置一下RGB的范围即可(因为黑白色在RGB颜色模式中,很好确定),我们可以大致封装一下。如下

- (UIImage *)removeWhiteColorWithImage:(UIImage *)image{
    return [self removeColorWithMaxR:255 minR:250 maxG:255 minG:240 maxB:255 minB:240 image:image];
}
- (UIImage *)removeBlackColorWithImage:(UIImage *)image{
    return [self removeColorWithMaxR:15 minR:0 maxG:15 minG:0 maxB:15 minB:0 image:image];
}

看一下我们对于白色背景的处理效果对比

看起来似乎还不错,但是对于纱质的衣服,就显得很不友好。看一下笔者做的几组图片的测试

很显然,如果不是白色背景,“衣衫褴褛”的效果非常明显。这个问题,在笔者尝试的三种方法中,无一幸免,如果哪位大佬知道好的处理方法,而且能告诉鶸,将不胜感激。(先放俩膝盖在这儿)

除了上述问题外,这种对比每个像素的方法,读取出来的数值会同作图时出现误差。但是这种误差肉眼基本不可见。


如下图中,我们作图时,设置的RGB值分别为100/240/220 但是通过CG上述处理时,读取出来的值则为92/241/220。对比图中的“新的”“当前”,基本看不出色差。这点小问题各位知道就好,对实际去色效果影响并不大

Masking an Image with Color

笔者尝试过理解并使用上一种方法后,在重读文档时发现了这个方法,简直就像是发现了Father Apple的恩赐。直接上代码

- (UIImage *)removeColorWithMaxR:(float)maxR minR:(float)minR maxG:(float)maxG minG:(float)minG maxB:(float)maxB minB:(float)minB image:(UIImage *)image{

    const CGFloat myMaskingColors[6] = {minR, maxR,  minG, maxG, minB, maxB};
    CGImageRef ref = CGImageCreateWithMaskingColors(image.CGImage, myMaskingColors);
    return [UIImage imageWithCGImage:ref];
    
}

官方文档点这儿

总结

HSV颜色模式相对于RGB模式而言,更利于我们抠除图片中的彩色,而RGB则正好相反。笔者因为项目中,只需要去除白色背景,所以最终采用了最后一种方式。

查看原文

赞 2 收藏 8 评论 1

ACChe 赞了回答 · 2017-12-27

在 swift 生命周期中绑定的函数,声明时候怎么不用写 @objc

一般的方法前面是不需要写@objc的,但selector对应的方法前面必须加,因为selector其实是 Objective-C runtime 的概念。在 Swift4 中,默认情况下所有的 Swift 方法在Objective-C 中都是不可见的,所以你需要在这类方法前面加上@objc关键字,将这个方法暴露给 Objective-C,才能进行使用。
其实这是@objc的其中一种作用,其他作用你可以再查下资料。

关注 5 回答 3

ACChe 关注了问题 · 2017-12-27

在 swift 生命周期中绑定的函数,声明时候怎么不用写 @objc

当定义一个 UIButton 组件时候,为其绑定点击事件

override func viewDidLoad() {
    super.viewDidLoad()
    let btn = UIButton(type: .system)
    btn.frame = CGRect(x: 0, y: 0, width: 275, height: 40)
    self.view.addSubview(btn)
    btn.setTitle("Go", for: UIControlState.normal)
    btn.addTarget(self, action: #selector(handleClick), for: UIControlEvents.touchUpInside)
}

声明这个 handleClick 函数时候这样写

func handleClick(){
    // do something
}

这样就会报错

图片描述

我知道在函数前面加一个 @objc 就行了,但是好丑,我想知道有没有别的处理方法,可以不用在声明函数时候写 @objc

求路过的大神帮帮忙

关注 5 回答 3

ACChe 回答了问题 · 2017-11-08

解决Swift JSON 映射到对应的model对象,有什么好的框架或实践

codable 协议。

关注 6 回答 5

ACChe 关注了问题 · 2017-08-02

解决img下几像素空白产生原因

img标签在HTML5和HTML4.0.1的严格模式渲染的时候,下面会有几像素的空白。
解决方法就不用说了,都晓得,求问下产生原因。
又测试了下行内块。里面没有文字的行内块表现和img一样,有文字的行内块下面不会出现空白。
网上搜全是说解决方法的,求大牛解释下产生原因。

问题效果如下:

请输入图片描述

关注 15 回答 5

ACChe 赞了文章 · 2017-07-13

全屏滚动实现:fullPage.js和fullPage

fullPage.js和fullPage都能实现全屏滚动,二者区别是:fullPage.js需依赖于JQuery库,而fullPage不需要依赖任何一个js库,可以单独使用。

一、fullPage.js实现全屏

fullPage.js是开源的JQuery插件库,其Github地址:https://github.com/alvarotrigo/fullPage.js

1、基本演示

1.1 引入文件

<!-- 引入css -->
<link rel="stylesheet" type="text/css" href="./fullPage.js-master/jquery.fullPage.css" />
<!-- fullpage.js依赖于jquery库 -->
<script type="text/javascript" data-original="./jquery.js"></script>
<script type="text/javascript" data-original="./fullPage.js-master/jquery.fullPage.min.js"></script>

1.2 css:引入的css并不是给元素设置样式的,元素的样式需要自己写

    <style type="text/css">
        body
        {
            color: #FFFFFF;
        }
        .section1
        {
            background-color: #BFDA00;
        }
        .section2
        {
            background-color: #2EBE21;
        }
        .section3
        {
            background-color: #2C3E50;
        }
        .section4
        {
            background-color: #FF9900;
        }
    </style>

1.3 html:每一个section代码一屏,默认从第一屏显示,若需要自定义从某屏开始显示,为section添加active类。示例默认从第三屏显示

<div id="ido">
    <div class="section section1">
        <h1>每一个section是一屏,这是第一屏</h3>
    </div>
    <div class="section section2">
        <h1>每一个section是一屏,这是第二屏</h3>
    </div>
    <div class="section section3 active">
        <h1>每一个section是一屏,这是第三屏</h3>
    </div>
    <div class="section section4">
        <h1>每一个section是一屏,这是第四屏</h3>
    </div>
</div>

1.4 js:

<script type="text/javascript">
    $(function() {
        $("#ido").fullpage();
    });
</script>

效果:http://denon-7c931.coding.io/fullpagejs.html

1.5 可以在某屏中再添加子滚动屏,借助slide类。修改上述第二个section如下:

    <div class="section section2" style="text-align:center">
            <h1 class="slide">第二屏的第一屏</h1>
            <h1 class="slide">第二屏的第二屏</h1>
            <h1 class="slide">第二屏的第三屏</h1>
            <h1 class="slide">第二屏的第四屏</h1>
    </div>

1.6 添加背景屏,在html添加两个section

<div class="section section5">
    <h1>每一个section是一屏,这是第5屏--图片背景</h3>
</div>
<div class="section section6">
    <h1>每一个section是一屏,这是第6屏--图片背景</h3>
</div>

添加css

.section5 { background: url(http://idowebok.u.qiniudn.com/77/1.jpg) 50%;}
.section6 { background: url(http://idowebok.u.qiniudn.com/77/2.jpg) 50%;}
    效果:http://denon-7c931.coding.io/bjfull.html  (滚动到5和6屏)

1.7 循环演示:continuousVertical设置为true

$(function() {
    $("#ido").fullpage(
        {
            continuousVertical: true
        });
});

效果:http://denon-7c931.coding.io/xhfull.html (滚动到第6屏,再向下滚动时自动回到第一屏)

1.8 绑定菜单:添加菜单项

<ul id="menu">
    <li data-menuanchor="page1" class="active"><a href="#page1">第一屏</a></li>
    <li data-menuanchor="page2"><a href="#page2">第二屏</a></li>
    <li data-menuanchor="page3"><a href="#page3">第三屏</a></li>
    <li data-menuanchor="page4"><a href="#page4">第四屏</a></li>
    <li data-menuanchor="page5"><a href="#page5">第5屏</a></li>
    <li data-menuanchor="page6"><a href="#page6">第6屏</a></li>
</ul>

添加css

#menu { margin: 0; padding: 0; position: fixed; left: 10px; top: 10px; list-style-type: none; z-index: 70;}
#menu li { float: left; margin:  0 10px 0 0; font-size: 14px;}
#menu a { float: left; padding: 10px 20px; background-color: #fff; color: #333; text-decoration: none;}
#menu .active a { color: #fff; background-color: #333;}

修改js

$(function() {
            $("#ido").fullpage(
                {
                    continuousVertical: true,  //循环演示
                        //绑定菜单
                         anchors: ['page1', 'page2', 'page3', 'page4','page5','page6'],
                    menu: '#menu',

                });
        });

效果:http://denon-7c931.coding.io/memufull.html

1.9 导航演示:设置’navigation': true,

$(function() {
            $("#ido").fullpage(
                {
                    continuousVertical: true,  //循环演示
                    //绑定菜单
                    anchors: ['page1', 'page2', 'page3', 'page4','page5','page6'],
                    menu: '#menu',

                    // 导航
                    'navigation': true,
                });
        });

效果:http://denon-7c931.coding.io/navfull.html (导航在右侧)

2、配置如图

config.png

如果需要配置easing和scrollOverflow,则需要引入额外的js(在vendors目录下)

<!-- jquery.easings.min.js 用于 easing 参数,也可以使用完整的 jQuery UI 代替,如果不需要设置 easing 参数,可去掉改文件 -->
<script type="text/javascript" data-original="./fullPage.js-master/vendors/jquery.easings.min.js"></script>
<!-- 如果 scrollOverflow 设置为 true,则需要引入 jquery.slimscroll.min.js,一般情况下不需要 -->
<script type="text/javascript" data-original="./fullPage.js-master/vendors/jquery.slimscroll.min.js"></script>

二、fullPage实现全屏

 fullPage 是一款不依赖任何 js 库的全屏滚动组件,支持垂直/水平滚动、CSS3 旋转/缩放动画,支持 IE5.5+,支持移动设备。其Github地址:https://github.com/powy1993/fullpage

1、基本演示


<script type="text/javascript" data-original="./fullpage-master/js/fullPage.min.js"></script>
1.1 垂直滚动

css

body {
      width: 100%;
          *cursor: default;
      overflow: hidden;
      font: 16px/1.5 "Microsoft YaHei";
  }
  div,p {
      margin: 0;
      padding: 0;
  }
  ul {
          list-style: none;
  }
  #pageContain {
      overflow: hidden;
  }
  .page {
      display: none;
      width: 100%;
      height: 100%;
      overflow: hidden;
      position: absolute;
      top: 0;
      left: 0;
  }
  .contain {
      width: 100%;
      height: 100%;
      display: none;
      position: relative;
      z-index: 0;
  }
  .current .contain,.slide .contain {
      display: block;
  }
  .current {
      display: block;
      z-index: 1;
  }
  .slide {
      display: block;
      z-index: 2;
  }
  .page1 {
      background: #37c1e3;
  }
  .page2 {
      background: #009922;
  }
  .page3 {
      background: #992211;
  }
  .page4 {
      background: #ff00ff;
  }
  .page5 {
      background: #00ff00;
  }
  .page6 {
      background: #22ffff;
  }
  #navBar {
      z-index: 3;
      position: absolute;
      top: 10%;
      right: 3%;
  }
  #navBar .active {
      background: #ccc;
  }
  #navBar li {
      cursor: pointer;
      margin-bottom: 10px;
      transition: all .7s ease;
      border-radius: 50%;
      line-height: 40px;
      text-align: center;
      width: 40px;
      height: 40px;
  }
h1
 {
     text-align: center;
     margin-top: 20%;
 }

html

<div id="pageContain">
  <div class="page page1 current">
      <div class="contain">
          <h1 class="txt">第一屏</h1>
      </div>
  </div>

  <div class="page page2">
      <div class="contain">
          <h1 class="txt">第二屏</h1>
      </div>
  </div>

  <div class="page page3">
      <div class="contain">
          <h1 class="txt">第三屏</h1>
      </div>
  </div>

  <div class="page page4">
      <div class="contain">
          <h1 class="txt">第四屏</h1>
      </div>
  </div>

  <div class="page page5">
      <div class="contain">
          <h1 class="txt">第五屏</h1>
      </div>
  </div>
</div>

<ul id="navBar">
    <li>1</li>
    <li>2</li>
    <li>3</li>
    <li>4</li>
    <li>5</li>
</ul>

js

var runPage;
runPage = new FullPage({
    id: 'pageContain',
    slideTime: 800,
    effect: {
        transform: {
            translate: 'Y'    //垂直滚动,改为X则是水平滚动
        },
        opacity: [0, 1]
    },
    mode: 'wheel, touch, nav:navBar',
    easing: 'ease'
});

效果:http://denon-7c931.coding.io/fullpage.html

1.2 css3动画:修改js就行

var runPage;
runPage = new FullPage({
    id: 'pageContain',
    slideTime: 800,
    effect: {
        transform: {
           translate: 'X',
           scale: [0, 1],
    rotate: [270, 0]
        },
        opacity: [0, 1]
    },
    mode: 'wheel, touch, nav:navBar',
    easing: 'ease'
});

效果:http://denon-7c931.coding.io/fulldh.html

1.3 自动滚动,js修改如下

var runPage, interval, autoPlay;

    autoPlay = function(to) {
        clearTimeout(interval);
        interval = setTimeout(function() {
            runPage.go(to);
        }, 1000);
    }
    runPage = new FullPage({
        id: 'pageContain',
        slideTime: 800,
        effect: {
            transform: {
               translate: 'X',
               scale: [0, 1],
        rotate: [270, 0]
            },
            opacity: [0, 1]
        },
        mode: 'wheel, touch, nav:navBar',
        easing: 'ease',
        callback: function(index, thisPage){
        index = index + 1 > 3 ? 0 : index + 1;
        autoPlay(index);
         }
    });
    interval = setTimeout(function() {
            runPage.go(runPage.thisPage() + 1);
        }, 1000);

效果:http://denon-7c931.coding.io/fullauto.html

2、配置如图

condig.png

原文:http://www.ido321.com/1452.html

查看原文

赞 18 收藏 123 评论 16

ACChe 赞了回答 · 2017-07-12

vue和react实现折叠或者显示隐藏效果复杂度?

  1. 没太懂第一个样式局部化啥意思...

  2. react state 与 vue data 确实有异曲同工之妙, 从props -> state 与 props -> data道理是一样的, 我记得vue作者也说过 vue 集 react 与 angular 优点于一身。。。 细细体会的确是这样, 你肯定有这样的感觉, v-if 不是 ng-if吗 v-shou 不是ng-show 吗?data 不就是 state 吗?vue的生命周期跟react的生命周期好像啊... 但vue不光借鉴甚至做的更好, 从函数使用流程上比react友好太多了

  3. 模版与指令配合的好处不用多说, 这种使用方式比react爽多了, computed watch这些就是比react与angular做的更好的了,还有一个就是 vue是双向绑定!!! form的操作简洁太多太多了。

  4. 文档自然无须多说, 国人出品自然不会忽略的中文文档, vue上手更是容易的很(并非是因为中文文档完善才上手快)

之前我一直倾向于react, 现在越来越喜欢vue了

关注 6 回答 3

ACChe 赞了回答 · 2017-07-12

解决请问下面的技术哪些在企业中会用的比较多

chrome有个扩展叫Wappalyzer,你可以去安装,然后...

去你认为一些优秀的产品(WEB)站点去看看就知道了

比如,segmentfault是这样的:

clipboard.png

然后你看了后就可以知道大致用了哪些

关注 7 回答 6

ACChe 关注了问题 · 2017-07-12

vue和react实现折叠或者显示隐藏效果复杂度?

问题
1、reactJs 组件样式怎么实现局部化? [类似于vue中的scoped ]
2、我对react中的state理解为vueJs中的data?欢迎指教react中实现不好的地方或者可以改进的地方
3、感觉vuejs提供的v-if v-show computed watch 这些指令和处理函数非常实用,可以简化很多高频的状态维护。
4、vue的文档比react友好太多太多太多了。。。。【vue一两天可以搭建起来项目,react基本自己摸】

备注:
angularJs没玩过,也没打算去玩。。。

效果图 【不要太在意样式,之前都是写vue项目】

clipboard.png

reactJs 实现 【有点麻烦】

"use strict";
import React from 'react';
import PropTypes from 'prop-types';

require('./classify_item.less');

export default class ClassifyItem extends React.Component {
    constructor(props) {
        console.log(props);
        super(props);
        this.state = {
            showMore: false
        }
        ;
    };

    static defaultProps = {
        data: {
            child: []
        },
        clickItem: function (data) {
            console.log(JSON.stringify(data));
        }
    };

    static propsTypes = {
        data: PropTypes.object,
        clickItem: PropTypes.func
    };

    clickToggle = () => {
        this.setState({
            showMore: !this.state.showMore
        });
    };

    clickItem = (data) => {
        console.log("you have tap item!");
        console.log(JSON.stringify(data));
        this.props.clickItem(data);
    };

    couputedItems = () => {
        let _data = this.props.data;
        let _dom;
        //@formatter:off
        if (_data.child.length > 8) {
            let _arr = _data.child.concat([{
                name: "收起"
            }]);
            _arr.splice(7, 0, {name: "展开"});
            console.log(_arr);

            _dom = _arr.map((item, index) => {
                if (index < 7) {
                    return <p className="item" key={index}onClick={this.clickItem.bind(this,item)}>{item.name}</p>
                } else if (index > 7) {
                    return item.code ?
                        <p className={this.state.showMore ? "item" : "item hide"} key={index}onClick={this.clickItem.bind(this,item)}>{item.name}</p> :
                        <p className={this.state.showMore ? "item" : "item hide"} key={index} onClick={this.clickToggle}><span className="iconfont icon-xiangshang2"></span></p>
                } else {
                    return <p className={this.state.showMore ? "item hide" : "item"} key={index} onClick={this.clickToggle}><span className="iconfont icon-xiangxia2"></span></p>
                }

            })
        } else {
            _dom = _data.child.map((item, index) => <p className="item" key={index} onClick={this.clickItem.bind(this,item)}>{item.name}</p>);
        }
        //@formatter:on
        return _dom;
    };


    render() {
        console.log(this.props.data);
        let _data = this.props.data;


        return (<div className="classify-item">
            <p className="title">{_data.name}</p>
            <div className="items">{
                this.couputedItems()
            }
            </div>
        </div>)
    }
}

vuejs实现 【非常简洁明了】

1.<template>
2.    <div class="classify-item">
3.        <div class="title">
4.            <img :data-original="industryTitle.industryImg">
5.            <span>{{industryTitle.industryNm}}</span>
6.        </div>
7.        <div class="content">
8.            <a class="grid" v-for="(_item, _index) in industryItems" v-if="_index<7" @click="clickEvent(_item)">
9.                {{_item.industrySubNm}}
10.            </a>
11.            <a class="grid" v-if="industryItems.length>7" @click="toggleTap" v-show="!showMore">
12.                <i class="iconfont icon-angle-down"></i>
13.            </a>
14.            <a class="grid" v-for="(_item, _index) in industryItems" v-if="_index>6" v-show="showMore" @click="clickEvent(_item)">
15.                {{_item.industrySubNm}}
16.            </a>
17.            <a class="grid" v-if="industryItems.length>7" @click="toggleTap" v-show="showMore">
18.                <i class="iconfont icon-angle-up"></i>
19.            </a>
20.        </div>
21.    </div>
22.
23.</template>
24.<style lang="less" scoped>
25.    .classify-item {
26.        position: relative;
27.        line-height: 0.8rem;
28.        color: #333;
29.        .title{
30.            overflow: hidden;
31.            img{
32.                height: 0.48rem;
33.                width: 0.48rem;
34.                margin: 0.16rem 0;
35.                float: left;
36.            }
37.            span{
38.                float: left;
39.                margin-left: 0.16rem;
40.            }
41.        }
42.
43.        //content
44.        .content {
45.            overflow: hidden;
46.            box-sizing: border-box;
47.            border-top: 1px solid #D8D8D8;
48.            border-left: 1px solid #D8D8D8;
49.        }
50.        .grid {
51.            height: 0.8rem;
52.            overflow: hidden;
53.            text-align: center;
54.            background-color: #fff;
55.            width: 25%;
56.            box-sizing: border-box;
57.            float: left;
58.            border-bottom: 1px solid #D8D8D8;
59.            border-right: 1px solid #D8D8D8;
60.        }
61.    }
62.</style>
63.<script type="text/babel">
64.
65.    export default{
66.        data(){
67.            return {
68.                showMore:false
69.            }
70.        },
71.        props: {
72.            industryTitle: {
73.                type: Object,
74.                required: true
75.            },
76.            industryItems: {
77.                type: Array,
78.                required: true
79.            }
80.        },
81.        computed: {
82.
83.        },
84.        methods: {
85.            toggleTap(e){
86.                console.log(e);
87.                this.showMore = !this.showMore;
88.            },
89.            clickEvent(item){
90.                console.log(item);
91.                // alert('根据类别跳转到相应的展示页面\n'+JSON.stringify(item));
92.                this.$emit('tapEvent',item);
93.            }
94.        }
95.    }
96.</script>

关注 6 回答 3

ACChe 赞了回答 · 2017-05-29

解决如何配置nginx 同一ip,多域名,不同端口?

你需要配置虚拟主机,让 Nginx 监听不同的域名的80端口,然后转发到各自应用的实际端口

首先,你需要编辑/etc/nginx/nginx.conf,在http模块中引入其他配置文件:

include /etc/nginx/conf.d/*.conf;

这样你就可以在/etc/nginx/conf.d文件夹中分别设置每个虚拟主机。

然后在上面的文件夹下分别新建文件/etc/nginx/conf.d/a.conf/etc/nginx/conf.d/b.conf,当然文件名ab随你起。

server {
    listen       80;
    server_name  a.abc.com;

    access_log /data/node/log/host.access.log  main;

    location / {
        proxy_set_header Host $http_host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Real-Ip $remote_addr;
        proxy_set_header X-NginX-Proxy true;
        proxy_pass http://127.0.0.1:<YOUR PORT>/;
        proxy_redirect off;
    }
}

b.abc.com的应用只需要修改上面的server_name就可以。

这样每个访问http://a.abc.com的请求就会被转发到相应的端口上,由各自的应用处理。

关注 8 回答 5

ACChe 赞了回答 · 2017-05-29

解决如何配置nginx 同一ip,多域名,不同端口?

添加配置如下:
server {

    listen 80;
    server_name www.a.abc.com a.abc.com;
    #charset koi8-r;
    access_log /var/log/nginx/a.access.log;
    location / {
        proxy_pass http://192.168.1.101:80;(修改为你自己的IP)
        proxy_read_timeout 180s;
        break;
    }

}

server {

    listen 80;
    server_name www.b.abc.com b.abc.com;
    #charset koi8-r;
    access_log /var/log/nginx/b.access.log;
    location / {
        proxy_pass http://192.168.1.102:80;(修改为你自己的IP)
        proxy_read_timeout 180s;
        break;
    }

}

更详细的请参考下面2个链接:
使用Nginx实现同一固定IP上,多个Web站点访问不加端口号
使用Nginx代理IP转发错误,图片/CSS/JS无法正常加载,或请求无法访问

关注 8 回答 5