├── 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 | 
45 |
46 | 其他的一些照片可以看看 [https://500px.me/axuebin](https://500px.me/axuebin) 这里。
47 |
48 | 最喜欢的照片应该是这几张:
49 |
50 | 在赶去桥对面想找个好机位拍照的时候突然看到远处有彩虹,马上掏出相机啪了一张。
51 |
52 | 
53 |
54 | 去九溪打卡的时候低头看看脚下,落叶在阳光下显得格外好看,这样的光影让人着迷。
55 |
56 | 
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 | 
--------------------------------------------------------------------------------
/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 | 
64 |
65 | 此时`name`,`age`,`job`三种基本数据类型是直接存在栈内存中的,而`arr`,`obj`在栈内存中只是存了一个地址来表示对堆内存中的引用。
66 |
67 | ### 复制
68 |
69 | #### 基本数据类型
70 |
71 | 对于基本数据类型,如果进行复制,系统会自动为新的变量在栈内存中分配一个新值,很容易理解。
72 |
73 | #### 引用数据类型
74 |
75 | 如果对于数组、对象这样的引用数据类型而言,复制的时候就会有所区别了:
76 |
77 | 系统也会自动为新的变量在栈内存中分配一个值,但这个值仅仅是一个地址。也就是说,复制出来的变量和原有的变量具有相同的地址值,指向堆内存中的同一个对象。
78 |
79 | 
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 | 
18 |
19 | ### 博客页
20 |
21 | 
22 |
23 | ### 文章内容页
24 |
25 | 
26 |
27 | ### Demo页
28 |
29 | 
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 |