ES6学习之解构赋值

本文选自我在SegmentFault的#21天阅读分享#中,所记录的两篇笔记。
因为对自己帮助较大,所以分享在此。

起因

前两天在项目中,需要应用到vuex(类似redux的状态管理工具)。而vuex中,关于变量的赋值是ES6中的解构赋值。
恰巧今天在看犀牛书时,也看到了关于解构赋值的介绍,所以今天准备专门学习解构赋值。

解构赋值

之前我们声明变量,是这样的:

1
2
var one = 1;
var two = 2;

这种变量声明的方式,写的少了还好说。写多了,却会感觉繁琐。也容易出错。
而ES6中,关于解构赋值的写法,是这样的:

1
2
3
4
5
var [one, two] = [1, 2]
console.log(one)
// 1
console.log(two)
// 2

这样的话,一次性就命名了两个变量。
但只是这样的话,功能其实是不够用的。
结构赋值还支持如下的形式:

1
2
3
var [one,,three,] = [1,2,3,4]
console.log(three)
// 3

这种方式,可以留空位,从而是变量赋值达到精准的要求。
在阮一峰老师的ES6文档中,关于解构赋值有这么一句:

解构赋值可以方便地将一组参数与变量名对应起来。

1
2
3
4
5
6
7
// 参数是一组有次序的值
function f([x, y, z]) { ... }
f([1, 2, 3])
// 参数是一组无次序的值
function f({x, y, z}) { ... }
f({z: 3, y: 2, x: 1})

现在看来,只是把传入的dispatch参数,给解构赋值了。

对象的解构赋值

先写一个demo。

1
2
3
4
5
6
7
8
var { foo, bar } = {
foo: "Hi i'm foo",
bar: "Hi i'm bar"
}
console.log(foo)
// "Hi i'm foo"
console.log(bar)
// "Hi i'm bar"

对象的属性没有次序,变量必须与属性同名,才能取到正确的值。
在这儿,我设置了foo和bar,自然就取到了相应的变量。
但是如果要名字不一样呢?

1
2
3
4
5
6
7
8
var { foo: Anotherfoo, bar: Anotherbar } = {
foo: "Hi i'm foo",
bar: "Hi i'm bar"
}
console.log(Anotherfoo)
"Hi i'm foo"
console.log(Anotherbar)
"Hi i'm bar"

这儿相当于把获取到的foo值,赋值给Anotherfoo。从而达到变量名不同也能变量赋值的效果。
这部分的机制,就借用阮一峰老师的话语:

这实际上说明,对象的解构赋值是下面形式的简写

1
var { foo: foo, bar: bar } = { foo: "aaa", bar: "bbb" };

也就是说,对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。真正被赋值的是后者,而不是前者。

1
2
3
var { foo: baz } = { foo: "aaa", bar: "bbb" };
baz // "aaa"
foo // error: foo is not defined

上面代码中,真正被赋值的是变量baz,而不是模式foo

同时之前在使用vuex中,对这一句话很不理解:

1
2
3
4
5
6
7
8
const vm = new Vue({
vuex: {
getters: { ... },
actions: {
plus: ({ dispatch }) => dispatch('INCREMENT')
}
}
})

2016.04.16更新

在看到Vuex源代码时,发现有这么一部分:

1
2
3
4
5
6
7
constructor ({
state = {},
mutations = {},
modules = {},
middlewares = [],
strict = false
} = {})

感兴趣的,是函数中,指定了变量的默认参数并进行了变量解构赋值,但给整个参数又指定了默认值。
于是手写了一个demo:

1
2
3
4
5
6
7
8
9
10
11
12
13
function getInfo({
a = '我是默认参数A',
b = '我是默认参数B'
} = {}) {
console.log(a, b)
}
getInfo({ a: 'A被覆盖了', b: 'B被覆盖了' })
// A被覆盖了 B被覆盖了
getInfo({})
// 我是默认参数A 我是默认参数B
getInfo()
// 我是默认参数A 我是默认参数B

也就是说,这种写法,当函数未传入覆盖默认值的参数,则默认参数将被解构赋值。从而保证默认参数100%得到使用。
而不会出现下面,没有传入参数时报错的现象。

1
2
3
4
function getInfo({……一些默认参数} = {}) {}
getAnotherInfo()
// Uncaught TypeError: Cannot match against 'undefined' or 'null'.

嵌套对象的解构

感觉,这应该是解构赋值中最实用的部分了(个人认为)。
因为经常套数据,所以也经常需要把变量的数据取出,转成变量。写多了的话,也是感觉很繁琐的。
而ES6,提供了一种全新的解决方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var json = {
'name': 'Lxxyx',
'info': {
'age': 19,
'subject': 'HRM'
}
}
var {
name,
info: {
age,
subject
}
} = json
name // "Lxxyx"
age //19
subject //"HRM"

如果直接写变量,代表把相应变量赋值。
如果加个:号,则表示操作符。表示要去这里面找变量。
如果不理解的话,自己写一遍demo也就理解了。

结尾

前端路漫漫,且行且歌~


如果你觉得我的文章对你有帮助,请支持我。让我写出更好的文章。

支付宝付款 微信付款

前端基础HTTP知识学习之HTTP状态码

起因

之前我在我的2016,寒假前端学习计划中,发现自己对HTTP的知识很不熟悉。而作为一个前端,应该对HTTP知识了如指掌。
这也是三月份我在找实习的过程中,和几家公司的CTO交流中,他们给我的指点与建议。很感激他们对我的帮助,让我了解到自己知识的漏洞。
在补习HTTP相关知识的时候,我手头上有两本书,一本是《图解HTTP》,另外一本是《HTTP权威指南》。
在这儿比较推荐前端初学者,去看《图解HTTP》这本书。
《图解HTTP》
就是图里这本书,整本书深入浅出,书里面的图示非常多。所以整本书读下来非常轻松,却又能恰好掌握HTTP的基础知识。所以我在读的时候,一口气记了十几页的笔记。

那学习HTTP知识,最大的好处就是当前后端交互出错时,至少知道是前端的问题还是后台的问题。这样能省下很多扯皮的时间。(对于我来说)

HTTP与前端

既然作为HTTP的基础知识,我们这儿就不介绍HTTP的起源,发展,与TCP/IP啥啥啥的关系了,有兴趣的同学可以自己看《图解HTTP》或者《计算机网络》这本书。
作为前端,最多接触的HTTP知识是什么?我认为,是状态码。
作为一个前端,使用Ajax完成前后端交互,通过Chrome控制台的Network来看资源加载情况等事情,简直和家常便饭一样。
比如说,看到 404 就知道这个资源没能加载出来,而看到200就知道这次加载稳了。
曾经的我,对于HTTP实际应用的大概了解,就只有这些了。但是这些还不够呀。所以在看完《图解HTTP》时,就过来分享一些我HTTP知识的盲点,希望能对大家有所帮助。

HTTP常见状态码分类

大家都知道,在进行请求时,会产生各种各样的HTTP状态码,所以在这儿,首先要介绍的就是HTTP的常见状态码。
最常见的,就是HTTP 2XX - 5XX了。
这儿,我列了一个表格,来描述

HTTP状态码 状态码 原因解释
2xx 成功 请求正常处理完毕
3xx 重定向 需要附加操作
4xx 客户端错误 服务器无法处理请求
5xx 服务器错误 服务器处理请求出错

关于各种状态码的意义,在这儿就一目了然了,比如说,发生4XX时,一般是前端的锅。发生5XX时则是后台的锅。
当要设置页面重定向时,应该设置3XX的状态码,而不是在原网页上使用

1
location.href = 'http://xxxxx'

这种方式。(别笑,真的见过这样的……)

常见状态码

这儿,我将会记下几个常见的状态码,虽然《图解HTTP》这本书提供了14个常见的HTTP状态码,但是对我来说,可能知识盲点和常见的也就那几个。
所以这儿就着重介绍他们,如果想了解详细的,各位可以自行借阅或购买《HTTP权威指南》这本书,绝对满足你的需求。

2XX - 成功

在这儿,首先介绍的是HTTP 200,200的意思很好理解,就是客户端的请求已被处理完毕。

204 - No Content

这个呢,代表服务器请求已被成功处理,但是返回的结果中,是不带实体信息的。

206 - Partial Content

表示范围请求,既返回的实体信息只是一部分。比如,返回某张照片的下半部分,某个文本的最后几行。用得好的话,可以大大节省网络传输的流量,达到指哪打哪的效果。

3XX - 重定向

这部分,其实是有点绕的。不过仔细看看,想一想也能很好的分辨。

301 - 永久重定向

永久重定向,告诉客户端以后应从新地址访问。同时书签(如果有的话)也应该更换为新地址。

302 - 临时重定向

告诉客户端我换了位置了,但你还可以用这个地址来访问。现在基本重定向,返回的都是302状态码。

303 - 临时重定向 - POST请求改为GET

对于POST请求,它表示请求已经被处理,客户端可以接着使用GET方法去请求Location里的URI。

307 - 临时重定向 - POST请求不变

对于POST请求,表示请求还没有被处理,客户端应该向Location里的URI重新发起POST请求。

304 - Not Modified

这个算是很重要的状态码,常见于网站的缓存处理中。
这儿,我摘抄了维基百科的解释:

如果客户端发送了一个带条件的GET请求且该请求已被允许,而文档的内容(自上次访问以来或者根据请求的条件)并没有改变,则服务器应当返回这个状态码。

304的作用,还是很大的。简单点说就是:我给你发送请求,查看我本地这个资源是否是最新的,如果是,服务器就返回304。浏览器从本地读取缓存,而不用去服务器上请求资源。
想深入了解的话,可以阅读这篇文章: [译]理解HTTP/304响应

4XX - 客户端错误

这部分属于客户端错误,也就是说如果出错了,很大可能是前端的锅。

400 - Bad Request

该状态码表示请求报文存在语法错误,需要修改。

401 - Unauthorized

代表请求未经认证,不给过。

403 - Forbidden

服务器拒绝请求,但是不想告诉你理由。

404 - Not Found

哈哈,这个就是大名鼎鼎的HTTP 404 Not Found 状态码。
这个状态码有两层意思,第一层是请求的资源未找到,第二层则是和HTTP 403一样,服务器拒绝请求,但是不想告诉你理由。

5XX - 服务器错误

这部分属于服务器错误,也就是说如果出错了,则是后台的锅。

500 - Internal Server Error

Internal Server Error翻译成中文就是服务器处理请求出错。也就是后台那儿出了问题,所以发送了500给你。
这种时候,放心的找后台报BUG去吧。

503 - Service Unavailable

这个状态码,则是告诉你现在服务器暂时无法处理你的请求,你待会儿再试试。
当然,出现这种问题,一般也可以直接找后台。

总结

今天所写的HTTP状态码,只是HTTP整个知识的一部分,但却很重要。熟悉常见的HTTP状态码,能帮我们快速定位问题,同时如果你是后台,给出正确的HTTP状态码,也方便自己将来DEBUG。

话说这儿得吐槽一句,那就是七牛的图片上传工具不太方便。在Windows下还好,有个图形界面,自动监控文件夹内容就自动上传的。
但是在Mac和Linux下只有纯命令行界面的工具。很是不方便。于是在写这篇博客时,自己用Gulp写了个小工具,专门配合七牛的Node.js SDK,实现自动监控文件变化并上传。
怎么说,既然选择做程序员,那么能让电脑帮你完成的,就不要动手啦。
况且,这个博客从配置完成的那一天起,我就基本没有折腾过。因为只是想安安静静的写博客而已,目前HEXO和这个主题的效果就挺好的。博客博客,最重要的还是里面的内容。

就酱~


如果你觉得我的文章对你有帮助,请支持我。让我写出更好的文章。

支付宝付款 微信付款

博客将于近期恢复更新

停滞的一个月

在02-25,我写下了《寒假前端学习总结》。直到今天,整整一个多月的时间,没有去更新博客。在这儿对关注我博客的各位,说声抱歉。
因为所处的学期是大二下学期,课程超级多。而且因为刚开学,学生会和工作室两头的事情也忙得不可开交。
所以整整一个月,都是忙过来的。
不过这一个月,倒也不是全无收获。

二十一天阅读分享

虽然在三月份,一篇博客都没写,但是我也没有停止学习。
在三月初参加了前端早读课与Segmentfault的二十一天阅读分享活动。要求连续二十一天,每天都写一篇技术笔记。
很高兴的是我坚持下来了,Segmentfault的奖品就是我一直想买的那本《ES6 标准入门》。所以算是有所收获。
近期会把一些自己写的比较好的笔记誊抄到博客里。
这是我的笔记链接。

Lxxyx 的笔记

校招与暑期实习

三月份还有不得不提的一件事,就是校招与暑期实习了。

校招

因为大二结束,就会退出现在的工作室。相当于没有项目可以做,练手的机会也很少,所以想着找份实习。
因为是大二,只有暑期能去实习。所以时间上算是比较尴尬的,也很少会有公司去招聘一个大二的实习生(总觉得技术不够或者不靠谱等等)。
第一个想到的就是BAT这种大公司,因为只有这种大公司会招聘暑期实习生。所以很高兴的找了阿里的一个前辈帮我内推,幸运的是内推申请过了。
衰的是在三月四号进行了第一轮面试,在今天,简历的状态依然是在评估中。据消息是已经挂了。
====> 4.9号更新 确实,已经挂了。

暑期实习

其实中间还是有些慌乱的,因为想着如果暑期找不到相应的前端实习,自己只能继续闭门造车了。还好,在对路上申请实习,获得了三个实习机会。
于是选择了最喜欢的一家,约定好暑期去实习。两轮电话面试,CTO和另外一个前辈给我的感觉都非常好。
怎么说呢,应该是以进入一家这样的公司为荣吧。

博客将于近期恢复更新

这是今天的正题,博客这种东西,不一直写下去其实是没有意义的。
今后会比较侧重前端相关的知识,和一些自己学习的感悟。
希望能帮助到大家,也算是给自己的知识备个份,需要的时候也能方便的查阅。
好些天没有写博客,说实话写这篇博客的时候自己的脑子是不连贯的。
至于写博客的好处,可以参见阮一峰老师的文章:

为什么要写Blog?

就酱,吃早餐去啦。


如果你觉得我的文章对你有帮助,请支持我。让我写出更好的文章。

支付宝付款 微信付款

2016,寒假前端学习总结

今天是2.25,寒假的最后一天。
一月九号我写下《寒假前端学习计划》一文作为寒假的开始,今天我同样写下《寒假前端学习总结》一文作为寒假的结束。

疯狂生长

给自己的寒假,定的总结词是“疯狂生长”。这也算是自己过的最为充实,最为努力的一个寒假。每天起床后,便
一头扎进编程的海洋中,从早到晚,乐此不疲。

读书

寒假带了三本书回家:

  1. 《JavaScript高级程序设计》
  2. 《JavaScript设计模式与开发实践》
  3. 《计算机科学导论》

颇为自豪的是,三本书我都看完了。
第一本《JavaScript高级程序设计》算是二战了,有种常看常新的感觉。感觉之前在做项目时,碰到的很多JavaScript问题,都可以在书上找到答案。难怪被人称之为红宝书。

第二本《JavaScript设计模式与开发实践》,给我带来了极大的震撼。书中每一个介绍的设计模式,都会有相应的实际案例相配套。从未有见过,代码还能以那样一种精巧的方式书写。算是在学习之余,极大的开拓了自己的眼界。从一个真正的程序员的视角,去审视前端与其性能。

第三本《计算机科学导论》,是我顺手带回家。结果在某一天的下午,翻开第一页后变便一口气连读十几个小时的书籍。要说《JavaScript高级程序设计》让我拥有了对JavaScript的大局观,那么《计算机科学导论》便是让我领略到计算机科学的万千精彩。

做项目

俗话说的好,光说不练假把式。在看书之余,自然就是不断的练习。把书上看到的设计模式,新方法运用到实际项目中,对自己之前的代码进行重构等。

移动端开发

之前一直做的是PC端,定宽的网页。对于移动端,可以算是一无所知。(用bootstrap等框架做出的网页虽然能适应移动端,但总觉得不算是真正的能力,逃)
于是把移动端开发的学习放进了寒假计划中。
先去网上看了看移动端开发的资料,被一大堆的dpi,px,ppi,dpr弄得晕头转向。随后便打开了慕课网,跟着视频学习。下面是两节学完的课程。
移动端开发
学习了一门《移动web入门》,又跟着慕课网,写了一个春节贺卡。顿时感觉有信心多了。
写完贺卡的我,开始思考该写点啥来巩固一下刚学的知识。恰巧看到了张秋怡学姐(学长?)的前端简历,顿时惊为天人。同时张秋怡学姐还做了响应式设计。
张秋怡学姐の简历

遂开始仿写学姐的简历。整个过程历时三天,没有去Github看源代码。素材也自己找。使用了Gulp+Sass等工具。可以说是像素级模仿。全面巩固了之前所学的移动端知识。
我仿写の简历

当然,网页底部带上了版权声明,证明是张秋怡学姐的作品。我只是仿写。
版权声明

简历目前挂在我的服务器上,使用Express为后台,Jade作为渲染引擎,Mongodb为数据库。因为想做一个简历生成页面,只需要填入信息,便可以自动生成张秋怡学姐那样的简历。同时呢也希望自己能在这个暑假,找到一份实习工作。

简历地址–刘子健,前端开发实习生

工作室项目

工作室的项目,是内网改造。使用了Vue+Vue-Router,来完成SPA的编写。寒假一个月,算是深入学习与使用了Vue这个框架,不得不感慨,Vue真的是太好用了。

工具学习

工具的学习,算是少的。因为在知乎上看到了这个问题:

前端深入到什么程度才可以本科就拿到bat google 的offer?

其中,一个我非常崇拜的前辈贺师俊回答了这个问题。

才大二,少做(react/angular/php)hello world级别的事情(除非你能作出点真的有点实际价值的产品),先打基础。

于是警醒了我,让我开始了补基础的旅程。
不过为了加速开发,还是学了一点点工具的。比如Gulp,Sass,Webpack,forever等
最具代表性算是Gulp了。
半年前想学习的时候,觉得怎么看都不理解Gulp的运作方式,遂放弃。结果寒假时候看了看Gulp的用法,最多半小时,便熟练上手使用。想来是这半年的前端与Node学习,让我在不知不觉中理解了Gulp的运转方式。自然上手就超级快。

写文章

这书也看了,项目也写了。自然就要写文章总结了。
整个寒假下来,一共写了十六篇文章。包括前端学习,管理学习,随笔杂文等。其中,前端学习10篇,主要发表于自己的博客、Segmentfault和慕课网中。
写博客两个月中,给我的博客网站带来了2.3k的浏览量。算是小有成就~
博客浏览量

同时,因为发表文章的缘故,在Segmentfault获得了1.2k的声望。这在我前两个月,是想都不敢想的事情。
Segmentfault

发表在慕课网的文章,获得了征文大赛的奖项,也得到了慕课网二月份优秀作者的称号~
慕课网

感想

要说寒假的感觉,就是自己更勇敢了。
之前还因为自己是个文科生,而惴惴不安,整日思考自己能否学好编程。

在前辈的帮助,指引与自己努力之下,一个寒假过后,感觉整个世界都是亮堂的。要说从哪儿开始的话,就是提笔写下第一篇博客之时,My life has changed.

前方路漫漫,编程的世界却又如此绚丽。一路前进,且行且歌!


如果你觉得我的文章对你有帮助,请支持我。让我写出更好的文章。

支付宝付款 微信付款

寒假前端学习(10)——理解DOM事件流的三个阶段

本文主要解决两个问题:

  1. 什么是事件流
  2. DOM事件流的三个阶段

起因

在学习前端的大半年来,对DOM事件了解甚少。一般也只是用用onclick来绑定个点击事件。在寒假深入学习JavaScript时,愈发觉得自己对DOM事件了解不够,遂打开我的《JavaScript高级程序设计》,翻到DOM事件那一章,开始第二次学习之旅。
当然,DOM事件所囊括的知识较为庞杂,所以本文专注与自己学习时所碰到的难点,DOM事件流。

流的概念,在现今的JavaScript中随处可见。比如说React中的单向数据流,Node中的流,又或是今天本文所讲的DOM事件流。都是流的一种生动体现。
至于流的具体概念,我们采用下文的解释:

用术语说流是对输入输出设备的抽象。以程序的角度说,流是具有方向的数据。
通通连起来——无处不在的流 淘宝FED–愈之

事件流之事件冒泡与事件捕获

在浏览器发展的过程中,开发团队遇到了一个问题。那就是页面中的哪一部分拥有特定的事件?
可以想象画在一张纸上的一组同心圆,如果你把手指放在圆心上,那么你的手指指向的其实不是一个圆,而是纸上所有的圆。放到实际页面中就是,你点击一个按钮,事实上你还同时点击了按钮所有的父元素。
开发团队的问题就在于,当点击按钮时,是按钮最外层的父元素先收到事件并执行,还是具体元素先收到事件并执行?所以这儿引入了事件流的概念。

事件流所描述的就是从页面中接受事件的顺序。

因为有两种观点,所以事件流也有两种,分别是事件冒泡和事件捕获。现行的主流是事件冒泡。

事件冒泡

事件冒泡即事件开始时,由最具体的元素接收(也就是事件发生所在的节点),然后逐级传播到较为不具体的节点。
举个栗子,就很容易明白了。

1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Event Bubbling</title>
</head>
<body>
<button id="clickMe">Click Me</button>
</body>
</html>

然后,我们给button和它的父元素,加入点击事件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var button = document.getElementById('clickMe');
button.onclick = function() {
console.log('1. You click Button');
};
document.body.onclick = function() {
console.log('2. You click body');
};
document.onclick = function() {
console.log('3. You click document');
};
window.onclick = function() {
console.log('4. You click window');
};

效果如图所示:
事件冒泡示例图

在代码所示的页面中,如果点击了button,那么这个点击事件会按如下的顺序传播(Chrome浏览器):

  1. button
  2. body
  3. document
  4. window

也就是说,click事件首先在<button>元素上发生,然后逐级向上传播。这就是事件冒泡。

事件捕获

事件捕获的概念,与事件冒泡正好相反。它认为当某个事件发生时,父元素应该更早接收到事件,具体元素则最后接收到事件。比如说刚才的demo,如果是事件捕获的话,事件发生顺序会是这样的:

  1. window
  2. document
  3. body
  4. button

事件捕获示例图
当然,由于时代更迭,事件冒泡方式更胜一筹。所以放心的使用事件冒泡,有特殊需要再使用事件捕获即可。

DOM事件流

DOM事件流包括三个阶段。

  1. 事件捕获阶段
  2. 处于目标阶段
  3. 事件冒泡阶段

如图所示(图片源于网络,若侵权请告知):
DOM事件流示例图

1. 事件捕获阶段

也就是说,当事件发生时,首先发生的是事件捕获,为父元素截获事件提供了机会。
例如,我把上面的Demo中,window点击事件更改为使用事件捕获模式。(addEventListener最后一个参数,为true则代表使用事件捕获模式,false则表示使用事件冒泡模式。不理解的可以去学习一下addEventListener函数的使用)

1
2
3
window.addEventListener('click', function() {
console.log('4. You click window');
}, true);

此时,点击button的效果是这样的。
DOM事件流中事件捕获示例图

可以看到,点击事件先被父元素截获了,且该函数只在事件捕获阶段起作用。

处于目标与事件冒泡阶段

事件到了具体元素时,在具体元素上发生,并且被看成冒泡阶段的一部分。
随后,冒泡阶段发生,事件开始冒泡。

阻止事件冒泡

事件冒泡过程,是可以被阻止的。防止事件冒泡而带来不必要的错误和困扰。
这个方法就是:stopPropagation()

stopPropagation() 方法
终止事件在传播过程的捕获、目标处理或起泡阶段进一步传播。调用该方法后,该节点上处理该事件的处理程序将被调用,事件不再被分派到其他节点。

我们对button的click事件做一些改造。

1
2
3
4
5
6
button.addEventListener('click', function(event) {
// event为事件对象
console.log('1. You click Button');
event.stopPropagation();
console.log('Stop Propagation!');
}, false);

点击后,效果如下图:
阻止冒泡示例图

不难看出,事件在到达具体元素后,停止了冒泡。但不影响父元素的事件捕获。

总结与感想

事件流:描述的就是从页面中接受事件的顺序。分有事件冒泡与事件捕获两种。
DOM事件流的三个阶段:

  1. 事件捕获阶段
  2. 处于目标阶段
  3. 事件冒泡阶段

在学习DOM事件的过程中,了解了DOM事件的三个阶段,也知道事件冒泡是干啥用的,又如何阻止。配合前期所学的二叉树的相关知识,受益匪浅。

前端路漫漫,且行且歌~


如果你觉得我的文章对你有帮助,请支持我。让我写出更好的文章。

支付宝付款 微信付款

寒假前端学习(9)——理解CSS盒模型与宽高计算

起因

盒模型是CSS的核心知识,属于那种不掌握好,在实际工作中就容易犯迷糊的知识。
至于本篇文章,主要解决一个问题,那就是CSS盒模型的计算方法。至于别的知识,也算是自己回忆和复习一次。

盒模型的构成

关于盒模型的构成,算是前端的基础知识了。网络上关于这方面的知识也是多如牛毛。所以这儿我就用Chrome浏览器控制台的盒模型图。(毕竟最贴近实际开发环境)
盒模型构成图
可以看到盒模型由margin,border,padding,content(中心部分0x0的那个框)四部分组成。
如果要形象化的理解呢,我们举个栗子~

这儿有一个仓库,仓库里是各式各样的箱子。仓库代表网页,箱子代表独立的div。
两个箱子之间的空隙,就是margin。
箱子当然有自己边框了,每个箱子边框的厚度不一。这个边框,就是border。厚度呢就是border的大小。
箱子里面当然也装着各式各样的货物,箱子里面所有的货物,就是content。
但是货物也有可能没把箱子堆满,那么箱子内除去货物的空白部分,就是padding了。

这就是我对盒模型在现实中的理解。

盒模型的宽度计算

盒模型的宽度计算,不复杂但也不好玩。因为一个盒模型的宽度,不只是计算其content的宽度,还会加上元素的边框与内边距。

用个demo,就很好理解了。在demo中,两个div的宽度是一致的。(demo出处在底部)

1
2
3
4
5
6
7
8
9
10
11
.simple {
width: 500px;
margin: 20px auto;
}
.fancy {
width: 500px;
margin: 20px auto;
padding: 50px;
border-width: 10px;
}

但实际情况,却是这样的:
盒模型demo
这是因为盒模型计算宽度时,加上了padding和border的宽度。所以第二个元素看起来要比第一个元素大。

这样对于计算盒模型宽度是不利的,因为比较繁琐。于是后来人为了解决这个问题,在CSS3中给盒模型加入了新属性:box-sizing

CSS3的box-sizing

box-sizing共两个属性,一个是content-box,一个是border-box
设置为content-box则盒模型宽度计算方法同CSS2.1,计算内边距和边框。所以这儿我们着重讲解border-box

当设置一个盒模型为box-sizing: border-box时,这个盒子的内边距和边框都不会再增加它的宽度。

继续看第二个demo。在这儿,我们给所有盒模型统一设置box-sizing: border-box

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
* {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
.simple {
width: 500px;
margin: 20px auto;
}
.fancy {
width: 500px;
margin: 20px auto;
padding: 50px;
border: solid blue 10px;
}

那么,出来的效果会是这样的。
box-sizing demo

可以看到两个盒模型的宽度一致了。
这是因为之前设置的宽度,是元素的宽度。而内边距和边框在元素宽度外绘制。
而设置border-box时,内边距和边框都在设定的宽度内进行绘制。元素宽度需要由设定宽度减去内边距和边框得到。

怎么样,是不是很容易理解呢?至于高度,计算原理同上。这儿就不赘述啦。

小tips

算是个自己写网页时经常碰到的问题,那就是如果给一个元素设置background时,背景颜色的范围将包括内边距。

总结

要说总结的话,这节应该是自己学的最轻松的一部分。之前都是盲点和难点,这里却只是似懂非懂。看了看文档就瞬间明白了。然后想了想,还是写篇博客出来,因为好记性不如烂笔头~

然后再放上参考链接,有兴趣多了解的同学,也可以点开看看。

前端路漫漫,且行且歌~

参考链接:

CSS - 盒模型(也是demo来源)
CSS - 盒模型 - box-sizing
CSS3 box-sizing 属性


如果你觉得我的文章对你有帮助,请支持我。让我写出更好的文章。

支付宝付款 微信付款

寒假前端学习(8)——理解CSS浮动与清除浮动

本文主要探讨两个问题:

  1. 为什么CSS设置浮动会引起父元素塌陷
  2. 为什么设置clear:both能清除浮动,并撑开父元素。

起因

CSS的浮动,算是我在写网页时用的最多的属性之一。但要说我对浮动的了解程度的话,只能说“知其然而不知其所以然”。虽然很多人都说浮动会用就行,但是要想成为一个优秀的前端,对这些常用属性得知根知底。

恰巧在慕课网,张鑫旭老师开了《CSS深入理解之float浮动》这门课。链接在文末,有兴趣的可以听听,老师讲课风格很风趣……

Float的历史

Float设计的初衷,是为了实现文字环绕效果。就像下图展示的一样(图片源于w3school):
Float效果图
嗯,就这么简单。

Float引起的父元素高度塌陷BUG?

在这儿,我们用一个例子来说明子元素设置浮动,从而引起父元素高度塌陷的问题。
首先写一个div,里面插入一张图片。

1
2
3
<div id="div">
<img src="./source/head.jpg">
</div>

我们再给div设置一个border,为了让大家看的清楚。
CSS设置如下:

1
2
3
4
#div {
border: 5px solid red;
width: 600px;
}

最后效果是这样的:
未Float的效果图
通过chrome控制台,可以看到此时div的高度为464px。
div高度

接下来,我们给那张图片添加浮动效果。

1
2
3
#div img{
float: left;
}

再看网页,发现父元素已经塌陷了,之前的边框也消失不见了,成为一条线了。
Float后的效果图
此时再去控制台查看div的高度,高度为0px。
div高度

不,不是BUG

很多人把这个现象称为浮动带来的BUG。但从一开始Float的用途来思考:

1
Float设计的初衷,是为了实现文字环绕效果。”

那么,在那远古蛮荒的互联网时代,要如何实现文字环绕图片的效果呢?机智的程序员加入了Float属性,也引入了子元素浮动,父元素高度塌陷的特性。
看到这句话的时候,我思考了很久。因为无法理解父元素高度塌陷为何能让文字环绕图片。于是把视频来来回回看了十多遍,又手写了个demo,总算理解了。

总结来说,核心要点在于一句话:

1
“浮动元素会脱离文档流。”

至于文档流是啥,我这儿就不介绍了。但浮动的元素脱离了文档流,所以是不计算高度的。
在此,我们加入一段话,看看div的高度。
段落高度
从图中可以看出,div因为段落的加入,高度被撑开了。

所以子元素浮动引起父元素高度塌陷的原因如下:
因为没有预先设置div高度,所以div高度由其包含的子元素高度决定。而浮动脱离文档流,所以图片并不会被计算高度。此时的div中,相当于div中子元素高度为0,所以发生了父元素高度塌陷现象。

文字环绕效果的实现

那么,文字环绕效果是如何实现的?
其实讲起来也很简单,因为父元素高度塌陷,所以文字会按正常顺序排列,无视图片高度。而图片宽带又还在,所以实现了文字环绕效果。

清除浮动

介绍完浮动,自然要介绍清除浮动。在此,我们不具体的去探讨各种清除浮动的方式。而是去探讨,为何设置clear:both能清除浮动,并撑开父元素。

clear:both的作用

clear:both的作用,对各位来说可以算是耳熟能详了。至于clear的left,right等属性,我们这儿就不一一列举了。

1
<div style="clear: both;"></div>

在父元素内的底部,加入这一行代码。看图易知,父元素因为子元素设置浮动而高度塌陷的问题,已经被解决了。
加入clear: both后的效果

然后如果只是会用clear:both,又怎么能满足我的求知欲呢?相比与这行代码产生的作用,我更关心为什么这行代码能清除浮动。
对此,我继续翻阅文档。
在w3school中,clear的定义如下:

1
clear 属性规定元素的哪一侧不允许其他浮动元素。

当然,这样看,还是很难理解为什么clear能清除浮动并撑开父元素高度。那我们举个栗子!
当先声明一个元素A向左浮动时,由于脱离文档流,这个元素的右边就会空出一片空间,空间的长宽与浮动元素长宽相同。
然后我们再声明另外一个元素B,如果元素A右侧空出的空间内,还能放下元素B的话,那么元素B就会自动补上去。
下面我写一个demo,应该就很好理解了。
HTML部分如下:

1
2
3
4
5
6
7
8
<div id="div">
<div id="a">
<p>I'm divA</p> //此处用p
</div>
<div id="b">
<span>I'm divB</span> //用span,防止两个都是p,不能展现父元素塌陷效果。
</div>
</div>

CSS部分如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
#div {
border:5px solid red;
width:400px;
}
#a {
background:rgba(149, 149, 149, 0.42); // 为了方便演示,A的背景色设置成半透明。
width:200px;
float: left;
}
#b {
background: #6EEBC1;
width:300px;
}

效果图长这样:
demo效果

从图上可以看到,元素B的一部分是在元素A空出的空间内的。并且I’m divB这句话在元素A的右侧。且父元素高度塌陷,父元素现在的高度就是元素B的高度。
那么如果元素A右侧空出的空间内,放不下元素B呢?
我们把元素B宽度调整为200px。

放不下时的效果
可以看出,元素B就自成一行了。

给元素B加入clear:both后,元素B将忽略左边浮动所产生的空间,不去补空缺。
元素B设置clear:both后
如图所示,元素B会另起一行。而不是缩到浮动产生的空间内。

撑开父元素空间的奥秘

在w3school中,clear的定义中还有这么一句话:

1
“在 CSS2.1 中,会在设置清除浮动的元素上外边距之上增加清除空间,而外边距本身并不改变。”

也就是说,因为浮动而产生的空白空间,会被填充为实际存在的空间。。那么,自然就能撑开父元素。

总结

总结下来,浮动与清除浮动的顺序关系如下:

  1. 设置元素A浮动,元素脱离文档流,不计算高度。父元素出现高度塌陷。
  2. 浮动元素A产生空白空间。空间长宽等于元素A的长宽。后面元素会自动补空缺。
  3. 给浮动元素之后的元素B设置clear:both,元素B将不去补空缺。
  4. 元素B不仅不补空缺,还会把元素A因浮动而产生的空白空间填充为实际空间。
  5. 实际空间被计算高度,父元素被撑开。

这么一番走下来,花了很多时间去思考,去写demo。但对CSS浮动的理解也加深了。浮动为何引起父元素塌陷和清除浮动为何能撑开父元素这两个问题,一直是我的盲点。所以这次寒假,集中了两天时间去攻克它(除夕和正月初一,大过年的写代码,感觉有点怪但效率却出奇的高……)。

参考链接:

《CSS深入理解之float浮动》– 张鑫旭(也是课程地址)
CSS clear 属性
clear:both 为什么不起作用?–知乎,田雅文的回答


如果你觉得我的文章对你有帮助,请支持我。让我写出更好的文章。

支付宝付款 微信付款

《计算机科学导论》读书笔记(一):浅析面向过程与面向对象编程

《计算机科学导论》的来源

仍记得那是15年的5月底,面临着毕业的学长学姐看着自己大学四年的诸多书籍,纷纷在校内开始了摆摊生活。当时才大一的我,看着一长条的书摊,仿佛看到了宝藏。才逛了一会儿,我心满意足的抱着好几本书回寝室了。其中就包括这两天看到入迷的《计算机科学导论》。

当初买下它的时候,只是模糊的觉得,自己可能会用到。既然又是书,所以就爽快的买了下来。反正也才5元(逃)。回去一翻开,更是惊喜,书如全新的一般,连名字都没有。然后……放在书柜上,就束之高阁了。直到寒假回家时,顺手把它带回了家。

就是下图的这本书。

计算机科学导论

结果昨天下午看了一眼,便一发不可收拾,一口气读到深夜两点。读完之后,感觉之前所有零散的知识点被串成了一串,有种拨云见月的感觉。之前很多无法理解的概念,也都迎刃而解了。

看来无论是学习社会科学或计算机科学,都如杨绛先生所说的那句话一样:

“你的问题主要在于读书不多而想得太多。”

或者又如《荀子》一书中劝学篇所言:

“吾尝终日而思矣,不如须臾之所学也;吾尝跂而望矣,不如登高之博见也。”

总而言之,万分庆幸自己回家时候带了这本《计算机专业导论》,让我领略到计算机科学的万千精彩。

关于面向过程与面向对象的疑惑

第一次学编程时,学习的是C语言。在刚开始学的时候,就知道C语言是一门面向过程的编程语言,除此之外还有面向对象的编程语言。当时的我,并没有想这么多。只是慢慢的看视频然后学习。

面向过程与面向对象是学编程过程中不可避免的问题。果然,这个问题在15年3月份,我学习Java后开始出现了。

当时在图书馆借阅了李刚老师的《疯狂Java 第二版》,在学习到面向对象部分,彻底晕头转向了。究竟什么是面向对象,什么又是面向过程?为什么说面向对象是一种良好编程方法?封装、继承、多态到底是什么?(这应该算自己第一次尝到计算机基础不牢的苦果,只是当时没有意识到)。

在15年6月学前端,到现在已有大半年。期间也看过诸如《JavaScript面向对象编程》等书,也去谷歌过相关文档。但总感觉似懂非懂。

还好,我碰见了《计算机专业导论》这本书,一本让我有“拨云见月”之感的书。

面向过程

在这儿,我们先介绍面向过程。
在面向过程的程序中,我们把程序看成是 操纵被动对象的活动主体。其中,被动对象本身不能开始一个动作,但能从活动主体(程序)接收动作。
被动对象的数据储存在内存中,程序为了操纵它们,会发布动作。称之过程

例如打印一个文件,文件就是被动对象。同时为了能被打印,文件会存储在内存中。而程序为了打印文件,会调用一个print过程,print过程中包含了计算机打印所需的步骤。

在过程式模式中,对象(文件)和过程(打印)是完全分开的实体。对象(文件)是能接收print动作的实体。而过程print是被编写的一个独立的实体,程序只是触发它。

看到上面这一大串,是不是有点晕了?简单来说,面向过程模式的程序由三部分组成:

  1. 对象创建部分
  2. 一组过程调用
  3. 每个过程的一组代码

结合上面的例子,这样就比较好理解了。

面向对象

面向对象模式与面向过程模式区别在于:面向对象模式处理活动对象,而非被动对象。如日常生活中的洗衣机,汽车等。 在这些对象上执行的动作都包含在这些对象中,对象只需要接收合适的外部刺激即可。

还是拿打印文件做例子,在面向对象模式中的文件能把所有被文件执行的过程(面向对象中成为方法)(打印,复制粘贴等)打包在一起。在这种模式下,程序只需要向文件发出打印或者复制的请求,文件就会被打印或复制。而这些方法,也被从这些对象继承的其它对象共享。
比较面向过程与面向对象,可以看出面向过程编程中的过程是独立的实体,但面向对象模式中的方法是属于对象的。

面向对象的核心要点,在于类。因为相同类型的对象需要一组方法,为了创建这些方法,C++或者Java都选择使用成为类的单元。

继承性

在面向对象模式中,作为本质,一个对象能从另外一个对象继承。这个概念称为继承性。例如,当一个几何形状类被定义后,我们就可以定义矩形类。矩形是拥有额外特性的几何形状。

多态性

面向对象的多态性是指我们可以定义一些具有相同名字操作的方法,但这些操作在不同类中会产生不同结果。
例如我们给几何图形类定义一个算面积的方法,同时定义圆形类和方形类继承几何图形类。那么同样是算面积,圆形类的结果和方形类的结果会不一样。因为两者计算公式不一样。
这就是多态。

感想

本以为只是简单的描述一下自己对面向过程和面向对象的理解。结果写的时候却是磕磕绊绊。看来和老师说的一样,自己学的好和教别人教的好是两回事。还是得努力去加强这一方面。

接下来可能会写好几篇《计算机专业导论》的读书笔记,因为解决了我颇多难点,所以算是值得一写。至于看的速度,我觉得算是较快的,因为计算机系统组成,计算机网络,算法等章节,我在之前就有过专门学习。只是没有一本书把知识点给串起来而已。

前端路漫漫,且行且歌~


如果你觉得我的文章对你有帮助,请支持我。让我写出更好的文章。

支付宝付款 微信付款

计科之路--Start!

起因

按照之前的学习进度表,现在的我应该在钻研CSS的float等属性。但是我在这两天却选择了读一本书。
书的全名是:《如何高效学习:1年完成MIT4年33门课程的整体性学习法》
就是下面图片这本:
《如何高效学习》
哈哈,看到这名字,是不是很像鸡汤?
曾经的我也是这么认为的。
我第一次接触到这本书时,是在大一下学期,也就是大约一年前。当初看这本书的初衷,只是因为名字听起来很流弊,学习后能功力大涨的样子。(心态大概和学习闭包一样,以为学习后便能习得不世神功,称霸武林。)
但是当时年轻气躁,这本书只翻了两页,便觉得不适合自己,学习还是得一板一眼的学等等。这本书就放在书架上吃灰了。

计科之路

白驹过隙,不知不觉已是一年。过年回家时,捎上了《JavaScript设计模式》、《数学与生活》与这本书。因为虽然人还是那个人,但是体验和感悟已经完全不一样了。
一年前的我,还在思考要考研还是考公务员。那时候对于编程,只是觉得很新奇而已。
一年后的我,不在思考考研或是公务员,而是一心想着编程,想着怎么让代码写的更漂亮,怎么让代码跑的更快。
但一个很严峻的问题也摆在我的眼前。那就是我的计算机基础怎么办?
虽然一直有人说前端不需要啥基础,但是我依然坚信并践行着那句话:

1
“编程中,业务能力决定下限,而计算机基础决定其上限。”

这句话给我的感觉,就像这学期《领导科学》课程中老师所说的“天花板现象”一样。只是“天花板现象”的原因有天然的和人为的之分。但是计算机基础的学习,人为因素占了绝大部分。同时也是因为热爱,决定开始从零开始学计算机基础。
嗯……其实也不算从0开始学,之前自己也有学过一些计算机科学的课程。比如我的选修课《计算机网络》,在Coursea上了一半的《计算机系统》,自学的《学习JavaScript数据结构与算法》(逃)等。
不过一直觉得知识学的过于零散,然后想系统学习一番。

于是轻车熟路的找到了网易云课堂的大学计算机专业体系:
网易云课堂的大学计算机专业体系

决定按知识路线图,彻底给自己补一补基础。

最后的最后

当然,前端的学习也不会落下。因为两者不会冲突,前端的学习与应用,在一定程度上是对所学的计算机技术的实践。

而且点开网易云课堂计算机体系的大一课程时,惊喜的发现计算机专业导论课所学习的内容,自己从前两年就有过接触,上个学期更是在Coursea有过专门的学习。只是名称不一致罢了。

看来课业会轻松很多,这两年的兴趣式上课,还是帮助了我非常多的。

无论是人文社科的经史哲政,或者是理工科的计算机、医学常识等。都切实的让我了解到另一个世界,一个文科生从未见过的理科世界。

前端路漫漫,且行且歌。
(本篇博客略凌乱,因为写这篇文章的两天,发生了很多事情,心境变化较大。所以前后对接会有些不协调。)


如果你觉得我的文章对你有帮助,请支持我。让我写出更好的文章。

支付宝付款 微信付款

寒假前端学习(7)——学习JavaScript之this,call,apply

学习起因:

在之前的JavaScript学习中,this,call,apply总是让我感到迷惑,但是他们的运用又非常的广泛。遂专门花了一天,来弄懂JavaScript的this,call,apply。
中途参考的书籍也很多,以《JavaScript设计模式与开发实践》为主,《JavaScript高级程序设计》、《你不知道的JavaScript》为辅。这三本书对我理解this,call,apply都起了很大的帮助。

this

首先,我们先讲述this。

在《JavaScript设计模式与开发实践》关于this的描述中,我认为有一句话切中了this的核心要点。那就是:

JavaScript的this总是指向一个对象

具体到实际应用中,this的指向又可以分为以下四种:

  1. 作为对象的方法调用
  2. 作为普通函数调用
  3. 构造器调用
  4. apply和call调用

接下来我们去剖析前3点,至于第4点的apply和call调用,会在call和apply部分详细讲解。

1.作为对象的方法调用

说明:作为对象方法调用时,this指向该对象。
举例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 1.作为对象的方法调用
*
* 作为对象方法调用时,this指向该对象。
*/
var obj = {
a: 1,
getA: function() {
console.log(this === obj);
console.log(this.a);
}
};
obj.getA(); // true , 1

2.作为普通函数调用

说明:作为普通函数调用时,this总是指向全局对象(浏览器中是window)。
举例:

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
/**
* 2.作为普通函数调用
*
* 不作为对象属性调用时,this必须指向一个对象。那就是全局对象。
*/
window.name = 'globalName';
var getName = function() {
console.log(this.name);
};
getName(); // 'globalName'
var myObject = {
name: "ObjectName",
getName: function() {
console.log(this.name)
}
};
myObject.getName(); // 'ObjectName'
// 这里实质上是把function() {console.log(this.name)}
// 这句话赋值给了theName。thisName在全局对象中调用,自然读取的是全局对象的name值
var theName = myObject.getName;
theName(); // 'globalName'

3.构造器调用

说明:作为构造器调用时,this指向返回的这个对象。
举例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 3.作为构造器调用
*
* 作为构造器调用时,this指向返回的这个对象。
*/
var myClass = function() {
this.name = "Lxxyx";
};
var obj = new myClass();
console.log(obj.name); // Lxxyx
console.log(obj) // myClass {name: "Lxxyx"}

但是如果构造函数中手动指定了return其它对象,那么this将不起作用。
如果return的是别的数据类型,则没有问题。

1
2
3
4
5
6
7
8
9
10
var myClass = function() {
this.name = "Lxxyx";
// 加入return时,则返回的是别的对象。this不起作用。
return {
name:"ReturnOthers"
}
};
var obj = new myClass();
console.log(obj.name); // ReturnOthers

Call和Apply

Call和Apply的用途一样。都是用来指定函数体内this的指向。

Call和Apply的区别

Call:第一个参数为this的指向,要传给函数的参数得一个一个的输入。
Apply:第一个参数为this的指向,第二个参数为数组,一次性把所有参数传入。

如果第一个参数为null,则this指向宿主环境,浏览器中是window。

1.改变this指向

说明:这是call和apply最常用的用途了。用于改变函数体内this的指向。
举例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var name = "GlobalName"
var func = function() {
console.log(this.name)
};
func(); // "GlobalName"
var obj = {
name: "Lxxyx",
getName: function() {
console.log(this.name)
}
};
obj.getName.apply(window) // "GlobalName" 将this指向window
func.apply(obj) // "Lxxyx" 将this指向obj

2.借用其它对象的方法

这儿,我们先以一个立即执行匿名函数做开头:

1
2
3
4
5
6
(function(a, b) {
console.log(arguments) // 1,2
// 调用Array的原型方法
Array.prototype.push.call(arguments, 3);
console.log(arguments) // 1,2,3
})(1,2)

函数具有arguments属性,而arguments是一个类数组。
但是arguments是不能直接调用数组的方法的,所以我们要用call或者apply来调用Array对象的原型方法。
原理也很容易理解,比如刚才调用的是push方法,而push方法在谷歌的v8引擎中,源代码是这样的:

1
2
3
4
5
6
7
8
9
function ArrayPush() {
var n = TO_UINT32(this.length); // 被push对象的长度
var m = % _ArgumentsLength(); // push的参数个数
for (var i = 0; i < m; i++) {
this[i + n] = % _Arguments(i); // 复制元素
}
this.length = n + m; //修正length属性
return this.length;
}

他只与this有关,所以只要是类数组对象,都可以调用相关方法去处理。

这部分内容比较复杂,再加上自己水平也不太够。所以推荐有条件的同学去购买相关书籍,或者等我的后续博客文章。

感想

通过对这部分的学习,算是加深了对JavaScript的理解。最直观的表现就是,去看一些优秀框架的源代码时,不再是被this,call,apply,bind绕的晕乎乎的。还是很开心的~

下一段时间,准备深入探索一下日常学习和使用的CSS。毕竟JavaScript学了,HTML和CSS也不能落下。

前端路漫漫,且行且歌。


如果你觉得我的文章对你有帮助,请支持我。让我写出更好的文章。

支付宝付款 微信付款