├── README.md
├── css
└── README.md
├── img
└── qrcode.png
├── javascript
├── README.md
├── promise的变体方法.md
└── react-hooks.md
└── pins.md
/README.md:
--------------------------------------------------------------------------------
1 | # javascript-css-common
2 |
3 | > 这里将一些常用的方法总结为了一个npm包,方便下载和复用,欢迎关注和下载:[utils](https://github.com/wenzi0github/utils)
4 |
5 | [团队常用的工具方法](https://github.com/wenzi0github/utils)
6 |
7 | 在工作中经常遇到的基础代码进行总结,主要分为 javascript 和 css 两部分:
8 |
9 | ## [JavaScript 常用代码总结](./javascript/README.md)
10 |
11 | - [如何获取当前所在周的起始和结束的日期](./javascript#如何获取当前所在周的起始和结束的日期)
12 | - [对 cookie 的添加/获取和删除](./javascript#对-cookie-的添加获取和删除)
13 | - [js 字符串翻转](./javascript#js-字符串翻转)
14 | - [js 产生随机数字](./javascript#js-产生随机数字)
15 | - [radio-checkbox-select](./javascript#radio-checkbox-select)
16 | - [requestAnimationFrame 的兼容性处理](./javascript#requestAnimationFrame-的兼容性处理)
17 | - [获取鼠标移动的方向](./javascript#获取鼠标移动的方向)
18 | - [扩展 String 中的 format](./javascript#扩展-String-中的-format)
19 | - [js 产生随机字符串](./javascript#js-产生随机字符串)
20 | - [解析 url 中的参数](./javascript#解析-url-中的参数)
21 | - [时间格式化](./javascript#时间格式化)
22 | - [获取当月的第一天和最后一天](./javascript#获取当月的第一天和最后一天)
23 | - [截断字符串并添加省略号](./javascript#截断字符串并添加省略号)
24 | - [控制使用的 class](./javascript#控制使用的-class)
25 | - [iOS 移动端键盘收起防止产生空白](./javascript#iOS-移动端键盘收起防止产生空白)
26 | - [页面可见性的检测](./javascript#页面可见性的检测)
27 | - [自定义的 react hooks](./javascript#自定义的-react-hooks)
28 | - [为 localStorage 添加过期时间](./javascript#为-localStorage-添加过期时间)
29 | - [前端生成 uuid](./javascript#前端生成-uuid)
30 |
31 | ## [CSS 常用代码总结](./css/README.md)
32 |
33 | - CSS 初始化样式 reset.css
34 | - 去除浮动 clearfix
35 | - css 强制换行/自动换行/强制不换行
36 | - table 边界的样式
37 | - div 上下左右居中
38 | - 图片上下左右居中
39 | - 标题两边的小横杠
40 |
41 | 欢迎大家访问我的网站: [https://www.xiabingbao.com](https://www.xiabingbao.com)。
42 |
43 | 关注我的公众号:
44 |
45 | 
46 |
--------------------------------------------------------------------------------
/css/README.md:
--------------------------------------------------------------------------------
1 | # CSS 常用的片段
2 |
3 | ## 1
4 |
5 | ### CSS 初始化样式 reset.css
6 |
7 | 不同的浏览器对各个标签默认的样式是不一样的,而且有时候我们也不想使用浏览器给出的默认样式,我们就可以用 reset.css 去掉其默认样式
8 |
9 | ```css
10 | body, h1, h2, h3, h4, h5, h6, hr, p, blockquote, dl, dt, dd, ul, ol, li, pre, form, fieldset, legend, button, input, textarea, th, td { margin:0; padding:0; }
11 | body, button, input, select, textarea { font:12px/1.5 tahoma, arial, \5b8b\4f53; }
12 | h1, h2, h3, h4, h5, h6{ font-size:100%; }
13 | address, cite, dfn, em, var { font-style:normal; }
14 | code, kbd, pre, samp { font-family:couriernew, courier, monospace; }
15 | small{ font-size:12px; }
16 | ul, ol { list-style:none; }
17 | a { text-decoration:none; }
18 | a:hover { text-decoration:underline; }
19 | sup { vertical-align:text-top; }
20 | sub{ vertical-align:text-bottom; }
21 | legend { color:#000; }
22 | fieldset, img { border:0; }
23 | button, input, select, textarea { font-size:100%; }
24 | table { border-collapse:collapse; border-spacing:0; }
25 | ```
26 |
27 | ### 去除浮动 clearfix
28 |
29 | 通常我们在有浮动元素的情况下,会在同级目录下再创建一个`
`;不过这样会增加很多无用的代码。此时我们用`:after`这个伪元素来解决浮动的问题,如果当前层级有浮动元素,那么在其父级添加上 clearfix 类即可。
30 |
31 | ```css
32 | .clearfix:after {
33 | content: '\00A0';
34 | display: block;
35 | visibility: hidden;
36 | width: 0;
37 | height: 0;
38 | clear: both;
39 | font-size: 0;
40 | line-height: 0;
41 | overflow: hidden;
42 | }
43 | .clearfix {
44 | zoom: 1;
45 | }
46 | ```
47 |
48 | ### css 强制换行/自动换行/强制不换行
49 |
50 | ```css
51 | /* 强制不换行 */
52 | div {
53 | white-space: nowrap;
54 | }
55 |
56 | /* 自动换行 */
57 | div {
58 | word-wrap: break-word;
59 | word-break: normal;
60 | }
61 |
62 | /* 强制英文单词断行 */
63 | div {
64 | word-break: break-all;
65 | }
66 | ```
67 |
68 | ### table 边界的样式
69 |
70 | ```css
71 | table {
72 | border: 1px solid #000;
73 | padding: 0;
74 | border-collapse: collapse;
75 | table-layout: fixed;
76 | margin-top: 10px;
77 | }
78 | table td {
79 | height: 30px;
80 | border: 1px solid #000;
81 | background: #fff;
82 | font-size: 15px;
83 | padding: 3px 3px 3px 8px;
84 | color: #000;
85 | width: 160px;
86 | }
87 | ```
88 |
89 | ### div 上下左右居中
90 |
91 | 相关文章:[https://www.xiabingbao.com/post/css/css-center.html](https://www.xiabingbao.com/post/css/css-center.html)
92 |
93 | 文字与元素居中的方式。
94 |
95 | #### 绝对定位与 margin
96 |
97 | 当我们提前知道要居中元素的长度和宽度时,可以使用这种方式:
98 |
99 | ```css
100 | .container {
101 | position: relative;
102 | width: 300px;
103 | height: 200px;
104 | border: 1px solid #333333;
105 | }
106 | .content {
107 | background-color: #ccc;
108 | width: 160px;
109 | height: 100px;
110 | position: absolute;
111 | top: 50%;
112 | left: 50%;
113 | margin-left: -80px; /* 宽度的一半 */
114 | margin-top: -50px; /* 高度的一半 */
115 | }
116 | ```
117 |
118 | #### 绝对定位与 transform
119 | 当要居中的元素不定宽和定高时,我们可以使用transform来让元素进行偏移。
120 |
121 | ```css
122 | .container {
123 | position: relative;
124 | width: 300px;
125 | height: 200px;
126 | border: 1px solid #333333;
127 | }
128 | .content {
129 | background-color: #ccc;
130 | position: absolute;
131 | top: 50%;
132 | left: 50%;
133 | transform: translate3d(-50%, -50%, 0);
134 | text-align: center;
135 | }
136 | ```
137 |
138 | #### line-height
139 |
140 | `line-height`其实是行高,我们可以用行高来调整布局!
141 |
142 | 不过这个方案有一个比较大的缺点是:文案必须是单行的,多行的话,设置的行高就会有问题。
143 |
144 | ```css
145 | .container {
146 | width: 300px;
147 | height: 200px;
148 | border: 1px solid #333333;
149 | }
150 | .content {
151 | line-height: 200px;
152 | }
153 | ```
154 |
155 | #### table 布局
156 |
157 | 给容器元素设置`display: table`,当前元素设置`display: table-cell`:
158 |
159 | ```css
160 | .container {
161 | width: 300px;
162 | height: 200px;
163 | border: 1px solid #333333;
164 | display: table;
165 | }
166 | .content {
167 | display: table-cell;
168 | vertical-align: middle;
169 | text-align: center;
170 | }
171 | ```
172 |
173 | #### flex 布局
174 |
175 | 我们可以给父级元素设置为`display: flex`,利用 flex 中的`align-items`和`justify-content`设置垂直方向和水平方向的居中。这种方式也不限制中间元素的宽度和高度。
176 |
177 | 同时,flex 布局也能替换`line-height`方案在某些 Android 机型中文字不居中的问题。
178 |
179 | ```css
180 | .container {
181 | width: 300px;
182 | height: 200px;
183 | border: 1px solid #333333;
184 | display: flex;
185 | align-items: center;
186 | justify-content: center;
187 | }
188 | .content {
189 | background-color: #ccc;
190 | text-align: center;
191 | }
192 | ```
193 |
194 | ### 图片上下左右居中
195 |
196 | 一种常用的方式是把外层的 div 设置为 table-cell;然后让内部的元素上下左右居中。当然也还有一种方式,就是把 img 当做 div,参考 6 中的代码进行设置。
197 | CSS 代码如下:
198 |
199 | ```css
200 | .content {
201 | width: 400px;
202 | height: 400px;
203 | border: 1px solid #ccc;
204 | text-align: center;
205 | display: table-cell;
206 | vertical-align: middle;
207 | }
208 | ```
209 |
210 | html 代码如下:
211 |
212 | ```html
213 |
214 |

215 |
216 | ```
217 |
218 | ### 标题两边的小横杠
219 |
220 | 我们经常会遇到这样的 UI 需求,就是标题两边有两个小横岗,之前是怎么实现的呢?比如用个`border-top`属性,然后再把中间的文字进行绝对定位,同时给这个文字一个背景颜色,把中间的这部分盖住。
221 |
222 | 现在我们可以使用伪元素来实现!
223 |
224 | ```html
225 | 标题
226 | ```
227 |
228 | ```scss
229 | title {
230 | color: #e1767c;
231 | font-size: 0.3rem;
232 | position: relative;
233 |
234 | &:before,
235 | &:after {
236 | content: '';
237 | position: absolute;
238 | display: block;
239 | left: 50%;
240 | top: 50%;
241 | -webkit-transform: translate3d(-50%, -50%, 0);
242 | transform: translate3d(-50%, -50%, 0);
243 | border-top: 0.02rem solid #e1767c;
244 | width: 0.4rem;
245 | }
246 | &:before {
247 | margin-left: -1.2rem;
248 | }
249 | &:after {
250 | margin-left: 1.2rem;
251 | }
252 | }
253 | ```
254 |
--------------------------------------------------------------------------------
/img/qrcode.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wenzi0github/javascript-css-common/14c58ce58a3d0070dcd193ab706bd73f468bcd70/img/qrcode.png
--------------------------------------------------------------------------------
/javascript/README.md:
--------------------------------------------------------------------------------
1 | # javascript 常用片段
2 |
3 | ## 1
4 |
5 | ### 如何获取当前所在周的起始和结束的日期
6 |
7 | ```javascript
8 | /**
9 | * 获取当前星期的起始日期和结束日期
10 | * @param {string} startFormat 周一的时间格式
11 | * @param {string} endFormat 周日的时间格式
12 | * @param {number} timestamp 所在周的时间戳,若不传入,则默认使用当前时刻的时间戳
13 | * @returns {string, string} {startDate, endDate} 返回的数据
14 | */
15 | export const getWeekStartAndEnd = (
16 | startFormat: string,
17 | endFormat: string,
18 | timestamp?: number
19 | ): {
20 | startDate: string,
21 | endDate: string,
22 | } => {
23 | const oneDayTime = 1000 * 3600 * 24;
24 | const nowDate = timestamp ? new Date(timestamp) : new Date();
25 | const now = nowDate.getTime();
26 | const nowDay = nowDate.getDay() === 0 ? 7 : nowDate.getDay();
27 | const startDate = new Date(now - oneDayTime * (nowDay - 1));
28 | const endDate = new Date(now + oneDayTime * (7 - nowDay));
29 |
30 | return {
31 | startDate: formatTime(startDate.getTime(), startFormat),
32 | endDate: formatTime(endDate.getTime(), endFormat),
33 | };
34 | };
35 | ```
36 |
37 | ### 对 cookie 的添加/获取和删除
38 |
39 | ```javascript
40 | var cookie = {
41 | //写cookies
42 | setCookie: function (name, value) {
43 | var Days = 365;
44 | var exp = new Date();
45 | exp.setTime(exp.getTime() + Days * 24 * 60 * 60 * 1000);
46 | document.cookie =
47 | name + '=' + escape(value) + ';expires=' + exp.toGMTString();
48 | },
49 |
50 | //读取cookies
51 | getCookie: function (name) {
52 | var arr,
53 | reg = new RegExp('(^| )' + name + '=([^;]*)(;|$)');
54 | if ((arr = document.cookie.match(reg))) return unescape(arr[2]);
55 | else return null;
56 | },
57 |
58 | //删除cookies, name可以为字符串('username')或数组(['username', 'password', ...])
59 | delCookie: function (name) {
60 | var delItem = function (item) {
61 | var exp = new Date();
62 | exp.setTime(exp.getTime() - 1);
63 | var cval = cookie.getCookie(item);
64 | if (cval !== null)
65 | document.cookie =
66 | item + '=' + cval + ';expires=' + exp.toGMTString();
67 | };
68 |
69 | if (typeof name === 'string') {
70 | delItem(name);
71 | } else {
72 | for (var i = 0, len = name.length; i < len; i++) {
73 | delItem(name[i]);
74 | }
75 | }
76 | },
77 | };
78 | ```
79 |
80 | ### js 字符串翻转
81 |
82 | js 中没有直接对字符串进行反转的,需要我们先转换成数组,然后使用数组中的`reverse()`方法翻转,最后在把数组拼接回字符串。
83 |
84 | ```javascript
85 | var str = 'abcdefg';
86 | var revs = str.split('').reverse().join('');
87 | console.log(revs);
88 | ```
89 |
90 | ### js 产生随机数字
91 |
92 | 这是利用 js 里的`Math.random()`产生的。若使用 \*1000000 然后再强制转成整型也行;不过使用下面的方式可以更加简洁一些,直接截取随机数的最后 6 位进行返回:
93 |
94 | ```javascript
95 | function getRanNum() {
96 | return ('' + Math.random()).slice(-6); // Math.random().toString().slice(-6)
97 | }
98 | ```
99 |
100 | 其实,产生 32 位的字母和数字混合的字符串也比较简单,先给出一个含有包含所有字符和数字的混合字符串,然后使用`Math.random()`摘取每位上的字符进行拼接,最后能够得到一个 32 位的随机字符串;或者使用 js 的 md5()进行加密也可以。可以参考本人收藏的 md5 加密代码【[md5 加密](https://github.com/wenzi0github/js-encrypt/blob/master/md5.js)】
101 |
102 | ### radio-checkbox-select
103 |
104 | jquery 对 radio, checkbox 的 input 标签和 select 标签的操作
105 |
106 | input[type=radio]的操作:
107 |
108 | ```javascript
109 | // boolean, 判断radio是否有被选中的元素
110 | $('#myradio input[type=radio]').is(':checked');
111 |
112 | // 设置radio选中某个元素
113 | $('#myradio input:eq(1)').prop('checked', true);
114 |
115 | // 设置radio取消选中某个元素
116 | $('#myradio input:eq(1)').prop('checked', false);
117 |
118 | // 获取选中的radio的值
119 | var val = $('#myradio input[type=radio]:checked').val();
120 | ```
121 |
122 | input[type=checkbox]的操作:
123 |
124 | ```javascript
125 | // 判断复选框是否选中
126 | var bool = $('#mycheckbox input[type=checkbox]').is(':checked');
127 |
128 | // 全选,所有的checkbox都添加上checked属性
129 | $('#checkall').click(function () {
130 | $('#like input[type=checkbox]').prop('checked', true);
131 | });
132 |
133 | // 反选,判断当前的checkbox是否被选中,若被选中则设置checked属性为false,否则设置checked属性为true
134 | $('#reverse').click(function () {
135 | $('#like input[type=checkbox]').each(function () {
136 | if ($(this).is(':checked')) {
137 | $(this).prop('checked', false);
138 | } else {
139 | $(this).prop('checked', true);
140 | }
141 | });
142 | });
143 |
144 | // 取消选中,去掉所有checkbox的checked属性
145 | $('#deleteall').click(function () {
146 | $('#like input[type=checkbox]').prop('checked', false);
147 | });
148 |
149 | // 获取选中的值
150 | $('#getcheckval').click(function () {
151 | var result = [];
152 | $('#mycheckbox input[type=checkbox]:checked').each(function () {
153 | result.push($(this).val());
154 | });
155 | console.log(result);
156 | });
157 | ```
158 |
159 | select 标签:
160 |
161 | ```javascript
162 | // 获取select选中的value值,给select一个id,直接使用`val()`获取就行
163 | $('#province').val();
164 | ```
165 |
166 | ### requestAnimationFrame 的兼容性处理
167 |
168 | ```javascript
169 | // http://paulirish.com/2011/requestanimationframe-for-smart-animating/
170 | // http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating
171 | // requestAnimationFrame polyfill by Erik Möller. fixes from Paul Irish and Tino Zijdel
172 | // MIT license
173 | (function () {
174 | var lastTime = 0;
175 | var vendors = ['ms', 'moz', 'webkit', 'o'];
176 | for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
177 | window.requestAnimationFrame =
178 | window[vendors[x] + 'RequestAnimationFrame'];
179 | window.cancelAnimationFrame =
180 | window[vendors[x] + 'CancelAnimationFrame'] ||
181 | window[vendors[x] + 'CancelRequestAnimationFrame'];
182 | }
183 | if (!window.requestAnimationFrame)
184 | window.requestAnimationFrame = function (callback, element) {
185 | var currTime = new Date().getTime();
186 | var timeToCall = Math.max(0, 16 - (currTime - lastTime));
187 | var id = window.setTimeout(function () {
188 | callback(currTime + timeToCall);
189 | }, timeToCall);
190 | lastTime = currTime + timeToCall;
191 | return id;
192 | };
193 | if (!window.cancelAnimationFrame)
194 | window.cancelAnimationFrame = function (id) {
195 | clearTimeout(id);
196 | };
197 | })();
198 | ```
199 |
200 | ### 获取鼠标移动的方向
201 |
202 | 我们一定遇见过鼠标从哪个地方进入到某 div 中,遮罩就从哪个方向出现,鼠标从哪个地方离开这个 div,遮罩就从哪个方向消失。整个动画实现的基础就是获取鼠标移动的方向。
203 |
204 | ```javascript
205 | /*
206 | * 获取元素移动的方向
207 | * @param $element 元素的jQuery对象
208 | * @param event 事件对象
209 | * @return direction 返回一个数字:0:上,1:右,2:下,3:左
210 | **/
211 | function getDirection($element, event) {
212 | var w = $element.width(),
213 | h = $element.height(),
214 | x =
215 | (event.pageX - $element.offset().left - w / 2) *
216 | (w > h ? h / w : 1),
217 | y = (event.pageY - $element.offset().top - h / 2) * (h > w ? w / h : 1),
218 | direction =
219 | Math.round((Math.atan2(y, x) * (180 / Math.PI) + 180) / 90 + 3) % 4;
220 |
221 | return direction;
222 | }
223 |
224 | $('#content')
225 | .on('mouseenter', function (event) {
226 | console.log('enter: ' + getDirection($(this), event));
227 | })
228 | .on('mouseleave', function (event) {
229 | console.log('leave: ' + getDirection($(this), event));
230 | });
231 | ```
232 |
233 | ### 扩展 String 中的 format
234 |
235 | - 对 String 原型进行扩展: String.prototype.methodName=function...
236 | - 正则表达式: /\{(\d+)\}/g ;取"{0}"这种格式的占位符,并对里面的数字放入子组
237 | - js 的 replace 方法有一种重载, string.format(regex , function(group0【匹配项】,group1【子组第一个】...){ //code... }) ;对于每次匹配到的一个占位符,都从参数相应的位置取得替换项。
238 |
239 | ```javascript
240 | String.prototype.format = function () {
241 | var args = arguments;
242 | var reg = /\{(\d+)\}/g;
243 | return this.replace(reg, function (g0, g1) {
244 | return args[+g1] || '';
245 | });
246 | };
247 | //用法:
248 | "hello {0},your age is {1},so {0}'s age is {1}".format('tom', 12);
249 | //"hello tom,your age is 12,so tom's age is 12"
250 | ```
251 |
252 | 若不想在`String`的类型上进行拓展,也可以这样修改:
253 |
254 | ```javascript
255 | var tool = {
256 | format: function (str) {
257 | var args = arguments;
258 | var reg = /\{(\d+)\}/g;
259 | return str.replace(reg, function (g0, g1) {
260 | g1++;
261 |
262 | return args[+g1] || '';
263 | });
264 | },
265 | };
266 |
267 | tool.format("hello {0},your age is {1},so {0}'s age is {1}", 'tom', 12);
268 | // "hello tom,your age is 12,so tom's age is 12"
269 | ```
270 |
271 | ### 字符串的替换
272 |
273 | 除了上面的format方法,这里还有一种方式:
274 |
275 | ```typescript
276 | /**
277 | * 替换字符串中特定符号中间的数据
278 | *
279 | * @param str
280 | * @param params
281 | */
282 | export const strReplace = (str: string, params: any = {}): string => {
283 | let reg = new RegExp('{(.+?)}', 'g'); // /{(.+?)}/g
284 | return str.replace(reg, ($1, $2) => {
285 | if ($1 && params.hasOwnProperty($2)) {
286 | return params[$2];
287 | }
288 | return $1;
289 | });
290 | };
291 | ```
292 |
293 | 使用样例:
294 |
295 | ```javascript
296 | const str = `my name is {nickname}, my age is {age}.`;
297 | const info = strReplace(str, {
298 | nickname: '蚊子',
299 | age: 24
300 | });
301 | console.log(info); // my name is 蚊子, my age is 24.
302 | ```
303 |
304 | ### js 产生随机字符串
305 |
306 | ```javascript
307 | Math.random().toString(36).substr(2);
308 | ```
309 |
310 | 很有意思,研究了一下,基本上 toString 后的参数规定可以是 2-36 之间的任意整数,不写的话默认是 10(也就是十进制),此时返回的值就是那个随机数。
311 |
312 | 若是偶数,返回的数值字符串都是短的,若是奇数,则返回的将是一个很大长度的表示值。
313 |
314 | 若<10 则都是数字组成,>10 才会包含字母。
315 |
316 | 所以如果想得到一长串的随机字符,则需使用一个 > 10 且是奇数的参数,另外根据长度自行使用 slice(2,n)截取!
317 |
318 | ### 解析 url 中的参数
319 |
320 | 用于解析当前 URL 中带的参数,如 [https://www.xiabingbao.com/post/javascript/2015/01/30/geturl-param.html?a=1&b=wenzi](https://www.xiabingbao.com/post/javascript/2015/01/30/geturl-param.html?a=1&b=wenzi)。
321 |
322 | ```javascript
323 | function parseUrl(search, name) {
324 | var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i');
325 | var r = url.substr(1).match(reg);
326 | if (r != null) return unescape(r[2]);
327 | return null;
328 | }
329 | parseUrl(window.location.search, 'id');
330 | ```
331 |
332 | ### 时间格式化
333 |
334 | ```javascript
335 | //格式化日期
336 | Date.prototype.format = function (fmt) {
337 | var o = {
338 | 'y+': this.getFullYear(),
339 | 'M+': this.getMonth() + 1, //月份
340 | 'd+': this.getDate(), //日
341 | 'h+': this.getHours(), //小时
342 | 'm+': this.getMinutes(), //分
343 | 's+': this.getSeconds(), //秒
344 | 'q+': Math.floor((this.getMonth() + 3) / 3), //季度
345 | 'S+': this.getMilliseconds(), //毫秒
346 | };
347 | for (var k in o) {
348 | if (new RegExp('(' + k + ')').test(fmt)) {
349 | if (k == 'y+') {
350 | fmt = fmt.replace(
351 | RegExp.$1,
352 | ('' + o[k]).substr(4 - RegExp.$1.length)
353 | );
354 | } else if (k == 'S+') {
355 | var lens = RegExp.$1.length;
356 | lens = lens == 1 ? 3 : lens;
357 | fmt = fmt.replace(
358 | RegExp.$1,
359 | ('00' + o[k]).substr(('' + o[k]).length - 1, lens)
360 | );
361 | } else {
362 | fmt = fmt.replace(
363 | RegExp.$1,
364 | RegExp.$1.length == 1
365 | ? o[k]
366 | : ('00' + o[k]).substr(('' + o[k]).length)
367 | );
368 | }
369 | }
370 | }
371 | return fmt;
372 | };
373 | ```
374 |
375 | 使用:
376 |
377 | ```javascript
378 | var date = new Date();
379 | console.log(date.format('yyyy年MM月dd日 hh:mm:ss.S')); //输出: 2016年04月01日 10:41:08.133
380 | console.log(date.format('yyyy-MM-dd hh:mm:ss')); //输出: 2016-04-01 10:41:08
381 | console.log(date.format('yy-MM-dd hh:mm:ss')); //输出: 16-04-01 10:41:08
382 | console.log(date.format('yy-M-d hh:mm:ss')); //输出: 16-4-1 10:41:08
383 | ```
384 |
385 | ### 获取当前月份的第一天和最后一天
386 |
387 | 通过【[如何获取某一天所在的星期](https://www.xiabingbao.com/post/javascript/week-start-end.html)】进行延展而来,这里是获取所在月份的第一天和最后一天。
388 |
389 | > 第 2 个参数是月份,从 0 开始,范围是 0~11;
390 |
391 | ```javascript
392 | const date = new Date();
393 | const monthFirstDate = new Date(date.getFullYear(), date.getMonth(), 1);
394 | const monthLastDate = new Date(date.getFullYear(), date.getMonth() + 1, 0);
395 |
396 | console.log(monthFirstDate, monthLastDate);
397 | ```
398 |
399 | 注意,月份从 0 开始计算,但是,天数从 1 开始计算。另外,除了日期的默认值为 1,小时、分钟、秒钟和毫秒的默认值都是 0。
400 |
401 | 这些参数如果超出了正常范围,会被自动折算。比如,如果月设为 15,就折算为下一年的 4 月。
402 |
403 | ### 截断字符串并添加省略号
404 |
405 | 当字符串过长时,可以按照设定的长度来截取:
406 |
407 | ```typescript
408 | /**
409 | * @param {string} str 要截取的字符串
410 | * @param {number} size 截取的长度
411 | * @param {string} tail 补充的字符,默认是3个点
412 | * @return {string} 返回截取后的字符串
413 | */
414 | const truncate = (str: string, size: number, tail?: string) => {
415 | function trim(str: string) {
416 | if (isEmpty(str)) {
417 | return str;
418 | }
419 | return str.replace(/(^\s*)|(\s*$)/g, '');
420 | }
421 |
422 | function isEmpty(str: any) {
423 | if (str === undefined || str === '' || str === null) {
424 | return true;
425 | }
426 | return false;
427 | }
428 |
429 | let nstr = trim(str);
430 |
431 | const arr = Array.from(str);
432 |
433 | let cLen = arr.length;
434 | let length = size <= 0 ? cLen : size;
435 | if (length > cLen) return nstr;
436 | nstr = arr.slice(0, length).join('');
437 | nstr += tail || '...';
438 |
439 | return nstr;
440 | };
441 | ```
442 |
443 | ### 控制使用的 class:
444 |
445 | 具体的 github 链接:[JedWatson/classnames](https://github.com/JedWatson/classnames)
446 |
447 | ```javascript
448 | const classnames = (...classes) => {
449 | const hasOwn = {}.hasOwnProperty;
450 | let _classes = [];
451 |
452 | for (let i = 0; i < classes.length; i++) {
453 | let arg = classes[i];
454 | if (!arg) continue;
455 |
456 | let argType = typeof arg;
457 |
458 | if (argType === 'string' || argType === 'number') {
459 | _classes.push(arg);
460 | } else if (Array.isArray(arg) && arg.length) {
461 | let inner = classnames.apply(null, arg);
462 | if (inner) {
463 | _classes.push(inner);
464 | }
465 | } else if (argType === 'object') {
466 | for (let key in arg) {
467 | if (hasOwn.call(arg, key) && arg[key]) {
468 | _classes.push(key);
469 | }
470 | }
471 | }
472 | }
473 |
474 | return _classes.join(' ');
475 | };
476 | ```
477 |
478 | 样例:
479 |
480 | ```javascript
481 | classNames('foo', 'bar'); // => 'foo bar'
482 | classNames('foo', { bar: true }); // => 'foo bar'
483 | classNames({ 'foo-bar': true }); // => 'foo-bar'
484 | classNames({ 'foo-bar': false }); // => ''
485 | classNames({ foo: true }, { bar: true }); // => 'foo bar'
486 | classNames({ foo: true, bar: true }); // => 'foo bar'
487 |
488 | // lots of arguments of various types
489 | classNames('foo', { bar: true, duck: false }, 'baz', { quux: true }); // => 'foo bar baz quux'
490 |
491 | // other falsy values are just ignored
492 | classNames(null, false, 'bar', undefined, 0, 1, { baz: null }, ''); // => 'bar 1'
493 | ```
494 |
495 | ### iOS 移动端键盘收起防止产生空白
496 |
497 | 在 iPhone 等设备中表单输入完成后,页面会产生空白,这里添加一个`失去焦点`的事件,滚动下页面:
498 |
499 | ```javascript
500 | // 防止键盘收起时,产生的空白
501 | const focusoutCallback = () => {
502 | if (os.ios) {
503 | //键盘收齐页面空白问题
504 | const scrollHeight = document.body.scrollHeight;
505 | document.documentElement.scrollTop = scrollHeight;
506 | document.body.scrollTop = scrollHeight;
507 |
508 | window.scrollBy(0, 1);
509 | window.scrollBy(0, -1);
510 | }
511 | };
512 |
513 | useEffect(() => {
514 | document.body.addEventListener('focusout', focusoutCallback);
515 | return () => {
516 | document.body.removeEventListener('focusout', focusoutCallback);
517 | };
518 | }, []);
519 | ```
520 |
521 | ### 页面可见性的检测
522 |
523 | 由于历史原因,这个 API 还定义了 document.hidden 属性。该属性只读,返回一个布尔值,表示当前页面是否可见。
524 | 当 document.visibilityState 属性返回 visible 时,document.hidden 属性返回 false;其他情况下,都返回 true。
525 | 该属性只是出于历史原因而保留的,只要有可能,都应该使用 document.visibilityState 属性,而不是使用这个属性。
526 |
527 | ```typescript
528 | export const pageVisibility: Function = (() => {
529 | let hidden = '',
530 | state = '';
531 |
532 | const keyWithPrefix = (prefix: string, key: string) => {
533 | if (prefix !== '') {
534 | // 首字母大写
535 | return prefix + key.slice(0, 1).toUpperCase() + key.slice(1);
536 | }
537 | return key;
538 | };
539 |
540 | const isPageVisibilitySupport = (() => {
541 | let support = false;
542 | if (typeof window === 'undefined') {
543 | return support;
544 | }
545 | ['', 'webkit', 'moz', 'ms', 'o'].forEach((item) => {
546 | let s = keyWithPrefix(item, 'hidden');
547 | if (!support && s in document) {
548 | hidden = s;
549 | state = keyWithPrefix(item, 'visibilityState');
550 | support = true;
551 | }
552 | });
553 | return support;
554 | })();
555 |
556 | return () => {
557 | // 若不支持,则默认页面一直可见
558 | if (!isPageVisibilitySupport) {
559 | return true;
560 | }
561 | if (state in document) {
562 | return (document as any)[state] === 'visible';
563 | } else if (hidden in document) {
564 | return !(document as any)[hidden];
565 | }
566 | };
567 | })();
568 | ```
569 |
570 | ### 为 localStorage 添加过期时间
571 |
572 | localStorage 是浏览器提供的本地存储功能,不用像 cookie 一样跟着 header 一起传递。但 localStorage 没有过期功能,所有存储的数据都会永久存储。为了方便数据的过期,我在这里对 localStorage 进行了下封装,提供了过期的功能。
573 |
574 | ```typescript
575 | class LocalStore {
576 | prefix: string = '';
577 |
578 | // 初始化,并设置字段的前缀
579 | constructor({ prefix }: any) {
580 | this.prefix = prefix + '.';
581 | }
582 |
583 | // 设置字段,并设置过期时间
584 | setItem(key: string, value: string | number, day: number = 30) {
585 | if (window.localStorage) {
586 | const expire: number =
587 | new Date().getTime() + day * 24 * 60 * 60 * 1000;
588 |
589 | window.localStorage.setItem(
590 | this.prefix + key,
591 | JSON.stringify({
592 | value,
593 | expire,
594 | })
595 | );
596 | }
597 | }
598 |
599 | // 若存在且在有效期,则正常返回,否则为null
600 | getItem(key: string) {
601 | if (window.localStorage) {
602 | const result = window.localStorage.getItem(this.prefix + key);
603 | if (result) {
604 | try {
605 | const { value, expire } = JSON.parse(result);
606 |
607 | if (Date.now() <= expire) {
608 | return value;
609 | }
610 |
611 | // 当过期的时候,自动删除
612 | this.delItem(key);
613 | return null;
614 | } catch (e) {
615 | console.warn('LocalStore getItem error: ' + e);
616 | }
617 | }
618 | return null;
619 | }
620 | }
621 |
622 | // 删除数据
623 | delItem(key: string) {
624 | window.localStorage &&
625 | window.localStorage.removeItem(this.prefix + key);
626 | }
627 |
628 | // 删除已过期的key,返回已删除的key的数量
629 | cleanExceed(): number {
630 | let num = 0;
631 | if (window.localStorage) {
632 | const length = localStorage.length;
633 | const now = Date.now();
634 | let key: string | null = '';
635 | for (let i = 0; i < length; i++) {
636 | key = localStorage.key(i);
637 | if (key && key.indexOf(this.prefix) === 0) {
638 | const result = window.localStorage.getItem(key);
639 | if (result) {
640 | try {
641 | const { expire } = JSON.parse(result);
642 | if (now > expire) {
643 | this.delItem(key);
644 | num++;
645 | }
646 | } catch (e) {
647 | console.warn('LocalStore getItem error: ' + e);
648 | }
649 | }
650 | }
651 | }
652 | }
653 | return num;
654 | }
655 | }
656 | ```
657 |
658 | 使用方法:
659 |
660 | ```typescript
661 | const local = new LocalStore('question'); // 添加字段的前缀,防止重复
662 | local.setItem('name', '蚊子', 2); // 存储name字段,值为`蚊子`,有效期为2天
663 | local.getItem('name'); // 获取name字段的值,若存在且在有效期,则正常返回,否则为null
664 | local.delItem('name'); // 删除name字段的数据
665 | local.cleanExceed(); // 清除所有过期的字段
666 | ```
667 |
668 | ### 对数组进行随机排序
669 |
670 | 比较简单的方式是利用自带的 sort 方法`Array.sort`:
671 |
672 | ```javascript
673 | function randomSort(arr) {
674 | arr.sort(() => Math.random() - 0.5);
675 | }
676 | ```
677 |
678 | 但这种算法随机起来可能会不均匀,这里还有洗牌算法:
679 |
680 | ```javascript
681 | function shuffleSort(arr) {
682 | var n = arr.length;
683 |
684 | while (n--) {
685 | var index = Math.floor(Math.random() * n);
686 | [arr[index], arr[n]] = [arr[n], arr[index]];
687 | }
688 | }
689 | ```
690 |
691 | ### 前端生成 uuid
692 |
693 | 从前端的角度,无法实现真的 uuid,只能靠一些时间戳和随机数,生成尽量少的 hash 碰撞的数据。
694 |
695 | 现在chrome浏览器已经随机方法了:[crypto.randomUUID()](https://www.xiabingbao.com/post/crypto/js-crypto-randomuuid-qxcuqj.html).
696 |
697 | 我之前生成的方法是:
698 |
699 | ```javascript
700 | const uuid = Date.now() + Math.random().toString().slice(-6);
701 | ```
702 |
703 | 用毫秒时间戳和随机数后 6 位的数据当做用户的 uuid,不过这种方式不符合[RFC4122](https://www.ietf.org/rfc/rfc4122.txt)标准。RFC4122 标准的格式如下:
704 |
705 | ```javascript
706 | const uuid = '1b671a64-40d5-491e-99b0-da01ff1f3341';
707 | const format = 'xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx'; // 格式
708 | ```
709 |
710 | 规则:
711 |
712 | 1. 除去横杠,有 32 位数字和字符(一共 36 位);
713 | 2. 每位都是 16 进制中的数据,即[0-9a-f];
714 | 3. 横杠分割开的位数分别是:8-4-4-4-12;
715 | 4. M:表示当前 uuid 的版本,目前只有五个版本,即只会出现 1,2,3,4,5;
716 | 5. N:只能是 8,9,a,b 其中的一个;
717 |
718 | 生成 uuid 的方法主要有:
719 |
720 | #### UUID Version 1:基于时间的 UUID
721 |
722 | 基于时间的 UUID 通过计算当前时间戳、随机数和机器 MAC 地址得到。由于在算法中使用了 MAC 地址,这个版本的 UUID 可以保证在全球范围的唯一性。但与此同时,使用 MAC 地址会带来安全性问题,这就是这个版本 UUID 受到批评的地方。如果应用只是在局域网中使用,也可以使用退化的算法,以 IP 地址来代替 MAC 地址--Java 的 UUID 往往是这样实现的(当然也考虑了获取 MAC 的难度)。
723 |
724 | #### UUID Version 2:DCE 安全的 UUID
725 |
726 | 分布式计算环境(Distributed Computing Environment)安全的 UUID 和基于时间的 UUID 算法相同,但会把时间戳的前 4 位置换为 POSIX 的 UID 或 GID。这个版本的 UUID 在实际中较少用到。
727 |
728 | #### UUID Version 3:基于名字的 UUID(MD5)
729 |
730 | 基于名字的 UUID 通过计算名字和名字空间的 MD5 散列值得到。这个版本的 UUID 保证了:相同名字空间中不同名字生成的 UUID 的唯一性;不同名字空间中的 UUID 的唯一性;相同名字空间中相同名字的 UUID 重复生成是相同的。
731 |
732 | #### UUID Version 4:随机 UUID
733 |
734 | 根据随机数,或者伪随机数生成 UUID。这种 UUID 产生重复的概率是可以计算出来的,但随机的东西就像是买彩票:你指望它发财是不可能的,但狗屎运通常会在不经意中到来。
735 |
736 | #### UUID Version 5:基于名字的 UUID(SHA1)
737 |
738 | 和版本 3 一样,不过散列函数换成了 SHA1。
739 |
740 | node 版本的 uuid:[uuid](https://github.com/uuidjs/uuid)。
741 |
742 | 这里提供一个前端可以使用的 v4 版本的方法:
743 |
744 | ```javascript
745 | function uuidv4() {
746 | return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
747 | var r = (Math.random() * 16) | 0,
748 | v = c == 'x' ? r : (r & 0x3) | 0x8;
749 | return v.toString(16);
750 | });
751 | }
752 | ```
753 |
754 | 还有一种是基于时间戳和随机数的综合体:
755 |
756 | ```javascript
757 | function generateUUID() {
758 | // Public Domain/MIT
759 | var d = new Date().getTime(); //Timestamp
760 | var d2 = (performance && performance.now && performance.now() * 1000) || 0; //Time in microseconds since page-load or 0 if unsupported
761 | return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
762 | var r = Math.random() * 16; //random number between 0 and 16
763 | if (d > 0) {
764 | //Use timestamp until depleted
765 | r = (d + r) % 16 | 0;
766 | d = Math.floor(d / 16);
767 | } else {
768 | //Use microseconds since page-load if supported
769 | r = (d2 + r) % 16 | 0;
770 | d2 = Math.floor(d2 / 16);
771 | }
772 | return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16);
773 | });
774 | }
775 | ```
776 |
--------------------------------------------------------------------------------
/javascript/promise的变体方法.md:
--------------------------------------------------------------------------------
1 | ### 1 Promise.first
2 |
3 | 场景:一个页面当前正处于 loading 状态,同时请求了多个接口,无论哪个接口正确返回结果,则 loading 效果取消!或者其他的要获取获取第一个完成状态的值。
4 |
5 | 这里就要用到了`Promise.first`了,只要任意一个 Promise 实例变成完成状态,则 Promise.first 变成完成状态。其实这里并不适合`Promise.race`方法,因为第一个变成拒绝状态的实例也会激活 Promise.race,
6 |
7 | ```javascript
8 | if (!Promise.first) {
9 | // get first resolve result
10 | Promise.first = promiseList => {
11 | return new Promise((resolve, reject) => {
12 | var num = 0;
13 | var len = promiseList.length;
14 | promiseList.forEach(pms => {
15 | Promise.resolve(pms)
16 | .then(resolve)
17 | .catch(() => {
18 | num++;
19 | if (num === len) {
20 | reject('all promises not resolve');
21 | }
22 | });
23 | });
24 | });
25 | };
26 | }
27 | ```
28 |
29 | 调用方式:
30 |
31 | ```javascript
32 | Promise.first([p4_fail_400, p2_suc_500, p3_suc_300])
33 | .then(res => console.log(res)) // p3-suc-300
34 | .catch(e => console.error(e));
35 | ```
36 |
37 | 可以看到每次获取的 p3_suc_300 的值,因为 p4 是失败的状态,p2 的完成状态没有 p3 快,因此这里获取到了 p3 的结果。
38 |
39 | ### 2 Promise.last
40 |
41 | 与 Promise.first 对应的则是`Promise.last`,获取最后变成完成状态的值。这里与 Promise.first 不同的是,只有最后一个 Promise 都变成最终态(完成或拒绝),才能知道哪个是最后一个完成的,这里我采用了计数的方式,`then`和`catch`只能二选一,等计数器达到 list.length 时,执行外部的 resolve。
42 |
43 | ```javascript
44 | if (!Promise.last) {
45 | // get last resolve result
46 | Promise.last = promiseList => {
47 | return new Promise((resolve, reject) => {
48 | let num = 0;
49 | let len = promiseList.length;
50 | let lastResolvedResult;
51 |
52 | const fn = () => {
53 | if (++num === len) {
54 | lastResolvedResult
55 | ? resolve(lastResolvedResult)
56 | : reject('all promises rejected');
57 | }
58 | };
59 | promiseList.forEach(pms => {
60 | Promise.resolve(pms)
61 | .then(res => {
62 | lastResolvedResult = res;
63 | fn();
64 | })
65 | .catch(fn);
66 | });
67 | });
68 | };
69 | }
70 | ```
71 |
72 | 调用方式:
73 |
74 | ```javascript
75 | Promise.last([p1_suc_100, p2_suc_500, p5_fail_200, p3_suc_300, p4_fail_400])
76 | .then(res => console.log(res)) // p2-suc-500
77 | .catch(e => console.error(e));
78 | ```
79 |
80 | p2 需要 500ms 才能完成,是最晚完成的。
81 |
82 | ### 3 Promise.none
83 |
84 | `Promise.none`与 Promise.all 正好相反,所有的 promise 都被拒绝了,则 Promise.none 变成完成状态。该方法可以用 Promise.first 来切换,当执行 Promise.first 的 catch 时,则执行 Promise.none 中的 resolve。不过这里我们使用 Promise.all 来实现。
85 |
86 | ```javascript
87 | if (!Promise.none) {
88 | // if all the promises rejected, then succes
89 | Promise.none = promiseList => {
90 | return Promise.all(
91 | promiseList.map(pms => {
92 | return new Promise((resolve, reject) => {
93 | // 将pms的resolve和reject反过来
94 | return Promise.resolve(pms).then(reject, resolve);
95 | });
96 | })
97 | );
98 | };
99 | }
100 | ```
101 |
102 | 调用方式:
103 |
104 | ```javascript
105 | Promise.none([p5_fail_200, p4_fail_400])
106 | .then(res => console.log(res))
107 | .catch(e => console.error(e));
108 |
109 | // then的输出结果:
110 | // [
111 | // Error: testPromise is error, name: p5-fail-200,
112 | // Error: testPromise is error, name: p4-fail-400
113 | // ]
114 | ```
115 |
116 | 两个 promise 都失败后,则 Promise.none 进入完成状态。
117 |
118 | ### 4 Promise.any
119 |
120 | Promise.any 表示只获取所有的 promise 中进入完成状态的结果,被拒绝的则忽略掉。
121 |
122 | ```javascript
123 | if (!Promise.any) {
124 | // get only resolve the results
125 | Promise.any = promiseList => {
126 | let result = [];
127 | return Promise.all(
128 | promiseList.map(pms => {
129 | return Promise.resolve(pms)
130 | .then(res => result.push(res))
131 | .catch(e => {});
132 | })
133 | ).then(res => {
134 | return new Promise((resolve, reject) => {
135 | result.length ? resolve(result) : reject();
136 | });
137 | });
138 | };
139 | }
140 | ```
141 |
142 | 调用方式:
143 |
144 | ```javascript
145 | Promise.any([p1_suc_100, p2_suc_500, p5_fail_200, p3_suc_300, p4_fail_400])
146 | .then(res => console.log(res)) // ["p1-suc-100", "p3-suc-300", "p2-suc-500"]
147 | .catch(e => console.error(e));
148 | ```
149 |
150 | ### 5 Promise.every
151 |
152 | 最后一个的实现比较简单,所有的 promise 都进入完成状态,则返回 true,否则返回 false。
153 |
154 | ```javascript
155 | if (!Promise.every) {
156 | // get the boolean if all promises resolved
157 | Promise.every = promiseList => {
158 | return Promise.all(promiseList)
159 | .then(() => Promise.resolve(true))
160 | .catch(() => Promise.resolve(false));
161 | };
162 | }
163 | ```
164 |
165 | 调用方式:
166 |
167 | ```javascript
168 | Promise.every([p1_suc_100, p2_suc_500, p3_suc_300]).then(result =>
169 | console.log('Promise.every', result)
170 | ); // Promise.every true
171 |
172 | Promise.every([p1_suc_100, p4_fail_400]).then(result =>
173 | console.log('Promise.every', result)
174 | ); // Promise.every false
175 | ```
176 |
--------------------------------------------------------------------------------
/javascript/react-hooks.md:
--------------------------------------------------------------------------------
1 | # react 中的自定义 hooks
2 |
3 | ### 获取浏览器窗口的宽度和高度
4 |
5 | ```typescript
6 | const useWinResize = () => {
7 | const [size, setSize] = useState({
8 | width: document.documentElement.clientWidth,
9 | height: document.documentElement.clientHeight
10 | });
11 | const resize = useCallback(() => {
12 | setSize({
13 | width: document.documentElement.clientWidth,
14 | height: document.documentElement.clientHeight
15 | });
16 | }, []);
17 | useEffect(() => {
18 | window.addEventListener('resize', resize);
19 | return () => window.removeEventListener('resize', resize);
20 | }, []);
21 | return size;
22 | };
23 | ```
24 |
25 | 使用方式:
26 |
27 | ```typescript
28 | const Home = () => {
29 | const { width, height } = useWinResize();
30 |
31 | return (
32 |
33 |
width: {width}
34 |
height: {height}
35 |
36 | );
37 | };
38 | ```
39 |
40 | ### 自定义的定时器
41 |
42 | 可以通过调整`delay`的值,来启动或停止定时器
43 |
44 | ```typescript
45 | const useInterval = (callback, delay) => {
46 | const saveCallback = useRef();
47 |
48 | useEffect(() => {
49 | // 每次渲染后,保存新的回调到我们的 ref 里
50 | saveCallback.current = callback;
51 | });
52 |
53 | useEffect(() => {
54 | function tick() {
55 | saveCallback.current();
56 | }
57 | if (delay !== null) {
58 | let id = setInterval(tick, delay);
59 | return () => clearInterval(id);
60 | }
61 | }, [delay]);
62 | };
63 | ```
64 |
65 | 使用方式:
66 |
67 | ```typescript
68 | const [count, setCount] = useState(0);
69 | const [diff, setDiff] = useState(500);
70 |
71 | useInterval(() => {
72 | setCount(count + 1);
73 | }, diff);
74 | ```
75 |
--------------------------------------------------------------------------------
/pins.md:
--------------------------------------------------------------------------------
1 | 一直都可以理直气壮地说自己贪生怕死还爱财,因为这是生活在世上的基础,所以从来都不觉得丢人。
2 |
3 | 我们都还没死,别觉得过不好这一生,最怕的就是心想事成。
4 |
5 | 多躁者必无沉毅之识,多畏者必无踔越之见,多欲者必无慷慨之节,多言者必无质实之心,多勇者必无文学之雅。——曾国藩
6 |
7 | 人们走向我,然后路过我。
8 |
9 | 我试着销声匿迹,原来我真的无人问津。
10 |
11 | 不是读书没用,是我没用。
12 |
13 | 小时候真傻,居然盼着长大。
14 |
15 | 人人都喜欢笑,以前我也是。
16 |
17 | 你有没有试过,当你再次打开宿舍的门, 发现所有的床铺都是空的,床板上只堆着自己的行李。他们都走了,你以为这只是一次暑假,他们去去就回,可其实不是了,他们走了,去奔前程。那些陪伴着你三年,你曾经烦他还在背后偷偷骂过的他,却在走了之后留给你一地悲伤。(2020/03/25)
18 |
19 | 到底是年轻,不知这世间,本来就是寒来暑往,日出日落,人聚了就有散。(2020/03/25)
20 |
21 | 人生就象一趟列车,有人上也有人下,没有人可以永远陪你到终点。但是,你,曾经被人需要过,被人关注过,在一起过,这些都不会消逝,会成为你这趟旅程的风景和记忆。(2020/03/25)
--------------------------------------------------------------------------------