├── 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 | ![蚊子的博客公众号](./img/qrcode.png) 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 | img 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) --------------------------------------------------------------------------------