├── 2017 ├── ES6变量命名方式以及块级作用域.md ├── Hello2018.md ├── JavaScript基础心法——call apply bind.md ├── JavaScript基础心法——this.md ├── JavaScript基础心法——数据类型.md ├── JavaScript基础心法——深浅拷贝.md ├── JavaScript数据结构及算法——排序.md ├── JavaScript数据结构及算法——查找.md ├── React V15.6 实现一个简单的个人博客.md ├── React中state和props分别是什么?.md ├── React的生命周期到底是怎么一回事?.md ├── 关于浏览器缓存我知道多少.md ├── 原生JS实现最简单的图片懒加载.md └── 用React实现一个简易的TodoList.md ├── 2018 └── JavaScript复制内容到剪贴板.md ├── 2019 ├── ES2019.md ├── JavaScript 判断 iPhone X Series 机型.md ├── TypeScript Start: 什么是 TypeScript.md ├── TypeScript Start:基础类型.md ├── quill 富文本编辑器自定义格式化.md ├── webpack loader 从上手到理解系列:file-loader.md ├── webpack loader 从上手到理解系列:style-loader.md ├── webpack loader 从上手到理解系列:url-loader.md ├── webpack loader 从上手到理解系列:vue-loader(一).md ├── 初学 babel.md └── 前端工程师都得掌握的 webpack Loader.md ├── 2020 ├── babel_require.md └── list.md ├── README.md └── images ├── ast-demo.png ├── avatar.png ├── babel-banner.png ├── babel-try.png ├── es-finished-proposals.png ├── quill_format.png ├── quill_template.png ├── quill_toolbar.png ├── weixin.png ├── yearly-arrow.png └── yearly-logo.png /2017/ES6变量命名方式以及块级作用域.md: -------------------------------------------------------------------------------- 1 | 买的《深入理解es6》终于到手了,赶紧看起来。。 2 | 3 | ## var声明及变量提升机制 4 | 5 | 在ES6之前,在函数作用域中或者全局作用域中通过`var`关键字来声明变量,无论是在代码的哪个位置,这条声明语句都会提到最顶部来执行,这就是变量声明提升。 6 | 7 | 注意:**只是声明提升,初始化并没有提升。** 8 | 9 | 看一个例子: 10 | 11 | ```javascript 12 | function getStudent(name){ 13 | if(name){ 14 | var age=25; 15 | }else{ 16 | console.log("name不存在"); 17 | } 18 | console.log(age); //undefined 19 | } 20 | ``` 21 | 22 | 如果按照预想的代码的执行顺序,当`name`有值时才会创建变量`age`,可是执行代码发现,即使不传入`name`,判断语句外的输出语句并没有报错,而是输出`undefined`。 23 | 24 | 这就是变量声明提升。 25 | 26 | ## 块级声明 27 | 28 | ES6前是没有块级作用域的,比如`{}`外可以访问内部的变量。 29 | 30 | ### let声明 31 | 32 | - 声明变量 33 | - 作用域限制在当前代码块 34 | - 声明不会提升 35 | - 禁止重声明(同一作用域不行,可以覆盖外部同名变量) 36 | 37 | ```javascript 38 | function getStudent(name){ 39 | if(name){ 40 | let age=25; 41 | console.log(age); //25 42 | }else{ 43 | console.log("name不存在"); 44 | } 45 | console.log(age); //age is not defined 46 | } 47 | ``` 48 | 49 | 和上文一样的代码,只是将`age`的命名关键字从`var`改成了`let`,在执行`getStudent()`和`getStudent("axuebin")`时都会报错。 50 | 51 | 原因: 52 | 53 | - 在if语句内部执行之后,`age`变量将立即被销毁 54 | - 如果`name`为空,则永远都不会创建`age`变量 55 | 56 | ### const声明 57 | 58 | - 声明常量 59 | - 必须初始化 60 | - 不可更改 61 | - 作用域限制在当前代码块 62 | - 声明不会提升 63 | - 禁止重声明(同一作用域不行,可以覆盖外部同名变量) 64 | 65 | 如果用`const`来声明对象,则**对象中**的值可以修改。 66 | 67 | ### 临时死区(Temporal Dead Zone) 68 | 69 | JavaScript引擎在扫面代码发现声明变量时,遇到`var`则提升到作用域顶部,遇到`let`和`const`则放到TDZ中。当执行了变量声明语句后,TDZ中的变量才能正常访问。 70 | 71 | ## 循环中的块作用域绑定 72 | 73 | 我们经常使用for循环: 74 | 75 | ```javascript 76 | for(var i=0;i<10;i++){ 77 | console.log(i); //0,1,2,3,4,5,6,7,8,9 78 | } 79 | console.log(i) //10 80 | ``` 81 | 82 | 发现了什么? 83 | 84 | 在for循环执行后,我们仍然可以访问到变量`i`。 85 | 86 | So easy ~ 把`var`换成`let`就解决了~ 87 | 88 | ```javascript 89 | for(let i=0;i<10;i++){ 90 | console.log(i); //0,1,2,3,4,5,6,7,8,9 91 | } 92 | console.log(i) //i is not defined 93 | ``` 94 | 95 | 还记得当初讲闭包时setTimeout循环各一秒输出i的那个例子吗~ 96 | 97 | 曾经熟悉的你 ~ 98 | 99 | ```javascript 100 | for(var i=0;i<10;i++){ 101 | setTimeout(function(){ 102 | console.log(i); //10,10,10..... 103 | },1000) 104 | } 105 | ``` 106 | 107 | 很显然,上面的代码输出了10次的10,`setTimeout`在执行了循环之后才执行,此时`i`已经是10了~ 108 | 109 | 之前,我们这样做 ~ 110 | 111 | ```javascript 112 | for(var i=0;i<10;i++){ 113 | setTimeout((function(i){ 114 | console.log(i); //0,1,2,3,4,5,6,7,8,9 115 | })(i),1000) 116 | } 117 | ``` 118 | 119 | 现在,我们这样做 ~ 来看看把`var`改成`let`会怎样~ 120 | 121 | ```javascript 122 | for(let i=0;i<10;i++){ 123 | setTimeout(function(){ 124 | console.log(i); //0,1,2,3,4,5,6,7,8,9 125 | },1000) 126 | } 127 | ``` 128 | 129 | nice~ 130 | 131 | ## 全局块作用域绑定 132 | 133 | 在全局作用域下声明的时 134 | 135 | - `var`会覆盖window对象中的属性 136 | - `let`和`const`会屏蔽,而不是覆盖,用`window.`还能访问到 -------------------------------------------------------------------------------- /2017/Hello2018.md: -------------------------------------------------------------------------------- 1 | 2017也就这样过去了,有失有得。 2 | 3 | ## 2017 4 | 5 | 看了看app中2017年的 `todoList` 6 | 7 | ### 写作方面 8 | 9 | 年初定的目标是: 10 | 11 | - 50篇博客 12 | - 开始写公众号,有第一个粉丝 13 | 14 | 数了数 [http://axuebin.com/blog](http://axuebin.com/blog) 里的也不够,更别说满意的文章了。 15 | 16 | 意料之外的是靠着一篇水文 [https://github.com/axuebin/articles/issues/1](https://github.com/axuebin/articles/issues/1) 收获了 `SF` 的 `Top Writer`。 17 | 18 | 真正有认真写的可能就是 [https://github.com/axuebin/articles](https://github.com/axuebin/articles) 这里的几篇文章了。 19 | 20 | 本来想看看 `lodash` 的源码,写一写源码解析的,但是最近在看论文就没心情看这些了。。。 21 | 22 | ### 工作方面 23 | 24 | 秋招拿到了满意的 `offer`,算是完成了这个目标了吧。 25 | 26 | [我的秋招经历,痛并快乐着](https://github.com/axuebin/articles/issues/21) 27 | 28 | ### 阅读方面 29 | 30 | - 20本书(每本书要求写读后感,技术书籍记录相关笔记) 31 | 32 | 想了想,买的书倒是超过20本了,基本还都是编程相关的书。 33 | 34 | 开心的是终于入了一套《诛仙》,虽然不是原版的。 35 | 36 | 嗯...有的书基本就是翻翻目录,看看感兴趣的部分,仔细看的数量可能一个手就可以数的过来了,希望来年能找到一个好的节奏多看一些书。 37 | 38 | ### 摄影方面 39 | 40 | - 50张看得顺眼的照片 41 | 42 | 看了看库存,没数有多少张满意的,倒是挑了12张女朋友的照片做了一个日历: 43 | 44 | ![](http://omufjr5bv.bkt.clouddn.com/2018%E5%BE%AE%E4%BF%A1%E5%9B%BE%E7%89%87_20171231135730.jpg) 45 | 46 | 其他的一些照片可以看看 [https://500px.me/axuebin](https://500px.me/axuebin) 这里。 47 | 48 | 最喜欢的照片应该是这几张: 49 | 50 | 在赶去桥对面想找个好机位拍照的时候突然看到远处有彩虹,马上掏出相机啪了一张。 51 | 52 | ![](http://omufjr5bv.bkt.clouddn.com/201821140981.jpg) 53 | 54 | 去九溪打卡的时候低头看看脚下,落叶在阳光下显得格外好看,这样的光影让人着迷。 55 | 56 | ![](http://omufjr5bv.bkt.clouddn.com/201821147711.jpg) 57 | 58 | ### 给自己的礼物 59 | 60 | - FE16-35 61 | - FE55 1.8 62 | - 一双篮球鞋 63 | - 一个背包 64 | 65 | 哈哈哈哈,这部分算是完成最好的了。。除了广角镜头没有买之外其它都买了,特别喜欢 `FE55 1.8` 的质感,可以多给女朋友拍拍照。 66 | 67 | 明年争取攒个钱买个全幅相机和广角镜头。 68 | 69 | ### 旅游 70 | 71 | 嗯...今年和家人去了一次湖南。其它想去的地方都因为各种原因(其实是没有钱)都没去成,明年希望能去一次香港或者云南。 72 | 73 | ## 2018 74 | 75 | 今年的计划没有想得太多,主要就几点: 76 | 77 | - 顺利毕业 78 | - 好好工作 79 | - 持续学习 80 | - 好好挣钱 81 | - 好好花钱 82 | 83 | ![](http://omufjr5bv.bkt.clouddn.com/2018plan.png) -------------------------------------------------------------------------------- /2017/JavaScript基础心法——call apply bind.md: -------------------------------------------------------------------------------- 1 | 之前[这篇文章](https://github.com/axuebin/articles/issues/6)提到过`this`的各种情况,其中有一种情况就是通过`call`、`apply`、`bind`来将`this`绑定到指定的对象上。 2 | 3 | 也就是说,这三个方法可以改变函数体内部`this`的指向。 4 | 5 | 这三个方法有什么区别呢?分别适合应用在哪些场景中呢? 6 | 7 | 先举个简单的栗子 ~ 8 | 9 | ```javascript 10 | var person = { 11 | name: "axuebin", 12 | age: 25 13 | }; 14 | function say(job){ 15 | console.log(this.name+":"+this.age+" "+job); 16 | } 17 | say.call(person,"FE"); // axuebin:25 FE 18 | say.apply(person,["FE"]); // axuebin:25 FE 19 | var sayPerson = say.bind(person,"FE"); 20 | sayPerson(); // axuebin:25 FE 21 | ``` 22 | 23 | 对于对象`person`而言,并没有`say`这样一个方法,通过`call`/`apply`/`bind`就可以将外部的`say`方法用于这个对象中,其实就是将`say`内部的`this`指向`person`这个对象。 24 | 25 | ## call 26 | 27 | `call`是属于所有`Function`的方法,也就是`Function.prototype.call`。 28 | 29 | > The call() method calls a function with a given this value and arguments provided individually. 30 | 31 | > call() 方法调用一个函数, 其具有一个指定的this值和分别地提供的参数(参数的列表)。 32 | 33 | 它的语法是这样的: 34 | 35 | ```javascript 36 | fun.call(thisArg[,arg1[,arg2,…]]); 37 | ``` 38 | 39 | 其中,`thisArg`就是`this`指向,`arg`是指定的参数。 40 | 41 | `call`的用处简而言之就是可以让call()中的对象调用当前对象所拥有的function。 42 | 43 | ### ECMAScript规范 44 | 45 | ECMAScript规范中是这样定义`call`的: 46 | 47 | 当以`thisArg`和可选的`arg1`,`arg2`等等作为参数在一个`func`对象上调用`call`方法,采用如下步骤: 48 | 49 | 1. 如果`IsCallable(func)`是`false`, 则抛出一个`TypeError`异常。 50 | 2. 令`argList`为一个空列表。 51 | 3. 如果调用这个方法的参数多余一个,则从`arg1`开始以从左到右的顺序将每个参数插入为`argList`的最后一个元素。 52 | 4. 提供`thisArg`作为`this`值并以`argList`作为参数列表,调用`func`的`[[Call]]`内部方法,返回结果。 53 | 54 | `call`方法的`length`属性是1。 55 | 56 | 在外面传入的`thisArg`值会修改并成为`this`值。`thisArg`是`undefined`或`null`时它会被替换成全局对象,所有其他值会被应用`ToObject`并将结果作为`this`值,这是第三版引入的更改。 57 | 58 | ### 使用call调用函数并且指定this 59 | 60 | ```javascript 61 | var obj = { 62 | a: 1 63 | } 64 | function foo(b, c){ 65 | this.b = b; 66 | this.c = c; 67 | console.log(this.a + this.b + this.c); 68 | } 69 | foo.call(obj,2,3); // 6 70 | ``` 71 | 72 | ### call实现继承 73 | 74 | 在需要实现继承的子类构造函数中,可以通过`call`调用父类构造函数实现继承。 75 | 76 | ```javascript 77 | function Person(name, age){ 78 | this.name = name; 79 | this.age = age; 80 | this.say = function(){ 81 | console.log(this.name + ":" + this.age); 82 | } 83 | } 84 | function Student(name, age, job){ 85 | Person.call(this, name ,age); 86 | this.job = job; 87 | this.say = function(){ 88 | console.log(this.name + ":" + this.age + " " + this.job); 89 | } 90 | } 91 | var me = new Student("axuebin",25,"FE"); 92 | console.log(me.say()); // axuebin:25 FE 93 | ``` 94 | 95 | ## apply 96 | 97 | `apply`也是属于所有`Function`的方法,也就是`Function.prototype.apply`。 98 | 99 | > The apply() method calls a function with a given this value, and arguments provided as an array (or an array-like object). 100 | 101 | > apply() 方法调用一个函数, 其具有一个指定的this值,以及作为一个数组(或类似数组的对象)提供的参数。 102 | 103 | 它的语法是这样的: 104 | 105 | ```javascript 106 | fun.apply(thisArg, [argsArray]); 107 | ``` 108 | 109 | 其中,`thisArg`就是`this`指向,`argsArray`是指定的参数数组。 110 | 111 | 通过语法就可以看出`call`和`apply`的在参数上的一个区别: 112 | 113 | - `call`的参数是一个列表,将每个参数一个个列出来 114 | - `apply`的参数是一个数组,将每个参数放到一个数组中 115 | 116 | ### ECMAScript规范 117 | 118 | 当以`thisArg`和`argArray`为参数在一个`func`对象上调用`apply`方法,采用如下步骤: 119 | 120 | 1. 如果`IsCallable(func)`是`false`, 则抛出一个`TypeError`异常 . 121 | 2. 如果`argArray`是`null`或`undefined`, 则 122 | 1. 返回提供`thisArg`作为`this`值并以空参数列表调用`func`的`[[Call]]`内部方法的结果。 123 | 3. 如果`Type(argArray)`不是`Object`, 则抛出一个`TypeError`异常 . 124 | 4. 令`len`为以`"length"`作为参数调用`argArray`的`[[Get]]`内部方法的结果。 125 | 5. 令`n`为`ToUint32(len)`. 126 | 6. 令`argList`为一个空列表 . 127 | 7. 令`index`为0. 128 | 8. 只要`index`<`n`就重复 129 | 1. 令`indexName`为`ToString(index)`. 130 | 2. 令`nextArg`为以`indexName`作为参数调用`argArray`的`[[Get]]`内部方法的结果。 131 | 3. 将`nextArg`作为最后一个元素插入到`argList`里。 132 | 4. 设定`index`为`index + 1`. 133 | 9. 提供`thisArg`作为`this`值并以`argList`作为参数列表,调用`func`的`[[Call]]`内部方法,返回结果。 134 | 135 | `apply`方法的`length`属性是 2。 136 | 137 | 在外面传入的`thisArg`值会修改并成为`this`值。`thisArg`是`undefined`或`null`时它会被替换成全局对象,所有其他值会被应用`ToObject`并将结果作为`this`值,这是第三版引入的更改。 138 | 139 | ### 用法 140 | 141 | 在用法上`apply`和`call`一样,就不说了。 142 | 143 | ### 实现一个apply 144 | 145 | 参考链接:[https://github.com/jawil/blog/issues/16](https://github.com/jawil/blog/issues/16) 146 | 147 | #### 第一步,绑定上下文 148 | 149 | ```javascript 150 | Function.prototype.myApply=function(context){ 151 | // 获取调用`myApply`的函数本身,用this获取 152 | context.fn = this; 153 | // 执行这个函数 154 | context.fn(); 155 | // 从上下文中删除函数引用 156 | delete context.fn; 157 | } 158 | 159 | var obj ={ 160 | name: "xb", 161 | getName: function(){ 162 | console.log(this.name); 163 | } 164 | } 165 | 166 | var me = { 167 | name: "axuebin" 168 | } 169 | 170 | obj.getName(); // xb 171 | obj.getName.myApply(me); // axuebin 172 | ``` 173 | 174 | 确实成功地将`this`指向了`me`对象,而不是本身的`obj`对象。 175 | 176 | #### 第二步,给定参数 177 | 178 | 上文已经提到`apply`需要接受一个参数数组,可以是一个类数组对象,还记得获取函数参数可以用`arguments`吗? 179 | 180 | ```javascript 181 | Function.prototype.myApply=function(context){ 182 | // 获取调用`myApply`的函数本身,用this获取 183 | context.fn = this; 184 | // 通过arguments获取参数 185 | var args = arguments[1]; 186 | // 执行这个函数,用ES6的...运算符将arg展开 187 | context.fn(...args); 188 | // 从上下文中删除函数引用 189 | delete context.fn; 190 | } 191 | 192 | var obj ={ 193 | name: "xb", 194 | getName: function(age){ 195 | console.log(this.name + ":" + age); 196 | } 197 | } 198 | 199 | var me = { 200 | name: "axuebin" 201 | } 202 | 203 | obj.getName(); // xb:undefined 204 | obj.getName.myApply(me,[25]); // axuebin:25 205 | ``` 206 | 207 | `context.fn(...arg)`是用了ES6的方法来将参数展开,如果看过[上面那个链接](https://github.com/jawil/blog/issues/16),就知道这里不通过`...`运算符也是可以的。 208 | 209 | 原博主通过拼接字符串,然后用`eval`执行的方式将参数传进`context.fn`中: 210 | 211 | ```javascript 212 | for (var i = 0; i < args.length; i++) { 213 | fnStr += i == args.length - 1 ? args[i] : args[i] + ','; 214 | } 215 | fnStr += ')';//得到"context.fn(arg1,arg2,arg3...)"这个字符串在,最后用eval执行 216 | eval(fnStr); //还是eval强大 217 | ``` 218 | 219 | #### 第三步,当传入apply的this为null或者为空时 220 | 221 | 我们知道,当`apply`的第一个参数,也就是`this`的指向为`null`时,`this`会指向`window`。知道了这个,就简单了~ 222 | 223 | ```javascript 224 | Function.prototype.myApply=function(context){ 225 | // 获取调用`myApply`的函数本身,用this获取,如果context不存在,则为window 226 | var context = context || window; 227 | context.fn = this; 228 | //获取传入的数组参数 229 | var args = arguments[1]; 230 | if (args == undefined) { //没有传入参数直接执行 231 | // 执行这个函数 232 | context.fn() 233 | } else { 234 | // 执行这个函数 235 | context.fn(...args); 236 | } 237 | // 从上下文中删除函数引用 238 | delete context.fn; 239 | } 240 | 241 | var obj ={ 242 | name: "xb", 243 | getName: function(age){ 244 | console.log(this.name + ":" + age); 245 | } 246 | } 247 | 248 | var name = "window.name"; 249 | 250 | var me = { 251 | name: "axuebin" 252 | } 253 | 254 | obj.getName(); // xb:25 255 | obj.getName.myApply(); // window.name:undefined 256 | obj.getName.myApply(null, [25]); // window.name:25 257 | obj.getName.myApply(me, [25]); // axuebin:25 258 | ``` 259 | 260 | #### 第四步 保证fn函数的唯一性 261 | 262 | ES6中新增了一种基础数据类型`Symbol`。 263 | 264 | ```javascript 265 | const name = Symbol(); 266 | const age = Symbol(); 267 | console.log(name === age); // false 268 | 269 | const obj = { 270 | [name]: "axuebin", 271 | [age]: 25 272 | } 273 | 274 | console.log(obj); // {Symbol(): "axuebin", Symbol(): 25} 275 | console.log(obj[name]); // axuebin 276 | ``` 277 | 278 | 所以我们可以通过`Symbol`来创建一个属性名。 279 | 280 | ```javascript 281 | var fn = Symbol(); 282 | context[fn] = this; 283 | ``` 284 | 285 | #### 完整的apply 286 | 287 | ```javascript 288 | Function.prototype.myApply=function(context){ 289 | // 获取调用`myApply`的函数本身,用this获取,如果context不存在,则为window 290 | var context = context || window; 291 | var fn = Symbol(); 292 | context[fn] = this; 293 | //获取传入的数组参数 294 | var args = arguments[1]; 295 | if (args == undefined) { //没有传入参数直接执行 296 | // 执行这个函数 297 | context[fn]() 298 | } else { 299 | // 执行这个函数 300 | context[fn](...args); 301 | } 302 | // 从上下文中删除函数引用 303 | delete context.fn; 304 | } 305 | ``` 306 | 307 | 这样就是一个完整的`apply`了,我们来测试一下: 308 | 309 | ```javascript 310 | var obj ={ 311 | name: "xb", 312 | getName: function(age){ 313 | console.log(this.name + ":" + age); 314 | } 315 | } 316 | 317 | var name = "window.name"; 318 | 319 | var me = { 320 | name: "axuebin" 321 | } 322 | 323 | obj.getName(); // xb:25 324 | obj.getName.myApply(); // window.name:undefined 325 | obj.getName.myApply(null, [25]); // window.name:25 326 | obj.getName.myApply(me, [25]); // axuebin:25 327 | ``` 328 | 329 | ok 没啥毛病 ~ 330 | 331 | 再次感谢[1024大佬](https://github.com/jawil/blog/issues/16) ~ 332 | 333 | ## bind 334 | 335 | > The bind() method creates a new function that, when called, has its this keyword set to the provided value, with a given sequence of arguments preceding any provided when the new function is called. 336 | 337 | > bind()方法创建一个新的函数, 当被调用时,将其this关键字设置为提供的值,在调用新函数时,在任何提供之前提供一个给定的参数序列。 338 | 339 | 语法: 340 | 341 | ```javascript 342 | fun.bind(thisArg[, arg1[, arg2[, ...]]]) 343 | ``` 344 | 345 | 其中,`thisArg`就是`this`指向,`arg`是指定的参数。 346 | 347 | 可以看出,`bind`会创建一个新函数(称之为绑定函数),原函数的一个拷贝,也就是说不会像`call`和`apply`那样立即执行。 348 | 349 | 当这个绑定函数被调用时,它的`this`值传递给`bind`的一个参数,执行的参数是传入`bind`的其它参数和执行绑定函数时传入的参数。 350 | 351 | ## 用法 352 | 353 | 当我们执行下面的代码时,我们希望可以正确地输出`name`,然后现实是残酷的 354 | 355 | ```javascript 356 | function Person(name){ 357 | this.name = name; 358 | this.say = function(){ 359 | setTimeout(function(){ 360 | console.log("hello " + this.name); 361 | },1000) 362 | } 363 | } 364 | var person = new Person("axuebin"); 365 | person.say(); //hello undefined 366 | ``` 367 | 368 | 369 | 这里`this`运行时是指向`window`的,所以`this.name`是`undefined`,为什么会这样呢?看看MDN的解释: 370 | 371 | > 由setTimeout()调用的代码运行在与所在函数完全分离的执行环境上。这会导致,这些代码中包含的 this 关键字在非严格模式会指向 window。 372 | 373 | 有一个常见的方法可以使得正确的输出: 374 | 375 | ```javascript 376 | function Person(name){ 377 | this.name = name; 378 | this.say = function(){ 379 | var self = this; 380 | setTimeout(function(){ 381 | console.log("hello " + self.name); 382 | },1000) 383 | } 384 | } 385 | var person = new Person("axuebin"); 386 | person.say(); //hello axuebin 387 | ``` 388 | 389 | 没错,这里我们就可以用到`bind`了: 390 | 391 | ```javascript 392 | function Person(name){ 393 | this.name = name; 394 | this.say = function(){ 395 | setTimeout(function(){ 396 | console.log("hello " + this.name); 397 | }.bind(this),1000) 398 | } 399 | } 400 | var person = new Person("axuebin"); 401 | person.say(); //hello axuebin 402 | ``` 403 | 404 | ### MDN的Polyfill 405 | 406 | ```javascript 407 | Function.prototype.bind = function (oThis) { 408 | var aArgs = Array.prototype.slice.call(arguments, 1); 409 | var fToBind = this; 410 | var fNOP = function () {}; 411 | var fBound = function () { 412 | fBound.prototype = this instanceof fNOP ? new fNOP() : fBound.prototype; 413 | return fToBind.apply(this instanceof fNOP ? this : oThis || this, aArgs ) 414 | } 415 | if( this.prototype ) { 416 | fNOP.prototype = this.prototype; 417 | } 418 | return fBound; 419 | } 420 | ``` 421 | 422 | ## 总结 423 | 424 | - 三者都是用来改变函数的`this`指向 425 | - 三者的第一个参数都是`this`指向的对象 426 | - `bind`是返回一个绑定函数可稍后执行,`call`、`apply`是立即调用 427 | - 三者都可以给定参数传递 428 | - `call`给定参数需要将参数全部列出,`apply`给定参数数组 429 | 430 | ## 感谢 431 | 432 | [不用call和apply方法模拟实现ES5的bind方法](https://github.com/jawil/blog/issues/16) 433 | 434 | [深入浅出妙用 Javascript 中 apply、call、bind](http://web.jobbole.com/83642/) 435 | 436 | [回味JS基础:call apply 与 bind](https://juejin.im/post/57dc97f35bbb50005e5b39bd) -------------------------------------------------------------------------------- /2017/JavaScript基础心法——this.md: -------------------------------------------------------------------------------- 1 | ## 什么是this 2 | 3 | 在传统面向对象的语言中,比如Java,`this`关键字用来表示当前对象本身,或当前对象的一个实例,通过`this`关键字可以获得当前对象的属性和调用方法。 4 | 5 | 在JavaScript中,`this`似乎表现地略有不同,这也是让人“讨厌”的地方~ 6 | 7 | ECMAScript规范中这样写: 8 | 9 | > this 关键字执行为当前执行环境的 ThisBinding。 10 | 11 | MDN上这样写: 12 | 13 | > In most cases, the value of this is determined by how a function is called. 14 | 15 | > 在绝大多数情况下,函数的调用方式决定了this的值。 16 | 17 | 可以这样理解,在JavaScript中,`this`的指向是调用时决定的,而不是创建时决定的,这就会导致`this`的指向会让人迷惑,简单来说,`this`具有运行期绑定的特性。 18 | 19 | 参考资料:[this - JavaScript | MDN](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/this "this - JavaScript | MDN") 20 | 21 | 来看看不同的情况五花八门的`this`吧~ 22 | 23 | ## 调用位置 24 | 25 | 首先需要理解调用位置,调用位置就是函数在代码中被调用的位置,而不是声明的位置。 26 | 27 | 通过分析调用栈(到达当前执行位置所调用的所有函数)可以找到调用位置。 28 | 29 | ```javascript 30 | function baz(){ 31 | console.log("baz"); 32 | bar(); 33 | } 34 | function bar(){ 35 | console.log("bar"); 36 | foo(); 37 | } 38 | function foo(){ 39 | console.log("foo"); 40 | } 41 | baz(); 42 | ``` 43 | 44 | 当我们调用`baz()`时,它会以此调用`baz()`→`bar()`→`foo()`。 45 | 46 | 对于`foo()`:调用位置是在`bar()`中。 47 | 对于`bar()`:调用位置是在`baz()`中。 48 | 而对于`baz()`:调用位置是全局作用域中。 49 | 50 | 可以看出,调用位置应该是当前正在执行的函数的前一个调用中。 51 | 52 | ## 全局上下文 53 | 54 | 在全局执行上下文中`this`都指代全局对象。 55 | 56 | - `this`等价于`window`对象 57 | - `var` === `this.` === `winodw.` 58 | 59 | ```javascript 60 | console.log(window === this); // true 61 | var a = 1; 62 | this.b = 2; 63 | window.c = 3; 64 | console.log(a + b + c); // 6 65 | ``` 66 | 67 | 在浏览器里面`this`等价于`window`对象,如果你声明一些全局变量,这些变量都会作为this的属性。 68 | 69 | ## 函数上下文 70 | 71 | 在函数内部,`this`的值取决于函数被调用的方式。 72 | 73 | ### 直接调用 74 | 75 | **`this`指向全局变量。** 76 | 77 | ```javascript 78 | function foo(){ 79 | return this; 80 | } 81 | console.log(foo() === window); // true 82 | ``` 83 | 84 | ### call()、apply() 85 | 86 | **`this`指向绑定的对象上。** 87 | 88 | ```javascript 89 | var person = { 90 | name: "axuebin", 91 | age: 25 92 | }; 93 | function say(job){ 94 | console.log(this.name+":"+this.age+" "+job); 95 | } 96 | say.call(person,"FE"); // axuebin:25 97 | say.apply(person,["FE"]); // axuebin:25 98 | ``` 99 | 100 | 可以看到,定义了一个`say`函数是用来输出`name`、`age`和`job`,其中本身没有`name`和`age`属性,我们将这个函数绑定到`person`这个对象上,输出了本属于`person`的属性,说明此时`this`是指向对象`person`的。 101 | 102 | 如果传入一个原始值(字符串、布尔或数字类型)来当做`this`的绑定对象, 这个原始值会被转换成它的对象形式(`new String()`),这通常被称为“装箱”。 103 | 104 | `call`和`apply`从`this`的绑定角度上来说是一样的,唯一不同的是它们的第二个参数。 105 | 106 | ### bind() 107 | 108 | **`this`将永久地被绑定到了`bind`的第一个参数。** 109 | 110 | `bind`和`call`、`apply`有些相似。 111 | 112 | ```javascript 113 | var person = { 114 | name: "axuebin", 115 | age: 25 116 | }; 117 | function say(){ 118 | console.log(this.name+":"+this.age); 119 | } 120 | var f = say.bind(person); 121 | console.log(f()); 122 | ``` 123 | 124 | ### 箭头函数 125 | 126 | **所有的箭头函数都没有自己的`this`,都指向外层。** 127 | 128 | 关于箭头函数的争论一直都在,可以看看下面的几个链接: 129 | 130 | [ES6 箭头函数中的 this?你可能想多了(翻译)](http://www.cnblogs.com/vajoy/p/4902935.html) 131 | 132 | [关于箭头函数this的理解几乎完全是错误的 #150](https://github.com/ruanyf/es6tutorial/issues/150) 133 | 134 | MDN中对于箭头函数这一部分是这样描述的: 135 | 136 | > An arrow function does not create its own this, the this value of the enclosing execution context is used. 137 | 138 | > 箭头函数会捕获其所在上下文的this值,作为自己的this值。 139 | 140 | ```javascript 141 | function Person(name){ 142 | this.name = name; 143 | this.say = () => { 144 | var name = "xb"; 145 | return this.name; 146 | } 147 | } 148 | var person = new Person("axuebin"); 149 | console.log(person.say()); // axuebin 150 | ``` 151 | 152 | 箭头函数常用语回调函数中,例如定时器中: 153 | 154 | ```javascript 155 | function foo() { 156 | setTimeout(()=>{ 157 | console.log(this.a); 158 | },100) 159 | } 160 | var obj = { 161 | a: 2 162 | } 163 | foo.call(obj); 164 | ``` 165 | 166 | 附上MDN关于箭头函数`this`的解释: 167 | 168 | [https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Functions/Arrow_functions#不绑定_this](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Functions/Arrow_functions#不绑定_this) 169 | 170 | ### 作为对象的一个方法 171 | 172 | **`this`指向调用函数的对象。** 173 | 174 | ```javascript 175 | var person = { 176 | name: "axuebin", 177 | getName: function(){ 178 | return this.name; 179 | } 180 | } 181 | console.log(person.getName()); // axuebin 182 | ``` 183 | 184 | 这里有一个需要注意的地方。。。 185 | 186 | ```javascript 187 | var name = "xb"; 188 | var person = { 189 | name: "axuebin", 190 | getName: function(){ 191 | return this.name; 192 | } 193 | } 194 | var getName = person.getName; 195 | console.log(getName()); // xb 196 | ``` 197 | 198 | 发现`this`又指向全局变量了,这是为什么呢? 199 | 200 | 还是那句话,`this`的指向得看函数调用时。 201 | 202 | ### 作为一个构造函数 203 | 204 | **`this`被绑定到正在构造的新对象。** 205 | 206 | 通过构造函数创建一个对象其实执行这样几个步骤: 207 | 208 | 1. 创建新对象 209 | 2. 将this指向这个对象 210 | 3. 给对象赋值(属性、方法) 211 | 4. 返回this 212 | 213 | 所以`this`就是指向创建的这个对象上。 214 | 215 | ```javascript 216 | function Person(name){ 217 | this.name = name; 218 | this.age = 25; 219 | this.say = function(){ 220 | console.log(this.name + ":" + this.age); 221 | } 222 | } 223 | var person = new Person("axuebin"); 224 | console.log(person.name); // axuebin 225 | person.say(); // axuebin:25 226 | ``` 227 | 228 | ### 作为一个DOM事件处理函数 229 | 230 | **`this`指向触发事件的元素,也就是始事件处理程序所绑定到的DOM节点。** 231 | 232 | ```javascript 233 | var ele = document.getElementById("id"); 234 | ele.addEventListener("click",function(e){ 235 | console.log(this); 236 | console.log(this === e.target); // true 237 | }) 238 | ``` 239 | 240 | ### HTML标签内联事件处理函数 241 | 242 | **`this`指向所在的DOM元素** 243 | 244 | ```html 245 | 246 | ``` 247 | 248 | ### jQuery的this 249 | 250 | **在许多情况下JQuery的`this`都指向DOM元素节点。** 251 | 252 | ```javascript 253 | $(".btn").on("click",function(){ 254 | console.log(this); 255 | }); 256 | ``` 257 | 258 | ## 总结 259 | 260 | 如果要判断一个函数的`this`绑定,就需要找到这个函数的直接调用位置。然后可以顺序按照下面四条规则来判断`this`的绑定对象: 261 | 262 | 1. 由`new`调用:绑定到新创建的对象 263 | 2. 由`call`或`apply`、`bind`调用:绑定到指定的对象 264 | 3. 由上下文对象调用:绑定到上下文对象 265 | 4. 默认:全局对象 266 | 267 | 注意:箭头函数不使用上面的绑定规则,根据外层作用域来决定`this`,继承外层函数调用的`this`绑定。 -------------------------------------------------------------------------------- /2017/JavaScript基础心法——数据类型.md: -------------------------------------------------------------------------------- 1 | 一个很基础的知识点,JavaScript中基本数据类型和引用数据类型是如何存储的。 2 | 3 | 由于自己是野生程序员,在刚开始学习程序设计的时候没有在意内存这些基础知识,导致后来在提到“什么什么是存在栈中的,栈中只是存了一个引用”这样的话时总是一脸懵逼。。 4 | 5 | 后来渐渐的了解了一些内存的知识,这部分还是非常有必要了解的。 6 | 7 | ## 基本数据结构 8 | 9 | ### 栈 10 | 11 | > 栈,只允许在一段进行插入或者删除操作的线性表,是一种先进后出的数据结构。 12 | 13 | ### 堆 14 | 15 | > 堆是基于散列算法的数据结构。 16 | 17 | ### 队列 18 | 19 | > 队列是一种先进先出(FIFO)的数据结构。 20 | 21 | ## JavaScript中数据类型的存储 22 | 23 | JavaScript中将数据类型分为基本数据类型和引用数据类型,它们其中有一个区别就是存储的位置不同。 24 | 25 | ### 基本数据类型 26 | 27 | 我们都知道JavaScript中的基本数据类型有: 28 | 29 | - String 30 | - Number 31 | - Boolean 32 | - Undefined 33 | - Null 34 | - Symbol(暂时不管) 35 | 36 | 基本数据类型都是一些简单的数据段,它们是存储在栈内存中。 37 | 38 | ### 引用数据类型 39 | 40 | JavaScript中的引用数据类型有: 41 | 42 | - Array 43 | - Object 44 | 45 | 引用数据类型是保存在堆内存中的,然后再栈内存中保存一个对堆内存中实际对象的引用。所以,JavaScript中对引用数据类型的操作都是操作对象的引用而不是实际的对象。 46 | 47 | 可以理解为,栈内存中保存了一个地址,这个地址和堆内存中的实际值是相关的。 48 | 49 | ### 图解 50 | 51 | 现在,我们声明几个变量试试: 52 | 53 | ```javascript 54 | var name="axuebin"; 55 | var age=25; 56 | var job; 57 | var arr=[1,2,3]; 58 | var obj={age:25}; 59 | ``` 60 | 61 | 可以通过下图来表示数据类型在内存中的存储情况: 62 | 63 | ![](http://omufjr5bv.bkt.clouddn.com/JS%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B%E5%86%85%E5%AD%98.png) 64 | 65 | 此时`name`,`age`,`job`三种基本数据类型是直接存在栈内存中的,而`arr`,`obj`在栈内存中只是存了一个地址来表示对堆内存中的引用。 66 | 67 | ### 复制 68 | 69 | #### 基本数据类型 70 | 71 | 对于基本数据类型,如果进行复制,系统会自动为新的变量在栈内存中分配一个新值,很容易理解。 72 | 73 | #### 引用数据类型 74 | 75 | 如果对于数组、对象这样的引用数据类型而言,复制的时候就会有所区别了: 76 | 77 | 系统也会自动为新的变量在栈内存中分配一个值,但这个值仅仅是一个地址。也就是说,复制出来的变量和原有的变量具有相同的地址值,指向堆内存中的同一个对象。 78 | 79 | ![](http://omufjr5bv.bkt.clouddn.com/JS%E5%86%85%E5%AD%98%E7%A9%BA%E9%97%B4%E5%BC%95%E7%94%A8%E7%B1%BB%E5%9E%8B%E5%A4%8D%E5%88%B6.png) 80 | 81 | 如果所示,执行了`var objCopy=obj`之后,`obj`和`objCopy`具有相同的地址值,执行堆内存中的同一个实际对象。 82 | 83 | 这有什么不同呢? 84 | 85 | 当我修改`obj`或`objCopy`时,都会引起另一个变量的改变。 86 | 87 | ### 为什么? 88 | 89 | 为什么基础数据类型存在栈中,而引用数据类型存在堆中呢? 90 | 91 | 1. 堆比栈大,栈比对速度快。 92 | 2. 基础数据类型比较稳定,而且相对来说占用的内存小。 93 | 3. 引用数据类型大小是动态的,而且是无限的。 94 | 4. 堆内存是无序存储,可以根据引用直接获取。 95 | 96 | ## 参考文章 97 | 98 | [http://www.jianshu.com/p/996671d4dcc4](http://www.jianshu.com/p/996671d4dcc4) 99 | [http://blog.sina.com.cn/s/blog_8ecde0fe0102vy6e.html](http://blog.sina.com.cn/s/blog_8ecde0fe0102vy6e.html) -------------------------------------------------------------------------------- /2017/JavaScript基础心法——深浅拷贝.md: -------------------------------------------------------------------------------- 1 | 浅拷贝和深拷贝都是对于JS中的引用类型而言的,浅拷贝就只是复制对象的引用,如果拷贝后的对象发生变化,原对象也会发生变化。只有深拷贝才是真正地对对象的拷贝。 2 | 3 | ## 前言 4 | 5 | 说到深浅拷贝,必须先提到的是JavaScript的数据类型,之前的一篇文章[JavaScript基础心法——数据类型](https://github.com/axuebin/articles/issues/3)说的很清楚了,这里就不多说了。 6 | 7 | 需要知道的就是一点:JavaScript的数据类型分为基本数据类型和引用数据类型。 8 | 9 | 对于基本数据类型的拷贝,并没有深浅拷贝的区别,我们所说的深浅拷贝都是对于引用数据类型而言的。 10 | 11 | ## 浅拷贝 12 | 13 | 浅拷贝的意思就是只复制引用,而未复制真正的值。 14 | 15 | ```javascript 16 | const originArray = [1,2,3,4,5]; 17 | const originObj = {a:'a',b:'b',c:[1,2,3],d:{dd:'dd'}}; 18 | 19 | const cloneArray = originArray; 20 | const cloneObj = originObj; 21 | 22 | console.log(cloneArray); // [1,2,3,4,5] 23 | console.log(originObj); // {a:'a',b:'b',c:Array[3],d:{dd:'dd'}} 24 | 25 | cloneArray.push(6); 26 | cloneObj.a = {aa:'aa'}; 27 | 28 | console.log(cloneArray); // [1,2,3,4,5,6] 29 | console.log(originArray); // [1,2,3,4,5,6] 30 | 31 | console.log(cloneObj); // {a:{aa:'aa'},b:'b',c:Array[3],d:{dd:'dd'}} 32 | console.log(originArray); // {a:{aa:'aa'},b:'b',c:Array[3],d:{dd:'dd'}} 33 | ``` 34 | 35 | 上面的代码是最简单的利用 `=` 赋值操作符实现了一个浅拷贝,可以很清楚的看到,随着 `cloneArray` 和 `cloneObj` 改变,`originArray` 和 `originObj` 也随着发生了变化。 36 | 37 | ## 深拷贝 38 | 39 | 深拷贝就是对目标的完全拷贝,不像浅拷贝那样只是复制了一层引用,就连值也都复制了。 40 | 41 | 只要进行了深拷贝,它们老死不相往来,谁也不会影响谁。 42 | 43 | 目前实现深拷贝的方法不多,主要是两种: 44 | 45 | 1. 利用 `JSON` 对象中的 `parse` 和 `stringify` 46 | 2. 利用递归来实现每一层都重新创建对象并赋值 47 | 48 | ### JSON.stringify/parse的方法 49 | 50 | 先看看这两个方法吧: 51 | 52 | > The JSON.stringify() method converts a JavaScript value to a JSON string. 53 | 54 | `JSON.stringify` 是将一个 `JavaScript` 值转成一个 `JSON` 字符串。 55 | 56 | > The JSON.parse() method parses a JSON string, constructing the JavaScript value or object described by the string. 57 | 58 | `JSON.parse` 是将一个 `JSON` 字符串转成一个 `JavaScript` 值或对象。 59 | 60 | 很好理解吧,就是 `JavaScript` 值和 `JSON` 字符串的相互转换。 61 | 62 | 它能实现深拷贝呢?我们来试试。 63 | 64 | ```javascript 65 | const originArray = [1,2,3,4,5]; 66 | const cloneArray = JSON.parse(JSON.stringify(originArray)); 67 | console.log(cloneArray === originArray); // false 68 | 69 | const originObj = {a:'a',b:'b',c:[1,2,3],d:{dd:'dd'}}; 70 | const cloneObj = JSON.parse(JSON.stringify(originObj)); 71 | console.log(cloneObj === originObj); // false 72 | 73 | cloneObj.a = 'aa'; 74 | cloneObj.c = [1,1,1]; 75 | cloneObj.d.dd = 'doubled'; 76 | 77 | console.log(cloneObj); // {a:'aa',b:'b',c:[1,1,1],d:{dd:'doubled'}}; 78 | console.log(originObj); // {a:'a',b:'b',c:[1,2,3],d:{dd:'dd'}}; 79 | ``` 80 | 81 | 确实是深拷贝,也很方便。但是,这个方法只能适用于一些简单的情况。比如下面这样的一个对象就不适用: 82 | 83 | ```javascript 84 | const originObj = { 85 | name:'axuebin', 86 | sayHello:function(){ 87 | console.log('Hello World'); 88 | } 89 | } 90 | console.log(originObj); // {name: "axuebin", sayHello: ƒ} 91 | const cloneObj = JSON.parse(JSON.stringify(originObj)); 92 | console.log(cloneObj); // {name: "axuebin"} 93 | ``` 94 | 95 | 发现在 `cloneObj` 中,有属性丢失了。。。那是为什么呢? 96 | 97 | 在 `MDN` 上找到了原因: 98 | 99 | >If undefined, a function, or a symbol is encountered during conversion it is either omitted (when it is found in an object) or censored to null (when it is found in an array). JSON.stringify can also just return undefined when passing in "pure" values like JSON.stringify(function(){}) or JSON.stringify(undefined). 100 | 101 | `undefined`、`function`、`symbol` 会在转换过程中被忽略。。。 102 | 103 | 明白了吧,就是说如果对象中含有一个函数时(很常见),就不能用这个方法进行深拷贝。 104 | 105 | ### 递归的方法 106 | 107 | 递归的思想就很简单了,就是对每一层的数据都实现一次 `创建对象->对象赋值` 的操作,简单粗暴上代码: 108 | 109 | ```javascript 110 | function deepClone(source){ 111 | const targetObj = source.constructor === Array ? [] : {}; // 判断复制的目标是数组还是对象 112 | for(let keys in source){ // 遍历目标 113 | if(source.hasOwnProperty(keys)){ 114 | if(source[keys] && typeof source[keys] === 'object'){ // 如果值是对象,就递归一下 115 | targetObj[keys] = source[keys].constructor === Array ? [] : {}; 116 | targetObj[keys] = deepClone(source[keys]); 117 | }else{ // 如果不是,就直接赋值 118 | targetObj[keys] = source[keys]; 119 | } 120 | } 121 | } 122 | return targetObj; 123 | } 124 | ``` 125 | 126 | 我们来试试: 127 | 128 | ```javascript 129 | const originObj = {a:'a',b:'b',c:[1,2,3],d:{dd:'dd'}}; 130 | const cloneObj = deepClone(originObj); 131 | console.log(cloneObj === originObj); // false 132 | 133 | cloneObj.a = 'aa'; 134 | cloneObj.c = [1,1,1]; 135 | cloneObj.d.dd = 'doubled'; 136 | 137 | console.log(cloneObj); // {a:'aa',b:'b',c:[1,1,1],d:{dd:'doubled'}}; 138 | console.log(originObj); // {a:'a',b:'b',c:[1,2,3],d:{dd:'dd'}}; 139 | ``` 140 | 141 | 可以。那再试试带有函数的: 142 | 143 | ```javascript 144 | const originObj = { 145 | name:'axuebin', 146 | sayHello:function(){ 147 | console.log('Hello World'); 148 | } 149 | } 150 | console.log(originObj); // {name: "axuebin", sayHello: ƒ} 151 | const cloneObj = deepClone(originObj); 152 | console.log(cloneObj); // {name: "axuebin", sayHello: ƒ} 153 | ``` 154 | 155 | 也可以。搞定。 156 | 157 | 是不是以为这样就完了?? 当然不是。 158 | 159 | ## JavaScript中的拷贝方法 160 | 161 | 我们知道在 `JavaScript` 中,数组有两个方法 `concat` 和 `slice` 是可以实现对原数组的拷贝的,这两个方法都不会修改原数组,而是返回一个修改后的新数组。 162 | 163 | 同时,ES6 中 引入了 `Object.assgn` 方法和 `...` 展开运算符也能实现对对象的拷贝。 164 | 165 | 那它们是浅拷贝还是深拷贝呢? 166 | 167 | ### concat 168 | 169 | > The concat() method is used to merge two or more arrays. This method does not change the existing arrays, but instead returns a new array. 170 | 171 | 该方法可以连接两个或者更多的数组,但是它不会修改已存在的数组,而是返回一个新数组。 172 | 173 | 看着这意思,很像是深拷贝啊,我们来试试: 174 | 175 | ```javascript 176 | const originArray = [1,2,3,4,5]; 177 | const cloneArray = originArray.concat(); 178 | 179 | console.log(cloneArray === originArray); // false 180 | cloneArray.push(6); // [1,2,3,4,5,6] 181 | console.log(originArray); [1,2,3,4,5]; 182 | ``` 183 | 184 | 看上去是深拷贝的。 185 | 186 | 我们来考虑一个问题,如果这个对象是多层的,会怎样。 187 | 188 | ```javascript 189 | const originArray = [1,[1,2,3],{a:1}]; 190 | const cloneArray = originArray.concat(); 191 | console.log(cloneArray === originArray); // false 192 | cloneArray[1].push(4); 193 | cloneArray[2].a = 2; 194 | console.log(originArray); // [1,[1,2,3,4],{a:2}] 195 | ``` 196 | 197 | `originArray` 中含有数组 `[1,2,3]` 和对象 `{a:1}`,如果我们直接修改数组和对象,不会影响 `originArray`,但是我们修改数组 `[1,2,3]` 或对象 `{a:1}` 时,发现 `originArray` 也发生了变化。 198 | 199 | **结论:`concat` 只是对数组的第一层进行深拷贝。** 200 | 201 | ### slice 202 | 203 | > The slice() method returns a shallow copy of a portion of an array into a new array object selected from begin to end (end not included). The original array will not be modified. 204 | 205 | 解释中都直接写道是 `a shallow copy` 了 ~ 206 | 207 | 但是,并不是! 208 | 209 | ```javascript 210 | const originArray = [1,2,3,4,5]; 211 | const cloneArray = originArray.slice(); 212 | 213 | console.log(cloneArray === originArray); // false 214 | cloneArray.push(6); // [1,2,3,4,5,6] 215 | console.log(originArray); [1,2,3,4,5]; 216 | ``` 217 | 218 | 同样地,我们试试多层的数组。 219 | 220 | ```javascript 221 | const originArray = [1,[1,2,3],{a:1}]; 222 | const cloneArray = originArray.slice(); 223 | console.log(cloneArray === originArray); // false 224 | cloneArray[1].push(4); 225 | cloneArray[2].a = 2; 226 | console.log(originArray); // [1,[1,2,3,4],{a:2}] 227 | ``` 228 | 229 | 果然,结果和 `concat` 是一样的。 230 | 231 | **结论:`slice` 只是对数组的第一层进行深拷贝。** 232 | 233 | ### Object.assign() 234 | 235 | > The Object.assign() method is used to copy the values of all enumerable own properties from one or more source objects to a target object. It will return the target object. 236 | 237 | 复制复制复制。 238 | 239 | 那到底是浅拷贝还是深拷贝呢? 240 | 241 | 自己试试吧。。 242 | 243 | **结论:`Object.assign()` 拷贝的是属性值。假如源对象的属性值是一个指向对象的引用,它也只拷贝那个引用值。** 244 | 245 | ### ... 展开运算符 246 | 247 | ```javascript 248 | const originArray = [1,2,3,4,5,[6,7,8]]; 249 | const originObj = {a:1,b:{bb:1}}; 250 | 251 | const cloneArray = [...originArray]; 252 | cloneArray[0] = 0; 253 | cloneArray[5].push(9); 254 | console.log(originArray); // [1,2,3,4,5,[6,7,8,9]] 255 | 256 | const cloneObj = {...originObj}; 257 | cloneObj.a = 2; 258 | cloneObj.b.bb = 2; 259 | console.log(originObj); // {a:1,b:{bb:2}} 260 | ``` 261 | 262 | **结论:`...` 实现的是对象第一层的深拷贝。后面的只是拷贝的引用值。** 263 | 264 | 265 | ### 首层浅拷贝 266 | 267 | 我们知道了,会有一种情况,就是对目标对象的第一层进行深拷贝,然后后面的是浅拷贝,可以称作“首层浅拷贝”。 268 | 269 | 我们可以自己实现一个这样的函数: 270 | 271 | ```javascript 272 | function shallowClone(source) { 273 | const targetObj = source.constructor === Array ? [] : {}; // 判断复制的目标是数组还是对象 274 | for (let keys in source) { // 遍历目标 275 | if (source.hasOwnProperty(keys)) { 276 | targetObj[keys] = source[keys]; 277 | } 278 | } 279 | return targetObj; 280 | } 281 | ``` 282 | 283 | 我们来测试一下: 284 | 285 | ```javascript 286 | const originObj = {a:'a',b:'b',c:[1,2,3],d:{dd:'dd'}}; 287 | const cloneObj = shallowClone(originObj); 288 | console.log(cloneObj === originObj); // false 289 | cloneObj.a='aa'; 290 | cloneObj.c=[1,1,1]; 291 | cloneObj.d.dd='surprise'; 292 | ``` 293 | 294 | 经过上面的修改,`cloneObj` 不用说,肯定是 `{a:'aa',b:'b',c:[1,1,1],d:{dd:'surprise'}}` 了,那 `originObj` 呢?刚刚我们验证了 `cloneObj === originObj` 是 `false`,说明这两个对象引用地址不同啊,那应该就是修改了 `cloneObj` 并不影响 `originObj`。 295 | 296 | ```javascript 297 | console.log(cloneObj); // {a:'aa',b:'b',c:[1,1,1],d:{dd:'surprise'}} 298 | console.log(originObj); // {a:'a',b:'b',c:[1,2,3],d:{dd:'surprise'}} 299 | ``` 300 | 301 | What happend? 302 | 303 | `originObj` 中关于 `a`、`c`都没被影响,但是 `d` 中的一个对象被修改了。。。说好的深拷贝呢?不是引用地址都不一样了吗? 304 | 305 | 原来是这样: 306 | 307 | 1. 从 `shallowClone` 的代码中我们可以看出,我们只对第一层的目标进行了 `深拷贝` ,而第二层开始的目标我们是直接利用 `=` 赋值操作符进行拷贝的。 308 | 2. so,第二层后的目标都只是复制了一个引用,也就是浅拷贝。 309 | 310 | ## 总结 311 | 312 | 1. 赋值运算符 `=` 实现的是浅拷贝,只拷贝对象的引用值; 313 | 2. JavaScript 中数组和对象自带的拷贝方法都是“首层浅拷贝”; 314 | 3. `JSON.stringify` 实现的是深拷贝,但是对目标对象有要求; 315 | 4. 若想真正意义上的深拷贝,请递归。 -------------------------------------------------------------------------------- /2017/JavaScript数据结构及算法——排序.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: "JavaScript数据结构及算法——排序" 4 | date: 2017-10-22 20:13:00 5 | categories: 数据结构及算法 6 | tags: JavaScript 7 | author: 薛彬 8 | --- 9 | 10 | * content 11 | {:toc} 12 | 13 | 本文主要记录的是JavaScript实现常用的排序算法,冒泡排序、快速排序、归并排序等。 14 | 15 | 16 | 17 | 18 | ## 前言 19 | 20 | 用JavaScript写算法是种怎么样的体验?不喜欢算法的我最近也对数据结构和算法有点兴趣。。。所以,将会有这些: 21 | 22 | - JavaScript数据结构及算法——栈 23 | - JavaScript数据结构及算法——队列 24 | - JavaScript数据结构及算法——链表 25 | - [JavaScript数据结构及算法——排序](https://github.com/axuebin/articles/issues/12) 26 | - [JavaScript数据结构及算法——查找](https://github.com/axuebin/articles/issues/13) 27 | - JavaScript数据结构及算法——树 28 | 29 | 现阶段我对于数据结构、算法的理解还很浅,希望各位大佬多多指导。 30 | 31 | ## 排序 32 | 33 | > 介绍排序算法 34 | 35 | ## 冒泡排序 36 | 37 | 说到冒泡排序,大家都很熟悉,顾名思义,是一种“冒泡”的过程。 38 | 39 | **主要思想**:比较任何两个相邻的项,如果第一个比第二个大,则交换它们。 40 | 41 | **时间复杂度**:O(n2) 42 | 43 | **空间复杂度**:O(1) 44 | 45 | 如何实现呢?是不是遍历所有需要排序的数据,然后将它和所有数比较一次,然后就可以了? 46 | 47 | 道理是有的,我们试试看: 48 | 49 | ```javascript 50 | function bubbleSort(arr) { 51 | const len = arr.length; // 声明一个len来存储数组的长度 52 | let temp = 0; 53 | for (let i = 0; i < len; i += 1) { // 外循环遍历数组 54 | for (let j = 0 ; j < len - 1 ; j += 1) { // 内循环执行当前项和下一项进行比较 55 | if (arr[j] > arr[j + 1]) { // 如果当前项比下一项大,则交换它们 56 | temp = arr[j]; 57 | arr[j] = arr[j + 1]; 58 | arr[j + 1] = temp; 59 | } 60 | console.log(arr); 61 | } 62 | } 63 | return arr; 64 | } 65 | ``` 66 | 67 | 我们通过输出数组来看一下整个流程: 68 | 69 | ```javascript 70 | [5, 4, 3, 2, 1] 71 | [4, 5, 3, 2, 1] // 5>4,交换 72 | [4, 3, 5, 2, 1] // 5>3,交换 73 | [4, 3, 2, 5, 1] // 5>2,交换 74 | [4, 3, 2, 1, 5] // 5>1,交换 75 | [3, 4, 2, 1, 5] // 4>3,交换 76 | [3, 2, 4, 1, 5] // 4>2,交换 77 | [3, 2, 1, 4, 5] // 4>1,交换 78 | [3, 2, 1, 4, 5] // 4<5,不交换 79 | [2, 3, 1, 4, 5] // 3>2,交换 80 | [2, 1, 3, 4, 5] // 3>1,交换 81 | [2, 1, 3, 4, 5] // 3<4,不交换 82 | [2, 1, 3, 4, 5] // 4<5,不交换 83 | [1, 2, 3, 4, 5] // 2>1,交换 84 | [1, 2, 3, 4, 5] // 2<3,不交换 85 | [1, 2, 3, 4, 5] // 3<4,不交换 86 | [1, 2, 3, 4, 5] // 4<5,不交换 87 | [1, 2, 3, 4, 5] // 1<2,不交换 88 | [1, 2, 3, 4, 5] // 2<3,不交换 89 | [1, 2, 3, 4, 5] // 3<4,不交换 90 | [1, 2, 3, 4, 5] // 4<5,不交换 91 | ``` 92 | 93 | 排序确实是排好了,但是我们发现,有很多的不必要的比较,我们应该想办法避免这些。想一想,这些都是在内循环中对已经排序过的数进行比较,所以我们可以稍稍改进一下代码: 94 | 95 | ```javascript 96 | function bubbleSort(arr) { 97 | const len = arr.length; 98 | let temp = 0; 99 | for (let i = 0; i < len; i += 1) { 100 | for (let j = 0 ; j < len - 1 - i ; j += 1) { 101 | if (arr[j] > arr[j + 1]) { 102 | temp = arr[j]; 103 | arr[j] = arr[j + 1]; 104 | arr[j + 1] = temp; 105 | } 106 | console.log(arr); 107 | } 108 | } 109 | return arr; 110 | } 111 | ``` 112 | 113 | 在内循环中,我们另 `j` 的取值到 `len-1-i` 为止,因为再往后的数已经排序好了。同样地,我们来看看流程: 114 | 115 | ```javascript 116 | [5, 4, 3, 2, 1] 117 | [4, 5, 3, 2, 1] // 5>4,交换 118 | [4, 3, 5, 2, 1] // 5>3,交换 119 | [4, 3, 2, 5, 1] // 5>2,交换 120 | [4, 3, 2, 1, 5] // 5>1,交换 121 | [3, 4, 2, 1, 5] // 4>3,交换 122 | [3, 2, 4, 1, 5] // 4>2,交换 123 | [3, 2, 1, 4, 5] // 4>1,交换 124 | [2, 3, 1, 4, 5] // 3>2,交换 125 | [2, 1, 3, 4, 5] // 3>1,交换 126 | [1, 2, 3, 4, 5] // 2>1,交换 127 | ``` 128 | 129 | nice,没必要的比较已经完全没有了。 130 | 131 | ## 选择排序 132 | 133 | **主要思想**:找到数组中的最小值然后将其放置在第一位,接着第二位第三位。。。 134 | 135 | **时间复杂度**:O(n2) 136 | 137 | **空间复杂度**:O(1) 138 | 139 | 直接看代码吧: 140 | 141 | ```javascript 142 | function selectionSort(arr) { 143 | const len = arr.length; // 用len存储数组长度 144 | let indexMin = 0; // 最小值索引 145 | let temp = 0; 146 | for (let i = 0; i < len - 1; i += 1) { //外循环遍历数组 147 | indexMin = i; // 先假设这一轮循环的第一个值是最小的 148 | for (let j = i; j < len; j += 1) { // 比较i时候会比它之后的数小,如果小,则令indexMin存储这个更小值的索引 149 | if (arr[indexMin] > arr[j]) { 150 | indexMin = j; 151 | } 152 | } 153 | if (i !== indexMin) { // 执行完内循环之后判断当前值i是否是最小的,如果不是,就要交换 154 | temp = arr[i]; 155 | arr[i] = arr[indexMin]; 156 | arr[indexMin] = temp; 157 | } 158 | console.log(arr); 159 | } 160 | return arr; 161 | } 162 | ``` 163 | 164 | ```javascript 165 | [5, 4, 3, 2, 1] 166 | [1, 4, 3, 2, 5] // 寻找最小值1,交换1和5 167 | [1, 2, 3, 4, 5] // 寻找最小值2,交换2和4 168 | [1, 2, 3, 4, 5] // 寻找最小值3,不交换 169 | [1, 2, 3, 4, 5] // 寻找最小值4,不交换 170 | [1, 2, 3, 4, 5] // 寻找最小值5,不交换 171 | ``` 172 | 173 | 是不是很酷,然而它的时间复杂度其实还是 `O(n2)`。 174 | 175 | ## 插入排序 176 | 177 | **主要思想**:每次将一个元素与已排序的元素进行逐一比较,直到找到合适的位置按大小插入。 178 | 179 | **时间复杂度**:O(n2) 180 | 181 | **空间复杂度**:O(1) 182 | 183 | 直接看代码吧: 184 | 185 | ```javascript 186 | function insertionSort(arr) { 187 | const len = arr.length; // 数组长度 188 | let j = 0; // 使用的辅助变量 189 | let temp = 0; 190 | for (let i = 1; i < len; i++) { // 外循环,从1开始 191 | j = i; // 当前索引赋给j 192 | temp = arr[i]; // 当前值存在temp 193 | while (j > 0 && arr[j - 1] > temp) { // 如果j前面的数比它大,就往前移,直到第一位 194 | arry[j] = arr[j - 1]; 195 | j--; 196 | } 197 | arr[j] = temp; // temp是要排序的那个数,放到正确的j的位置上 198 | } 199 | return arr; 200 | } 201 | ``` 202 | 203 | ## 归并排序 204 | 205 | **主要思想**:思想主要是分治。将原始数组划分成较小的数组,直到每个小数组只有一个位置,然后将小数组归并成较大的数组。 206 | 207 | **时间复杂度**:O(nlogn) 208 | 209 | **空间复杂度**:O(n) 210 | 211 | 直接看代码吧: 212 | 213 | ```javascript 214 | // 分 215 | function mergeSort(arr) { 216 | const len = arr.length; 217 | if (len === 1) { 218 | return arr; 219 | } 220 | const mid = Math.floor(len / 2); 221 | const left = arr.slice(0, mid); 222 | const right = arr.slice(mid, len); 223 | return merge(mergeSort(left), mergeSort(right)); 224 | } 225 | 226 | // 合 227 | function merge(left, right) { 228 | const result = []; 229 | let il = 0; 230 | let ir = 0; 231 | while (il < left.length && ir < right.length) { 232 | if (left[il] < right[ir]) { 233 | result.push(left[il++]); 234 | } else { 235 | result.push(right[ir++]); 236 | } 237 | } 238 | while (il < left.length) { 239 | result.push(left[il++]); 240 | } 241 | while (ir < right.length) { 242 | result.push(right[ir++]); 243 | } 244 | return result; 245 | } 246 | ``` 247 | 248 | ## 快速排序 249 | 250 | 来看看面试中最喜欢考察的快速排序。 251 | 252 | **主要思想**:每次将一个元素与已排序的元素进行逐一比较,直到找到合适的位置按大小插入。 253 | 254 | **时间复杂度**:O(nlogn) 255 | 256 | **空间复杂度**:O(logn) 257 | 258 | ```javascript 259 | function quickSort(arr) { 260 | if (arr.length <= 1) { 261 | return arr; 262 | } 263 | const pivotIndex = Math.floor(arr.length / 2); 264 | const pivot = arr.splice(pivotIndex, 1)[0]; // 将这个元素取出并从原数组中删除 265 | const left = []; 266 | const right = []; 267 | for (let i = 0; i < arr.length; i++) { 268 | if (arr[i] < pivot) { 269 | left.push(arr[i]); 270 | } else { 271 | right.push(arr[i]); 272 | } 273 | } 274 | return quickSort(left).concat(pivot, quickSort(right)); 275 | } 276 | ``` 277 | -------------------------------------------------------------------------------- /2017/JavaScript数据结构及算法——查找.md: -------------------------------------------------------------------------------- 1 | 本文主要记录的是JavaScript实现常用的查找算法。 2 | 3 | ## 前言 4 | 5 | 用JavaScript写算法是种怎么样的体验?不喜欢算法的我最近也对数据结构和算法有点兴趣。。。所以,将会有这些: 6 | 7 | - JavaScript数据结构及算法——栈 8 | - JavaScript数据结构及算法——队列 9 | - JavaScript数据结构及算法——链表 10 | - [JavaScript数据结构及算法——排序](https://github.com/axuebin/articles/issues/12) 11 | - [JavaScript数据结构及算法——查找](https://github.com/axuebin/articles/issues/13) 12 | - JavaScript数据结构及算法——树 13 | 14 | 现阶段我对于数据结构、算法的理解还很浅,希望各位大佬多多指导。 15 | 16 | ## 查找 17 | 18 | > 查找是在大量的信息中寻找一个特定的信息元素,在计算机应用中,查找是常用的基本运算,例如编译程序中符号表的查找。本文简单概括性的介绍了常见的七种查找算法,说是七种,其实二分查找、插值查找以及斐波那契查找都可以归为一类——插值查找。插值查找和斐波那契查找是在二分查找的基础上的优化查找算法。 19 | 20 | 这里主要提到如何用JavaScript实现顺序查找和二分查找。 21 | 22 | ## 顺序查找 23 | 24 | **主要思想**:将每一个数据结构中的元素和要查找的元素做比较,类似于JavaScript中indexOf 25 | 26 | **时间复杂度**:O(n) 27 | 28 | 代码: 29 | 30 | ```javascript 31 | function sequentialSearch(array,item){ 32 | for (let i = 0; i < array.length; i += 1) { 33 | if ( item === array[i] ) { 34 | return i; 35 | } 36 | } 37 | return -1; 38 | } 39 | ``` 40 | 41 | 比如我现在有这样一个数组 `[5, 4, 3, 2, 1]` ,然后我们需要在其中找到 `3` ,整个流程应该是这样: 42 | 43 | ```javascript 44 | [5, 4, 3, 2, 1] // 5 !== 3,继续遍历 45 | [5, 4, 3, 2, 1] // 4 !== 3,继续遍历 46 | [5, 4, 3, 2, 1] // 3 === 3,找到了 47 | ``` 48 | 49 | ## 二分查找 50 | 51 | **主要思想**:首先这个数组是排好序的,然后将数组一直二分缩小范围,直到找到为止。 52 | 53 | **时间复杂度**:O(logn) 54 | 55 | 代码: 56 | 57 | ```javascript 58 | function binarySearch(array, item) { 59 | const sortArray = quickSort(array); // 对数组进行快排 60 | let low = 0; // 设置左边界 61 | let high = sortArray.length - 1; // 设置右边界 62 | let mid = 0; // 设置中间值 63 | let element = 0; 64 | while (low < high) { 65 | mid = Math.floor((low + high) / 2); // 选择整个数组的中间值 66 | element = sortArray[mid]; 67 | if (element < item) { // 如果待搜索值比选中值要大,则返回步骤一在右边的字数组中寻找 68 | low = mid + 1; 69 | } else if (element > item) { // 如果待搜索值比选中值要小,则返回步骤一在左边的字数组中寻找 70 | high = mid - 1; 71 | } else { 72 | return mid; // 如果刚好选中,恭喜你,直接返回 73 | } 74 | } 75 | return -1; 76 | } 77 | ``` -------------------------------------------------------------------------------- /2017/React V15.6 实现一个简单的个人博客.md: -------------------------------------------------------------------------------- 1 | 学习 React 的过程中实现了一个个人主页,没有复杂的实现和操作,适合入门 ~ 2 | 3 | 这个项目其实功能很简单,就是常见的主页、博客、demo、关于我等功能。 4 | 5 | 页面样式都是自己写的,黑白风格,可能有点丑。不过还是最低级的 CSS ,准备到时候重构 ~ 6 | 7 | 如果有更好的方法,或者是我的想法有偏差的,欢迎大家交流指正 8 | 9 | 欢迎参观:[http://axuebin.com/react-blog](http://axuebin.com/react-blog) 10 | 11 | Github:[https://github.com/axuebin/react-blog](https://github.com/axuebin/react-blog) 12 | 13 | ## 预览图 14 | 15 | ### 首页 16 | 17 | ![](http://omufjr5bv.bkt.clouddn.com/article%E9%A6%96%E9%A1%B5.png) 18 | 19 | ### 博客页 20 | 21 | ![](http://omufjr5bv.bkt.clouddn.com/article%E5%8D%9A%E5%AE%A2%E9%A1%B5.png) 22 | 23 | ### 文章内容页 24 | 25 | ![](http://omufjr5bv.bkt.clouddn.com/article%E6%96%87%E7%AB%A0%E5%86%85%E5%AE%B9.png) 26 | 27 | ### Demo页 28 | 29 | ![](http://omufjr5bv.bkt.clouddn.com/articledemo%E9%A1%B5.png) 30 | 31 | ## 关键技术 32 | 33 | - ES6:项目中用到 ES6 的语法,在写的过程中尽量使用,可能有的地方没想到 34 | - React 35 | - React-Router:前端路由 36 | - React-Redux:状态管理 37 | - webpack:打包 38 | - marked:Markdown渲染 39 | - highlight.js:代码高亮 40 | - fetch:异步请求数据 41 | - eslint:代码检查 42 | - antd:部分组件懒得自己写。。 43 | 44 | ## 准备工作 45 | 46 | 由于不是使用 React 脚手架生成的项目,所以每个东西都是自己手动配置的。。。 47 | 48 | ### 模块打包器 49 | 50 | 打包用的是 `webpack 2.6.1`,准备入坑 `webpack 3` 。 51 | 52 | 官方文档:[https://webpack.js.org/](https://webpack.js.org/) 53 | 54 | 中文文档:[https://doc.webpack-china.org/](https://doc.webpack-china.org/) 55 | 56 | 对于 `webpack` 的配置还不是太熟,就简单的配置了一下可供项目启动: 57 | 58 | ```javascript 59 | var webpack = require('webpack'); 60 | var path = require('path'); 61 | 62 | module.exports = { 63 | context: __dirname + '/src', 64 | entry: "./js/index.js", 65 | module: { 66 | loaders: [ 67 | { 68 | test: /\.js?$/, 69 | exclude: /(node_modules)/, 70 | loader: 'babel-loader', 71 | query: { 72 | presets: ['react', 'es2015'] 73 | } 74 | }, { 75 | test: /\.css$/, 76 | loader: 'style-loader!css-loader' 77 | }, { 78 | test: /\.js$/, 79 | exclude: /(node_modules)/, 80 | loader: 'eslint-loader' 81 | }, { 82 | test: /\.json$/, 83 | loader: 'json-loader' 84 | } 85 | ] 86 | }, 87 | output: { 88 | path: __dirname + "/src/", 89 | filename: "bundle.js" 90 | } 91 | } 92 | 93 | ``` 94 | 95 | `webpack` 有几个重要的属性:`entry`、`module`、`output`、`plugins`,在这里我还没使用到插件,所以没有配置 `plugins` 。 96 | 97 | `module` 中的 `loaders`: 98 | 99 | - babel-loader:将代码转换成es5代码 100 | - css-loader:处理css中路径引用等问题 101 | - style-loader:动态把样式写入css 102 | - eslin-loader:使用eslint 103 | 104 | ### 包管理 105 | 106 | 包管理现在使用的还是 `NPM` 。 107 | 108 | 官方文档:[https://docs.npmjs.com/](https://docs.npmjs.com/) 109 | 110 | 1. npm init 111 | 2. npm install 112 | 3. npm uninstall 113 | 114 | 关于`npm`,可能还需要了解 `dependencies` 和 `devDependencies` 的区别,我是这样简单理解的: 115 | 116 | - dependencies:项目跑起来后需要使用到的模块 117 | - devDependencies:开发的时候需要用的模块,但是项目跑起来后就不需要了 118 | 119 | ### 代码检查 120 | 121 | 项目使用现在比较流行的 `ESLint` 作为代码检查工具,并使用 `Airbnb` 的检查规则。 122 | 123 | ESLint:[https://github.com/eslint/eslint](https://github.com/eslint/eslint) 124 | 125 | eslint-config-airbnb:[https://www.npmjs.com/package/eslint-config-airbnb](https://www.npmjs.com/package/eslint-config-airbnb) 126 | 127 | 在 `package.json` 中可以看到,关于 `ESLint` 的包就是放在 `devDependencies` 底下的,因为它只是在开发的时候会使用到。 128 | 129 | #### 使用 130 | 131 | - 在 `webpack` 配置中加载 `eslint-loader`: 132 | 133 | ```javascript 134 | module: { 135 | loaders: [ 136 | { 137 | test: /\.js$/, 138 | exclude: /(node_modules)/, 139 | loader: 'eslint-loader' 140 | } 141 | ] 142 | } 143 | ``` 144 | 145 | - 创建 `.elintrc`文件: 146 | 147 | ```javascript 148 | { 149 | "extends": "airbnb", 150 | "env":{ 151 | "browser": true 152 | }, 153 | "rules":{} 154 | } 155 | ``` 156 | 157 | 然后在运行 `webpack` 的时候,就会执行代码检查啦,看着一堆的 `warning` 、`error` 是不是很爽~ 158 | 159 | 这里有常见的ESLint规则:[http://eslint.cn/docs/rules/](http://eslint.cn/docs/rules/) 160 | 161 | ### 数据源 162 | 163 | 由于是为了练习 `React`,暂时就只考虑搭建一个静态页面,而且现在越来越多的大牛喜欢用 `Github Issues` 来写博客,也可以更好的地提供评论功能,所以我也想试试用 `Github Issues` 来作为博客的数据源。 164 | 165 | API在这:[https://developer.github.com/v3/issues/](https://developer.github.com/v3/issues/) 166 | 167 | 我也没看完全部的API,就看了看怎么获取 `Issues` 列表。。 168 | 169 | ```javascript 170 | https://api.github.com/repos/axuebin/react-blog/issues?creator=axuebin&labels=blog 171 | ``` 172 | 173 | 通过控制参数 `creator` 和 `labels`,可以筛选出作为展示的 `Issues`。它会返回一个带有 `issue` 格式对象的数组。每一个 `issue` 有很多属性,我们可能不需要那么多,先了解了解底下这几种: 174 | 175 | ```javascript 176 | // 为了方便,我把注释写在json中了。。 177 | [{ 178 | "url": , // issue 的 url 179 | "id": , // issue id , 是一个随机生成的不重复的数字串 180 | "number": , // issue number , 根据创建 issue 的顺序从1开始累加 181 | "title": , // issue 的标题 182 | "labels": [], // issue 的所有 label,它是一个数组 183 | "created_at": , // 创建 issue 的时间 184 | "updated_at": , // 最后修改 issue 的时间 185 | "body": , // issue 的内容 186 | }] 187 | ``` 188 | 189 | #### 异步请求数据 190 | 191 | 项目中使用的异步请求数据的方法时 `fetch`。 192 | 193 | 关于 `fetch` :[https://segmentfault.com/a/1190000003810652](https://segmentfault.com/a/1190000003810652) 194 | 195 | 使用起来很简单: 196 | 197 | ```javascript 198 | fetch(url).then(response => response.json()) 199 | .then(json => console.log(json)) 200 | .catch(e => console.log(e)); 201 | ``` 202 | 203 | ### markdown 渲染 204 | 205 | 在 `Github` 上查找关于如何在 `React` 实现 `markdown` 的渲染,查到了这两种库: 206 | 207 | - react-markdown:[https://github.com/rexxars/react-markdown](https://github.com/rexxars/react-markdown) 208 | - marked:[https://github.com/chjj/marked](https://github.com/chjj/marked) 209 | 210 | 使用起来都很简单。 211 | 212 | 如果是 `react-markdown`,只需要这样做: 213 | 214 | ```javascript 215 | import ReactMarkdown from 'react-markdown'; 216 | 217 | const input = '# This is a header\n\nAnd this is a paragraph'; 218 | ReactDOM.render( 219 | , 220 | document.getElementById('container') 221 | ); 222 | ``` 223 | 224 | 如果是`marked`,这样做: 225 | 226 | ```javascript 227 | import marked from 'marked'; 228 | 229 | const input = '# This is a header\n\nAnd this is a paragraph'; 230 | const output = marked(input); 231 | ``` 232 | 233 | 这里有点不太一样,我们获取到了一个字符串 `output`,注意,是一个字符串,所以我们得将它插入到 `dom`中,在 `React` 中,我们可以这样做: 234 | 235 | ```html 236 |
237 | ``` 238 | 239 | 由于我们的项目是基于 `React` 的,所以想着用 `react-markdown`会更好,而且由于安全问题 `React` 也不提倡直接往 `dom` 里插入字符串,然而在使用过程中发现,`react-markdown` 对表格的支持不友好,所以只好弃用,改用 `marked`。 240 | 241 | ### 代码高亮 242 | 243 | 代码高亮用的是`highlight.js`:[https://github.com/isagalaev/highlight.js](https://github.com/isagalaev/highlight.js) 244 | 245 | 它和`marked`可以无缝衔接~ 246 | 247 | 只需要这样既可: 248 | 249 | ```javascript 250 | import hljs from 'highlight.js'; 251 | 252 | marked.setOptions({ 253 | highlight: code => hljs.highlightAuto(code).value, 254 | }); 255 | ``` 256 | 257 | `highlight.js`是支持多种代码配色风格的,可以在`css`文件中进行切换: 258 | 259 | ```css 260 | @import '~highlight.js/styles/atom-one-dark.css'; 261 | ``` 262 | 263 | 在这可以看到每种语言的高亮效果和配色风格:[https://highlightjs.org/](https://highlightjs.org/) 264 | 265 | ## React 266 | 267 | ### state 和 props 是什么 268 | 269 | 可以看之前的一篇文章:[https://github.com/axuebin/react-blog/issues/8](https://github.com/axuebin/react-blog/issues/8) 270 | 271 | ### 关于React组件的生命周期 272 | 273 | 可以看之前的一篇文章:[https://github.com/axuebin/react-blog/issues/9](https://github.com/axuebin/react-blog/issues/9) 274 | 275 | ## 前端路由 276 | 277 | 项目中前端路由用的是 `React-Router V4`。 278 | 279 | 官方文档:[https://reacttraining.com/react-router/web/guides/quick-start](https://reacttraining.com/react-router/web/guides/quick-start) 280 | 281 | 中文文档:[http://reacttraining.cn/](http://reacttraining.cn/) 282 | 283 | ### 基本使用 284 | 285 | ```javascript 286 | Blog 287 | ``` 288 | 289 | ```javascript 290 | 291 | 292 | 293 | 294 | 295 | ``` 296 | 297 | 注意:一定要在根目录的 `Route` 中声明 `exact`,要不然点击任何链接都无法跳转。 298 | 299 | ### 2级目录跳转 300 | 301 | 比如我现在要在博客页面上点击跳转,此时的 `url` 是 `localhost:8080/blog`,需要变成 `localhost:8080/blog/article`,可以这样做: 302 | 303 | ```javascript 304 | 305 | ``` 306 | 307 | 这样就可以跳转到 `localhost:8080/blog/article` 了,而且还传递了一个 `number` 参数,在 `article` 中可以通过 `this.props.params.number`获取。 308 | 309 | ### HashRouter 310 | 311 | 当我把项目托管到 `Github Page` 后,出现了这样一个问题。 312 | 313 | > 刷新页面出现 `Cannot GET /` 提示,路由未生效。 314 | 315 | 通过了解,知道了原因是这样,并且可以解决: 316 | 317 | - 由于刷新之后,会根据URL对服务器发送请求,而不是处理路由,导致出现 `Cannot GET /` 错误。 318 | - 通过修改 `` → `` 。 319 | - `` 借助URL上的哈希值(hash)来实现路由。可以在不需要全屏刷新的情况下,达到切换页面的目的。 320 | 321 | ### 路由跳转后不会自动回到顶部 322 | 323 | 当前一个页面滚动到一定区域后,点击跳转后,页面虽然跳转了,但是会停留在滚动的区域,不会自动回到页面顶部。 324 | 325 | 可以通过这样来解决: 326 | 327 | ```javascript 328 | componentDidMount() { 329 | this.node.scrollIntoView(); 330 | } 331 | 332 | render() { 333 | return ( 334 |
this.node = node} >
335 | ); 336 | } 337 | ``` 338 | 339 | ## 状态管理 340 | 341 | 项目中多次需要用到从 `Github Issues` 请求来的数据,因为之前就知道 `Redux` 这个东西的存在,虽然有点大材小用,为了学习还是将它用于项目的状态管理,只需要请求一次数据即可。 342 | 343 | 官方文档:[http://redux.js.org/](http://redux.js.org/) 344 | 345 | 中文文档:[http://cn.redux.js.org/](http://cn.redux.js.org/) 346 | 347 | 简单的来说,每一次的修改状态都需要触发 `action` ,然而其实项目中我现在还没用到修改数据2333。。。 348 | 349 | 关于状态管理这一块,由于还不是太了解,就不误人子弟了~ 350 | 351 | ## 主要组件 352 | 353 | React是基于组件构建的,所以在搭建页面的开始,我们要先考虑一下我们需要一些什么样的组件,这些组件之间有什么关系,哪些组件是可以复用的等等等。 354 | 355 | ### 首页 356 | 357 | ![](http://omufjr5bv.bkt.clouddn.com/article%E9%A6%96%E9%A1%B5.gif) 358 | 359 | 可以看到,我主要将首页分成了四个部分: 360 | 361 | - header:网站标题,副标题,导航栏 362 | - banner:about me ~,准备用自己的照片换个背景,但是还没有合适的照片 363 | - card area:暂时是三个卡片 364 | - blog card:最近的几篇博文 365 | - demo card:几个小demo类别 366 | - me card:算是我放飞自我的地方吧 367 | - footer:版权信息、备案信息、浏览量 368 | 369 | ### 博客页 370 | 371 | ![](http://omufjr5bv.bkt.clouddn.com/article%E5%8D%9A%E5%AE%A2%E9%A1%B5.gif) 372 | 373 | 博客页就是很中规中矩的一个页面吧,这部分是整个项目中代码量最多的部分,包括以下几部分: 374 | 375 | - 文章列表组件 376 | - 翻页组件 377 | - 归档按钮组件 378 | - 类别组件 379 | - 标签组件 380 | 381 | #### 文章列表 382 | 383 | 文章列表其实就是一个 `list`,里面有一个个的 `item`: 384 | 385 | ```html 386 |
387 |
文章1
388 |
文章2
389 |
390 | ``` 391 | 392 | 对于每一个 `item`,其实是这样的: 393 | 394 | ![](http://omufjr5bv.bkt.clouddn.com/article%E6%96%87%E7%AB%A0item.png) 395 | 396 | 一个文章item组件它可能需要包括: 397 | 398 | - 文章标题 399 | - 文章发布的时间、类别、标签等 400 | - 文章摘要 401 | - ... 402 | 403 | 如果用 `DOM` 来描述,它应该是这样的: 404 | 405 | ```html 406 |
407 |
文章标题
408 |
时间
409 |
类别
410 |
标签
411 |
摘要
412 |
413 | ``` 414 | 415 | 所以,我们可以有很多个组件: 416 | 417 | - 文章列表组件 `` 418 | - 文章item组件 `` 419 | - 类别标签组件 `` 420 | 421 | 它们可能是这样一个关系: 422 | 423 | ```javascript 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | ``` 435 | 436 | #### 分页 437 | 438 | 对于分页功能,传统的实现方法是在后端完成分页然后分批返回到前端的,比如可能会返回一段这样的数据: 439 | 440 | ```javascript 441 | { 442 | total:500, 443 | page:1, 444 | data:[] 445 | } 446 | ``` 447 | 448 | 也就是后端会返回分好页的数据,含有表示总数据量的`total`、当前页数的`page`,以及属于该页的数据`data`。 449 | 450 | 然而,我这个页面只是个静态页面,数据是放在Github Issues上的通过API获取的。(Github Issues的分页貌似不能自定义数量...),所以没法直接返回分好的数据,所以只能在前端强行分页~ 451 | 452 | 分页功能这一块我偷懒了...用的是 `antd` 的翻页组件 ``。 453 | 454 | 官方文档:[https://ant.design/components/pagination-cn/](https://ant.design/components/pagination-cn/) 455 | 456 | 文档很清晰,使用起来也特别简单。 457 | 458 | 前端渲染的逻辑(有点蠢):将数据存放到一个数组中,根据当前页数和每页显示条数来计算该显示的索引值,取出相应的数据即可。 459 | 460 | 翻页组件中: 461 | 462 | ```javascript 463 | constructor() { 464 | super(); 465 | this.onChangePage = this.onChangePage.bind(this); 466 | } 467 | 468 | onChangePage(pageNumber) { 469 | this.props.handlePageChange(pageNumber); 470 | } 471 | 472 | render() { 473 | return ( 474 |
475 | 476 |
477 | ); 478 | } 479 | ``` 480 | 481 | 当页数发生改变后,会触发从父组件传进 `` 的方法 `handlePageChange`,从而将页数传递到父组件中,然后传递到 `` 中。 482 | 483 | 父组件中: 484 | 485 | ```javascript 486 | handlePageChange(pageNumber) { 487 | this.setState({ currentPage: pageNumber }); 488 | } 489 | 490 | render() { 491 | return ( 492 |
493 | 494 | 495 |
496 | ); 497 | } 498 | ``` 499 | 500 | 列表中: 501 | 502 | ```javascript 503 | render() { 504 | const articlelist = []; 505 | const issues = this.props.issues; 506 | const currentPage = this.props.pageNumber; 507 | const defaultPageSize = this.props.defaultPageSize; 508 | const start = currentPage === 1 ? 0 : (currentPage - 1) * defaultPageSize; 509 | const end = start + defaultPageSize < issues.length ? start + defaultPageSize : issues.length; 510 | for (let i = start; i < end; i += 1) { 511 | const item = issues[i]; 512 | articlelist.push(); 513 | } 514 | } 515 | ``` 516 | 517 | #### label 518 | 519 | 在 `Github Issues` 中,可以为一个 `issue` 添加很多个 `label`,我将这些对于博客内容有用的 `label` 分为三类,分别用不同颜色来表示。 520 | 521 | 这里说明一下, `label` 创建后会随机生成一个 `id`,虽然说 `id` 是不重复的,但是文章的类别、标签会一直在增加,当新加一个 `label` 时,程序中可能也要进行对应的修改,当作区分 `label` 的标准可能就不太合适,所以我采用颜色来区分它们。 522 | 523 | - 表示这是一篇文章的blog:只有有 `blog` 的 `issue` 才能显示在页面上,过滤 `bug` 、`help` 等 524 | - 表示文章类别的:用来表示文章的类别,比如“前端”、“摄影”等 525 | - 表示文章标签的:用来表示文章的标签,比如“JavaScript”、“React”等 526 | 527 | 即使有新的 `label` ,也只要根据颜色区分是属于哪一类就好了。 528 | 529 | ##### 类别 530 | 531 | ![](http://omufjr5bv.bkt.clouddn.com/article%E7%B1%BB%E5%88%AB.gif) 532 | 533 | 在这里的思路主要就是:遍历所有 `issues`,然后再遍历每个 `issue`的 `labels`,找出属于类别的 `label`,然后计数。 534 | 535 | ```javascript 536 | const categoryList = []; 537 | const categoryHash = {}; 538 | for (let i = 0; i < issues.length; i += 1) { 539 | const labels = issues[i].labels; 540 | for (let j = 0; j < labels.length; j += 1) { 541 | if (labels[j].color === COLOR_LABEL_CATEGORY) { 542 | const category = labels[j].name; 543 | if (categoryHash[category] === undefined) { 544 | categoryHash[category] = true; 545 | const categoryTemp = { category, sum: 1 }; 546 | categoryList.push(categoryTemp); 547 | } else { 548 | for (let k = 0; k < categoryList.length; k += 1) { 549 | if (categoryList[k].category === category) { 550 | categoryList[k].sum += 1; 551 | } 552 | } 553 | } 554 | } 555 | } 556 | } 557 | ``` 558 | 559 | 这样实现得要经历三次循环,复杂度有点高,感觉有点蠢,有待改进,如果有更好的方法,请多多指教~ 560 | 561 | ##### 标签 562 | 563 | ![](http://omufjr5bv.bkt.clouddn.com/article%E6%A0%87%E7%AD%BE.gif) 564 | 565 | 这里的思路和类别的思路基本一样,只不过不同的显示方式而已。 566 | 567 | 本来这里是想通过字体大小来体现每个标签的权重,后来觉得可能对于我来说,暂时只有那几个标签会很频繁,其它标签可能会很少,用字体大小来区分就没有什么意义,还是改成排序的方式。 568 | 569 | ### 文章页 570 | 571 | ![](http://omufjr5bv.bkt.clouddn.com/article%E6%96%87%E7%AB%A0%E9%A1%B5.gif) 572 | 573 | 文章页主要分为两部分: 574 | 575 | - 文章内容区域:显示文章内容,显示在页面的主体区域 576 | - 章节目录:文章的章节目录,显示在文章的右侧区域 577 | 578 | #### 文章内容 579 | 580 | 有两种方式获取文章具体内容: 581 | 582 | - 从之前已经请求过的数组中去遍历查找所需的文章内容 583 | - 通过 `issue number` 重新发一次请求直接获取内容 584 | 585 | 最后我选择了后者。 586 | 587 | 文章是用 `markdown` 语法写的,所以要先转成 `html` 然后插入页面中,这里用了一个 `React` 不提倡的属性:`dangerouslySetInnerHTML`。 588 | 589 | 除了渲染`markdown`,我们还得对文章中的代码进行高亮显示,还有就是定制文章中不同标签的样式。 590 | 591 | #### 章节目录 592 | 593 | 首先,这里有一个 `issue`,希望大家可以给一些建议~ 594 | 595 | 文章内容是通过 `markdown` 渲染后插入 `dom` 中的,由于 `React` 不建议通过 `document.getElementById` 的形式获取 `dom` 元素,所以只能想办法通过字符串匹配的方式获取文章的各个章节标题。 596 | 597 | 由于我不太熟悉正则表达式,曾经还在sf上咨询过,就采用了其中一个答案: 598 | 599 | ```javascript 600 | const issues = content; 601 | const menu = []; 602 | const patt = /(#+)\s+?(.+)/g; 603 | let result = null; 604 | while ((result = patt.exec(issues))) { 605 | menu.push({ level: result[1].length, title: result[2] }); 606 | } 607 | ``` 608 | 609 | 这样可以获取到所有的 `#` 的字符串,也就是 `markdown` 中的标题, `result[1].length` 表示有几个 `#`,其实就是几级标题的意思,`title` 就是标题内容了。 610 | 611 | 这里还有一个问题,本来通过 `` 的方式可以实现点击跳转,但是现在渲染出来的 `html` 中对于每一个标题没有独一无二的标识。。。 612 | 613 | ### 归档页 614 | 615 | 按年份归档: 616 | 617 | ![](http://omufjr5bv.bkt.clouddn.com/article%E5%BD%92%E6%A1%A31.png) 618 | 619 | 按类别归档: 620 | 621 | ![](http://omufjr5bv.bkt.clouddn.com/article%E5%BD%92%E6%A1%A3.png) 622 | 623 | 按标签归档: 624 | 625 | ![](http://omufjr5bv.bkt.clouddn.com/article%E5%BD%92%E6%A1%A32.png) 626 | 627 | ## 问题 628 | 629 | 基本功能是已经基本实现了,现在还存在着以下几个问题,也算是一个 `TodoList` 吧 630 | 631 | - 评论功能。拟利用 `Github Issues API` 实现评论,得实现 `Github` 授权登录 632 | - 回到顶部。拟利用 `antd` 的组件,但是 `state` 中 `visibility` 一直是 `false` 633 | - 首页渲染。现在打包完的js文件还是太大了,导致首页渲染太慢,这个是接下来工作的重点,也了解过关于这方面的优化: 634 | - `webpack` 按需加载。这可能是目前最方便的方式 635 | - 服务端渲染。这就麻烦了,但是好处也多,不仅解决渲染问题,还有利于SEO,所以也是 `todo` 之一 636 | - 代码混乱,逻辑不对。这是我自己的问题,需要再修炼。 637 | 638 | 639 | 原文地址:[https://github.com/axuebin/react-blog/issues/17](https://github.com/axuebin/react-blog/issues/17) -------------------------------------------------------------------------------- /2017/React中state和props分别是什么?.md: -------------------------------------------------------------------------------- 1 | 在任何应用中,数据都是必不可少的。我们需要直接的改变页面上一块的区域来使得视图的刷新,或者间接地改变其他地方的数据。React的数据是自顶向下单向流动的,即从父组件到子组件中,组件的数据存储在`props`和`state`中,这两个属性有啥子区别呢? 2 | 3 | ## props 4 | 5 | React的核心思想就是组件化思想,页面会被切分成一些独立的、可复用的组件。 6 | 7 | 组件从概念上看就是一个函数,可以接受一个参数作为输入值,这个参数就是`props`,所以可以把`props`理解为从外部传入组件内部的数据。由于React是单向数据流,所以`props`基本上也就是从服父级组件向子组件传递的数据。 8 | 9 | ### 用法 10 | 11 | 假设我们现在需要实现一个列表,根据React组件化思想,我们可以把列表中的行当做一个组件,也就是有这样两个组件:``和``。 12 | 13 | 先看看`` 14 | 15 | ```javascript 16 | import Item from "./item"; 17 | export default class ItemList extends React.Component{ 18 | const itemList = data.map(item => ); 19 | render(){ 20 | return ( 21 | {itemList} 22 | ) 23 | } 24 | } 25 | ``` 26 | 27 | 列表的数据我们就暂时先假设是放在一个`data`变量中,然后通过`map`函数返回一个每一项都是``的数组,也就是说这里其实包含了`data.length`个``组件,数据通过在组件上自定义一个参数传递。当然,这里想传递几个自定义参数都可以。 28 | 29 | 在``中是这样的: 30 | 31 | ```javascript 32 | export default class Item extends React.Component{ 33 | render(){ 34 | return ( 35 |
  • {this.props.item}
  • 36 | ) 37 | } 38 | } 39 | ``` 40 | 41 | 在`render`函数中可以看出,组件内部是使用`this.props`来获取传递到该组件的所有数据,它是一个对象,包含了所有你对这个组件的配置,现在只包含了一个`item`属性,所以通过`this.props.item`来获取即可。 42 | 43 | ### 只读性 44 | 45 | `props`经常被用作渲染组件和初始化状态,当一个组件被实例化之后,它的`props`是只读的,不可改变的。如果`props`在渲染过程中可以被改变,会导致这个组件显示的形态变得不可预测。只有通过父组件重新渲染的方式才可以把新的`props`传入组件中。 46 | 47 | ### 默认参数 48 | 49 | 在组件中,我们最好为`props`中的参数设置一个`defaultProps`,并且制定它的类型。比如,这样: 50 | 51 | ```javascript 52 | Item.defaultProps = { 53 | item: 'Hello Props', 54 | }; 55 | 56 | Item.propTypes = { 57 | item: PropTypes.string, 58 | }; 59 | ``` 60 | 61 | 关于`propTypes`,可以声明为以下几种类型: 62 | 63 | ```javascript 64 | optionalArray: PropTypes.array, 65 | optionalBool: PropTypes.bool, 66 | optionalFunc: PropTypes.func, 67 | optionalNumber: PropTypes.number, 68 | optionalObject: PropTypes.object, 69 | optionalString: PropTypes.string, 70 | optionalSymbol: PropTypes.symbol, 71 | ``` 72 | 73 | 注意,`bool`和`func`是简写。 74 | 75 | 这些知识基础数据类型,还有一些复杂的,附上链接: 76 | 77 | [https://facebook.github.io/react/docs/typechecking-with-proptypes.html](https://facebook.github.io/react/docs/typechecking-with-proptypes.html) 78 | 79 | ### 总结 80 | 81 | `props`是一个从外部传进组件的参数,主要作为就是从父组件向子组件传递数据,它具有可读性和不变性,只能通过外部组件主动传入新的`props`来重新渲染子组件,否则子组件的`props`以及展现形式不会改变。 82 | 83 | ## state 84 | 85 | `state`是什么呢? 86 | 87 | > State is similar to props, but it is private and fully controlled by the component. 88 | 89 | 一个组件的显示形态可以由数据状态和外部参数所决定,外部参数也就是`props`,而数据状态就是`state`。 90 | 91 | ### 用法 92 | 93 | ```javascript 94 | export default class ItemList extends React.Component{ 95 | constructor(){ 96 | super(); 97 | this.state = { 98 | itemList:'一些数据', 99 | } 100 | } 101 | render(){ 102 | return ( 103 | {this.state.itemList} 104 | ) 105 | } 106 | } 107 | ``` 108 | 109 | 首先,在组件初始化的时候,通过`this.state`给组件设定一个初始的`state`,在第一次`render`的时候就会用这个数据来渲染组件。 110 | 111 | ### setState 112 | 113 | `state`不同于`props`的一点是,`state`是可以被改变的。不过,不可以直接通过`this.state=`的方式来修改,而需要通过`this.setState()`方法来修改`state`。 114 | 115 | 比如,我们经常会通过异步操作来获取数据,我们需要在`didMount`阶段来执行异步操作: 116 | 117 | ```javascript 118 | componentDidMount(){ 119 | fetch('url') 120 | .then(response => response.json()) 121 | .then((data) => { 122 | this.setState({itemList:item}); 123 | } 124 | } 125 | ``` 126 | 127 | 当数据获取完成后,通过`this.setState`来修改数据状态。 128 | 129 | 当我们调用`this.setState`方法时,React会更新组件的数据状态`state`,并且重新调用`render`方法,也就是会对组件进行重新渲染。 130 | 131 | **注意:通过`this.state=`来初始化`state`,使用`this.setState`来修改`state`,`constructor`是唯一能够初始化的地方。** 132 | 133 | `setState`接受一个对象或者函数作为第一个参数,只需要传入需要更新的部分即可,不需要传入整个对象,比如: 134 | 135 | ```javascript 136 | export default class ItemList extends React.Component{ 137 | constructor(){ 138 | super(); 139 | this.state = { 140 | name:'axuebin', 141 | age:25, 142 | } 143 | } 144 | componentDidMount(){ 145 | this.setState({age:18}) 146 | } 147 | } 148 | ``` 149 | 150 | 在执行完`setState`之后的`state`应该是`{name:'axuebin',age:18}`。 151 | 152 | `setState`还可以接受第二个参数,它是一个函数,会在`setState`调用完成并且组件开始重新渲染时被调用,可以用来监听渲染是否完成: 153 | 154 | ```javascript 155 | this.setState({ 156 | name:'xb' 157 | },()=>console.log('setState finished')) 158 | ``` 159 | 160 | ### 总结 161 | 162 | `state`的主要作用是用于组件保存、控制以及修改自己的状态,它只能在`constructor`中初始化,它算是组件的私有属性,不可通过外部访问和修改,只能通过组件内部的`this.setState`来修改,修改`state`属性会导致组件的重新渲染。 163 | 164 | 165 | ## 区别 166 | 167 | 1. `state`是组件自己管理数据,控制自己的状态,可变; 168 | 2. `props`是外部传入的数据参数,不可变; 169 | 3. 没有`state`的叫做无状态组件,有`state`的叫做有状态组件; 170 | 4. 多用`props`,少用`state`。也就是多写无状态组件。 171 | 172 | ### 未完待续 173 | 174 | > 未完待续 -------------------------------------------------------------------------------- /2017/React的生命周期到底是怎么一回事?.md: -------------------------------------------------------------------------------- 1 | 尽量全面详细的整理一下React的生命周期中的知识点。 2 | 3 | ## 组件 4 | 5 | 组件是独立的封装的可以复用的一个小部件,它是React的核心思想之一。通过划分组件,可以将一个页面划分成独立的多个可复用的组件,各个组件通过嵌套、组合形成一个完整的页面。 6 | 7 | 在React中,组件基本由三个部分组成:属性(props)、状态(state)以及生命周期方法。可以将组件简单地看作一个“状态机”,根据不同的`state`和`props`呈现不同的UI,通过与用户的交互实现不同的状态,然后重新渲染组件,UI可以跟随数据变化而变化。 8 | 9 | ### 创建组件 10 | 11 | 组件常分为两种:`Class Component`和`Functional Component`。 12 | 13 | #### 无状态组件 14 | 15 | `Functional Component`也称为无状态组件,它多用于纯展示组件,这种组件只负责根据传入的`props`来渲染组件,而不涉及`state`状态管理。 16 | 17 | > 在大部分React代码中,大多数组件被写成无状态的组件,通过简单组合可以构建成其他的组件等;这种通过多个简单然后合并成一个大应用的设计模式被提倡。 18 | 19 | 无状态组件可以通过函数形式或者ES6的箭头函数来创建: 20 | 21 | ```javascript 22 | // 函数 23 | function HelloFunctional(props){ 24 | return
    hello {props.name}
    ; 25 | } 26 | 27 | // ES6箭头函数 28 | const HelloFunctional = (props) => (
    hello {props.name}
    ); 29 | ``` 30 | 31 | 无状态组件有以下几个特点: 32 | 33 | 1. 代码可读性更好 34 | 2. 组件不会被实例化,渲染性能提升 35 | 3. 无生命周期方法 36 | 4. 只能输入`props`,同样的输入一定会有同样的输出 37 | 38 | 所以,在项目中如果不需要进行状态管理,应该尽量写成无状态组件的形式。 39 | 40 | #### 有状态组件 41 | 42 | 现在主流的创建有状态组件的形式是通过ES6的Class来创建,取代`React.createClass`: 43 | 44 | ```javascript 45 | Class HelloClass extends React.Component{ 46 | constructor(){ 47 | this.state = { 48 | name:'axuebin' 49 | } 50 | } 51 | render(){ 52 | return (
    hello {this.state.name}
    ); 53 | } 54 | } 55 | ``` 56 | 57 | 这是最简洁的一个组件,它需要使用到内部状态`state`。 58 | 59 | **当组件需要使用内部状态时或者需要使用生命周期方法时就需要使用有状态组件。** 60 | 61 | ## 组件的生命周期 62 | 63 | React组件的生命周期可以分为挂载、渲染和卸载这几个阶段,当渲染后的组件更新后,会重新渲染组件,直到卸载。先分阶段来看看每个阶段有哪些生命周期函数。 64 | 65 | ### 挂载阶段(Mounting) 66 | 67 | 属于这个阶段的生命周期函数有: 68 | 69 | 1. constructor() 70 | 2. componentWillMount() 71 | 3. render() 72 | 4. componentDidMount() 73 | 74 | #### constructor() 75 | 76 | ```javascript 77 | constructor() { 78 | super(); 79 | this.state = {name: 'axuebin'}; 80 | this.handleClick = this.handleClick.bind(this); 81 | } 82 | ``` 83 | 84 | 这个阶段就是组件的初始化,`constructor()`可以理解为组件的构造函数,从组件的类`class`实例化一个组件实例。这个函数是组件形成时就被调用的,是生命周期中最先执行的。 85 | 86 | 在`constructor()`函数内,首先必须执行`super()`,否则`this.props`将是未定义,会引发异常。 87 | 88 | 然后,如果有必要,可以进行: 89 | 90 | - `state`的初始化 91 | - 方法的绑定 92 | 93 | 如果不需要这两步,可以直接省略`constructor`函数。 94 | 95 | 有一点,在`constructor()`中,`this.props`返回`undefined`。 96 | 97 | #### componentWillMount() 98 | 99 | 这个函数按照驼峰法的命名规则可以理解为“组件即将被挂载”,所以这个函数是组件首次渲染(render)前调用的。 100 | 101 | 在每次页面加载、刷新时,或者某个组件第一次展现时都会调用这个函数。通常地,我们推荐使用`constructor()`来替代。 102 | 103 | **注意:在这个函数中,不可以调用`setState`来修改状态。** 104 | 105 | #### render() 106 | 107 | ```javascript 108 | render() { 109 | return( 110 |
    hello {this.state.name} {this.props.age}
    111 | ) 112 | } 113 | ``` 114 | 115 | `render()`在生命周期中是必须的,是渲染组件用的。 116 | 117 | 当这个函数被调用时,需要检查`this.props`和`this.state`并且返回一个元素(有且只有一个元素),这个元素可能是一个原生DOM元素,也有可能是另一个React组件。 118 | 119 | 可以在`state`或`props`状态为空时试着返回一个`null`或者`false`来声明不想渲染任何东西。 120 | 121 | 在这个函数中,不应该改变组件的状态,也就是不执行`this.setState`,需要保持`render()`函数的纯净。 122 | 123 | 在这个函数中,可以对`props`进行调用并组合,但不可修改。 124 | 125 | #### componentDidMount() 126 | 127 | ```javascript 128 | componentDidMount() { 129 | this.setState({name:'xb'}); 130 | } 131 | ``` 132 | 133 | 这个函数在组件加载渲染完成后立即调用,此时页面上已经渲染出真实的DOM了,可以在这个函数中访问到真实的DOM(可以通过`this.refs`来访问真实DOM)。 134 | 135 | 在这个阶段,还可以做一件事,可以修改`state`了!!! 136 | 137 | 而且,异步获取数据在这个阶段执行也是比较合理的,获取数据之后`setState`,然后重新渲染组件。 138 | 139 | ### 更新阶段(Updating) 140 | 141 | 属性或状态的改变会触发一次更新。当一个组件在被重新渲染时,这些方法将会被调用: 142 | 143 | 1. componentWillReceiveProps() 144 | 2. shouldComponentUpdate() 145 | 3. componentWillUpdate() 146 | 4. render() 147 | 5. componentDidUpdate() 148 | 149 | #### componentWillReceiveProps() 150 | 151 | 已加载的组件在`props`发生变化时调用,若需要更新状态,可能需要对比`this.props`和`nextProps`然后在该方法中使用`this.setState`来处理状态的改变。 152 | 153 | 需要注意的是,有些情况下,即使`props`未改变也会触发该函数,所以一定要先比较`this.props`和`nextProps`再做操作。 154 | 155 | 该函数只监听`props`的改变,`this.setState`不会触发这个函数。 156 | 157 | ```javascript 158 | componentWillReceiveProps(nextProps){ 159 | if (this.props.color !== nextProps.color){ 160 | this.setState({}); 161 | } 162 | } 163 | ``` 164 | 165 | #### shouldComponentUpdate() 166 | 167 | 这个函数只返回`true`或`false`,表示组件是否需要更新(重新渲染)。 168 | 169 | 1. 返回`true`就是紧接着以下的生命周期函数; 170 | 2. 返回`false`表示组件不需要重新渲染,不再执行任何生命周期函数(包括render)。 171 | 172 | 这个函数使用需谨慎,react官方文档中说道,在未来这个函数返回`false`可能仍然使得组件重新渲染。 173 | 174 | #### componentWillUpdate() 175 | 176 | 这个函数看名字就和`componentWillMount`很像,它执行的阶段也很像。在接收到新的`props`或者`state`之后,这个函数就会在`render`前被调用。 177 | 178 | 同样的,在这个函数中不能使用`this.setState()`。如果需要更新状态,请在`componentWillReceiveProps`中调用`this.setState()`。 179 | 180 | #### render() 181 | 182 | 又是一次的`render`。这和挂载阶段的`render`有什么区别呢? 183 | 184 | 在函数的性质上来说,两者毫无区别,只不过是在生命周期的不同阶段的调用。 185 | 186 | - 前一个`render`是在组件第一次加载时调用的,也就是初次渲染,可以理解为`mount`; 187 | - 后一个`render`是除去第一次之后调用的,也就是再渲染,`re-render`; 188 | 189 | #### componentDidUpdate() 190 | 191 | 同样地,这个方法是在组件`re-render`之后调用的,该方法不会在初始化的时候调用。和`componentDidMount`一样,在这个函数中可以使用`this.refs`获取真实DOM。 192 | 193 | 还可以修改`state`哦,不过会导致组件再次`re-render`。 194 | 195 | ### 卸载阶段(Unmounting) 196 | 197 | 该方法将会在 component 从DOM中移除时调用 198 | 199 | - componentWillUnmount() 200 | 201 | #### componentWillUnmount() 202 | 203 | 卸载阶段就很简单了,就这一个生命周期函数,在组件被卸载和销毁之前立刻调用。 204 | 205 | 在这个函数中,应该处理任何必要的清理工作,比如销毁定时器、取消网络请求、清除之前创建的相关DOM节点等。 206 | 207 | ## 生命周期流程图 208 | 209 | ![](http://omufjr5bv.bkt.clouddn.com/react-lifecycle%E6%9C%AA%E5%91%BD%E5%90%8D%E6%96%87%E4%BB%B6.png) 210 | 211 | 212 | -------------------------------------------------------------------------------- /2017/关于浏览器缓存我知道多少.md: -------------------------------------------------------------------------------- 1 | 在前端开发中,我们在提到性能优化的时候总会提到一点:合理设置缓存。我们该如何从这方面入手来考虑提高网站性能呢? 2 | 3 | ### 前言 4 | 5 | 我们都知道 HTML5 引入了应用程序缓存,可以在没有网络的情况下进行访问,同时,HTML5 还引入了 storage 本地存储。这些都属于应用缓存。 6 | 7 | 本篇文章主要内容是和浏览器缓存相关的,也可以说是 HTTP 缓存。 8 | 9 | ### 什么是浏览器缓存 10 | 11 | MDN 上是这样解释浏览器缓存的: 12 | 13 | > A browser cache holds all documents downloaded via HTTP by the user ... without requiring an additional trip to the server. 14 | 15 | 意思就是,浏览器缓存保存着用户通过 `HTTP` 获取的所有资源,再下一次请求时可以避免重复向服务器发出多余的请求。 16 | 17 | 通俗的说,就是在你访问过一次某个网站之后,这个站点的文字、图片等所有资源都被下载到本地了,下次再访问该网站时判断是否满足缓存条件,如果满足就不用再花费时间去等待资源的获取了。 18 | 19 | ### 浏览器缓存的分类 20 | 21 | 一般来说浏览器缓存可以分为两类: 22 | 23 | - 强缓存 24 | - 协商缓存(对比缓存) 25 | 26 | 我们需要知道的是,浏览器在加载资源时,会先判断是否命中**强缓存**再验证是命中**协商缓存**。 27 | 28 | 其它的的具体细节,稍后会展开来说。 29 | 30 | ### 强缓存 31 | 32 | 浏览器在加载资源时,会先根据本地缓存资源的 `header` 中的信息判断是否命中强缓存,如果命中则直接使用缓存中的资源不会再向服务器发送请求。 33 | 34 | ![](http://omufjr5bv.bkt.clouddn.com/%E5%BC%BA%E7%BC%93%E5%AD%98.png) 35 | 36 | 从图中可以看出,强缓存一般是这样一个流程: 37 | 38 | 1. 查看 `header` 头中的 `Expire` 和 `Cache-control` 来判断是否满足规则; 39 | 2. 如果满足规则,就返回缓存的数据; 40 | 3. 如果不满足规则,就向服务器发送请求; 41 | 4. 服务器返回数据; 42 | 5. 将新数据存入缓存。 43 | 44 | 所以我们主要就是关注 `Expire` 和 `Cache-control` 这两个字段。 45 | 46 | #### Expire 47 | 48 | 同样地,我们看看MDN中如何解释这个字段: 49 | 50 | > The Expires header contains the date/time after which the response is considered stale. 51 | 52 | 这个字段包含了一个时间,过了这个时间,响应将会失效。 53 | 54 | 也就是说,`Expire` 这个字段表示缓存到期时间,我们来打开一个网站并查看 `Response Header` 看看这个字段: 55 | 56 | ``` 57 | Expires:Fri, 27 Oct 2017 07:55:30 GMT 58 | ``` 59 | 60 | 可能在你查看这的时候发现时间不对啊,怎么都已经是过去了 ~ 61 | 62 | `GMT` 表示的是格林威治时间,和北京时间相差8小时。 63 | 64 | 上面的这个时间表示的是 `2017年10月27日15:55:30`。 65 | 66 | 通过设置 `Expire` 来设置缓存有一个致命缺点: 67 | 68 | 可以看出,这个是个绝对时间,也就是说,如果我修改了客户端的本地时间,是不是就会导致判断缓存失效了呢。 69 | 70 | #### Cache-Control 71 | 72 | 既然不能设置绝对时间,那我就设置个相对时间呗。 73 | 74 | 在 `HTTP/1.1` 中,增加了一个字段 `Cache-Control` ,它包含一个 `max-age` 属性,该字段表示资源缓存的最大有效时间,这就是一个相对时间。 75 | 76 | ``` 77 | Cache-Control:max-age=600 78 | ``` 79 | 80 | 这个表示的就是最大有效时间是 `600s` ,对的,它的单位是秒。 81 | 82 | `Cache-Control` 除了 `max-age` 属性之外还有一些属性: 83 | 84 | - no-cache:需要进行协商缓存,发送请求到服务器确认是否使用缓存。 85 | - no-store:禁止使用缓存,每一次都要重新请求数据。 86 | - public:默认设置。 87 | - private:不能被多用户共享。 88 | 89 | 现在基本上都会同时设置 `Expire` 和 `Cache-Control` ,`Cache-Control` 的优先级别更高。 90 | 91 | ### 协商缓存 92 | 93 | 当强缓存没有命中的时候,浏览器会发送一个请求到服务器,服务器根据请求头中的部分信息来判断是否命中缓存。如果命中,则返回 `304` ,告诉浏览器资源未更新,可使用本地的缓存。 94 | 95 | ![](http://omufjr5bv.bkt.clouddn.com/%E5%8D%8F%E5%95%86%E7%BC%93%E5%AD%98.png) 96 | 97 | 从图中可以看出,协商缓存一般是这样一个流程: 98 | 99 | 1. 把资源标识,比如 `If-Modify-Since` 或 `Etag` 发送到服务器,确认资源是否更新; 100 | 2. 如果资源未更新,请求响应返回的http状态为 `304` 并且会显示一个 `Not Modified` 的字符串,告诉浏览器使用本地缓存; 101 | 3. 如果资源已经更新,返回新的数据; 102 | 4. 将新数据存入缓存。 103 | 104 | #### Last-Modified,If-Modified-Since 105 | 106 | 浏览器第一次请求资源的时候,服务器返回的 `header` 上会带有一个 `Last-Modified` 字段,表示资源**最后修改**的时间。 107 | 108 | ``` 109 | Last-Modified: Fri, 27 Oct 2017 07:55:30 GMT 110 | ``` 111 | 112 | 同样的,这是一个 `GMT` 的绝对时间。 113 | 114 | 当浏览器再次请求该资源时,请求头中会带有一个 `If-Modified-Since` 字段,这个值是第一次请求返回的 `Last-Modified` 的值。服务器收到这个请求后,将 `If-Modified-Since` 和当前的 `Last-Modified` 进行对比。如果相等,则说明资源未修改,返回 `304`,浏览器使用本地缓存。 115 | 116 | well,这个方法也是有缺点的: 117 | 118 | - 最小单位是秒。也就是说如果我短时间内资源发生了改变,`Last-Modified` 并不会发生变化; 119 | - 周期性变化。如果这个资源在一个周期内修改回原来的样子了,我们认为是可以使用缓存的,但是 `Last-Modified` 可不这样认为。 120 | 121 | 所以,后来又引入一个 `Etag`。 122 | 123 | #### Etag 124 | 125 | `Etag` 一般是由文件内容 `hash` 生成的,也就是说它可以保证资源的唯一性,资源发生改变就会导致 `Etag` 发生改变。 126 | 127 | 同样地,在浏览器第一次请求资源时,服务器会返回一个 `Etag` 标识。当再次请求该资源时, 会通过 `If-no-match` 字段将 `Etag` 发送回服务器,然后服务器进行比较,如果相等,则返回 `304` 表示未修改。 128 | 129 | **`Last-Modified` 和 `Etag` 是可以同时设置的,服务器会优先校验 `Etag`,如果 `Etag` 相等就会继续比对 `Last-Modified`,最后才会决定是否返回 `304`。 ** 130 | 131 | ### 总结 132 | 133 | 当浏览器再次访问一个已经访问过的资源时,它会这样做: 134 | 135 | 1. 看看是否命中强缓存,如果命中,就直接使用缓存了; 136 | 2. 如果没有命中强缓存,就发请求到服务器检查是否命中协商缓存; 137 | 3. 如果命中协商缓存,服务器会返回 `304` 告诉浏览器使用本地缓存; 138 | 4. 否则,返回最新的资源。 -------------------------------------------------------------------------------- /2017/原生JS实现最简单的图片懒加载.md: -------------------------------------------------------------------------------- 1 | Demo地址:[http://axuebin.com/lazyload](http://axuebin.com/lazyload) 2 | 3 | 照片都是自己拍的哦~ 4 | 5 | ## 懒加载 6 | 7 | ### 什么是懒加载 8 | 9 | 懒加载其实就是延迟加载,是一种对网页性能优化的方式,比如当访问一个页面的时候,优先显示可视区域的图片而不一次性加载所有图片,当需要显示的时候再发送图片请求,避免打开网页时加载过多资源。 10 | 11 | ### 什么时候用懒加载 12 | 13 | 当页面中需要一次性载入很多图片的时候,往往都是需要用懒加载的。 14 | 15 | ### 懒加载原理 16 | 17 | 我们都知道HTML中的``标签是代表文档中的一个图像。。说了个废话。。 18 | 19 | ``标签有一个属性是`src`,用来表示图像的URL,当这个属性的值不为空时,浏览器就会根据这个值发送请求。如果没有`src`属性,就不会发送请求。 20 | 21 | 嗯?貌似这点可以利用一下? 22 | 23 | 我先不设置`src`,需要的时候再设置? 24 | 25 | nice,就是这样。 26 | 27 | 我们先不给``设置`src`,把图片真正的URL放在另一个属性`data-src`中,在需要的时候也就是图片进入可视区域的之前,将URL取出放到`src`中。 28 | 29 | ## 实现 30 | 31 | ### HTML结构 32 | 33 | ```html 34 |
    35 |
    36 | loading 37 |
    38 |
    39 | loading 40 |
    41 |
    42 | loading 43 |
    44 |
    45 | loading 46 |
    47 |
    48 | loading 49 |
    50 |
    51 | ``` 52 | 53 | 仔细观察一下,``标签此时是没有`src`属性的,只有`alt`和`data-src`属性。 54 | 55 | > alt 属性是一个必需的属性,它规定在图像无法显示时的替代文本。 56 | > data-* 全局属性:构成一类名称为自定义数据属性的属性,可以通过`HTMLElement.dataset`来访问。 57 | 58 | ### 如何判断元素是否在可视区域 59 | 60 | #### 方法一 61 | 62 | 网上看到好多这种方法,稍微记录一下。 63 | 64 | 1. 通过`document.documentElement.clientHeight`获取屏幕可视窗口高度 65 | 2. 通过`element.offsetTop`获取元素相对于文档顶部的距离 66 | 3. 通过`document.documentElement.scrollTop`获取浏览器窗口顶部与文档顶部之间的距离,也就是滚动条滚动的距离 67 | 68 | 然后判断②-③<①是否成立,如果成立,元素就在可视区域内。 69 | 70 | #### 方法二 getBoundingClientRect 71 | 72 | 通过`getBoundingClientRect()`方法来获取元素的大小以及位置,MDN上是这样描述的: 73 | 74 | > The Element.getBoundingClientRect() method returns the size of an element and its position relative to the viewport. 75 | 76 | 这个方法返回一个名为`ClientRect`的`DOMRect`对象,包含了`top`、`right`、`botton`、`left`、`width`、`height`这些值。 77 | 78 | MDN上有这样一张图: 79 | 80 | ![](https://mdn.mozillademos.org/files/15087/rect.png) 81 | 82 | 可以看出返回的元素位置是相对于左上角而言的,而不是边距。 83 | 84 | 我们思考一下,什么情况下图片进入可视区域。 85 | 86 | 假设`const bound = el.getBoundingClientRect();`来表示图片到可视区域顶部距离; 87 | 并设 `const clientHeight = window.innerHeight;`来表示可视区域的高度。 88 | 89 | 随着滚动条的向下滚动,`bound.top`会越来越小,也就是图片到可视区域顶部的距离越来越小,当`bound.top===clientHeight`时,图片的上沿应该是位于可视区域下沿的位置的临界点,再滚动一点点,图片就会进入可视区域。 90 | 91 | 也就是说,在`bound.top<=clientHeight`时,图片是在可视区域内的。 92 | 93 | 我们这样判断: 94 | 95 | ```javascript 96 | function isInSight(el) { 97 | const bound = el.getBoundingClientRect(); 98 | const clientHeight = window.innerHeight; 99 | //如果只考虑向下滚动加载 100 | //const clientWidth = window.innerWeight; 101 | return bound.top <= clientHeight + 100; 102 | } 103 | ``` 104 | 105 | 这里有个+100是为了提前加载。 106 | 107 | ### 加载图片 108 | 109 | 页面打开时需要对所有图片进行检查,是否在可视区域内,如果是就加载。 110 | 111 | ```javascript 112 | function checkImgs() { 113 | const imgs = document.querySelectorAll('.my-photo'); 114 | Array.from(imgs).forEach(el => { 115 | if (isInSight(el)) { 116 | loadImg(el); 117 | } 118 | }) 119 | } 120 | 121 | function loadImg(el) { 122 | if (!el.src) { 123 | const source = el.dataset.src; 124 | el.src = source; 125 | } 126 | } 127 | ``` 128 | 129 | 这里应该是有一个优化的地方,设一个标识符标识已经加载图片的index,当滚动条滚动时就不需要遍历所有的图片,只需要遍历未加载的图片即可。 130 | 131 | ### 函数节流 132 | 133 | 在类似于滚动条滚动等频繁的DOM操作时,总会提到“函数节流、函数去抖”。 134 | 135 | 所谓的函数节流,也就是让一个函数不要执行的太频繁,减少一些过快的调用来节流。 136 | 137 | 基本步骤: 138 | 139 | 1. 获取第一次触发事件的时间戳 140 | 2. 获取第二次触发事件的时间戳 141 | 3. 时间差如果大于某个阈值就执行事件,然后重置第一个时间 142 | 143 | ```javascript 144 | function throttle(fn, mustRun = 500) { 145 | const timer = null; 146 | let previous = null; 147 | return function() { 148 | const now = new Date(); 149 | const context = this; 150 | const args = arguments; 151 | if (!previous){ 152 | previous = now; 153 | } 154 | const remaining = now - previous; 155 | if (mustRun && remaining >= mustRun) { 156 | fn.apply(context, args); 157 | previous = now; 158 | } 159 | } 160 | } 161 | ``` 162 | 163 | 这里的`mustRun`就是调用函数的时间间隔,无论多么频繁的调用`fn`,只有`remaining>=mustRun`时`fn`才能被执行。 164 | 165 | ## 实验 166 | 167 | ### 页面打开时 168 | 169 | ![](http://omufjr5bv.bkt.clouddn.com/lazyload1.png) 170 | 171 | 可以看出此时仅仅是加载了img1和img2,其它的img都没发送请求,看看此时的浏览器 172 | 173 | ![](http://omufjr5bv.bkt.clouddn.com/lazyload2.png) 174 | 175 | 第一张图片是完整的呈现了,第二张图片刚进入可视区域,后面的就看不到了~ 176 | 177 | ### 页面滚动时 178 | 179 | 当我向下滚动,此时浏览器是这样 180 | 181 | ![](http://omufjr5bv.bkt.clouddn.com/lazyload3.png) 182 | 183 | 此时第二张图片完全显示了,而第三张图片显示了一点点,这时候我们看看请求情况 184 | 185 | ![](http://omufjr5bv.bkt.clouddn.com/lazyload4.png) 186 | 187 | img3的请求发出来,而后面的请求还是没发出~ 188 | 189 | ### 全部载入时 190 | 191 | 当滚动条滚到最底下时,全部请求都应该是发出的,如图 192 | 193 | ![](http://omufjr5bv.bkt.clouddn.com/lazyload5.png) 194 | 195 | ### 完整demo 196 | 197 | 在这哦:[http://axuebin.com/lazyload](http://axuebin.com/lazyload) 198 | 199 | ## 更新 200 | 201 | ### 方法三 IntersectionObserver 202 | 203 | 经大佬提醒,发现了这个方法 204 | 205 | 先附上链接: 206 | 207 | jjc大大:[https://github.com/justjavac/the-front-end-knowledge-you-may-dont-know/issues/10](https://github.com/justjavac/the-front-end-knowledge-you-may-dont-know/issues/10) 208 | 209 | 阮一峰大大:[http://www.ruanyifeng.com/blog/2016/11/intersectionobserver_api.html](http://www.ruanyifeng.com/blog/2016/11/intersectionobserver_api.html) 210 | 211 | API Sketch for Intersection Observers:[https://github.com/WICG/IntersectionObserver](https://github.com/WICG/IntersectionObserver) 212 | 213 | `IntersectionObserver`可以自动观察元素是否在视口内。 214 | 215 | ```javascript 216 | var io = new IntersectionObserver(callback, option); 217 | // 开始观察 218 | io.observe(document.getElementById('example')); 219 | // 停止观察 220 | io.unobserve(element); 221 | // 关闭观察器 222 | io.disconnect(); 223 | ``` 224 | 225 | callback的参数是一个数组,每个数组都是一个`IntersectionObserverEntry`对象,包括以下属性: 226 | 227 | |属性 |描述 | 228 | | ---------------- | -------------------------------------------------------------------------------------------- | 229 | |time |可见性发生变化的时间,单位为毫秒 | 230 | |rootBounds |与getBoundingClientRect()方法的返回值一样 | 231 | |boundingClientRect|目标元素的矩形区域的信息 | 232 | |intersectionRect |目标元素与视口(或根元素)的交叉区域的信息 | 233 | |intersectionRatio |目标元素的可见比例,即intersectionRect占boundingClientRect的比例,完全可见时为1,完全不可见时小于等于0| 234 | |target |被观察的目标元素,是一个 DOM 节点对象 | 235 | 236 | 我们需要用到`intersectionRatio`来判断是否在可视区域内,当`intersectionRatio > 0 && intersectionRatio <= 1`即在可视区域内。 237 | 238 | #### 代码 239 | 240 | ```javascript 241 | function checkImgs() { 242 | const imgs = Array.from(document.querySelectorAll(".my-photo")); 243 | imgs.forEach(item => io.observe(item)); 244 | } 245 | 246 | function loadImg(el) { 247 | if (!el.src) { 248 | const source = el.dataset.src; 249 | el.src = source; 250 | } 251 | } 252 | 253 | const io = new IntersectionObserver(ioes => { 254 | ioes.forEach(ioe => { 255 | const el = ioe.target; 256 | const intersectionRatio = ioe.intersectionRatio; 257 | if (intersectionRatio > 0 && intersectionRatio <= 1) { 258 | loadImg(el); 259 | } 260 | el.onload = el.onerror = () => io.unobserve(el); 261 | }); 262 | }); 263 | ``` -------------------------------------------------------------------------------- /2017/用React实现一个简易的TodoList.md: -------------------------------------------------------------------------------- 1 | 初学React,撸一个TodoList熟悉熟悉基本语法,只有最简单最简单的功能。 2 | 3 | ![](http://i.imgur.com/tT18EpC.png) 4 | 5 | 如上图所示,是一个最简单的TodoList的样子了,我们应该怎样把它拆成一个个的组件呢? 6 | 7 | 在之前看来,可能就是这样一个HTML结构: 8 | 9 | ```html 10 |
    11 |

    12 |
    13 |
      14 |
    • 15 |
    • 16 |
    • 17 |
    18 |
    19 |
    20 | 21 | 22 |
    23 |
    24 | ``` 25 | 26 | > React的核心思想是:封装组件。 27 | 28 | 我们也可以按照这个思路来进行组件设计 29 | 30 | ## 组件设计 31 | 32 | ![](http://i.imgur.com/bp6NaWf.png) 33 | 34 | 从小到大,从内到外 ~ 35 | 36 | 我是这样进行设计的。 37 | 38 | 除去按钮,input这些之外,`
  • `是HTML中最小的元素,我们可以先每一个`
  • `当成是一个最小的组件,也就是图中橙色框的部分,它对应着每一条内容,我们先把它命名为`TodoItem`吧。 39 | 40 | `
  • `的父级元素是`
      `,那就把它看作一个组件呗,图中位于上方的蓝色部分,命名为`TodoList`。 41 | 42 | 恩,此时Todo内容的展示组件已经是够的了,我们再来加一个添加Todo内容的组件`AddTodoItem`吧,命名貌似有点丑- -,图中位于下方的蓝色部分。 43 | 44 | 最后就是最外层的红色部分了,它就是整个app的主体部分,包含着其它小组件,命名为`TodoBox`。 45 | 46 | ok,暂时就这几个小组件 ~ 47 | 48 | 然我们开始愉快的撸代码吧 ~ 49 | 50 | ## 代码部分 51 | 52 | ### Index 53 | 54 | 先看看入口程序,很简单。 55 | 56 | ```javascript 57 | var React = require('react'); 58 | var ReactDOM = require('react-dom'); 59 | import TodoBox from './components/todobox'; 60 | import './../css/index.css'; 61 | 62 | export default class Index extends React.Component { 63 | constructor(){ 64 | super(); 65 | }; 66 | render() { 67 | return ( 68 | 69 | ); 70 | } 71 | } 72 | 73 | ReactDOM.render(,document.getElementById("example")) 74 | ``` 75 | 76 | ### TodoItem 77 | 78 | 让我们想想啊,对于每一条内容来说,需要什么呢? 79 | 80 | - 一个确认是否完成的`checkbox` [ ] 81 | - 一条内容`text` 82 | - 一个删除`button` 83 | - zzzzzz.....其他的暂时先不加了~ 84 | 85 | 那不是太简单了 ~ 86 | 87 | ```html 88 |
    • 89 | 找工作啊找工作啊 90 | 91 |
    • 92 | ``` 93 | 94 | 不不不,我们现在是在写`React`,要这样: 95 | 96 | ```javascript 97 | import React from 'react'; 98 | import {Row, Col, Checkbox, Button} from 'antd'; 99 | 100 | export default class TodoItem extends React.Component { 101 | constructor(props) { 102 | super(props) 103 | this.toggleComplete = this.toggleComplete.bind(this) 104 | this.deleteTask = this.deleteTask.bind(this) 105 | } 106 | toggleComplete() { 107 | this.props.toggleComplete(this.props.taskId) 108 | } 109 | deleteTask() { 110 | this.props.deleteTask(this.props.taskId) 111 | } 112 | render() { 113 | let task = this.props.task 114 | let itemChecked 115 | if (this.props.complete === "true") { 116 | task = {task} 117 | itemChecked = true 118 | } else { 119 | itemChecked = false 120 | } 121 | return ( 122 |
    • 123 | 124 | 125 | {task} 126 | 127 | 128 | 129 | 130 | 131 |
    • 132 | ) 133 | } 134 | } 135 | ``` 136 | 137 | `import {Row, Col, Checkbox, Button} from 'antd'`是引入Ant Design。 138 | 139 | > 我们采用 React 封装了一套 Ant Design 的组件库,也欢迎社区其他框架的实现版本。 140 | 141 | 引入这个之后,我们可以直接使用一些简单的UI组件,比如`Row`,`Col`,`Checkbox`,`Button`等,我们可以更加注重业务逻辑的实现。 142 | 143 | ### TodoList 144 | 145 | 接下来就是拿一个`
        `把item包起来呗: 146 | 147 | ```javascript 148 | import React from 'react'; 149 | import TodoItem from './todoitem'; 150 | export default class TodoList extends React.Component{ 151 | constructor(props) { 152 | super(props); 153 | } 154 | render(){ 155 | var taskList=this.props.data.map(listItem=> 156 | 162 | ) 163 | return( 164 |
          165 | {taskList} 166 |
        167 | ) 168 | } 169 | } 170 | ``` 171 | 172 | ### AddTodoItem 173 | 174 | 添加内容这个组件也比较简单,就只需要一个`input`和一个`button`即可: 175 | 176 | ```javascript 177 | import React from 'react'; 178 | import ReactDOM from 'react-dom'; 179 | import {Row, Col, Form, Input, Button,notification } from 'antd'; 180 | export default class AddTodoItem extends React.Component { 181 | constructor(props) { 182 | super(props) 183 | this.saveNewItem = this.saveNewItem.bind(this) 184 | } 185 | saveNewItem(e) { 186 | e.preventDefault() 187 | let element = ReactDOM.findDOMNode(this.refs.newItem) 188 | let task = element.value 189 | if (!task) { 190 | notification.open({ 191 | description: 'Todo内容不得为空!', 192 | }); 193 | } else { 194 | this.props.saveNewItem(task) 195 | element.value = "" 196 | } 197 | } 198 | render() { 199 | return ( 200 |
        201 | 202 | 203 | 204 | 205 | 206 |
        207 | ) 208 | } 209 | } 210 | ``` 211 | 212 | ### TodoBox 213 | 214 | 我们的小组件已经都实现了,拿一个大`box`包起来呗 ~ 215 | 216 | ```javascript 217 | import React from 'react'; 218 | import TodoList from './todolist'; 219 | import AddTodoItem from './addtodoitem'; 220 | import {Button, Icon, Row, Col} from 'antd'; 221 | export default class TodoBox extends React.Component { 222 | constructor(props) { 223 | super(props) 224 | this.state = { 225 | data: [ 226 | { 227 | "id": "1", 228 | "task": "做一个TodoList Demo", 229 | "complete": "false" 230 | }, { 231 | "id": "2", 232 | "task": "学习ES6", 233 | "complete": "false" 234 | }, { 235 | "id": "3", 236 | "task": "Hello React", 237 | "complete": "true" 238 | }, { 239 | "id": "4", 240 | "task": "找工作", 241 | "complete": "false" 242 | } 243 | ] 244 | } 245 | this.handleToggleComplete = this.handleToggleComplete.bind(this); 246 | this.handleTaskDelete = this.handleTaskDelete.bind(this); 247 | this.handleAddTodoItem = this.handleAddTodoItem.bind(this); 248 | } 249 | generateGUID() { 250 | return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { 251 | var r = Math.random() * 16 | 0, 252 | v = c == 'x' ? r : (r & 0x3 | 0x8) 253 | return v.toString(16) 254 | }) 255 | } 256 | handleToggleComplete(taskId) { 257 | let data = this.state.data; 258 | for (let item of data) { 259 | if (item.id === taskId) { 260 | item.complete = item.complete === "true" ? "false" : "true" 261 | } 262 | } 263 | this.setState({data}) 264 | } 265 | handleTaskDelete(taskId) { 266 | let data = this.state.data 267 | data = data.filter(task => task.id !== taskId) 268 | this.setState({data}) 269 | } 270 | handleAddTodoItem(task) { 271 | let newItem = { 272 | id: this.generateGUID(), 273 | task, 274 | complete: "false" 275 | } 276 | let data = this.state.data 277 | data = data.concat([newItem]) 278 | this.setState({data}) 279 | } 280 | render() { 281 | return ( 282 |
        300 | ) 301 | } 302 | } 303 | ``` 304 | 305 | 注意: 306 | 307 | - 通过props传递子组件需要的值和方法 308 | - 传递方法时一定要bind(this),不然内部this会指向不正确 309 | 310 | ## 源码 311 | 312 | 完整的Demo代码在这:[https://github.com/axuebin/react-todolist](https://github.com/axuebin/react-todolist "https://github.com/axuebin/react-todolist") 313 | -------------------------------------------------------------------------------- /2018/JavaScript复制内容到剪贴板.md: -------------------------------------------------------------------------------- 1 | 最近一个活动页面中有一个小需求,用户点击或者长按就可以复制内容到剪贴板,记录一下实现过程和遇到的坑。 2 | 3 | ## 常见方法 4 | 5 | 查了一下万能的Google,现在常见的方法主要是以下两种: 6 | 7 | - 第三方库:clipboard.js 8 | - 原生方法:document.execCommand() 9 | 10 | 分别来看看这两种方法是如何使用的。 11 | 12 | ## clipboard.js 13 | 14 | 这是clipboard的官网:[https://clipboardjs.com/](https://clipboardjs.com/),看起来就是这么的简单。 15 | 16 | ### 引用 17 | 18 | 直接引用: `` 19 | 20 | 包: `npm install clipboard --save` ,然后 `import Clipboard from 'clipboard';` 21 | 22 | ### 使用 23 | 24 | #### 从输入框复制 25 | 26 | 现在页面上有一个 `` 标签,我们需要复制其中的内容,我们可以这样做: 27 | 28 | ```html 29 | 30 | 31 | ``` 32 | 33 | ```javascript 34 | import Clipboard from 'clipboard'; 35 | const btnCopy = new Clipboard('btn'); 36 | ``` 37 | 38 | 注意到,在 ` 46 | ``` 47 | 48 | ```javascript 49 | import Clipboard from 'clipboard'; 50 | const btnCopy = new Clipboard('btn'); 51 | this.copyValue = 'hello world'; 52 | ``` 53 | 54 | #### 事件 55 | 56 | 有的时候我们需要在复制后做一些事情,这时候就需要回调函数的支持。 57 | 58 | 在处理函数中加入以下代码: 59 | 60 | ```javascript 61 | // 复制成功后执行的回调函数 62 | clipboard.on('success', function(e) { 63 | console.info('Action:', e.action); // 动作名称,比如:Action: copy 64 | console.info('Text:', e.text); // 内容,比如:Text:hello word 65 | console.info('Trigger:', e.trigger); // 触发元素:比如: 66 | e.clearSelection(); // 清除选中内容 67 | }); 68 | 69 | // 复制失败后执行的回调函数 70 | clipboard.on('error', function(e) { 71 | console.error('Action:', e.action); 72 | console.error('Trigger:', e.trigger); 73 | }); 74 | ``` 75 | 76 | ### 小结 77 | 78 | 文档中还提到,如果在单页面中使用 `clipboard` ,为了使得生命周期管理更加的优雅,在使用完之后记得 `btn.destroy()` 销毁一下。 79 | 80 | `clipboard` 使用起来是不是很简单。但是,就为了一个 `copy` 功能就使用额外的第三方库是不是不够优雅,这时候该怎么办?那就用原生方法实现呗。 81 | 82 | ## document.execCommand()方法 83 | 84 | 先看看这个方法在 `MDN` 上是怎么定义的: 85 | 86 | > which allows one to run commands to manipulate the contents of the editable region. 87 | 88 | 意思就是可以允许运行命令来操作可编辑区域的内容,注意,是**可编辑区域**。 89 | 90 | ### 定义 91 | 92 | > bool = document.execCommand(aCommandName, aShowDefaultUI, aValueArgument) 93 | 94 | 方法返回一个 `Boolean` 值,表示操作是否成功。 95 | 96 | - `aCommandName` :表示命令名称,比如: `copy`, `cut` 等(更多命令见[命令](https://developer.mozilla.org/en-US/docs/Web/API/Document/execCommand#%E5%91%BD%E4%BB%A4)); 97 | - `aShowDefaultUI`:是否展示用户界面,一般情况下都是 `false`; 98 | - `aValueArgument`:有些命令需要额外的参数,一般用不到; 99 | 100 | ### 兼容性 101 | 102 | 这个方法在之前的兼容性其实是不太好的,但是好在现在已经基本兼容所有主流浏览器了,在移动端也可以使用。 103 | 104 | ![兼容性](http://omufjr5bv.bkt.clouddn.com/execCommand.png) 105 | 106 | ### 使用 107 | 108 | #### 从输入框复制 109 | 110 | 现在页面上有一个 `` 标签,我们想要复制其中的内容,我们可以这样做: 111 | 112 | ```html 113 | 114 | 115 | ``` 116 | 117 | ```javascript 118 | const btn = document.querySelector('#btn'); 119 | btn.addEventListener('click', () => { 120 | const input = document.querySelector('#demoInput'); 121 | input.select(); 122 | if (document.execCommand('copy')) { 123 | document.execCommand('copy'); 124 | console.log('复制成功'); 125 | } 126 | }) 127 | ``` 128 | 129 | #### 其它地方复制 130 | 131 | 有的时候页面上并没有 `` 标签,我们可能需要从一个 `
        ` 中复制内容,或者直接复制变量。 132 | 133 | 还记得在 `execCommand()` 方法的定义中提到,它只能操作**可编辑区域**,也就是意味着除了 ``、`