响应式布局的那些事

响应式设计在如今的web开发过程中已经是必不可少,它可以针对不同的设备环境对页面进行调整,并且可以在PC端和移动端达到很好效果的情况下,不用开发多套页面,可以提升开发速度,可维护性打打增强。

响应式布局

响应式布局的一种实现方式的原理是使用CSS3新引入的Media Query来调整元素在不同分辨率下的显示效果,并且通过JavaScript进行交互。总结起来,响应式布局有以下几个需要注意的点:

  1. 设置Viewport

我们知道,在移动设备中,页面被放置在虚拟的窗口中,这个窗口也称作视口(Viewport),对于未进行移动端适配或者是未进行响应式设计的页面,往往页面的宽高都会大于移动设备的宽高,所以为了能够在移动设备上进行页面交互,缩放是不可避免的,但是频繁的放大缩小带来的浏览体验肯定不会好。所以,在响应式设计的第一步,就是要禁止移动设备的缩放,这很容易实现,我们只需要在html页面中的head元素下添加一个meta标签用于规定禁止缩放就可以了:

1
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no, maxinum-scale=1.0" />
  1. 使用Media Query

媒介查询才是响应式布局的关键所在,我们使用Media Query 来实现在不同尺寸下使用不同的样式。Media Query的规则有很多,例如@media screen and (max-width: 980px){...}就表示了在980px下的屏幕下使用在此定义的各种样式,同样还有min-widthorientation(设备方向)等属性,我们需要按需进行设置。

  1. 使用JavaScript

如果能做到上面的两点,在一般情况下,响应式布局是可以实现的。但是如果在布局的过程中需要改变交互,那么JavaScript久必须派上用场了。例如一个菜单栏,在十分小的屏幕下需要折叠,那么就需要用到JavaScript。

Code

上面是我能够想到的响应式布局的一些要点,在实际学习过程中,我并没有在一些项目中使用相应式设计的方式(貌似很悲哀…)。在目前移动为先的时代,为移动端做更好的优化是不可避免的,无论是使用重新写一套移动端页面,还是使用响应式布局,或者使用其他的例如Flex Box来进行布局。作为一个工作在浏览器端的🐒,这都是我们必须具备的素质。

好了,写一个简单的小例子吧。如果你从未接触过响应式布局,那么希望接下来的code会帮助你更快地了解并应用它。

我们来写一个菜单栏,其HTML结构‍如下:

1
2
3
4
5
6
7
8
9
<div id="nav">
<ul id="nav-list">
<li><a href="#" id="home">Home</a></li>
<li><a href="#" id="topic">Topic</a></li>
<li><a href="#" id="today">Today</a></li>
<li><a href="#" id="about">About</a></li>
<li><a href="#" id="concat">Concat</a></li>
</ul>
</div>

CSS如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
* {
margin: 0;
padding: 0;
}
#nav {
position: relative;
}
#nav-list ul li {
list-style: none;
box-sizing: border-box;
width: 20%;
}

#nav-list ul li a {
display: block;
text-align: center;
text-decoration: none;
color: #FFF;
line-height: 4em;
font-size: 1.4em;
}

#nav-list ul li:nth-child(1) a {
background-color: #bcbcbc;
}
#nav-list ul li:nth-child(2) a {
...
/*添加背景色*/
}
#nav-list ul li:nth-child(1) a:hover {
background-color: rgba(188, 188, 188, .8);
/*添加鼠标移上去的样式*/
}
#nav-list ul li:nth-child(1) a::before {
content: ''
/*使用伪类来添加图标字体等*/
}

现在,我们来为屏幕宽度小于768px写一个样式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@media screen and (max-width: 768px) {
ul li a::before {
font-size: 20px;
line-height: 60px;
}

ul li a {
font-size: 0;
height: 60px;
}
/*
上面的的样式指明了再768px宽度及以下,我们设置a标签的font-size为0,不显示字体。
设置伪元素所在的图标字体的行高等于a标签的宽度,使其垂直居中。
通过上面的简单设置,我们在小于768px跨度的屏幕下,对于该菜单就只能看到图标了。
*/
}

接着,我们可以为更窄的屏幕设置折叠菜单,我们通过css来绘制折菜单,使用JS来显示和隐藏。具体的实现这里就不贴出来了。

ok,到此为止,我们已经写好了一个响应式菜单栏了(虽然…)。

分享到 评论

孩子

对于大多数家庭来说,孩子就是未来、是未来的希望和精神寄托;我们的父母总是不予余力的帮助我们的成长;可是当一个孩子成为家庭的负担呢?看看下面这个家庭的故事(这是一个真实的故事)。

写在前面

2017年7月23日,我从济南出发回家“享受”也许是学生时代的最后一个暑假,同往常的匆忙不一样,这一次来到火车站才离火车发车还有1小时,一切都有条不紊的进行,取票、进站、候车…除了在检票的时候闸机不能识别我的车票外、上车、找到座位、驶离济南。故事当然发生在火车上,我的邻座–一位高大的面黄精瘦的头花花白的中年妇女。

为了度过12小时的车程,我一般会准备两部电影用于消遣,食物则是两瓶纯水一个大面包和少许便捷的水果。和往常一样,我在整一上午的时间看完了一部电影,此时已经到了午饭点,火车上的人都开始拿起自己带的食物开始吃起来。我从我的书包拿出大面包,开始啃起来,对两口面包一口水的节奏。我邻座的那位中年妇女也吃面包,可是连水都不要,对,也许她就没带水。出于此,我把书包里剩下的一瓶水给了她,她起先推诿不好意思接受,后来我告诉她我还有她才收下。后来的一段时间,我们有过一段时间的交流,而这故事就包含在短暂的交流中。

侧脸

经过短暂的聊天,我知道了她来自湖北恩施州,地地道道的农民,来到济南照顾自己生病的孩子。一开始,我并没有问她孩子的具体情况。我打量着她,个头估摸着超过175CM,而身体廋弱得像竹竿,估摸着和自己父母相近的年龄,头发却花白。一眼望过去,正好是侧脸,廋高的颧骨、满脸的雀斑、偏黑的肤色,哇,是经历过怎样事情的人才会在这样的年龄变成这样一个样子。

这个时候,她打了一个电话,我一直在旁听她说话,可是方言的确让人很难懂,唯一知道的就是打给她女儿的。时间很短,更多的像是嘱咐,她挂了电话。看我一副饶有兴致的样子,她给我讲了她打给的是自己的13岁女儿,正在济南住院治疗。她随后的话让我觉得十分震惊:女儿从5岁开始开始患病治疗,直到现在已经治疗了8年,得的是世界罕见的baihua病(这里真的记不住了,根据她的病情描述大概是一种血液病),中国第一例!8年花掉了超过100万元。如今女孩病情恶化,医院不再收取任何费用,开始进行实验性治疗。她每一个月济南待半个月,回家半个月,连往返车费都是医院帮忙。说完这些,我分明看到她眼角产生着泪花。其实此时不仅仅是我对此时感兴趣,旁边前后排的都有在关注她说的,有人问她这么些年有没有得到社会帮助,她说当地电视台报道了这件事,收到了部分爱心捐助,但是绝大部分的治疗费用还是自己承担。还有人问为什么选择在济南治疗,她说只有那个主治医生能够稍微有效,所以那个医生在哪她们就跟着去哪。当然,还有其他问题,她都一一述说。期间她给我们看了她小孩的照片,看起来就像那种天生聪慧的孩子,可是因为病情没有上过学,不然这个年龄应该是一名初中生了吧。

这样的聊天持续到直到所有发问的人都沉默不语,恰好也快到她下车的站点了。我给她说,我在济南上学,我给她留个电话,如果需要任何我帮忙的,打电话给我。她匆匆忙忙的存下我的号码,拖着好大的行李箱,下车了。那拖着行李箱的背影,突然让我想起了我的母亲(/哭)。

每个人的母亲和作为孩子的我们

在接下来3个小时的剩余车程中,我回想了我和母亲的一切回忆,由近及远。早上上车时,远在千里之外的母亲挂念着自己的儿子是否能够乘上火车;回家前一天,又千叮呤万嘱咐一定要带够食物;在学校的每个星期,短暂的通话过程中的叮嘱…还有最初母亲反对我去北方读书,送别抹眼泪的情景也是历历在目。时间一晃,那么些年过去了,母亲越来越老,我越来越成熟;母亲越来越唠叨,我越发能够理解。想起小时候,经常和母亲吵架,甚至伤了母亲的心。到后来高中远离父母,到大学更是相隔千里,见到她的时间从一年几次到现在一年一次,从以前因不想见到母亲想要离家出走,到现在即使回到家乡想见母亲一面,母亲却说,太远太热太折腾坚决不让我来。这就是我的母亲,而我是母亲的孩子。

我是从我家乡中为数不多走出的大学生,这一点让母亲十分骄傲。当别人问起孩子,母亲总会告诉他们孩子在XX大学上学,可能对大部分家乡人来说,XX大学如何,985,211这些是什么,他们都不在意,也不知道其中的含义。作为孩子的自己,我有幸能够在这样一个家庭中长大,虽然家庭不算特别贫苦,但是所有的环境都是父母通过自己的劳动带来的。我敬佩我的父母,也期待能早日独挡一面。从小到大,我的事情都是由我做主,大到选择大学选择专业,小到家庭琐事,父母从不干涉的我的决定,最多就是提提建议,并且从任何方面支持我。前段时间我放弃了去企业实习的机会,选择留在学校补修课程等待秋招,父母第一时间支持我,这让我很感动。我想,还有什么理由不努力呢!

尾巴

记得很多个第一次,但是最有感触的莫非第一次在济南生病,母亲得知我生病的消息从早到晚都很焦虑,反而自己生病了。后来的几年,我很少生病,但是只要我生病,我就会去第一时间就医,并且不告诉母亲。到此结束,共勉!

分享到 评论

Demo--Canvas with React

一个使用Canvas处理图片的Demo,使用React + webpack + Redux 的技术栈,非常适合初学者,希望你喜欢!

为什么

学习canvas已经有一阵子了,忙完了计组课设,考完了数据挖掘,终于有时间来做一点小Demo来巩固自己所学的知识了。就像上面介绍的那样,这是一个使用Canvas进行图片处理的Demo,其可以选择本地图片,改变其R G B 以及透明度,然后可以选择保存到本地。并且为了重温很久没碰的React,前端使用了React,使用Redux进行数据的管理(虽然简单到没必要使用),并且使用了css modules 以便直接在组件中使用css。当然这一切都是在使用webpack进行编译打包的情况下。

这个Demo十分简单,特别适合React初学者食用,相信会对你的React学习有所帮助!

如何运行

  1. 从我的github上clone到本地;
  2. 进入Demo根目录, 执行npm init 安装依赖;
  3. 安装完毕后,执行 npm run build 进行构建;
  4. 在Chrome浏览器(下载功能只能在Chrome中使用,所以…)中打开index.html。

至此,你可以体验这个简单的Demo了。

像什么

如果你觉得在你的机器上run很麻烦,或者你只是想看看长得怎么样。

在浏览器器中打开,是这个样子的:

初始化效果

我承认的确很简单,简单到显得简陋了!接下来你可以选左下角的选择文件按钮来选择任何一张图片,比如我选择了一张图片后:

初始化图片

任何被选中的图片都会被居中显示,宽高都会适应600*400的图片操作区域。现在,可以对图片进行操作了:

处理后

我们选择对图片的R、G、B、以及透明度进行调整,实时调整的效果将会在左侧的图片区域实时显示出来。

第四步,点击图片区下的按钮,就可以吧处理过的图片下载到本地了,我们打开下载后的图片和处理的图片进行对比,就像这样:
保存与对比

至此,我已经演示完了所有的功能。

不足

如果你细心一点的话,你会发现这个Demo还有很多问题:

  1. 我们导入任何宽高的图片,其都会被自适应到框中,所以处理后的图片品质会下降。
  2. 保存图片只能在Chrome浏览器中进行,已测试在Firfox中无法使用这个功能。
  3. 右侧的工具栏在选择新图片后不会被初始化。
  4. 功能单一。
  5. 界面简陋

你需要注意的是

如果你想学习React和canvas,那么我希望我的这个Demo会对你有所帮助,这里提几个需要注意的点,这些点也是我在开发过程中遇到的问题:

  1. 如何使用input file来选择一张图片并绘制到canvas中。
  2. 如何保存图片。
  3. 图片在React中绘制的时机。
  4. 如何使用redux进行数据管理,特别是如何使用带参数的action。
  5. 你所关注的。

未来

这虽然是一个很简单的Demo,但是我会在此基础上进行继续跟进,现在能想到的是解决上面提到的不足,比如设置两种模式,处理图片品质下降的问题;兼容主流浏览器;增添新功能;修改工具栏的状态初始化的bug;以及其它我以后能够想到并且我能够实现的。

以及…我目前有想法开发一个可交互的视频编辑器,有兴趣的同学可以关注下咯!

写到后面

还有不到3个小时我就21岁了,想想前面走过的20年,尤其是上大学的三年来,感慨颇多。谢天谢地,就算无论如何,我都完好无损的度过了。接下来的一岁中,我将面临人生中一个个重大的转折点,实习、毕业、工作、走向社会。从小到大,我对我所有的事情做出选择,接下来,也不例外。我做好准备了,并且一直在准备着!

共勉!

新!

5.28日

  1. 解决了再次选择图片工具栏初始化的问题;
  2. 工具栏的调节精度下沉到0.01;
  3. 修改页面细节。

现在看起来长这样!
新的页面

6.3日

  1. 同样的功能,不同的界面和实现方式,采用react但是去除redux使用state进行状态管理;
  2. 操作更加主流和人性化;
  3. 已知BUG,下载某些图片的时候可能会失败,暂不知原因。

新版地址:https://github.com/xiaomoer/picture-editor-with-canvas

看起来是这样的:

还有这样:

加油!

分享到 评论

初识 requestAnimationFrame

事情的起因是这样的,前段时间面试的时候面试官问我会canvas不,作为一名未来的前端猿,我只有过一点了解,后来居然收到了offer,当然在闲暇之余是要学习一下canvas,并且在学习过程中首次接触到了本文的主角requestAnimationFrame

web中实现动画

老实说,如果有人问我如何在web开发中实现动画,我第一时间想到的就是使用定时器setTimeout()或者setTimeInterval()来实现。其实实现的方式远远不止这一种,在CSS3的时代,我们实现动画有了更多的选择,比如使用关键帧动画,使用transition,我们也可以在canvas上绘图来实现动画;当然,还有requestAnimationFrame

使用setTimeout()/setInterval()实现的方式很简单,我前面有一篇文章就简要介绍了JS中的定时器。使用这种方式实现动画其实是有其性能瓶颈的,例如:

1
2
3
4
function animation(){
// do something
setTimeout(animation, 1000/60)
}

上面我们以60帧/秒的速度执行动画,但是如果浏览器不是60帧/秒,就会掉帧;并且由于JS单线程的特点,所有不能保证每一次执行回调都是1000/60毫秒;还有,当窗口处于非激活状态的时候,它同样可能会执行。

其实很好理解,作为定时器,setTimeout/setInterval并不是专门做动画的,存在各种各样的问题也是很好接受的,但是当我们认识到这种实现动画的方式的各种缺点时,我们也许会考虑另一种动画的实现方式,而requestAnimationFrame是一种更好的方案。

初识 requestAnimationFrame

当我们执行window.requestAnimationFrame(callback)的时候,浏览器会在下次重绘的时候执行回调函数,它会告诉浏览器马上就要执行动画了,而callback则是用于更新动画。

requestAnimationFrame使用起来很简单,通过递归不断来执行回调来更新画面从而让画面动起来,我们甚至不需为其指定动画执行的时间和帧率。其优点是:1)从名字上就可以看出这是一个专门用于实现动画的API,优化是自然少不了的;2)其如果处于非激活状态,会自动暂停执行,有效节省了CPU资源。

小实例

我们在做动画的时候,有时希望背景移动起来,结合目前正在学习的canvas,我们可以很轻易的做到这点.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let j = 0;
let image = document.querySelectorAll('img')[0];
function moveBackground(){
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.translate(10, 0);
ctx.drawImage(image, 0, 0);
j++;
if(j < 20){
requestAnimationFrame(moveBackground);
}
}
let moveBtn = document.getElementById('move');
moveBtn.onclick = function(e){
e.preventDefault();
requestAnimationFrame(moveBackground);
}

上面我们点击button的时候,开始执行动画,通过不断的坐标变换和清除重绘,达到背景图片向右移的效果。

最后,请注意,不是所有浏览器都支持该方法,所以你可能需要一个polyfill,关于如何实现这个polyfill,网络上的资源比较多了,这里就不在赘述。

分享到 评论

实用的JavaScript技巧、经验总结

  1. 避免给一个未申明变量赋值,因为这会直接创建一个全局变量。

  2. 总是使用 ‘===’ 而不是 ‘==’,’===’会直接比较,而’==’必要时会进行类型转换等造成错误。

  3. 使用typeof instanceof 应当小心。

    1
    2
    3
    4
    typeof null // object
    function A(){}
    new A() instanceof A // true
    new A() instanceof Object // true
  4. arguments 对象转换成一个数组

    1
    2
    Array.ptototype.slice.call(arguments);
    Array.from(arguments) //ES6
  5. 验证一个参数是否是数组

    1
    Array.prototype.toString.call(a) === '[object Array]'
  6. 取得一个数组中最大值与最小值

    1
    2
    Math.max.apply(Math, arr);
    Math.min.apply(Math, arr);
  7. 使用splice删除数组中某一个/一些元素,而不是使用delete,如果使用delete的话,相当于只是把原值变为undefined

  8. 使用for .. of来遍历数组,使用for .. in 要避免遍历到原型上面的可枚举属性,使用hasOwnProperty()来检测

  9. 不要扩展Object.prototype,因为这会给所有(?)对象增加属性/方法,从而产生很多意想不到的行为和错误!

  10. 对于一个构造函数,总是使用 new进行构造函数调用,否则默认返回空(对象)。

  11. arguments.callee() 可执行当前函数,不推荐使用。

  12. 认识 ‘+’运算符, 对于对象而言,会转换成字符串,对于其他运算符则会尝试转成数字。

  13. 在使用if语句是,如果需要在条件中赋值,需要加上括号:
    `javascript
    if((x = y)){
    // do something
    }
    并且结果是否为真取决于y的真假。

  14. 判断一个数是否为NAN使用 x !== x,为true则该变量为NAN(NAN不等于自身)

分享到 评论

在Express中使用Cookie

文章来自于我在express框架上使用cookie引发的一些问题,但在具体介绍cookie以及如何正确的使用cookie之前,我觉得我有必要说一说cookie到底是什么。

Cookie是服务器保存在浏览器中的一段小(一般而言size<4KB)的文本信息,而浏览器每次想服务器发出请求,就会携带上这段信息。Cookie一般包含了key、value、到期时间、所属域名、所属路径等信息。

在浏览器中我们只需要使用document.cookie来得到当前页面所属的cookie。请注意,返回的cookie是以字符串形式存在的,不同的key-value之间通过’;’来分割,所以如果你想对齐进行进一步操作,需要相应的处理。

这里需要注意的是,document.cookie属性是可写的,这就意味着你可以手动添加cookie,使用document.cookie="name=value"的形式。注意,这里是添加,而不产生覆盖。

好了,关于cookie的属性的具体含义和用法,大家可以自行去了解。

我的问题

我在使用服务器端使用cookie的时候出现了问题,出现这样问题的原因很简单,首先我对cookie存在错误的理解,请务必注意,cookie是服务器发送给客户端,而客户端在发起请求的时候携带cookie而已。在正确认识cookie之后,并且成功的将cookie发送到浏览器过后,问题又来了,我在请求的时候,cookie却不能发送到服务端。我使用的是下面一段代码:

1
2
3
4
5
6
7
8
9
10
fetch('/login?'+stringify_data, {
method: 'GET'
}).then(function(res) {
return res.json();
}).then(function(json){
console.log(json.status);
}).catch(function(err) {
console.log('oh ! error!')
})
}

这里我使用了fetch API,在能够正确的发送请求的情况下,服务器无法读取到相应的cookie信息,同样在chrome开发者工具中查看请求头也发现请求并没有携带cookie信息。我想着一定是fetch API的问题,所以我赶快写了一个使用Ajax的请求,很显然,能够正确的发起携带cookie的请求。好吧,写到现在,我想的确是fetch API的问题了,阅读文档发现fetch API发送的请求默认是不带cookie的,必须手动设置(无论是出于什么样的考虑,但还是觉得坑)!好吧,问题迎刃而解,我们只需要在fetch函数第二个参数设置credentials: 'include'就可以发送cookie了/无奈!

在express中使用cookie

在express中使用cookie是一件十分惬意的事情,因为如果你使用cookie-parser中间件的话,那么我们只需要使用res.cookie(name, value[,options])就可以设置cookie了,关于options相关的参数可以自行学习!

如果想删除cookie,也很简单,使用res.clearCookie(name)就可以啦。

当然如果想获取请求头发过来的cookie,我们只需要使用req.cookies就可以了,这里返回的是一个JS对象,我们直接可以使用name来读取值,从而做进一步的操作。

在使用了cookie-parser中间件过后,在服务端操作cookie已经足够简单,并且cookie-parser不但提供了非签名使用的方式,还提供了签名的使用方式,具体使用是在使用中间件的时候添加一个secret,app.use(cookieParser('secret'))即可,当然,在获取cookie的时候使用 req.signedCookies属性就好了。

好了,如果你不想使用cookie-parser,我们也能够通过req.headers.cookies(感到罪恶所以不推荐/无奈)访问到cookie,如果想写cookie的话,使用res.setHeader(name, value)(再次感到罪恶)或者res.writeHead(status[,options])就可以了…

尾巴

一般情况下,文章末尾,我总会写一点鸡汤/无奈,这次也不例外。距离写上一篇博客已经过了很久了,起初有两篇想写的文章,一篇是在RN中使用Navigator,另外一篇则是介绍我自己正在学习的几种分类算法。可是当创建好文件准备开工时,我因为写一篇文章可能需要2-3个小时(我速度慢)或者是因为真的动笔写的时候反而觉得没什么要说的就放弃了。然后一段时间过后,或许是因为忙,或许是因为懒,或许是因为浮躁,就是没有去实践,没有去巩固,而把一切都抛之脑后,然后把前面学习到的忘得一干二净!

嗯,这的确的真实的!仅此而已!

END

分享到 评论

兑水的心灵鸡汤

好久不逛简书!

记不住曾经是以什么方式登录过简书,在我把QQ,微信,手机号,微博都试了一遍过后,我终于登录上去了。随便翻了翻首页,第一篇文章就是《心灵鸡汤是怎么忽悠我们的?》。匆忙看完,尚在回味之中,突然发现自己曾经也是一个鸡汤高手,生产过数十篇鸡汤软文,然后着急翻看,发现果真如此!

总结下来,我熬的心灵鸡汤大概有这样的特点:写自己的故事,还算完美的结局,极高的迷惑性,不认真看或者不了解我的人根本意识不到他们已经喝了我的鸡汤。

丑小鸭都能变成白天鹅,所以一切都会变好的。可实际上,丑小鸭之所以能够成为白天鹅,并不仅仅是因为它努力,勤奋,而是他的父母本身就是白天鹅。

爱笑的女孩子运气都不会太差。可实际上,如果一个女孩运气一直不好,她怎么可能笑得出来?

刚刚看完自己三年前的一篇鸡汤文,讲述的是一个不起眼的男孩的成长历程。这个故事就像丑小鸭的故事那样,那个男孩通过不懈的努力最终成为佼佼者。当然那个男孩不是我自己,而我的真实情况却是,整个高中三年,我因上课期间逃到校外上网不下10次,每个星期天唯一的半天假期我大都在网吧度过;由于我对手机的痴狂,我甚至一个月会换好几个手机;篮球水平也没有那么好。而至于我最终为什么能够考上大学,这个我真的不知道,也许我真有过刻苦和努力的那段时光吧。

我开始揣摩我当时的心理,可是依然没有任何头绪。我猜,我写文章的时候从来没想过让别人看见,我在给自己炖鸡汤,然后自己大口大口的喝下去。当刚刚喝完的时候可能我很解气,顿时觉得神清气爽有精神,可一段时间过后,我又感到烦躁无比,因为我又要面对摆在眼前那么真切的问题,我最终从自己幻想的那个世界中爬出来,面对强烈的反差,消极和怠慢丛生,然后开始耽误,一天,一周,一年,一辈子…

我继续看文章,有几篇是在我在外校做交流生的时候写的。不知道能不能算鸡汤文吧,但至少还是有一股鸡汤味。然后我又发现了一个共性,所有的文章都是我在遇到问题踌躇满怀的时候写的,比如刚来到陌生校园而找不到上课教室过后,和室友闹了小矛盾过后,离别的时候。这又能说明什么问题呢?当我开始把心中所有的烦躁和愤怒写出来过后,我的确好了很多,因为在构思一篇文章的时候,我出奇的冷静。当文章写完,消极和愤怒开始消退,短时间内信心爆棚,此时鸡汤的功效正在起作用;一段时间过后,我又开始烦躁,负能量急速增长,我想,我似乎得了鸡汤依赖症。所以不断的熬鸡汤,不断的喝自己熬的鸡汤,已经成为了我的常态。

做菜也许是我为数不过get到的技能,所以文章的最后讲讲我认为的好的熬鸡汤的方式。

材料:4-6个月母鸡,腌制萝卜整只多只,当归若干,枸杞少量,鲜竹笋数只,姜蒜,料酒,食盐若干。
步骤:

  1. 在剔除多余的胸脯肉和鸡腿肉过后,切成小块洗净;
  2. 加入食盐,料酒搅拌均匀静置半小时;
  3. 使用60度热水清洗干净,放锅里炒干水汽备用(可适当加入油和盐);
  4. 加入适当清水,片状的姜,整只蒜,以及大块的腌制萝卜,大火煮开;
  5. 倒入鸡肉,枸杞,小火炖上1小时;
  6. 加入切好的竹笋,再小火炖上1小时,鸡汤就可以出锅了,如果觉得不够鲜,还可以加入适当鸡精调味。
  7. 热腾腾的鸡汤,享用吧!

曾经有人给我说过,做程序员写代码要开心,不然就不要写了。我想说,鸡汤要好好做,不然还不如不做。

共勉!

END

分享到 评论

Flex弹性布局

说起布局,我脑子里一下就想到了DIV+CSS布局,毕竟曾经被那么多写着DIV+CSS网页开发的书籍洗过脑,然后到现在还不怎么会用这种大众的布局方式。当然了,其实页面还有其它的一些布局方式,比如表格布局,框架布局这样已经逐渐被淘汰的布局方式,也有今天的主角–一颗冉冉上升的新星,弹性盒子布局。

Flex是Flexible Box的简称,我们这里把其翻译为弹性布局,至于为什么不叫“灵活的盒子布局”。额,这个问题也许会在读完本篇文章找到答案。好吧,正式开始。

容器

flex是display的一个属性,当然对于行内元素还有一个叫做flex-inline的属性,这里我们不多说,但是要注意的是,一旦对一个元素的display属性设为flex,那么它的子元素就不能使用“浮动(float)”这个神奇的属性,而这个元素将会一跃成为容器(container),而其的子元素将会成为项目。好吧,先从容器说起。

一旦把一个元素的display属性设置成为’flex’,这个元素就成为一个容器容器有几个比较重要的属性,学习和掌握这几个属性是学习弹性布局的关键。它们分别是:

  1. flex-direction: 决定子元素(项目)的排列方向。
  2. justify-content: 指定子元素在主轴上的对齐方式。
  3. flex-wrap: 指定多行显示以及显示形式。
  4. align-items: 决定项目在交叉轴上的对齐方式。
  5. align-content: 定义项目在多轴线上的对齐方式。

好吧,大概就是这几个了,我们注意到在上面解释的时候提到了主轴交叉轴,这里我先简单解释一下:对于这个概念,我们可以很简单的在一个容器上画一个十字坐标轴,如果我们设置flex-direction为row(行),那么横坐标就为主轴,纵坐标就是交叉轴,这里要注意坐标轴的指向,因为同样有一个属性为row-reverse,此时主轴的方向指向和设置为row的相反方向。

项目

作为容器的子元素,项目同样有几个重要的属性:

  1. order: 控制项目的排列,默认为0,值越小则越靠前。
  2. flex-grow: 用于定义Flex项目的放大比例,默认为0,即使存在剩余空间,也不放大。
  3. flex-shrink: 用于定义Flex项目的缩小比例,默认为1,即空间不足,Flex项目将等比缩小。
  4. align-self: 允许单个Flex项目有不同于其他Flex项目的对齐方式。

然后

我并不会把每一个属性具体来讲,因为这样的文章在网络上的确太多了。我正在学习RN,所以在这里就写一个界面,其中的布局将会采用弹性布局,中途我会对布局进行简单的分析,以加深理解。
当写到这里的时候我就开始布局这个简单的页面,然后到现在才布局好,时间用了那么长,感觉像是过了一年…好了,不多说了,最终的界面如下图所示。

其实我也不知道自己写了啥,还是勉为其难的就叫其“登录页面”吧,现在我来分析一下这个页面哪些元素是容器和项目(当然都是项目啦),又在什么地方使用了什么属性。

页面整体采用了弹性布局,所以从整体来看我们一定使用了flex-direction属性,并且其值是column,所以这里的主轴一定是Y轴并且是方向向上。并且我们能够观察到,页面上所有的元素都是居中的,我们于是想到了在交叉轴上的对齐方式是: align-items: ‘center’,而其主轴上的对齐方式则是默认的justify-content: ‘flex-start’。

接下来来看两个不明显的,页面上有两个输入框,每个输入框其实都是一个View组件包裹,那么在这个组件内部,我们仍然使用了弹性布局(竖轴为主轴),为了让输入框上下居中,这里必须让justify-content: ‘center’;紧接着最后一行有两个按钮,这两个按钮同样在一个View组件中,并且这个View也是弹性布局,并且一定要设置flex-direction: ‘row’才能让这两个按钮排列在一行。我们同样可以给每个按钮赋予不同的order值,让其进行排序。

总结一下:
我们是用弹性布局完成了一个基本页面的布局(虽然这真的很丑陋),在这个简单的布局中我们使用到的属性并不多,但是的确比使用css+div布局来得更快,尤其是垂直上的居中,css是比较难以实现的。这里我们一共有四个容器,分别是最外层容器,每一个输入框外层容器,按钮组外层容器,当然,这里面所有的元素都能称为项目,这里就不在多说。

尾巴

说了这么多,那么我对flex布局的态度到底是什么?一句话总结,学习它,了解它,使用它。弹性布局我在刚开始学习React的时候就有了解过,过了也快半年了吧,当时看着阮一峰老师的文章,感觉怎么都不明白,然后渐渐抛之脑后。直到学期开始,我开始学习RN,再一次接触到Flex布局,才想起来画一些时间去了解,然后试着使用,最后再让自己记忆下来。相比第一次我接触Flex布局,我做出了改变,而这种改变是在时间并不充裕的情况下,弥足珍贵的。希望自己能够加油,也希望和我有同样压力的同学加油!

分享到 评论

生成器

大家对于异步这个词想必都不陌生,看到异步我可能最先想到的就是使用回调,再者我会使用Promise,可是使用回调处理异步控制流是有缺陷的:第一,基于回调的异步方式实在不适合大脑对于任务的思考(这点我深有体会);第二,存在控制反转的问题。而Promise链提供了我们以顺序的方式处理和表达异步流。而这篇文章的主角生成器(generator)也是一种顺序和看似同步的处理异步的方式,并且它比Promise更加优秀。

有关函数

不知道大家有没有这样思考过,一个函数一旦执行,其是否能够中断,我也没有仔细想这个问题,并且当看到这个问题,我会经验判断函数在执行过程中不会中断。到底是否中断,我们先写一个例子:

1
2
3
4
5
6
7
8
9
10
var a = 1;
function foo(){
a++;
bar();
console.log(a);
}
function bar(){
a++;
}
foo(); // 3

运行上面这个例子,最终的结果是3,这就意味着,当函数执行完a++后,函数foo从表面上来看被中断了,然后执行了bar函数,最后执行权交给foo,函数返回已经被修改的变量a的值。虽然通过上面的小例子,并且因为JS单线程的特性,我们似乎能够肯定JS是抢占式的,但实际情况是JS并不是抢占式的,虽然函数bar的执行打断了函数foo的执行,但这其实是一种“关联”(参考原型继承)。

初识生成器

现在让我们来认识生成器表达式,
同样是上面的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
var a = 1;
function *foo(){
a++;
yield;
console.log(a);
}
function bar(){
a++;
}
var iter = foo();
iter.next();
bar();
iter.next(); // 3

分析一下:
首先我们创建了一个生成器foo(注意*),该函数里面多了一个yield,有过python经验的大概知道这是干什么的。然后创建了一个函数bar;再来看执行,这里var iter = foo()并没有执行生成器foo,而是构建了一个迭代器,然后使用next方法启动了迭代器foo,并且在碰到yield停止执行,此时已经执行了x++,然后执行函数bar,执行完后,x经过两轮自增,此时x的值为3;最后我们调用next方法,从上一次中断处继续执行,并且没有碰到yield表达式,一直执行到函数结束,打印变量x的值为3。

现在我们来回答什么是生成器,其是一个特殊的函数,在函数声明的时候函数名前面包含一个”*“,并且能够多次启动和暂停。好了,我认为仅此而已。

一些问题

为了把生成器运用到异步流程控制中,我们还需要更深层次的了解生成器。

生成器仍然是一种函数

既然说生成器其本质上还是一种函数,所以其仍然具备函数最基本的特性,能够传递参数,也能返回值。我们不妨写一个例子测试一下:

1
2
3
4
5
6
7
8
9
function *test(a, b){
a++;
yield;
return a + b;
}
var it = test(1,2);
it.next();
var res = it.next();
console.log(res.value); // 4

其实生成器和普通函数的一个很大区别是在运行期间,它不会直接运行,而是创建了一个迭代器,然后每调用迭代器的next方法,便向下执行直到到碰到yield或者执行完成暂停。而我们注意到调用next方法返回的其实是一个对象,其包含一个value属性,如果生成器返回值的话,那么该属性的值就为生成器返回的值。总的来说,生成器的运行到目前为止完全是依托迭代器。

好了,继续。

继续来看例子:

1
2
3
4
5
6
7
8
9
10
function *foo(a, b){
a++;
yield a;
return a + b;
}
var it = foo(1,2);
var mid = it.next();
console.log(mid.value); // 2
var res = it.next();
console.log(res.next); // 4

这个例子想表达的就是对于生成器而言,yield表达式总会返回一个值,而我们可以通过迭代器next方法生成的对象来得到。

继续来看一个更复杂的例子:

1
2
3
4
5
6
7
8
9
function *foo(a){
var b = a + (yield 'ok return!');
return b;
}
var it = foo(1);
var res = it.next();
console.log(res.value); // ok return!
res = it.next(2);
console.log(res.value); // 3

这个例子向我们展示了生成器是如何双向传递消息的,yield表达式可以发出消息响应next(…)的调用,而next(…)可以向暂停的yield表达式发送值。

多个迭代器

一个生成器的多个实例可以并发执行,并且可以彼此交互。

1
2
3
4
5
6
7
8
9
10
function *gen(a){
var b = a + (yield)
return b
}
var it1 = gen(1);
var it2 = gen(2);
it1.next();
var res = it1.next(3).value;
it2.next();
console.log(it2.next(res).value); // 6

这个例子想说明的是一个生成器的多个实例可以并发执行,并且是互不干扰的。

分享到 评论

Prototype

JS为了模拟面向对象“类”的实现,为了模拟类的复制行为,可能会使用一种叫做“混入”的方法,当然,这种方法和我们今天要说的原型并没有多大的关系。使用mixin的方式来模拟“类”的实现不常见,当然为了模拟”类“所付出的代价也会让我们得不偿失,JS中不并存在”类”,而是存在一种叫做原型的东西,请容我细细说来。

Prototype

我们直接来讲文章的主角Prototype,其实JavaScript中每个对象都有一个叫做[[prototype]]的属性,这个属性就是对其他对象的一个引用。基本上所有的对象在初始化时[[prototype]]都会被赋予一个值,关于这个值是什么以及如何访问这个[[prototype]]属性,我会在后面提到。

还是先看一个例子:

1
2
3
4
5
var obj = {
a: 1
}
var obj2 = Object.create(obj)
console.log(obj2.a); //1

上面的代码我们创建了一个对象obj,其包含一个属性a,我使用Object.create(obj)创建了一个新的对象,并把该对象的[[prototype]]属性赋值为obj,最后我们打印obj2中并没有显式声明的变量a,令人惊奇的是,我们成功的访问到了变量a,并且该变量的值为[[prototype]]属性引用对象obj的属性a的值!

我想解释下为什么要对这段代码写这么详细的解释,因为对于大多数接触过JS的童鞋而言,原型已经是见怪不怪了,可是当初我学习JS的时候,脑子里完全没有原型的概念,直到有一天我慢慢开始懂得原型,那个时刻,我的心情就像现在写这段解释的时候这么激动!

看完上面这段代码和冗长的解释,即使不了解JS的童鞋也对原型有了一定的认识。在这里我想再说一下,[[prototype]]到底有什么用,其实很简单,当我们试图引用某个对象的时候,在底层其实调用的是一个GET方法,而这个方法首先会查找对象本身存在这个属性与否,如果不存在则通过[[prototype]]访问其原型对象,如果还是不存在的话,则访问原型的原型对象(别忘了原型对象也是普通对象),知道找到或者达到尽头(Object.prototype)。这个道理很简单,如果你使用for…in循环遍历一个数组的话,也许你得到的结果除了数组成员,还包含一些其它成员(不信你试试看),这些成员就来自原型对象,并且是可枚举的,而对于in关键字,也会查找原型链上面属性。

在说类的时候,也许更恰当的是给类打上一个引号,因为JS中根本就不存在”类”,JavaScript中只存在对象,我们不使用“类”创建对象,更多时候我们直接创建对象。可有些时候,我们使用new关键字来初始化一个对象,我们甚至在ES6后开始使用class,extend等属于类的关键字,这貌似和我前面说的矛盾了…
接着看一个例子:

1
2
3
4
5
6
function A(){

}
console.log(A.prototype);// {}
var a = new A();
console.log(typeof a); // object

我们创建了一个函数A,并且这个函数有一个属性prototype,如果没记错的话,这是本篇文章第一次访问原型,然后我们使用new初始化了一个对象,有传统面向对象语言基础的同学就知道,这简直像极了“类”!我再次强调,JS中不存在类,而且此new非彼new,这里函数A在new关键字的作用下,新建了一个空白对象,并让其prototype指向的对象赋值给新建对象a的[[prototype]]属性(关联),当然这里面还会做一些其它的工作,不过大体上就这样了,很简单吧!

在JavaScript中,并不存在类的复制,我们不能创建一个类的多个实例,只能创建过个对象,只不过通过new这种方式创建的对象,其内部的[[prototype]]属性关联到同一个对象,这里所说的关联是建立一个联系,并不存在复制。

构造函数

既然不存在类了,这构造函数听着也很别扭,我们暂且给它打个引号吧。上面我们在说“类”的时候,我们就用到了”构造函数”,函数A就是所谓的“构造函数”,其本质上就是一普通函数,是JS的一等公民,要说真要有什么区别,函数名首字母大写算吗?也许是吧。

再来写一个例子:

1
2
3
4
5
function B() {}
console.log(B.prototype);
console.log(B.prototype.constructor === B);
var b = new B();
console.log(b.constructor === B);

我感觉我放了一个大招,突然让自己迷惑起来,这里我要说明的是,B.prototype和对象b有一个叫做constructor的属性,并且默认指向函数B。这个属性的名字会让我们对JS的误解加深,四级没过的都知道,constructor翻译过来可叫做“构造器”啊,那么既然B.prototype.constructor指向了B,我们还有什么理由不说B不是“构造函数”?讲到这里,我很无奈…

其实呢,JS中根本不存在什么“构造函数”,其就是普普通通的函数,只不过一旦加上new关键字,这个函数调用再也不是普通的函数调用,我们把它叫做“构造函数调用”。

这里不想再说下去了,写个复杂点的例子先:

1
2
3
4
5
6
7
8
9
function Student(name, city){
this.name = name;
this.city = city;
}
Student.prototype.showInfo = function(){
console.log(`name: ${this.name}, from: ${this.city}`);
}
var stu = new Student('limoer', 'Chongqing');
stu.showInfo(); // name: limoer, from: Chongqing

这里有两个值得注意的地方,每个通过”构造函数调用”而生成的对象都存在两个属性name和city;我们给Student.prototype上添加了一个“方法”,这样所有的新建对象都关联了这个对象,可以引用这个“方法”,关于this的使用,这里就不在提了。

在说了这么多过后,我想把“构造函数”称为“关联函数”,因为所谓的“构造函数”其实并不存在,或者说是,我们并不知道一个函数在创建好后是否是“构造函数”,而如果我们把它叫做“关联函数”,因为它本质上做的工作包含了建立对象和其原型对象的关联,当然,这个叫法是不恰当的。

再来看看constructor属性,一般情况下,任何一个普通对象都存在一个constructor属性,其实这个属性并不是其本身就有,而是当引用该属性的时候,其可以在该对象的原型链中找到。现在我急切的想写一个例子来表明一个问题:

1
2
3
4
function C(){}
C.prototype = {}
var c = new C();
console.log(c.constructor === C); // false

我不啰嗦了直接看问题,这里对象c的constructor属性竟然指向的不是创建它的那个函数C,这也侧面印证了我上面说的话,通过构造函数调用创建的对象不直接持有属性constructor而是从其原型链中“继承”而来,所以当我们想写一段包含继承的代码时,如果还想用constructor属性,需要做必要的修正。

1
2
3
4
5
6
function Main(){}
function Sub(){
Main.call(this)
}
Sub.prototype = Object.create(Main);
Sub.prototype.constructor = Sub;

在结束“构造函数”讨论的时候,提醒一句,尽量不要使用constructor属性,要问原因?我想我已经不那么直观的在前面说出来了。

如何关联

我在上面提到把“构造函数”叫做“关联函数”,这虽然是不恰当的,但也不是一无是处,因为使用new关键字的“构造函数调用”,其在创建一个对象过后,也把该对象的[[prototype]]属性关联到该函数的prototype上。当然,如何关联不止这一种方法,这里介绍一种使用更为普遍的方法,Object.create(proto)。

还是例子为先吧:

1
2
3
4
5
6
7
var obj = {
info: function(){
console.log('info');
}
}
var obj1 = Object.create(obj)
obj1.info(); // info

这里我们使用字面量的直接形式创建了一个对象obj,该对象包含一个方法info,然后使用Object.create()创建了一个新的对象,并且该对象内部的[[prototype]]属性指向obj,概括点来说,该方法创建了一个对象,并把其关联到指定的对象。

Object.create()是ES5才引进的,在这里我实现一个polyfill代码作为本篇的结束:

1
2
3
4
5
6
7
if(!Object.create){
Object.create = function(obj){
function Foo(){}
Foo.prototype = obj;
return new Foo();
}
}

当然,这个版本的polyfill代码无法做到更复杂的功能,而Object.create第二个参数可以指定要添加到新建属性的属性名、值等。

尾巴

我们如果要访问一个并不存在的属性,在内部将会使用[[GET]]方法,并且查找该对象[[prototype]]所关联的对象,该关联实际上定义了一条“原型链”,在查找属性的时候会遍历整个“原型链”。

关联两个对象的最常用的两种方法是:(1)使用new关键字进行“构造函数调用”,创建一个新对象并进行关联;(2)使用Object.create(),创建新的对象,并时期和传入对象关联。

最后再次强调,JavaScript并不存在类,所有继承的实现完全是基于原型链,不存在复制。

注:到底前面所说的原型链的尽头到底在哪里呢?答案是Object.prototype,对于一般的原型链而言,其最终都指向了Object.prototype,这个对象包含了许多对象通用的方法,例如obj.toString()&obj.valueOf()等。

分享到 评论