├── README.md ├── scaffolds ├── draft.md ├── page.md └── post.md ├── source └── _posts │ ├── 命令行工具揭秘.md │ ├── 常用GitApi整理.md │ ├── 作用域是干什么用的.md │ ├── 自定义页面系统设计(一).md │ ├── 对前端路由选择的思考.md │ ├── 提升.md │ ├── 对象复制之深复制和浅复制.md │ ├── 自定义一个遍历器.md │ ├── axios和fetch实战分析.md │ ├── 树形数组递归优化算法.md │ ├── 闭包.md │ ├── React简析之实例化.md │ ├── 自定义页面系统设计(二).md │ ├── 监听者模式实战应用.md │ ├── 理解一发“请求跨域”.md │ ├── Promise介绍.md │ ├── React简析之节点挂载.md │ ├── Promise原理.md │ └── Promise实战.md ├── .gitignore ├── package.json ├── _config.yml └── yarn.lock /README.md: -------------------------------------------------------------------------------- 1 | https://github.com/func-star/blog/issues 2 | -------------------------------------------------------------------------------- /scaffolds/draft.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: {{ title }} 3 | tags: 4 | --- 5 | -------------------------------------------------------------------------------- /scaffolds/page.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: {{ title }} 3 | date: {{ date }} 4 | --- 5 | -------------------------------------------------------------------------------- /scaffolds/post.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: {{ title }} 3 | date: {{ date }} 4 | tags: 5 | --- 6 | -------------------------------------------------------------------------------- /source/_posts/命令行工具揭秘.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 命令行工具揭秘 3 | date: 2018-10-12 15:32:29 4 | tags: 5 | --- 6 | 7 | todo -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | Thumbs.db 3 | db.json 4 | *.log 5 | node_modules/ 6 | public/ 7 | .deploy*/ 8 | .idea -------------------------------------------------------------------------------- /source/_posts/常用GitApi整理.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 常用Github Api整理 3 | date: 2018-10-13 14:36:19 4 | tags: 5 | --- 6 | 7 | * 获取指定仓库指定分支指定文件的原始内容(Raw) 8 | `https://raw.githubusercontent.com/用户名/仓库名/分支名/文件名` 9 | `https://raw.githubusercontent.com/capricornjs/capricorn-html-template/default/index.html` 10 | 11 | * 获取指定仓库的分支列表 12 | `https://api.github.com/repos/用户名/仓库名/branches` 13 | `https://api.github.com/repos/capricornjs/capricorn-html-template/branches` -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hexo-site", 3 | "version": "0.0.0", 4 | "private": true, 5 | "hexo": { 6 | "version": "3.7.1" 7 | }, 8 | "scripts": { 9 | "publish": "hexo clean && hexo generate && hexo deploy" 10 | }, 11 | "dependencies": { 12 | "hexo": "^3.7.0", 13 | "hexo-deployer-git": "^0.3.1", 14 | "hexo-generator-archive": "^0.1.5", 15 | "hexo-generator-category": "^0.1.3", 16 | "hexo-generator-feed": "^1.2.2", 17 | "hexo-generator-index": "^0.2.1", 18 | "hexo-generator-tag": "^0.2.0", 19 | "hexo-renderer-ejs": "^0.3.1", 20 | "hexo-renderer-marked": "^0.3.2", 21 | "hexo-renderer-stylus": "^0.3.3", 22 | "hexo-server": "^0.3.1" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /source/_posts/作用域是干什么用的.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 作用域是干什么用的 3 | date: 2018-06-26 19:49:12 4 | tags: 5 | --- 6 | 7 | 在理解作用之前,我先介绍一下另外两个家伙---引擎和编译器。 8 | 9 | * 引擎:负责`javascript`整个执行的过程 10 | * 编译器:负责分词-解析词法-生成抽象语法树-生成可执行代码。`javascript`的代码片段编译发生在执行之前,大部分场景发生在代码执行前的几微秒。 11 | * 作用域:总的来讲就是统一收集维护标识符变量,并管理访问权限 12 | 13 | ### 下面针对一个示例代码来讲述三者之间微妙的关系: 14 | ```js 15 | var demo = 1; 16 | ``` 17 | 18 | 实际上这段代码可以分解为两步,第一步是:`var demo`,第二步是`demo = 1`。 19 | 当引擎执行到这段代码时,编译器首先会对这段代码进行如下处理: 20 | 21 | * 编译器会询问作用域是否已经存在一个叫`demo`的标识符(即变量)在同一个作用域中,若是,直接忽略这个声明,否则编译器会要求在当前作用域中声明一个叫`demo`的标识符,并分配存储内存。 22 | * 在编译器生成完可执行的代码之后,引擎开始处理`demo = 1`这个命令。 23 | 1. 首先引擎会询问当前作用域是否已经存在一个叫做`demo`的标识符(变量)。 24 | 2. 如果存在,引擎开始使用这个变量。 25 | 3. 如果不存在,引擎继续向上访问上级作用域,直到找到这个标识符为止。如果最终询问到全局作用域仍然未找到,则抛出一个异常(`ReferenceError`)。 26 | 27 | 总结:变量的赋值操作会执行两个动作,首先编译器会在当前作用域中声明一个变量(如果之前没有声明过),然后在运行时引擎会在作用域中查找该变量,如果能够找到就会对它赋值。 28 | 29 | ### 接下来讲一下作用域嵌套: 30 | ```js 31 | var b = 1; 32 | function test() { 33 | console.log(b) 34 | } 35 | test(); 36 | ``` 37 | 38 | 当引擎执行到这一段代码时,会有以下几个步骤: 39 | * 引擎询问`test`的作用域是否存在`b`标识符 40 | * 结果在`test`的作用域中并不存在 41 | * 引擎接着询问`test`的上一级作用域(这里是全局作用域) 42 | * 结果找到了,开始使用`b` 43 | 44 | ### 总结下来,作用域就是一个集中管理变量,并且控制变量访问权限的枢纽。 45 | -------------------------------------------------------------------------------- /source/_posts/自定义页面系统设计(一).md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 自定义页面系统设计(一) 3 | date: 2018-10-15 19:56:56 4 | tags: 5 | --- 6 | 7 | ### 前言 8 | 9 | 在电商大环境里,活动页面的需求在前端领域是源源不断的,特别是在大促期间,就格外的多。 10 | 面对这堆枯燥无味还长的差不多的活动页面,前端🐶们往往是厌恶并且妈卖批的。 11 | 作为一个有梦想的搬砖的,我们当然是想办法解放我们的劳动力啦! 12 | 13 | ### 我们想要实现的究竟是个什么样的系统呢? 14 | 15 | 在回答这个问题之前,我们先来分析一下活动页面有哪些特征。 16 | * 活动页面的组成比较简单,通常由多个页面模块堆积组成,页面的布局不会很复杂 17 | * 模块的重用率很高,多个活动页面很大可能会用到同一个功能模块,只是数据会有不同 18 | * 绝大多数页面是一次性的,不需要后期维护 19 | * 活动页面的需求大,时间紧,运营说要就要 20 | * ... 21 | 22 | 逼逼了一大堆活动页面的特征,前端🐶心里想,这些简单的页面还让我来开发,简直就是浪费人才,要是运营自己能组装模块搭建页面就好了。 23 | 说了那么久终于进入正题了😄,对的!没错!我们就是要设计一个系统让运营来搭建活动页面,开发只需要负责开发功能模块。 24 | 25 | ### 开发过程中可能会遇到哪些问题 26 | 27 | 28 | #### 1.需要将页面拆分为基础模板和功能模块 29 | 30 | 页面由一个基础模板和多个功能模块组成,运营同学首先需要挑选一个页面的基础模板类型,然后再根据活动页面的功能组成,挑选功能模块来搭建页面。 31 | 32 | 举个例子:现在我们需要在微信容器内实现一个活动页面,这个页面涉及轮播图、页面导航和商品图墙三个功能,并且需要支持微信的一些功能。 33 | 这个场景下我们就需要一个微信基础模版,这个模版里面需要引入微信的一些`sdk`来支持微信独有的功能。并且我们需要实现三个功能模块,分别是轮播图模块、页面导航模块和商品图墙模块。 34 | 35 | #### 2.公共依赖抽离 36 | 一个页面由多个功能模块组成,而模块之间必然会存在公用的模块,比如`react`、`react-dom`等等。 37 | 那么如果每一个模块打包的时候都打把公用的三方包打进去,那么最后组装出来的页面就会包含很多重复的依赖包,体积过大就会影响页面性能。 38 | 39 | #### 3.模块通信 40 | 我们将页面拆分为多个模块之后,模块之间就是相互独立的了。我们需要通过另一种手段来实现模块之间的交互以及数据的通信。 41 | 42 | #### 4.模块信息配置 43 | 上面在分析特征的时候讲到模块重用的概率会很高,多个活动页面中可能存在同一个功能模块,只是数据会有些出入。 44 | 那么我们在实现公用功能模块的时候就需要考虑到如何通过配置信息生成一个模块?模块的配置信息存储在哪里?模块的后端数据请求怎么获取? 45 | 46 | #### 5.最终页面的拼装 47 | 解决完上述问题之后,我们就万事具备了哈,那么我们怎么将这些抽象的模块组装成一个可用的页面呢? 48 | 49 | 50 | 介绍完背景之后,让我们逐步来实现我们的构思吧!😁😁 51 | 52 | -------------------------------------------------------------------------------- /source/_posts/对前端路由选择的思考.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 对前端路由选择的思考 3 | date: 2018-08-09 20:11:01 4 | tags: 5 | --- 6 | 7 | ### 前言 8 | 随着 `SPA` 广泛应用在前端项目中,前端路由这个概念也伴随着火了起来。 9 | 这家伙目前主要是通过 `hash` 和 `hitory api` 这两个玩意实现,具体怎么实现的,后面稍微介绍一下。 10 | 11 | >本文不对这两个实现方案的好坏做过多评价,我依然觉得技术没有好坏,只有在具体的时间阶段和场景下,哪个更加合适一些。 12 | 13 | 接下来我来介绍一下这两个玩意的应用背景。 14 | 15 | ### `hash` 到 `history` 的变迁 16 | 在 `SPA` 单页面应用兴起的时候,大家都在寻找一个可以切换页面,且不会触发页面刷新的方案。 17 | 据我了解,`hash` 在被应用到前端路由之前,主要是被应用在页面锚点定位。 18 | 因为 `hash` 的`url` 信息携带主要依赖 `#`下面的字符,前端系统可以通过 `js` 解析页面路径信息来处理后续的逻辑,所以应该是当时的最优解决方案。 19 | 例如:`http://monajs.cn/docs/#home` 20 | 后来,`HTML5` 中新添加了 `History api` ,它支持切换和修改历史状态。主要通过 `back`、`forward`、`go` 21 | 三个方法,对应浏览器的前进,后退,跳转操。 22 | 修改历史状态包括了 `pushState`, `replaceState` 两个方法,这两个方法接收三个参数:`stateObj`,`title`,`url`。 23 | ```js 24 | history.pushState({name: 'yangxi'}, 'title', 'url') 25 | 26 | window.addEventListener('popstate', (e) => { 27 | if (e.state && e.state.name === 'yangxi') { 28 | return 29 | } 30 | //TODO 31 | }, false) 32 | ``` 33 | 从代码中可以看到,我们通过 `history` 做页面跳转的时候,可以携带信息,而且不像 `hash` 跳转那样只能携带有限的字符信息。`stateObj` 可以携带各种数据对象。 34 | >这是 `history api` 对前端路由切换的一次全新赋能。 35 | 36 | 另一方面,使用 `history api` 来设计前端路由,改善了之前 `#***` 的这种 `hash` 形式。一定程度上来讲美观了不少😄😄😄😄 37 | 38 | 以上特性,是我比较喜欢 `history api` 设计前端路由的重要理由,我自己也实现了一套基于 `history api` 的 [react-router](https://github.com/func-star/mo-react-router),有兴趣的朋友可以看一下。 39 | 40 | ### 前端路由实现思路 41 | 接下来我简单讲一下 `router` 的实现思路。 42 | 其实自己实现一个简单的 `router` 是比较简单的,我们只需要能监听到 `url` 的变化,然后对 `url` 进行一堆格式化分析,然后去匹配对应的页面实例。 43 | 44 | - `hash` 实现 45 | `hash` 实现路由是通过监听 `hashchange` 事件实现的,然后页面之间跳转则可以通过 `location.hash` 来做。 46 | - `history` 事件 47 | `history` 实现路由是通过监听 `popstate` 事件实现的,然后通过 `history api` 提供的 `pushState` 和 `replaceState` 来做页面的跳转和回退。 48 | 49 | >介绍的比较粗浅,仅代表个人的一些使用总结 50 | -------------------------------------------------------------------------------- /source/_posts/提升.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 提升 3 | date: 2018-06-26 19:50:18 4 | tags: 5 | --- 6 | 7 | 变量提升比较偏理论,可能会比较枯燥。不过了解原理能帮我们分析问题,解决问题。 8 | 9 | ### 在讲变量提升之前,我先简单的讲述一下函数作用域。 10 | ```js 11 | var a = 1; 12 | function test() { 13 | var b = 3; 14 | console.log( b ); // 3 15 | } 16 | test(); 17 | console.log( a ); // 1 18 | console.log(b); // 报错 19 | ``` 20 | 在这段代码中,在全局作用域中能访问到`a`和`test`却访问不到`b`,这是因为`test`在执行的时候生成了自己的函数作用域泡,只能在自身作用域内部能访问的到。 21 | 22 | ## 接下来直接从`demo`入手讲两个点: 23 | * 声明提升,赋值不会提升 24 | * 函数声明和变量声明都会被提升,但是函数声明优先与变量声明 25 | 26 | demo1: 27 | ```js 28 | console.log(a); // undefined 29 | var a = 1; 30 | ``` 31 | 很多人都理解这里能打印出`undefined`,而且都知道是因为变量声明被提升。但是为什么呢?? 32 | 33 | 我们在[作用域是干什么用的](https://github.com/func-star/scope/wiki/%E4%BD%9C%E7%94%A8%E5%9F%9F%E6%98%AF%E5%B9%B2%E4%BB%80%E4%B9%88%E7%94%A8%E7%9A%84)中讲到,代码的执行分为两个阶段,第一步是由编译器解析生成一个可执行的代码段,再由引擎负责来执行代码。简单的讲`var a =1`是由`var a`和`a = 1`两步组成的。 34 | 知道这个点之后我们来分析一下上面`demo1`的代码的逻辑。在引擎接手来执行这段代码之前,编译器会先解析这段代码,在解析过程中,`var a`会由编译器负责去询问当前作用域,让作用域创建这个`a`标识符并进行管理维护。 35 | 因此在引擎开始执行这段代码的时候,`a`变量已经存在与当前作用域中,只是未进行赋值操作。 36 | 37 | demo2: 38 | ```js 39 | test(); 40 | function test() { 41 | console.log( a ); // undefined 42 | var a = 1; 43 | } 44 | ``` 45 | 在demo2中,有两个点: 46 | 1. `test`函数声明被提升 47 | 1. 在`test`函数作用域中`a`变量声明也被提升 48 | 49 | demo3: 50 | ```js 51 | test(); // 1 52 | var test; 53 | function test() { 54 | console.log(1); 55 | } 56 | foo = function() { 57 | console.log(2); 58 | }; 59 | ``` 60 | 观察`demo3`中的代码,很多人可能以为`var test`在编译时已经被执行,所以在执行`test()`的时候应该是报`TypeError`,因为`test`当前是`undefined`,并未进行赋值操作。但是实际上却输出了结果:`1`。 61 | 因为函数声明会优先于变量声明,在编译器执行到`var test`时,`function test(){...}`已经被当前作用域所创建,因此`var test`这句代码被忽略,从而在访问`test()`的时候输出了`1` 。 62 | 63 | demo4: 64 | ```js 65 | test(); // 2 66 | var test; 67 | function test() { 68 | console.log(1); 69 | } 70 | function test() { 71 | console.log(2); 72 | } 73 | ``` 74 | 上述代码说明函数声明是可以被覆盖的。 75 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | # Hexo Configuration 2 | ## Docs: https://hexo.io/docs/configuration.html 3 | ## Source: https://github.com/hexojs/hexo/ 4 | 5 | # Site 6 | title: 杨玺的博客 7 | subtitle: 8 | description: 杨玺的博客 9 | keywords: blog 10 | author: yangxi 11 | language: zh-CN 12 | timezone: 13 | 14 | # URL 15 | ## If your site is put in a subdirectory, set url as 'http://yoursite.com/child' and root as '/child/' 16 | url: https://monajs.github.io 17 | root: / 18 | permalink: :year/:month/:day/:title/ 19 | permalink_defaults: 20 | 21 | # Directory 22 | source_dir: source 23 | public_dir: public 24 | tag_dir: tags 25 | archive_dir: archives 26 | category_dir: categories 27 | code_dir: downloads/code 28 | i18n_dir: :lang 29 | skip_render: 30 | 31 | # Writing 32 | new_post_name: :title.md # File name of new posts 33 | default_layout: post 34 | titlecase: false # Transform title into titlecase 35 | external_link: true # Open external links in new tab 36 | filename_case: 0 37 | render_drafts: false 38 | post_asset_folder: false 39 | relative_link: false 40 | future: true 41 | highlight: 42 | enable: false 43 | line_number: false 44 | auto_detect: false 45 | tab_replace: 46 | 47 | # Home page setting 48 | # path: Root path for your blogs index page. (default = '') 49 | # per_page: Posts displayed per page. (0 = disable pagination) 50 | # order_by: Posts order. (Order by date descending by default) 51 | index_generator: 52 | path: '' 53 | per_page: 0 54 | order_by: -date 55 | 56 | # Category & Tag 57 | default_category: uncategorized 58 | category_map: 59 | tag_map: 60 | 61 | # Date / Time format 62 | ## Hexo uses Moment.js to parse and display date 63 | ## You can customize the date format as defined in 64 | ## http://momentjs.com/docs/#/displaying/format/ 65 | date_format: YYYY-MM-DD 66 | time_format: HH:mm:ss 67 | 68 | # Pagination 69 | ## Set per_page to 0 to disable pagination 70 | per_page: 0 71 | pagination_dir: page 72 | 73 | # Extensions 74 | ## Plugins: https://hexo.io/plugins/ 75 | ## Themes: https://hexo.io/themes/ 76 | theme: hexo-theme-clean-blog 77 | 78 | # Deployment 79 | ## Docs: https://hexo.io/docs/deployment.html 80 | deploy: 81 | type: git 82 | repository: https://github.com/monajs/monajs.github.io.git 83 | branch: master 84 | -------------------------------------------------------------------------------- /source/_posts/对象复制之深复制和浅复制.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 对象复制之深复制和浅复制 3 | date: 2018-06-26 16:44:31 4 | tags: 5 | --- 6 | 7 | 首先我们先来了解一下深复制和浅复制之间的区别。 8 | 9 | 从字面上来看,我们会觉得浅复制就是非彻底的复制对象,而深复制即彻底的复制对象,新对象将跟原来的对象毫无关联。 10 | 实际上也差不多,浅复制只复制对象第一层的属性,而深复制则递归复制了所有层级的属性。 11 | 12 | 下面通过`demo`来分析一下 13 | ```js 14 | var origin = {a:1, b: {c:2}}; 15 | var origin1 = {a:1, b: [1,2,3]}; 16 | var obj = cloneObj(origin); 17 | var obj1 = cloneObj(origin1); 18 | 19 | function cloneObj(obj) { 20 | if(typeof obj !== 'object'){ 21 | return; 22 | } 23 | var res = {}; 24 | for(var i in obj) { 25 | if(obj.hasOwnProperty(i)){ 26 | res[i] = obj[i] 27 | } 28 | } 29 | return res; 30 | } 31 | 32 | console.log(obj); // {a:1, b: {c:2}}; 33 | console.log(obj1); // {a:1, b:[1,2,3]}; 34 | ``` 35 | 上面是一段简单的浅复制的代码,代码中`cloneObj`函数只对原对象的第一层做了赋值操作。这样一来,如果第一层属性中如果有对象,就会出现问题,如下代码所示: 36 | ```js 37 | obj.b.c = 5; 38 | console.log(origin.b.c); // 5 39 | obj1.b[0] = 5; 40 | console.log(origin1.b[0]); // 5 41 | ``` 42 | ### 可以看出,对于字符串类型,浅复制是对值的复制,但是对于对象来说,浅复制是对对象地址的复制,`obj`和`origin`指向的是同一块内存地址。 43 | 44 | 而对于深复制来讲,实际上就是用递归算法遍历原对象所有层级的属性,并复制到一个新生成的对象中,来看一下下面的`demo`: 45 | ```js 46 | var origin = {a:1, b: {c:2}}; 47 | var origin1 = {a:1, b: [1,2]}; 48 | var obj = deepCopy(origin); 49 | var obj1 = deepCopy(origin1); 50 | 51 | function deepCopy(obj) { 52 | if(typeof obj !== 'object') { 53 | return ; 54 | } 55 | var res; 56 | if(Object.prototype.toString.call(obj) === '[object Array]') { 57 | res = []; 58 | }else { 59 | res = {}; 60 | } 61 | 62 | for(var i in obj) { 63 | if(typeof obj[i] === 'object') { 64 | res[i] = deepCopy(obj[i]); 65 | }else { 66 | res[i] = obj[i]; 67 | } 68 | } 69 | return res; 70 | } 71 | 72 | console.log(obj); 73 | console.log(obj1); 74 | ``` 75 | 上述深度复制的代码考虑了数组和对象两种引用类型数据结构,接下来我们来测试一下: 76 | ```js 77 | obj.b.c = 5; 78 | console.log(origin.b.c); // 2 79 | obj1.b[0] = 5; 80 | console.log(origin1.b[0]); // 1 81 | ``` 82 | 83 | 然而在实际应用场景中,浅复制的场景要比深复制更为普遍,所以在 ES6 中定义了`Object.assign(..)`方法来实现浅复制。 84 | ```js 85 | var origin = {a:1, b: {c:2}}; 86 | var obj = Object.assign({}, origin); 87 | console.log(obj); // {a:1, b: {c:2}}; 88 | obj.b.c = 5; 89 | console.log(origin.b.c); // 5 90 | ``` 91 | `Object.assign` 会遍历一个或多个源对象的所有可枚举`(enumerable)`的自有键,并把它们复制(使用 = 操作符赋值)到目标对象,最后返回目标对象。 92 | -------------------------------------------------------------------------------- /source/_posts/自定义一个遍历器.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 自定义一个遍历器 3 | date: 2018-06-26 19:48:07 4 | tags: 5 | --- 6 | 7 | 在 es5 之前,遍历一个数组我们肯定会用标准的`for`循环。 8 | 9 | ```js 10 | var myArray = ['a', 'b', 'c']; 11 | for (var i = 0, l = myArray.length; i < l; i++) { 12 | console.log(myArray[i]); 13 | } 14 | // a, b, c 15 | ``` 16 | 实际上这里遍历的并不是数组的每一项值,而是数组的下标,通过下标指针来访问数组的值,如`myArray[i]`。 17 | 18 | 同样 `for..in` 遍历对象也是遍历对象的属性,无法直接获取属性值,需要通过手动去获取。 19 | 20 | 那么如何直接去遍历属性值呢?ES6 增加了一种用来遍历数组的 `for..of` 循环语法(如果对象本身定义了迭代器的话也可以遍历对象): 21 | ```js 22 | var myArray = ['a', 'b', 'c']; 23 | for (var v of myArray) { 24 | console.log( v ); 25 | } 26 | // a, b, c 27 | ``` 28 | `for..of`首先会向`myArray`请求一个迭代器对象,然后通过迭代器对象的`next()`方法来遍历访问所有值。 29 | 因为数组有内置的`@@itrator`,所以数组可以使用`for..of`,来看一下内部具体是怎么运行的: 30 | 31 | ```js 32 | var myArray = ['a', 'b', 'c']; 33 | var item = myArray[Symbol.iterator](); 34 | item.next(); // {value: "a", done: false} 35 | item.next(); // {value: "b", done: false} 36 | item.next(); // {value: "c", done: false} 37 | item.next(); // {value: undefined, done: true} 38 | ``` 39 | 我们可以理解为遍历器对象本质上,就是一个指针对象。 40 | 在执行遍历之前,我们先创建一个指针对象并指向数组的初始位置,在第一次调用`next()`的时候,指针往下移动一位,指向数组的第一个成员,并返回对应的`value`和是否已经遍历完毕。一直调用`next()`知道遍历完数组所有的成员,即`done: false`。 41 | 42 | ES6 规定,默认的 `Iterator` 接口部署在数据结构的`Symbol.iterator`属性,或者说,一个数据结构只要具有`Symbol.iterator`属性,就可以认为是“可遍历的”(`iterable`)。`Symbol.iterator`属性本身是一个函数,就是当前数据结构默认的遍历器生成函数。 43 | 44 | ### 接下来用进入主题,用几个例子来自定义一个遍历器。 45 | 46 | ```js 47 | var myObject = { a: 2,b: 3 }; 48 | 49 | for (var v of myObject) { 50 | console.log( v ); 51 | } 52 | // TypeError: myObject is not iterable 53 | ``` 54 | 当对一个对象进行遍历的时候,会抛出一个错误,因为对象内部没有定义`Symbol.iterator`返回函数。 55 | 当然我们可以自定义一个`@@iterator`来支持对象遍历,`@@iterator` 本身并不是一个迭代器对象,而是一个返回迭代器对象的函数。 56 | 57 | ```js 58 | var myObject = { a: 2,b: 3 }; 59 | 60 | Object.defineProperty(myObject, Symbol.iterator, { 61 | value: function() { 62 | var ins = this, 63 | index = 0, 64 | keys = Object.keys(ins); 65 | return { 66 | next: function () { 67 | return { 68 | value: ins[keys[index++]], 69 | done: index>keys.length 70 | } 71 | } 72 | } 73 | } 74 | }) 75 | for (var v of myObject) { 76 | console.log( v ); 77 | } 78 | // 2, 3 79 | ``` 80 | 81 | 自定义遍历器,我们相当于控制了`next()`的返回值(例如改变返回的值),通过这一个特性,可以实现很多个性化的逻辑。 82 | ```js 83 | var myObject = { a: 2,b: 3 }; 84 | 85 | Object.defineProperty(myObject, Symbol.iterator, { 86 | value: function() { 87 | var ins = this, 88 | index = 0, 89 | keys = Object.keys(ins); 90 | return { 91 | next: function () { 92 | return { 93 | value: ins[keys[index++]]*2, 94 | done: index>keys.length 95 | } 96 | } 97 | } 98 | } 99 | }) 100 | for (var v of myObject) { 101 | console.log( v ); 102 | } 103 | // 4, 6 104 | ``` 105 | -------------------------------------------------------------------------------- /source/_posts/axios和fetch实战分析.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: axios 和 fetch 实战分析 3 | date: 2018-08-08 20:09:42 4 | tags: 5 | --- 6 | 7 | ### 前言 8 | 趁着搭建公司新版 `react` 移动端脚手架的机会,对 `http-client` 库细致了解了一番。`axios` 和 `fetch`是目前比较火热的两款产品,这里就我了解到的一些信息记录一下。 9 | 10 | ### `fetch api` 使用总结 11 | 之前我在 PC 端项目中选择的是 `fetch api`,使用了将近两年左右,这里我将个人的使用体验分享一下。 12 | 13 | - `fetch` 默认不会携带 `cookie`, 我们需要手动设置 `credentials` 来支持 `cookie` 携带给服务端。 14 | - `fetch` 的 `response` 返回状态码比较特殊,它把`400`、`500`的返回码都当成成功的返回,我们在使用的时候可能需要特地封装一下。 15 | - `fetch` 没办法取消本次请求,只要你发送了本次请求,后续的情况都是不可控的。 16 | - 另外,我们知道 `fetch` 作为 `AJAX` 的一个替代品,它并不是基于 `XHR` 实现的,所以它并不能检测到请求的进度。 17 | 18 | 可能或许大概你会拿它和 `$.ajax`、`axios` 这类封装的比较完善的库做对比,而且开始吐槽,这东西怎么哪哪都不如 `axios` 。 19 | 这里我们先要确定一下 `fetch api` 的定位,它是一个更加偏底层的库,提供了丰富的 `API` 给开发者调用,可以支持很多的底层功能,但是可能找不到基于场景封装好的功能。 20 | 因此,我们在选择 `fetch` 的时候需要考虑我们是不是有这么复杂的需求需要通过 `fetch api` 来支持,用 `axios` 是不是会更省事? 21 | 22 | 上述介绍了一大堆的 `fetch api` 的缺点,可能大家会认为我对 `fetch api` 有什么偏见... 哈哈😄。实际上我近两年的 `http-client` 库选择的都是 `fetch api`。 23 | 还是那句话,没有最好的产品,只有最适合的产品。如果某个产品你用的习惯了,你就咋看咋顺眼。 24 | 25 | ### `axios` 使用总结 26 | 从去年下半年开始,我开始在线上移动端使用 `axios` 。接下来讲一下我个人的使用情况。 27 | 28 | 刚开始用的时候我也是看了很多的文档资料,毕竟网上把这东西夸的天花乱坠的,到底这东西魅力在哪里,得自己试试才知道呀。 29 | 30 | 这里先介绍一下我之前对 `axios` 做的分析,具体是不是真的有那么大魅力,我们后面用场景验证。 31 | - `axios` 支持设置全局默认配置,比如项目中基本不会变的根域名、`headers`配置这些,我们都可以初始化设置一发。 32 | - `axios` 拦截器也是一个比较好用的功能,它允许在发 `request` 之前和接收 `response` 之前进行自己的一些逻辑处理。 33 | - 还有一点比较重要的是,这家伙支持请求取消呀,这能一定程度的减少网络请求资源浪费。 34 | 35 | 以上三点是我比较 care 的特性,其他的我就不罗列介绍了。 36 | 下面我们来看一下怎么封一个 `axios` 类(临时写的一个思路,未测试),让它帮我们的业务更好的赋能。 37 | ```js 38 | import axios from 'axios' 39 | import Evnets from 'mona-events' 40 | 41 | const CancelToken = axios.CancelToken 42 | 43 | class Ajax extends Evnets { 44 | constructor(props) { 45 | super(props) 46 | axios.default.baseURL = 'https://api.monajs.cn' 47 | this.filter 48 | } 49 | 50 | get(url, params = {}, canCancel = false, cancelOption) { 51 | if (canCancel) { 52 | params.cancelToken = new CancelToken(function executor(c) { 53 | // executor 函数接收一个 cancel 函数作为参数 54 | cancelOption = c; 55 | }) 56 | } 57 | return axios.get(url, params); 58 | } 59 | 60 | post(url, params = {}, canCancel = false, cancelOption) { 61 | if (canCancel) { 62 | params.cancelToken = new CancelToken(function executor(c) { 63 | // executor 函数接收一个 cancel 函数作为参数 64 | cancelOption = c; 65 | }) 66 | } 67 | return axios.post(url, params) 68 | } 69 | 70 | filter() { 71 | axios.interceptors.request.use(config => { 72 | // 在发送请求之前做些什么 73 | return config; 74 | }, error => { 75 | // 对请求错误做些什么 76 | return Promise.reject(error); 77 | }); 78 | axios.interceptors.response.use(response => { 79 | // 对响应数据做点什么 80 | return response; 81 | }, error => { 82 | // 对响应错误做点什么 83 | return Promise.reject(error); 84 | }) 85 | } 86 | } 87 | ``` 88 | 89 | 上面基本上阐述了两个事情: 90 | - `axios` 配合我们的业务场景使用,例如结合服务端的业务返回码,接口请求异常,做统一逻辑处理,规避掉很多冗余代码。 91 | - `axios` 类初始化一些配置信息,在真实使用的时候其他的接口服务层类可以继承这个类,可以改写覆盖这些配置信息,也可以使用默认值。 92 | - `axios` 类配合 `Events` 类完成服务接口监听者模式,更高效的实现接口数据分发,减少掉代码中的回调函数传递。(这个点后续专门开一篇文章详细阐述其使用魅力) 93 | 94 | 总结下来我的观点是选择哪个 `http-client` 库进行业务开发取决于实际场景,在移动端开发,我个人比较偏向于 `axios` 。它能够帮我们较少成本的上手使用。 95 | -------------------------------------------------------------------------------- /source/_posts/树形数组递归优化算法.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 树形数组递归优化算法 3 | date: 2018-06-08 20:05:43 4 | tags: 5 | --- 6 | 7 | 最近在项目中遇到的场景,在这分享一下。 8 | 9 | 通常来讲,对一个树形数组做处理,特别是层级未知的数据结构,第一想法肯定是遍历。在不考虑性能的情况下,基本上都能解决。 10 | 11 | 下面来个例子: 12 | 数据结构: 13 | ```js 14 | let data = [{ 15 | id: 1, 16 | name: 'a', 17 | children: [ 18 | { id: 11, name: 'aa' }, 19 | { 20 | id: 12, 21 | name: 'ab', 22 | children: [ 23 | { id: 121, name: 'aba' }, 24 | { id: 122, name: 'abb' } 25 | ] 26 | }, 27 | { id: 13, name: 'ac' } 28 | ] 29 | }, 30 | { 31 | id: 2, 32 | name: 'b', 33 | children: [ 34 | { id: 21, name; 'ba' }, 35 | { id: 22, name; 'bb' } 36 | ] 37 | }, 38 | { id: 3, name; 'c' } 39 | ]; 40 | ``` 41 | 现在我们先用大家熟悉的递归来实现一遍。 42 | 43 | ```js 44 | let test = (() => { 45 | let res = ''; 46 | return inner = (data, id) => { 47 | if (!data || data.length === 0) { 48 | return 49 | } 50 | 51 | data.forEach(v => { 52 | if (v.id === id) { 53 | res = v.name 54 | } 55 | 56 | if (v.children && v.children.length > 0) { 57 | inner(v.children, id) 58 | } 59 | }) 60 | return res; 61 | } 62 | })() 63 | 64 | let val = test(data, 123); 65 | console.log(val); 66 | ``` 67 | 68 | 实现完成之后我们会思考怎么才能把它的性能提高,特别是在树层级特别深的场景下,递归的性能还是比较难以接受的。 69 | 70 | ## 优化方案: 71 | 针对二叉树遍历,有深度优先和广度优先两种通用解决方案,我们可以根据数据结构的广度和深度选择最优的解决方案。虽然在前端日常开发中,很少会用到算法,但是这种性能优化的方案还是可以借鉴的。 72 | 73 | ### 广度优先 74 | 根据广度优先的思路来看一下代码实现。 75 | ```js 76 | let test2 = (() => { 77 | let res = ''; 78 | return inner = (data, id) => { 79 | if (!data || data.length === 0) { 80 | return 81 | } 82 | let stark = []; // 初始化一个数据栈 83 | // 将第一层数据压入栈 84 | data.forEach(v => { 85 | stark.push(v); 86 | }) 87 | 88 | while (stark.length > 0) { 89 | let item = stark.shift(); 90 | if (item.id === id) { 91 | res = item.name; 92 | } 93 | if (item.children && item.children.length > 0) { 94 | stark = stark.concat(item.children) 95 | } 96 | } 97 | return res; 98 | } 99 | })() 100 | 101 | let val = test2(data, 123); 102 | console.log(val); 103 | ``` 104 | 105 | ### 深度优先 106 | 根据深度优先的思路来看一下代码实现。 107 | ```js 108 | let test3 = (() => { 109 | let res = ''; 110 | return inner = (data, id) => { 111 | if (!data || data.length === 0) { 112 | return 113 | } 114 | 115 | let stark = []; // 初始化一个数据栈 116 | // 将第一层数据压入栈 117 | data.forEach(v => { 118 | stark.push(v); 119 | }) 120 | 121 | while (stark.length > 0) { 122 | let item = stark.shift(); 123 | if (item.id === id) { 124 | res = item.name; 125 | } 126 | if (item.children && item.children.length > 0) { 127 | stark = item.children.concat(stark); 128 | } 129 | } 130 | return res; 131 | } 132 | })() 133 | 134 | let val = test3(data, 123); 135 | console.log(val); 136 | ``` 137 | 138 | -------------------------------------------------------------------------------- /source/_posts/闭包.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 闭包 3 | date: 2018-06-26 20:00:31 4 | tags: 5 | --- 6 | 7 | 闭包一直以一种神秘的姿态出现在前端领域中。大家都知道这东西在代码中无处不在,可又很难表达出这东西是什么干嘛用的。接下来我来简单聊一下我对闭包的理解,篇幅不会太长,意在让大家理解。 8 | 9 | 对闭包的使用释义,网上一搜一大堆,主要可以概括为以下几种: 10 | * 内部作用域访问到了上级作用域的变量 11 | * 内部函数被传递到所在词法作用域以外来调用 12 | * LIFE模式(立即执行函数) 13 | * 模块模式 14 | 15 | 这些都是闭包的使用场景,由此可见闭包的功能真的很强,而且细微实用。 16 | 17 | 在正式开始讲闭包之前,我先来回顾以下引擎这个东西,在[作用域是干什么用的](https://github.com/func-star/scope/wiki/%E4%BD%9C%E7%94%A8%E5%9F%9F%E6%98%AF%E5%B9%B2%E4%BB%80%E4%B9%88%E7%94%A8%E7%9A%84)中介绍过引擎负责执行经由编译器解析后生成的可执行的代码段。 18 | 这里再补充一点,引擎还拥有一垃圾回收机制,当作用域为标识符分配的内存空间不再被使用的时候,就会由垃圾回收器负责将其回收并释放该内存。 19 | 20 | ### 下面来看一段代码来给出我对闭包的理解定义: 21 | ```js 22 | function test() { 23 | var a = 1; 24 | function inner() { 25 | console.log(a); 26 | } 27 | return inner; 28 | } 29 | var fn = test(); 30 | fn(); // 1 31 | ``` 32 | 当`test()`执行结束之后,我们可能会以为整个`test()`的内部作用域都被销毁掉,因为引擎的垃圾回收器会负责释放不再被使用的内存。然后实际上`inner`被正常执行了,并且访问到了`a`。 33 | 34 | 这个现象的背后就是因为闭包在阻止着这次垃圾回收。我们观察代码可以看出`inner`函数在使用着`a`标识符(变量),而`a`变量却被定义在了其上级作用域中,因此在`inner`函数执行之前,引擎识别到`a`留着还有用,所以不能清理`a`所在的作用域,这就是闭包的一种现象。 35 | 36 | 现在我们来归纳以下,内部作用域保持着对外部作用域的引用,这个引用其实就是闭包。我个人理解闭包其实就是一个作用域的集合,它包含自身的词法作用域和对执行过程中用得到的作用域。在上面的代码中,`inner()`一直保持有对`test()`整个作用域的引用。 37 | 38 | ### 希望上面的解释可以为你解惑,下面来看一个用烂了的例子: 39 | 40 | ```js 41 | for (var i=1; i<=5; i++) { 42 | setTimeout( function test() { 43 | console.log(i); 44 | }, i*1000 ); 45 | } 46 | ``` 47 | 这个`demo`很多人肯定已经看很多遍了, 知道会输出5个`6`。这里我结合上面的理论来阐述一遍。 48 | 49 | 这里先重温一下两个点: 50 | 1. 词法作用域,引擎会先向当前作用域询问变量是否存在,如果找不到就往上级找。 51 | 2. `setTimeout`是一个异步方法,他的回掉函数并不会立马被添加到主进程中来执行。 52 | 53 | 下面我来翻译一下上面的代码: 54 | ```js 55 | var i = 1; 56 | setTimeout( function test() { 57 | console.log(i); 58 | }, 1000 ); 59 | 60 | i = 2; 61 | setTimeout( function test() { 62 | console.log(i); 63 | }, 2000 ); 64 | 65 | i = 3; 66 | setTimeout( function test() { 67 | console.log(i); 68 | }, 3000 ); 69 | 70 | i = 4; 71 | setTimeout( function test() { 72 | console.log(i); 73 | }, 4000 ); 74 | 75 | i = 5; 76 | setTimeout( function test() { 77 | console.log(i); 78 | }, 5000 ); 79 | 80 | i = 6 81 | ``` 82 | 实际上,`setTimeout`的回调函数`test`访问的都是同一个共享的作用域(这里其实就是全局作用域)。而且`setTimeout`是一个异步的执行过程,因此等`test`执行到的时候`i`早已经被赋值为`6`。所以会输出5个`6`。 83 | 84 | 其实想要让它输出`12345`,大家肯定也都会,我们只需要用LIFE(立即执行函数)包一下这个`for`循环中的执行体,并将即时的状态i记录在内部作用域中,那当`test`执行的时候就能访问到不同的`i`了,如下代码: 85 | ```js 86 | for (var i=1; i<=5; i++) { 87 | (function(j){ 88 | setTimeout( function test() { 89 | console.log(j); 90 | }, j*1000 ); 91 | }(i)) 92 | } 93 | ``` 94 | 95 | 下面我们也用代码来解释这个过程: 96 | ```js 97 | var i = 1; 98 | (function(){ 99 | var j = i 100 | setTimeout( function test() { 101 | console.log(j); 102 | }, 1000 ); 103 | }()) 104 | 105 | i = 2; 106 | (function(){ 107 | var j = i 108 | setTimeout( function test() { 109 | console.log(j); 110 | }, 2000 ); 111 | }()) 112 | 113 | i = 3; 114 | (function(){ 115 | var j = i 116 | setTimeout( function test() { 117 | console.log(j); 118 | }, 3000 ); 119 | }()) 120 | 121 | i = 4; 122 | (function(){ 123 | var j = i 124 | setTimeout( function test() { 125 | console.log(j); 126 | }, 4000 ); 127 | }()) 128 | 129 | i = 5; 130 | (function(){ 131 | var j = i 132 | setTimeout( function test() { 133 | console.log(j); 134 | }, 5000 ); 135 | }()) 136 | 137 | i = 6 138 | ``` 139 | 140 | 从上述代码可以看到实际上`test()`在执行的时候访问的标识符`j`已经是当前作用域的上级作用域私有的,不再是共享同一个作用域。 141 | 142 | 另一种方法当然是用`let`来生成块级作用域,道理都差不多,不再介绍。 143 | -------------------------------------------------------------------------------- /source/_posts/React简析之实例化.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: React简析之实例化 3 | date: 2018-07-16 20:07:19 4 | tags: 5 | --- 6 | 7 | ### 概述 8 | 这是一个 `React` 原理解读的系列,将带你一步步实现一个简化版的 `React`。不多说废话,现在开始!!! 9 | 代码地址:[v1.0](https://github.com/func-star/mo-react/tree/v1.0) 10 | 11 | 12 | ### `React`的主入口 13 | ```js 14 | import { render } from 'react-dom' 15 | ... 16 | render(123, document.getElementById('appWrapper')) 17 | ``` 18 | 这行代码大家肯定都非常熟悉,`render()` 函数是 `React` 最基本的方法,是整个 `React` 执行的开端。 19 | 我们可以看到 `render` 接收两个参数,一个 `dom` 模版,另一个是模版插入的位置,那么 `render()` 具体做了哪些事情呢? 20 | ```js 21 | // ReactMount.js 22 | 23 | import ReactInstantiate from './ReactInstantiate' 24 | 25 | class ReactMount { 26 | render(nextElement, container, callback) { 27 | // 节点实例化 28 | let instance = new ReactInstantiate(nextElement, null, true) 29 | console.log(instance) 30 | // 节点挂载 31 | let node = instance.mount(null, true) 32 | container.appendChild(node) 33 | } 34 | } 35 | 36 | export default new ReactMount 37 | ``` 38 | 39 | 从代码中我们可以看到 `render()` 先会将接收到的 `dom` 结构进行一个实例化的过程,并生成一个实例对象数据结构`instance`。 40 | 接着是对实例对象进行挂载,这一节主要是将 `React` 节点解析成浏览器原生节点,添加合成事件、绑定可识别属性等。这一块我们在下一节会进行讲解。 41 | 再接着就是节点插入。 42 | 43 | ### 下面我们来介绍一下节点实例化到底干了哪些事情 44 | 45 | 在讲实例化之前,首先我们先了解一下 `React` 节点分为4种节点类型,分别是空节点、文本节点、原生节点(浏览器节点)以及 `React` 节点。 46 | 根据这个我们先来定义一个数据字典,如下: 47 | ```js 48 | // constant.js 49 | 50 | export default { 51 | VERSION: '0.0.1', 52 | REACT_NODE: 'reactNode', 53 | EMPTY_NODE: 'emptyNode', 54 | TEXT_NODE: 'textNode', 55 | NATIVE_NODE: 'nativeNode' 56 | } 57 | 58 | ``` 59 | 实际上,每一个节点实例都会存储节点的一些信息,包含节点类型、节点的唯一key、节点的dom数据、子节点实例数组。 60 | ```js 61 | // ReactInstantiate.js 62 | 63 | import reactDom from './ReactDom' 64 | import reactEvents from './ReactEvents' 65 | import ReactUpdater from './ReactUpdater' 66 | import Constant from '../constant' 67 | import Util from '../util' 68 | import ValueData from '../data' 69 | 70 | export default class ReactInstantiate { 71 | constructor(element, key) { 72 | // react节点 73 | this.currentElement = element 74 | // 节点唯一key 75 | this.key = key 76 | // 节点类型 77 | this.nodeType = reactDom.getElementType(element) 78 | // 节点实例化数据结构 79 | this.childrenInstance = this.instanceChildren() 80 | } 81 | 82 | // 递归实例化所有节点,忽略react组件节点 83 | instanceChildren() { 84 | if (this.nodeType === Constant.EMPTY_NODE || this.nodeType === Constant.TEXT_NODE || !this.currentElement.props || !this.currentElement.props.children) { 85 | return 86 | } 87 | 88 | let child = Util.toArray(this.currentElement.props.children) 89 | let childrenInstance = [] 90 | // 为每一个节点添加唯一key 91 | child.forEach((v, i) => { 92 | let key = null 93 | if (null !== v && typeof v === 'object' && v.key) { 94 | key = '__@_' + v.key 95 | } 96 | if (Util.isArray(v)) { 97 | let c = [] 98 | v.forEach((item, index) => { 99 | let itemKey = '__$_' + i + '_' + index 100 | if (null !== v && typeof v === 'object') { 101 | if (!item.key) { 102 | console.error(ValueData.keyNeedMsg) 103 | } else { 104 | itemKey = '__@_' + i + '_' + item.key 105 | } 106 | } 107 | c.push(new ReactInstantiate(item, itemKey)) 108 | }) 109 | childrenInstance.push(c) 110 | } else { 111 | childrenInstance.push(new ReactInstantiate(v, key)) 112 | } 113 | }) 114 | return childrenInstance 115 | } 116 | } 117 | ``` 118 | 119 | 至此,节点的实例化过程已经完成,我们得到一个数据装载完整的`react`实例数据。 120 | 这边介绍的代码并不完整,可以前往[v1.0](https://github.com/func-star/mo-react/tree/v1.0),clone下来本地跑一遍。 121 | -------------------------------------------------------------------------------- /source/_posts/自定义页面系统设计(二).md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 自定义页面系统设计(二) 3 | date: 2018-10-16 19:39:37 4 | tags: 5 | --- 6 | 7 | 接着[上一节](https://monajs.github.io/2018/10/15/%E8%87%AA%E5%AE%9A%E4%B9%89%E9%A1%B5%E9%9D%A2%E7%B3%BB%E7%BB%9F%E6%9E%B6%E6%9E%84%E8%AE%BE%E8%AE%A1%EF%BC%88%E4%B8%80%EF%BC%89/)讲,这个章节主要会讲解如何解决下面几个点: 8 | 9 | * 需要将页面拆分为基础模板和功能模块 10 | * 公共依赖抽离 11 | * 模块通信 12 | 13 | > 建议按照顺序来阅读哟~ 14 | 15 | 16 | ### 将页面拆分为基础模板和功能模块 17 | 18 | [上一节](https://monajs.github.io/2018/10/15/%E8%87%AA%E5%AE%9A%E4%B9%89%E9%A1%B5%E9%9D%A2%E7%B3%BB%E7%BB%9F%E6%9E%B6%E6%9E%84%E8%AE%BE%E8%AE%A1%EF%BC%88%E4%B8%80%EF%BC%89/)讲到我们想要实现的是一个功能模块可以重复使用的系统,运营可以选择一个基础页面模版,然后自由的选择多个功能模块来搭建页面(可视化界面的设计后续会作介绍,这里先不涉及)。 19 | 20 | 基于这个点我们需要准备好以下几点: 21 | 22 | #### 1. 统一维护一个基础页面模板集合,开发同学可以方便的添加和修改 23 | 24 | 默认模版我们维护在[capricorn-html-template](https://github.com/capricornjs/capricorn-html-template)项目的`default`分支下,如果有需要新增一份模版,可以在该项目里添加分支并创建模板。 25 | 26 | 27 | #### 2.统一维护一个功能模块初始化项目集合,保证所有开发出来的功能模块格式统一,方便后期的维护 28 | 29 | 默认初始化项目我们维护在[capricorn-module-template](https://github.com/capricornjs/capricorn-module-template)的`default`分支下,如果有需要新增一份初始化代码,可以在该项目里添加分支并创建。 30 | 31 | #### 3.开发一个命令行工具,可以方便快捷的初始化一个功能模块项目 32 | 33 | * 安装 34 | ```bash 35 | npm i capricorn-cli -g 36 | ``` 37 | 38 | * 开始使用 39 | ```bash 40 | capricorn init 41 | ``` 42 | 43 | 现在我们可以来试一下了哈😁嘿嘿~ 44 | 我们通过`capricorn init`出来的项目,只需要`npm i`安装完依赖之后,`npm start`就可以启动项目了哈~ 45 | 接下来我们就可以随心所欲的进行 coding 了。 46 | 47 | 当功能模块实现完之后,我们可以方便的通过`npm run build`来进行模块打包,打包后的模块压缩包会生成在`assets`文件夹下,然后通过`npm run release`发布(目前`release`命令在`demo`阶段还未开发)。 48 | 49 | 这样一番操作之后一个独立的功能模块包就实现好了,是不是非常的简单😄~ 50 | 51 | 52 | ### 公共依赖抽离 53 | 54 | 上面我们介绍,我们只需要`capricorn init`就可以初始化一个模块项目,那么如果每一个项目都独立打包自己的三方依赖包,那么最终生成的页面体积就会非常的庞大。 55 | 出于这个考虑,我们肯定需要做三方依赖抽离,将这些公共的依赖统一在全局管理,供所有的功能模块使用。 56 | 那么将公共依赖放在基础页面模版里就是一个比较好的选择了。 57 | 58 | ```html 59 | 60 |
61 | 62 |