├── .DS_Store ├── .gitconfig ├── .zshrc ├── 09308211.bmp ├── HTML5笔记.docx ├── JS当中事件轮训机制.gif ├── docs ├── AngularJS笔记.md ├── ES5.md ├── ES6规范.md ├── HTML5读书笔记.md ├── IE6下fixed失效.md ├── JS基础深入理解.md ├── JS当中事件轮训机制.md ├── PM2服务器部署.md ├── README.md ├── compile │ ├── codegen.md │ ├── entrance.md │ ├── index.md │ ├── optimize.md │ └── parse.md ├── components │ ├── async-component.md │ ├── component-register.md │ ├── create-component.md │ ├── index.md │ ├── lifecycle.md │ ├── merge-option.md │ └── patch.md ├── cyarn使用手册.md ├── data-driven │ ├── create-element.md │ ├── index.md │ ├── mounted.md │ ├── new-vue.md │ ├── render.md │ ├── update.md │ └── virtual-dom.md ├── extend │ ├── event.md │ ├── index.md │ ├── keep-alive.md │ ├── slot.md │ ├── tansition-group.md │ ├── tansition.md │ └── v-model.md ├── git使用手册.md ├── grunt快速入门.md ├── gulp快速入门.md ├── htmlDom操作.txt ├── javascript.txt ├── javascript数据类型与运算符.txt ├── jquery常用方法.txt ├── js高级.md ├── module模块儿化编程.md ├── mongoDb命令.txt ├── mongoose 课件.txt ├── prepare │ ├── build.md │ ├── directory.md │ ├── entrance.md │ ├── flow.md │ └── index.md ├── reactive │ ├── component-update.md │ ├── computed-watcher.md │ ├── getters.md │ ├── index.md │ ├── next-tick.md │ ├── questions.md │ ├── reactive-object.md │ ├── setters.md │ └── summary.md ├── rsyncd.md ├── vscode_key_map.txt ├── vue-router │ ├── index.md │ ├── install.md │ ├── matcher.md │ ├── router.md │ └── transition-to.md ├── vue.txt ├── vuex.txt ├── vuex │ ├── api.md │ ├── index.md │ ├── init.md │ └── plugin.md ├── w3c规范基础知识-1.md ├── w3c规范基础知识.md ├── 作用域与对象.txt ├── 正则表达式2.md ├── 浏览器兼容性.md ├── 淘宝样式初始化代码.txt ├── 深入理解npm脚本script.md ├── 深入理解闭包代码.md ├── 百度地图使用.md ├── 移动端.md ├── 解决浏览器兼容性.txt └── 设备像素.md ├── greenWall.jpg ├── hosts ├── nginx.conf ├── nt9.zsh-theme ├── openssl-生成自签名https证书.docx ├── rsyncd.conf ├── settings.jar ├── test.js ├── test2.js ├── webstorm激活地址 ├── 日期和文件上传.js └── 移动端 └── 2_三个视口.md /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MrIrick/noteJS/49535042eb23d2d892eb355cd209fbbd925416b4/.DS_Store -------------------------------------------------------------------------------- /.gitconfig: -------------------------------------------------------------------------------- 1 | [user] 2 | name = chihaiping 3 | email = chihaiping@meituan.com 4 | [color] 5 | ui = auto 6 | branch = auto 7 | diff = auto 8 | status = auto 9 | [color "branch"] 10 | current = green 11 | local = yellow 12 | remote = red 13 | [color "diff"] 14 | meta = yellow bold 15 | frag = magenta bold 16 | old = red bold 17 | new = green bold 18 | [color "status"] 19 | added = yellow 20 | changed = green 21 | untracked = cyan 22 | [alias] 23 | di = diff 24 | ci = commit 25 | co = checkout 26 | br = branch 27 | gst = git status 28 | unstage = reset HEAD 29 | lst =log -1 30 | lg = log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit 31 | gba = git branch -a 32 | gbl = git blame -b -w 33 | cmt = commit 34 | cout = git checkout 35 | dif = git diff 36 | diffca = git diff --cached 37 | fh = git fetch 38 | ad = git add 39 | st = status 40 | last = log -1 41 | [core] 42 | editor = vim 43 | excludesfile = /Users/chihp/.gitignore_global 44 | [i18n] 45 | commitencoding = iso-8859 46 | logoutputencoding = iso-8859 47 | [difftool "sourcetree"] 48 | cmd = opendiff \"$LOCAL\" \"$REMOTE\" 49 | path = 50 | [mergetool "sourcetree"] 51 | cmd = /Applications/SourceTree.app/Contents/Resources/opendiff-w.sh \"$LOCAL\" \"$REMOTE\" -ancestor \"$BASE\" -merge \"$MERGED\" 52 | trustExitCode = true 53 | [commit] 54 | template = /Users/chihp/.stCommitMsg 55 | -------------------------------------------------------------------------------- /.zshrc: -------------------------------------------------------------------------------- 1 | # If you come from bash you might have to change your $PATH. 2 | # export PATH=$HOME/bin:/usr/local/bin:$PATH 3 | 4 | # Path to your oh-my-zsh installation. 5 | export ZSH=/Users/chihp/.oh-my-zsh 6 | 7 | export TTC_BOTS='tinycarebot,selfcare_bot,magicrealismbot' 8 | export TTC_REPOS='~/WebstormProjects/banma_page' 9 | export TTC_WEATHER='Beijing' 10 | export TTC_CELSIUS=true 11 | export TTC_APIKEYS=false 12 | export TTC_UPDATE_INTERVAL=20 13 | 14 | # Set name of the theme to load. Optionally, if you set this to "random" 15 | # it'll load a random theme each time that oh-my-zsh is loaded. 16 | # See https://github.com/robbyrussell/oh-my-zsh/wiki/Themes 17 | ZSH_THEME="nt9" 18 | PROMPT='%{$fg_bold[red]%}➜ %{$fg_bold[green]%}%p%{$fg[cyan]%}%d %{$fg_bold[blue]%}$(git_prompt_info)%{$fg_bold[blue]%}% %{$reset_color%}>' 19 | 20 | # Uncomment the following line to use case-sensitive completion. 21 | # CASE_SENSITIVE="true" 22 | 23 | # Uncomment the following line to use hyphen-insensitive completion. Case 24 | # sensitive completion must be off. _ and - will be interchangeable. 25 | # HYPHEN_INSENSITIVE="true" 26 | 27 | # Uncomment the following line to disable bi-weekly auto-update checks. 28 | # DISABLE_AUTO_UPDATE="true" 29 | 30 | # Uncomment the following line to change how often to auto-update (in days). 31 | # export UPDATE_ZSH_DAYS=13 32 | 33 | # Uncomment the following line to disable colors in ls. 34 | # DISABLE_LS_COLORS="true" 35 | 36 | # Uncomment the following line to disable auto-setting terminal title. 37 | # DISABLE_AUTO_TITLE="true" 38 | 39 | # Uncomment the following line to enable command auto-correction. 40 | # ENABLE_CORRECTION="true" 41 | 42 | # Uncomment the following line to display red dots whilst waiting for completion. 43 | # COMPLETION_WAITING_DOTS="true" 44 | 45 | # Uncomment the following line if you want to disable marking untracked files 46 | # under VCS as dirty. This makes repository status check for large repositories 47 | # much, much faster. 48 | # DISABLE_UNTRACKED_FILES_DIRTY="true" 49 | 50 | # Uncomment the following line if you want to change the command execution time 51 | # stamp shown in the history command output. 52 | # The optional three formats: "mm/dd/yyyy"|"dd.mm.yyyy"|"yyyy-mm-dd" 53 | # HIST_STAMPS="mm/dd/yyyy" 54 | 55 | # Would you like to use another custom folder than $ZSH/custom? 56 | # ZSH_CUSTOM=/path/to/new-custom-folder 57 | 58 | # Which plugins would you like to load? (plugins can be found in ~/.oh-my-zsh/plugins/*) 59 | # Custom plugins may be added to ~/.oh-my-zsh/custom/plugins/ 60 | # Example format: plugins=(rails git textmate ruby lighthouse) 61 | # Add wisely, as too many plugins slow down shell startup. 62 | plugins=(git autojump osx) 63 | #compile bash when loaded 64 | source ./.bash_profile 65 | source $ZSH/oh-my-zsh.sh 66 | 67 | # User configuration 68 | 69 | # export MANPATH="/usr/local/man:$MANPATH" 70 | 71 | # You may need to manually set your language environment 72 | # export LANG=en_US.UTF-8 73 | 74 | # Preferred editor for local and remote sessions 75 | # if [[ -n $SSH_CONNECTION ]]; then 76 | # export EDITOR='vim' 77 | # else 78 | # export EDITOR='mvim' 79 | # fi 80 | 81 | # Compilation flags 82 | # export ARCHFLAGS="-arch x86_64" 83 | 84 | # ssh 85 | # export SSH_KEY_PATH="~/.ssh/rsa_id" 86 | 87 | # Set personal aliases, overriding those provided by oh-my-zsh libs, 88 | # plugins, and themes. Aliases can be placed here, though oh-my-zsh 89 | # users are encouraged to define aliases within the ZSH_CUSTOM folder. 90 | # For a full list of active aliases, run `alias`. 91 | # 92 | # Example aliases 93 | # alias zshconfig="mate ~/.zshrc" 94 | # alias ohmyzsh="mate ~/.oh-my-zsh" 95 | 96 | export NVM_DIR="/Users/chihp/.nvm" 97 | [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm 98 | 99 | 100 | alias mnpm="npm --registry=http://r.npm.sankuai.com \ 101 | --cache=$HOME/.cache/mnpm \ 102 | --disturl=http://npm.sankuai.com/dist/node \ 103 | --userconfig=$HOME/.mnpmrc" 104 | 105 | [[ -s ~/.autojump/etc/profile.d/autojump.sh ]] && . ~/.autojump/etc/profile.d/autojump.sh 106 | 107 | 108 | 109 | #new paths added by me 110 | 111 | alias sub='open -a "Sublime Text3"' 112 | alias mkd='open -a "MWeb Lite"' 113 | alias lsl='ls -l' 114 | alias lsla='ls -l -a' 115 | alias mongodb='mongod dbpath /Users/chihp/DBStorage/data' 116 | alias mongod='mongod --dbpath /Users/chihp/DBStorage/data --logpath /Users/chihp/DBStorage/log/mongo.log' 117 | 118 | function powerline_precmd() { 119 | export PS1="$(~/powerline-shell.py $? --shell zsh 2> /dev/null)" 120 | } 121 | 122 | function install_powerline_precmd() { 123 | for s in "${precmd_functions[@]}"; do 124 | if [ "$s" = "powerline_precmd" ]; then 125 | return 126 | fi 127 | done 128 | precmd_functions+=(powerline_precmd) 129 | } 130 | 131 | -------------------------------------------------------------------------------- /09308211.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MrIrick/noteJS/49535042eb23d2d892eb355cd209fbbd925416b4/09308211.bmp -------------------------------------------------------------------------------- /HTML5笔记.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MrIrick/noteJS/49535042eb23d2d892eb355cd209fbbd925416b4/HTML5笔记.docx -------------------------------------------------------------------------------- /JS当中事件轮训机制.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MrIrick/noteJS/49535042eb23d2d892eb355cd209fbbd925416b4/JS当中事件轮训机制.gif -------------------------------------------------------------------------------- /docs/AngularJS笔记.md: -------------------------------------------------------------------------------- 1 | ## AngularJS笔记 2 | AngularJS 应用组成如下: 3 | View(视图), 即 HTML。 4 | Model(模型), 当前视图中可用的数据。 5 | Controller(控制器), 即 JavaScript 函数,可以添加或修改属性。 6 | scope 是模型。 7 | scope 是一个 JavaScript 对象,带有属性和方法,这些属性和方法可以在视图和控制器中使用。 8 | 指令和表达式都可以访问域模型对象中的属性和方法, 例:ng-model="name", {{name}} 9 | 控制器通过影响模型中数据,来实时修改页面中的数据,模型和视图中的数据存在双向绑定关系 10 | 11 | ### ng-app指令 12 | 这个指令用来表示angulajs根域模型对象的作用范围,当页面构建完毕,该指令会调用对应的模型构造函数来生成根对象 13 | 接收来自angularjs作用范围的页面数据,单向数据流 14 | 15 | * ng-model指令 16 | ``` 17 | ng-model 指令用于绑定应用程序数据到 HTML 控制器(input, select, textarea)的值。 18 | ``` 19 | - ng-model 指令可以将输入域的值与 AngularJS 创建的变量绑定。 20 | - 双向绑定,在修改输入域的值时, AngularJS 属性的值也将修改 21 | - ng-model 指令可以为应用数据提供状态值(invalid, dirty, touched, error): 22 | - ng-model 指令基于它们的状态为 HTML 元素提供了 CSS 类 23 | * ng-model 指令根据表单域的状态添加/移除以下类: 24 | * ng-empty 25 | * ng-not-empty 26 | * ng-touched 27 | * ng-untouched 28 | * ng-valid 29 | * ng-invalid 30 | * ng-dirty 31 | * ng-pending 32 | * ng-pristine 33 | * ng-show 34 | `用来验证用户输入,提示信息会在ng-show属性返回true的情况下显示` 35 |
36 | Email: 37 | 不是一个合法的邮箱地址 38 |
39 | * $scope(作用域) 40 | Scope(作用域) 是应用在 HTML (视图) 和 JavaScript (控制器)之间的纽带。 41 | Scope 是一个对象,有可用的方法和属性。 42 | Scope 可应用在视图和控制器上。 43 | * 当你在 AngularJS 创建控制器时,你可以将 $scope 对象当作一个参数传递(依赖注入) 44 | * 控制器中的属性对应了视图上的属性 45 | * 如果你修改了视图,模型和控制器也会相应更新 46 | * 所有的应用都有一个 $rootScope,它可以作用在 ng-app 指令包含的所有 HTML 元素中。 47 | * ng-repeat指令 48 | 重复当前指令所在的元素n次 49 | 50 | ### AngularJS 控制器 51 | AngularJS 控制器 控制 AngularJS 应用程序的数据。 52 | AngularJS 控制器是常规的 JavaScript 对象。 53 | 初始化全局或者局部域模型对象 54 | ng-controller 指令定义了应用程序控制器。 55 | 56 | ### AngularJS 过滤器 57 | * currency 过滤器 58 | * lowercase 59 | * uppercase 60 | * limitTo `用来过滤普通数组,arr in arrs | limitTo : 3 : 2` `表示从索引为2处开始过滤3条数据` 61 | * filter `用来过滤对象数组,person in persons | filter : {$ : hello}` `表示含有hello的所有属性` 62 | * orderBy`用来排序数组,根据对象属性或者普通数据进行倒叙正序排列数组` 63 | * 自定义过滤器,过滤器返回一个函数,过滤器参数是被遍历的当前数组中的元素 64 | ### 常用指令 65 | 0. ng-app 66 | 1. ng-model 67 | 2. ng-controller 68 | 3. ng-init 69 | 4. ng-click 70 | 5. ng-repeat 71 | 6. ng-bind : 如果{{}}的数据需要直接显示到页面上, 使用此指令代替 72 | 7. ng-show 73 | 8. ng-hide 74 | 75 | 76 | 注意:控制器可以修改和初始化作用域当中的数据,但是域模型对象并不属于控制器,我们在定义控制器的 77 | 时候将域模型对象显式注入到控制器内部,我们需要显式的定义控制器的构造函数,由angular应用自动实例化控制器对象,在指令编译和连接阶段,我们也可以对域模型对象的属性进行初始化 78 | 79 | ### 服务(Service) 80 | 81 | 1. 声明式依赖注入就是一种对内置服务的引用 82 | $rootScope, $scope, $routParams, $filter, $timeout, $interval 83 | 2. 控制器不断检查注入其中的域模型对象中数据进行脏数据检查,来实时的更新view中的数据,js原生代码中对域模型对象数据的修改,angular不会对其中数据的修改进行脏值检查 84 | 85 | 3. 自定义服务的几种方式: 86 | * factory() : 工厂函数,可以返回对象或者函数 87 | * service() : 只能是一个构造函数,由angular自动生成对象 88 | * value() :变量绑定 89 | * constant() : 常量绑定 90 | * provider() : 固定格式: this.$get(), 由函数内生成对象 91 | 92 | ### 自定义指令(directer) 93 | 94 | angular.module('moduleName', []).directe('directeName', function(){ //工厂函数,返回指令的配置信息 95 | return { 96 | restrict : 'ECMA', //表示指令的访问方式,标签,属性,类,注释的方式 97 | templateUrl : 'test.html' //指令覆盖的页面视图,也就是在视图中的作用范围 98 | template : '

hello world

' 99 | replace : true, //使用模版替换标签 100 | transclude : true/false, //是否保留原有标签体 101 | priority : 10 //优先解析该指令 102 | terminal : true/false //是否终止优先级较低指令的解析 103 | scope: false //使用上层指令作用域 104 | scope : true //开辟新的作用域,并继承外部作用域,可以访问上级作用域中的数据 105 | scope : { 106 | 模板中需要显示: {{msg}} 107 | msg : '@' --->tt 108 | msg : '=' ---->将tt作为表达式, 从外部作用域中取对应的属性显示 109 | 模板中需要显示: {{msg({name:'Tom'})}} 110 | msg : '&' ---->将tt(name)作为表达式, 从外部作用域中取对应的方法调用来显示 111 | 112 | } //开辟隔离作用域,外部数据不可见,开辟自己的作用域 113 | link : function(scope, elements, attr, controller){ 114 | 115 | }//使用外部模版文件时,必须要连接,使得指令和模版关联起来,才可以相互更新 116 | require : 找外部指令的controller对象, 并注入link中来 117 | } 118 | })//工厂函数,返回指令的域模型对象,和控制器 119 | 120 | ### 视图与路由 121 | 122 | 路由: 根据请求资源路径显示对应的页面,常用来做单页面应用spa,angular页面路由,请求路径需以#开头 123 | 用来显示包含不同页面的容器,路由资源会将该标签替换,来显示页面。局部html片段替换,不会刷新整个页面,若有数据更新,那么数据请求全部是ajax请求 124 | 125 | angular.module('myApp', ['ngRoute']).config(function($routeProvider){ 126 | $routeProvider 127 | .when('/aa', { 128 | templateUrl : 'aa.html', 129 | controller : 'AController' 130 | }) 131 | .when('/bb', { 132 | templateUrl : 'bb.html', 133 | controller : 'BController' 134 | }) 135 | .otherwise('/aa') 136 | }).controller('AController', function($scope){ 137 | 138 | }).controller('Bcontroller', function($scope){ 139 | 140 | }) 141 | 142 | 143 | ### Angular对象常用方法 144 | module() : 创建模型对象 145 | element() : 将dom对象/html标签包装为jQuery对象 146 | forEach() : 遍历数组和元素集合伪数组 147 | toJson()和fromJson() : js对象与Json字符串相互转换 148 | isArray(),isObject(),isFunction() : 类型判断 149 | lowercase()和uppercase() : 大小写转换 150 | bootstrap() : 编码启动angular, 代替ng-app 151 | ### module对象常用方法 152 | 创建module对象: 153 | angular.module(name, []) 154 | 使用module对象: 155 | 156 | controller() : 定义控制器 157 | factory() : 定义服务对象 158 | service() : 定义服务对象 159 | provider() : 定义服务对象 160 | value() : 定义简单值服务 161 | constant() : 定义常量服务 162 | filter() : 定义过滤器 163 | directive() : 定义指令 164 | run() : 165 | config() : 指定做一些配置的回调函数 166 | 167 | 168 | ### 总结 169 | 170 | 1. 指令和表达式都可以直接访问域模型对象中的属性和方法 171 | - ng-app指令定义了AngularJS应用程序的根元素 172 | - ng-init指令为Angular应用程序定义了初始值,通常不会使用该指令初始化模型对象中的数据,而是使用控制器或模型对象来初始化数据 173 | - ng-model指令, 双向数据绑定,将模型对象中的属性和视图中的表单输入域建立对应关系 174 | - 为应用程序提供类型验证(number email required) 175 | - 为应用程序数据提供状态值(invalid, dirty,touched,error) 176 | - ng-repeat,根据迭代的数组元素个数,克隆当前元素 177 | 178 | 179 | -------------------------------------------------------------------------------- /docs/ES5.md: -------------------------------------------------------------------------------- 1 | ## ES5常用对象和方法 2 | 3 | 1. 严格模式: 4 | 5 | ``` 6 | "use strict" 7 | 严格模式表示js的语法检查更加严格,js代码在更严格的语法环境下执行,例如, 8 | 不能在定义隐式变量, 函数调用必须显示指定上下文对象等一些不符合常规语法的特性 9 | ``` 10 | 11 | * 变量必须显式声明,也就是说不能隐式生成全局变量 var a = 0 12 | * 创建eval方法的作用域 13 | * 上下文对象this不能指向全局对象window, 就是所有函数的调用必须显式指定他的所属对象 14 | * 对象属性不能重复定义 15 | * 函数形参不能重复 16 | 2. JSON对象 17 | ``` 18 | JSON是一种轻量级的数据交换格式,是具有固定格式的一个字符串,字符串内可以包括一个以大括号包含的 19 | JSON对象,也可以是一个JSON数组,内部都是key和value形式的名值对儿,值可以是任何的js基本数据类 20 | 型和数组对象等。。 21 | ``` 22 | * JSON.stringify(obj/arr) `将js对象或者数组转 换成json对象或数组字符串` 23 | * JSON.parse(strObj/strArr)`将具有json格式的字符串转换为js对象或者数组` 24 | 3. Object对象: 25 | ``` 26 | ES5为Object扩展了一些静态方法,常用的有2个 27 | ES5之前并没有提供对对象的深度克隆,ES5新增扩展对象,提供了对对象的深度克隆 28 | ``` 29 | * Object.create(obj, {}) `复制对象的同时,可以给新对象添加新的属性,克隆的新对象是原有对象的子对象,子对象的隐式原型对象指向原来的对象` 30 | * Object.defineProperties(obj, descriptors) 31 | `不复制原对象,可以对原有对象进行2次制定,包括新增方法和属性 32 | 其中get和set方法都是用来监视属性值的变化实时返回和设置当前属性值` 33 | * Object.assign(target, source1, source2 ...) 34 | 将源对象的可枚举属性赋值到目标对象,可以进行对象的克隆。注意,如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性。 35 | function clone(origin) 36 | { 37 | return Object.assign({}, origin) 38 | } 39 | 40 | 4. Array对象 41 | * indexOf() `元素在数组中的索引,只返回第一个` 42 | * lastIndexOf();`元素在数组中的最后一个索引` 43 | * forEach(function(value, index, obj))`遍历数组` 44 | * map(function(value, index, obj){})`遍历数组,并返回数组的一个拷贝,可以对其中的元素进行定制` 45 | * filter(function(value, index, obj){})`遍历数组,并返回一个满足条件的子数组` 46 | * from()`主要用来将伪数组和集合转换为数组,任何有length属性的对象转为数组` 47 | * of() 48 | * find(function(value, index, arr){}) 49 | * findIndex(function(value, index, arr){}) 50 | * fill(value, startindex, endindex)`用给定的值填充数组,不指定位置则会替换掉所有成员 51 | * 数组推导`允许直接通过现有数组生成新数组` 52 | var a1 = [1,2,3,4] 53 | var a2 = [for(i of a1)i*2]; 54 | a2 //[2,,4,6,8] 55 | 注意:数组推导中for... of结构总是放在最前面,返回的表达式放在最后面 56 | var years = [ 1954, 1974, 1990, 2006, 2010, 2014 ] 57 | [for(year of years)if(year > 2000)year] 58 | //[2006, 2010, 2014] 59 | [for(year of years)if(year > 2000)if(year < 2010)year] 60 | //[2006] 61 | [for(year of years)if(year > 2000 && year < 2010)year] 62 | //[2006] 63 | 数组推导可以代替map和filter 64 | 数组推导需要注意的地方是,新数组会立即在内存中生成。这时,如果原数组是一个很大的数组,将会非常耗费内存。 65 | 5. Function对象 66 | * bind(obj) `为方法永久的绑定一个上下文执行对象` 67 | * call(obj, arguments)`用指定上下文对象调用函数` 68 | * apply(obj, [])`用指定上下文对象调用函数` 69 | 6. Date对象 70 | * Date.now(); 71 | 72 | -------------------------------------------------------------------------------- /docs/ES6规范.md: -------------------------------------------------------------------------------- 1 | ## ES6新增语法 2 | 3 | 1. let关键字 4 | ``` 5 | let关键字同var关键字,用于声明一个变量,let可以劫持一个函数块作为自身的可见范围,即作用域, 6 | let声明的关键字不存在变量提升的问题,只在自身作用域内有效 7 | ``` 8 | 2. const关键字 9 | ``` 10 | 用来定义一个常量,与其他强类型语言靠近,同let关键字相似,const同样可以劫持 11 | 一个函数块作为自身作用域,且只在自身作用域内有效 12 | ``` 13 | 3. 变量的解构赋值 14 | demo: 15 | var arr = new Array('apple', 'pair', 'banana'); 16 | var [apple, p, b] = arr ; 17 | console.log(apple); //apple 18 | console.log(p); //pair 19 | console.log(b); //banana 20 | 21 | demo 22 | var obj = {name:'zhangsan', age:22, gender:'male'}; 23 | var {name, age, gender} = obj ; 24 | 25 | console.log(name); //zhangsan 26 | console.log(age); //22 27 | console.log(gender); //male 28 | 29 | ``` 30 | 注意:数组的解构赋值,变量名随便起。对象的解构赋值,变量名必须和对象属性名相对应 31 | ``` 32 | 4. String对象扩展 33 | * 模版字符串,用来简化字符串拼接 34 | 35 | var person = {name:'zhangsan', age:22}; 36 | console.log('hello' + person.name); 37 | console.log(`hello ${person.name}`); 38 | * includes(str) //判断是否包含指定字符串 39 | * startsWith(str) //判断字符串是否是以指定字符串开头 40 | * endsWidth(str) //判读是否以指定字符串结尾 41 | * repeat(num) //字符串重复多次 42 | * stringValue.localeCompare("brick") 1,0,-1 //之后,相等,之前 43 | * fromCharCode() `这个方法的任务是接受一个或者多个字符编码,转换成一个字符串` 44 | * encodeURI() `会保留url中的英文字符和url中的特殊字符, 保留特殊字符和英文字符` 45 | * encodeURIComponent() `会编码任何非英文字符的字符,包括url里面的一些问好斜杠等特殊字符,只保留英文字符` 46 | ``` 47 | 一般来说,我们使用encodeURIComponent()比encodeURI()更加频繁,因为在实践中更常见的是对查询字符串参数而不是对基础uri进行编码 48 | 49 | ``` 50 | * decodeURI `与上面的encodeURI对应只能解码encodeURI编码的字符` 51 | * decodeURIComponent() `与上面的方法对应` 52 | 53 | 
 54 | 55 | 56 | 57 | 5. Number对象扩展 58 | 59 | * Number.isFinite(num); `判断结构是否是有穷大的数字` 60 | * Number.isNaN(num); `判断结构是否是无效的数字` 61 | * Number.isNumber(num);`判断结构是否是Number类型数字` 62 | * Number.parseInt(str); `将以数字开头的字符串转换为数字` 63 | * Math.trunc(num);`将浮点数无舍入的砍掉` 64 | * Number.prototype.toFixed()`四舍五入保留几位小数` 65 | * Number.prototype.toPrecision() `保留几位有效数字` 66 | 6. Array对象 67 | ``` 68 | 数组常用方法集合: 69 | • push() 70 | • pop() 71 | • shift() 72 | • unshift() 73 | • splice() 74 | • slice() 75 | • sort() 76 | • concat() 77 | • reverse() 78 | ``` 79 | * Array.from(btns); `将伪数组转换为数组` 80 | * Array.of(1,2,3); `将参数转换为数组存储` 81 | * Array.find(function(value, index, arr){}) `返回符合条件的数组元素,条件表达式为布尔值,只返回第一个符合条件的值` 82 | * Array.findIndex(function(value, index, arr){})`返回符合条件的数组元素的下标,同样返回布尔值的条件表达式` 83 | * Array.keys()`返回数组下标组成的迭代集合, 只返回自身的所有可枚举属性` 84 | * Array.values()`返回包含所有数组元素的可迭代集合` 85 | * Array.entries();`返回所有key和value的可迭代集合` 86 | * Array.map(function(value,index, obj){}) 87 | * Array.filter(function(value,index, obj){}) 88 | * Array.forEach(function(value,index, obj){}) 89 | * Array.indexOf() 90 | * Array.lastIndexOf() 91 | * Array.fill(value, startIndex, lastIndex) 92 | * array.every(function(num){return num % 2 === 0}) `判断一个数组是否全部符合某个条件` 93 | * some() `方法也接受一个返回值为布尔类型的函数, 只要有一个元素使得该函数返回 true,该方法就返回 true` array.some(function(num){return num % 2 === 0}) 94 | * array.reduce(function(sum, num){return sum + num}) `可以对数组进行求和` 95 | * array.reduceRight(function(sum, num){return sum + num})`从数组末尾倒着计算,常用来将字符数组倒叙` 96 | 97 | 7. Object扩展(新功能都是为了简化代码的编写) 98 | * 增强的对象定义 99 | demo 100 | var name = 'zhangsan' ; 101 | var age = 22 ; 102 | var sayHello = function() 103 | { 104 | console.log(this.name); 105 | } 106 | var obj = {name, age, sayHello} ; 107 | * 增强的方法定义 108 | demo 109 | var obj = { 110 | name : 'zhangsan', 111 | age : 22 , 112 | sayHello() 113 | { 114 | console.log(this.name); 115 | } 116 | } ; 117 | * Object.is(obj1, obj2); `判断俩个对象或者数据是否一致,is方法对===进行了增强,对NaN,0,-0做了特殊处理` 118 | * Object.assign(target, source...); `将源对象的属性复制到目标对象 119 | * 显式指定对象隐式原型的对象,指定对象的父对象 120 | ``` 121 | var obj2 = {name : 'zhangsan'} ; 122 | obj.__proto__ = obj2 ; 123 | console.log(obj.name); ; 124 | ``` 125 | 8. 函数的扩展 126 | * 胖箭头函数 127 | ``` 128 | ()=>{console.log('hello')} 129 | 胖函数常用来定义匿名回调函数,前面是形参,后面是方法体,形参若只有一个,可以省略(),语句若只有一句, 130 | 也可以省略函数体 131 | ``` 132 | * 形参默认值 133 | ``` 134 | function addPerson(x = 0, y = 1) 135 | { 136 | this.x = x ; 137 | ⁃ this.y = y ; 138 | } 139 | ``` 140 | * 可变参数 141 | ...nums 142 | 9. Set和Map容器(同java中的HashSet和HashMap) 143 | Set当中元素不能重复 144 | Map中的key不可以重复,value可以重复 145 | * Set 146 | - add(value) 147 | - delete(value); 148 | - has(value) 149 | - clear(); 150 | - size 151 | * Map 152 | - set(key, value); 153 | - get(key) ; 154 | - delete(key) ; 155 | - has(key); 156 | - clear() 157 | - size; 158 | - keys(); `返回所有key的一个` 159 | - values() 160 | - entries(); 161 | 10. for...of循环 162 | 这个循环返回一个迭代器,所有可以循环遍历的对象都可以使用for...of进行遍历 163 | * 数组 164 | * 集合set map 165 | * 字符串 166 | * 伪数组 167 | * 对象 168 | 169 | 11. 对象的四个属性描述符 170 | * configurable 171 | * enumerable 172 | * writable 173 | * value 174 | * set 175 | * get 176 | 177 | 178 | -------------------------------------------------------------------------------- /docs/HTML5读书笔记.md: -------------------------------------------------------------------------------- 1 | ## HTML5特点 2 | 3 | 1. 用于即时 2D 绘图的 Canvas 标签 4 | 2. 定时媒体回放 5 | 3. 离线数据库存储 6 | 4. 文档编辑 7 | 5. 拖拽控制 8 | 6. 浏览历史管理 9 | 10 | ### HTML 5 有两大特点: 11 | * 首先,强化了 Web 网页的表现性能。除了可描绘二维图形外,还准备了用于播放视频和音频的标签。 12 | * 其次,追加了本地数据库等 Web 应用的功能。 13 | * HTML5最激动人心的地方: 14 | 全新的,更合理的 Tag,多媒体对象将不再全部绑定在 object 或 embed Tag 中,而是视频有视频的 Tag,音频有音频的 Tag。本地数据库。这个功能将内嵌一个本地的 SQL 数据库,以加速交互式搜索,缓存以及索引功能。同时,那些离线 Web 程序也将因此获益匪浅。不需要插件的富动画。 Canvas对象将给浏览器带来直接在上面绘制矢量图的能力,这意味着我们可以脱离 Flash 和 Silverlight,直接在浏览器中显示图形或动画。一些最新的浏览器,除了 IE,已经开始支持 Canvas。浏览器中的真正程序。将提供 API 实现浏览器内的编辑,拖放,以及各种图形用户界面的能力。内容修饰 Tag 将被剔除,而使用 CSS。理论上讲, HTML 5 是培育新 Web 标准的土壤,让各种设想在他的组织者之间分享,但 HTML 5 目前仍处于试验阶段 15 | * 新特性应该基于 HTML、 CSS、 DOM 以及 JavaScript。 16 | * 减少对外部插件的需求(比如 Flash) 17 | * 更优秀的错误处理 18 | * 更多取代脚本的标记 19 | * HTML 5 应该独立于设备 20 | * 开发进程应对公众透明 21 | 22 | 1. video&audio标签 23 | Internet Explorer 8 不支持 video 元素。在 IE 9 中,将提供对使用 MPEG4 的 video 元素的支持 24 | 属性: 25 | autoplay : autoplay 如果出现该属性,视频在就绪后马上播放 26 | controls : controls 如果出现该属性,则向用户显示控件,比如播放按钮 27 | height:piexs 设置视频元素的高度 28 | loop : loop 视频是否循环播放,用于广告视频 29 | preload :preload  如果出现该属性,则视频在页面加载时进行加载,并预备播放。如果使用 "autoplay",则忽略该属性 30 | src:要播放的视频路径 31 | width: 视频元素的宽度 32 | 33 | 2. Canvas 34 | canvas 元素用于在网页上绘制图形。HTML 5 的 canvas 元素使用 JavaScript 在网页上绘制图像。canvas 元素本身是没有绘图能力的。所有的绘制工作必须JavaScript 内部完成: 35 | 3. Web存储, HTML5提供了两种客户端存储数据的方法: 36 | * localStorage 没有时间限制的存储 37 | * sessionStorage 针对一个session对象的数据存储 38 | 之前,这些都是由 cookie 完成的。但是 cookie 不适合大量数据的存储,因为它们由每个对服务器的请求来传递,这使得 cookie 速度很慢而且效率也不高,在 HTML5 中,数据不是由每个服务器请求传递的,而是只有在请求时使用数据。它使的在不影响网站性能的情况下存储大量数据成为可能。 39 | localStorage.lastName = 'helloworld' ; //设值 40 | var value = localStorage.lastName ; //取值 41 | 42 | 43 | 4. H5新的表单属性 44 | * form属性: 45 | - autocomplete 46 | - novalidate 47 | * input属性: 48 | - autocopmlete 49 | - autofocus 50 | - form `属性规定输入域所属的一个或多个表单。` 51 | - form overrides (formaction, formenctype, formmethod, formnovalidate, formtarget) 52 | - height和width 53 | - list 54 | - min, max, step 55 | - multiple 56 | - pattern(regexp) 57 | - placeholder 58 | - required 59 | - 60 | 61 | -------------------------------------------------------------------------------- /docs/IE6下fixed失效.md: -------------------------------------------------------------------------------- 1 | #IE6下fixed失效问题 2 | ## overflow 3 | - overflow 有三个属性 4 | - scroll 出现滚动条,不管内容有没有溢出都会出现难看的滚动条边框 5 | - auto 只有内容溢出时才会出现 6 | - hidden 溢出的内容会被隐藏,且滚动条被禁止出现 7 | - overflow 设置给 html 或者 body 8 | 28 |
29 | 从浏览器渲染的效果看,当单独给html或者body设置overflow属性时,滚动条并没有加在html或body本身上,而是加到了document上。 30 | 但是,当同时给body和html添加overflow属性时,滚动条加在了body上。 31 | ## 利用overflow加absolute模拟fixed 32 | 54 |
55 |
56 | 首先通过两次`height: 100%`,让body等于初始包含块,然后禁掉系统默认滚动条的同时,开启body的滚动条。开启元素的`position:absolute`,此时元素就相对初始包含块定位,当把body往下拉时,拖动时拖动的是body,没有拖动初始包含块,元素不动 57 | ##用absolute加js模拟fixed 58 | 71 |
72 |
73 | 82 | 动态的计算出滚动条滚动的距离,也就是body往上走的距离,让后把这个距离添加给absolute的元素,让它跟着移动相应距离。 83 | 84 | -------------------------------------------------------------------------------- /docs/JS基础深入理解.md: -------------------------------------------------------------------------------- 1 | ## JS基础深入理解 2 | 1. 3 | console.log("1"); 4 | setTimeout(function(){ 5 | console.log("3") 6 | },0); 7 | console.log("2"); 8 | //控制台输出1,2,3 9 | 2. 10 | function fn() { 11 | setTimeout(function(){alert('can you see me?');},1000); 12 | while(true) {} 13 | } 14 | //alert永远不会输出 15 | 3. 闭包深入理解 16 | * var name = "The Window"; 17 |    var object = { 18 |     name : "My Object", 19 |     getNameFunc : function(){ 20 |       return function(){ 21 |         return this.name; 22 |       }; 23 |     } 24 |   }; 25 |   alert(object.getNameFunc()());//The Window 26 | 27 | * var name = "The Window"; 28 |    var object = { 29 |     name : "My Object", 30 |     getNameFunc : function(){ 31 |       var that = this; 32 |       return function(){ 33 |         return that.name; 34 |       }; 35 |     } 36 |   };  alert(object.getNameFunc()());//My Object 37 | 38 | * function fun(n,o) { 39 | console.log(o) 40 | return { 41 | fun:function(m){ 42 | return fun(m,n); 43 | } 44 | }; 45 | } 46 | var a = fun(0); a.fun(1); a.fun(2); a.fun(3); 47 | var b = fun(0).fun(1).fun(2).fun(3);// 48 | var c = fun(0).fun(1); c.fun(2); c.fun(3); 49 | ``` 50 | //答案: 51 | //a: undefined,0,0,0 52 | //b: undefined,0,1,2 53 | //c: undefined,0,1,1 54 | ``` 55 | 4. Array/map,Number/parseInt 56 | ["1", "2", "3"].map(parseInt)//求输出结果 57 | 58 | ``` 59 | 首先, map接受两个参数, 一个回调函数 callback, 一个回调函数的this值 60 | 其中回调函数接受三个参数 currentValue, index, arrary;而题目中, map只传入了回调函数--parseInt.其次, parseInt 只接受两个两个参数 string, radix(基数). radix的合法区间是2-36. 0或是默认是10.所以本题即问 61 | 62 | parseInt('1', 0); 63 | parseInt('2', 1); 64 | parseInt('3', 2); 65 | 后两者参数不合法.所以答案是:[1, NaN, NaN]; 66 | ``` 67 | 5. 0.1+0.2!=0.3和9999999999999999 == 10000000000000000; 68 | ``` 69 | JavaScript 不区分整数值和浮点数值,所有数字在 JavaScript 中均用浮点数值表示,所以在进行数字运算的时候要特别注意。精度丢失 70 | 0.1 + 0.2 = 0.30000000000000004 71 | 大整数精度在2的53次方以内是不会丢失的,也就是说浏览器能精确计算Math.pow(2,53)以内所有的数,小数精度,当十进制小数的二进制表示的有限数字不超过 52 位时,在 JavaScript 里也是可以精确存储的。 72 | 解决办法:Math.round( (.1+.2)*100)/100; 73 | ``` 74 | 6. [1<2<3,3<2<1] 75 | 76 | 此题会让人误以为是2>1&&2<3,其实不是的,这个题等价于 77 | 78 | 1<2=>true; 79 | true<3=>1<3=>true; 80 | 3<2=>true; 81 | false<1=>0<1=>true; 82 | 答案:[true,true] 这个题的重点是对于运算符的理解,一是javascript对于不同类型数值的比较规则,详见js比较表,javascript相等性判断;二是对于比较操作符和赋值运算符的理解,即一个自左向右一个自右向左~ 83 | 84 | ![](http://i.imgur.com/nqlyxWi.png) 85 | 86 | 7. 浏览器蒙逼了 87 | 3.toString; 88 | 3..toString; 89 | 3...toString; 90 | 这个题感觉脑洞很大啊~先说答案:error,'3',error; 91 | 可如果是 92 | 93 | var a=3; 94 | a.toString; 95 | 却又合法了答案就是'3'; 96 | 为啥呢? 97 | 因为在JS中1.1,1.,.1都是合法数字啊!那么在解析3.toString的时候到底是这是个数字呢,还是方法调用呢?浏览器就懵逼了呗,只能抛出一个error,所以说感觉此题就是在戏耍浏览器...... 98 | 8. 声明提升 99 | ``` 100 | var name = 'World!'; 101 | (function () { 102 | if (typeof name === 'undefined') { 103 | var name = 'Jack'; 104 | console.log('Goodbye ' + name); 105 | } else { 106 | console.log('Hello ' + name); 107 | } 108 | })(); 109 | ``` 110 | 答案是什么呢...笔者第一次做的时候傻傻的觉得是Hello,world...实则不然,正确答案是:Goodbye Jack; 111 | 为什么呢,声明提升...上述代码相当于下面的代码: 112 | ``` 113 | var name = 'World!'; 114 | (function () { 115 | var name; 116 | if (typeof name === 'undefined') { 117 | name = 'Jack'; 118 | console.log('Goodbye ' + name); 119 | } else { 120 | console.log('Hello ' + name); 121 | } 122 | })(); 123 | ``` 124 | 9. 125 | var a = [0]; 126 | if ([0]) { 127 | console.log(a == true); 128 | } else { 129 | console.log("wut"); 130 | } 131 | 10. 132 | function sidEffecting(ary) { 133 | ary[0] = ary[2]; 134 | } 135 | function bar(a,b,c) { 136 | c = 10 137 | sidEffecting(arguments); 138 | return a + b + c; 139 | } 140 | bar(1,1,1) 141 | 142 | The arguments object is an Array-like object corresponding to the arguments passed to a function.也就是说 arguments 是一个 object, c 就是 arguments2, 所以对于 c 的修改就是对 arguments2 的修改. 143 | 所以答案是 21. 144 | 145 | 当函数参数涉及到 any rest parameters, any default parameters or any destructured parameters 的时候, 这个 arguments 就不在是一个 mapped arguments object 了.....请看: 146 | 147 | function sidEffecting(ary) { 148 | ary[0] = ary[2]; 149 | } 150 | function bar(a,b,c=3) { 151 | c = 10 152 | sidEffecting(arguments); 153 | return a + b + c; 154 | } 155 | bar(1,1,1) 156 | 答案是12... 157 | 158 | 11 . 159 | [,,,].join(", ") 160 | [,,,] => [undefined × 3] 161 | 因为javascript 在定义数组的时候允许最后一个元素后跟一个,, 所以这是个长度为三的稀疏数组(这是长度为三, 并没有 0, 1, 2三个属性哦) 162 | 答案: ", , " 163 | 164 | -------------------------------------------------------------------------------- /docs/JS当中事件轮训机制.md: -------------------------------------------------------------------------------- 1 | ## JS 事件轮训机制深入理解 2 | 3 | 众所周知,JS是单线程运行的,当我们实现功能比较复杂的逻辑或者代码中涉及大量计算的时候,会阻塞主线程的执行,用户体验不是很好,所以JS提供了基于事件的异步执行,就是可以将某一个特定功能封装到一个函数,通过事件轮训的机制来完成与主线程的交互。 4 | 5 | 1. 时间片轮转: 6 | 时间片轮转是计算机操作系统任务调度的时间表,就是将一段时间分割成很多块,在不同的时间片内,将cpu的控制权转让给其他进程,将之前的进程挂起,等待下一次获得执行的时间片来唤醒任务继续执行。时间片轮转的概念使得计算机中的很多应用程序能够宏观上并行,就是用户不会感觉到内部cpu任务的转换,但是却可以同时打开多个应用程序而互不干扰,宏观上看上去好像可以同步执行。举个栗子,你通过qq扯淡的时候可以同时打开网易云音乐听歌。 7 | 8 | 2. 事件的轮训机制: 9 | 我们在主线程上注册的每一个异步的回调函数,浏览器内核会有相应的模块儿(timer模块儿)来管理这些回调函数,一旦这些函数满足了触发条件,那么它就会被推入事件队列当中,当主线程上任务执行完毕,主线程在空闲的时候会不断的检查事件队列当中是否有可执行的任务,如果有的化,那么主线程会调用相应的回调函数来完成之前注册的任务。 10 | 11 | demo1 : 12 | 13 | console.log('start') ; 14 | 15 | setTimeout(function(){ 16 | console.log('hello'); 17 | }, 200) 18 | 19 | setTimeout(function(){ 20 | console.log('world') ; 21 | }, 300) 22 | 23 | console.log('end'); 24 | 25 | 26 | 如果这个一眼看出了,结果那么改动一下。 27 | 28 | demo2 : 29 | 30 | console.log('start'); 31 | 32 | setTimeout(function(){ 33 | console.log('hello'); 34 | } , 200); 35 | 36 | setTimeout(function(){ 37 | console.log('world'); 38 | }, 300) 39 | 40 | for(var i = 0 ; i < 100000; i++) 41 | { 42 | console.log(i); 43 | } 44 | 45 | console.log('end'); 46 | 47 | 48 | ### JS 代码的执行流程 49 | 50 | 先看一段简单的代码: 51 | function A() 52 | { 53 | var a = 3 ; 54 | B(3) ; 55 | } 56 | 57 | function B(num) 58 | { 59 | var newNum = num * num ; 60 | console.log(newNum); 61 | } 62 | 63 | A(); 64 | 65 | 代码执行的时候首先开辟全局上下文执行环境 66 | 1. 将全局可见的变量和函数全部收集起来 67 | 2. 调用A,将A压入栈中,同时将A中定义的变量进行提升 68 | 3. 调用B, 将B压入栈中,同时将B中定义的变量进行提升 69 | 4. 打印输出结果9 ,将B出栈,会到A的执行上下文 70 | 5. 继续执行A中的代码,将A出栈 71 | 6. 清空调用栈 72 | 在回到最开始的代码当中,明白了上面的代码执行流程,就很容易理解基于异步的事件轮训机制了, 不管是Ajax,Dom操作,或者setTimeout等,都是注册了批量异步回调函数,这写函数都有对应的触发时机,当条件一旦满足,那么就会将其处理函数推送到事件队列当中,一旦主线程处于空闲状态,那么它就会不断检查事件队列当中有那些任务,并调用相应的处理函数。 73 | 最后上一张gif图,更加真切一些。 74 | 75 | ![](http://i.imgur.com/i7hrHHX.gif) -------------------------------------------------------------------------------- /docs/PM2服务器部署.md: -------------------------------------------------------------------------------- 1 | 安装:npm install -g pm2 2 | 启动程序:pm2 start 3 | 列举进程:pm2 list 4 | 退出程序:pm2 stop 5 | 重起应用:pm2 restart 6 | 程序信息:pm2 describe id|all 7 | 监控:pm2 monit 8 | 实时集中log处理: pm2 logs 9 | API:pm2 web (端口:9615 ) -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Vue.js 技术揭秘 2 | 3 | 目前社区有很多 Vue.js 的源码解析文章,但是质量层次不齐,不够系统和全面,这本电子书的目标是全方位细致深度解析 Vue.js 的实现原理,让同学们可以彻底掌握 Vue.js。目前分析的版本是 Vue.js 的最新版本 Vue.js 2.5.17-beta.0,并且之后会随着版本升级而做相应的更新,充分发挥电子书的优势。 4 | 5 | 这本电子书是作为 [《Vue.js 源码揭秘》](http://coding.imooc.com/class/228.html)视频课程的辅助教材。电子书是开源的,同学们可以免费阅读,视频是收费的,25+小时纯干货课程,如果有需要的同学可以购买来学习,**但请务必支持正版,请尊重作者的劳动成果**。 6 | 7 | ## 章节目录 8 | 9 | 为了把 Vue.js 的源码讲明白,课程设计成由浅入深,分为核心、编译、扩展、生态四个方面去讲,并拆成了八个章节,如下图: 10 | 11 | 12 | 13 | **第一章:准备工作** 14 | 15 | 介绍了 Flow、Vue.js 的源码目录设计、Vue.js 的源码构建方式,以及从入口开始分析了 Vue.js 的初始化过程。 16 | 17 | **第二章:数据驱动** 18 | 19 | 详细讲解了模板数据到 DOM 渲染的过程,从 `new Vue` 开始,分析了 `mount`、`render`、`update`、`patch` 等流程。 20 | 21 | **第三章:组件化** 22 | 23 | 分析了组件化的实现原理,并且分析了组件周边的原理实现,包括合并配置、生命周期、组件注册、异步组件。 24 | 25 | **第四章:深入响应式原理** 26 | 27 | 详细讲解了数据的变化如何驱动视图的变化,分析了响应式对象的创建,依赖收集、派发更新的实现过程,一些特殊情况的处理,并对比了计算属性和侦听属性的实现,最后分析了组件更新的过程。 28 | 29 | **第五章:编译** 30 | 31 | 从编译的入口函数开始,分析了编译的三个核心流程的实现:`parse` -> `optimize` -> `codegen`。 32 | 33 | **第六章:扩展** 34 | 35 | 详细讲解了 `event`、`v-model`、`slot`、`keep-alive`、`transition`、`transition-group` 等常用功能的原理实现,该章节作为一个可扩展章节,未来会分析更多 Vue 提供的特性。 36 | 37 | **第七章:Vue-Router** 38 | 39 | 分析了 Vue-Router 的实现原理,从路由注册开始,分析了路由对象、`matcher`,并深入分析了整个路径切换的实现过程和细节。 40 | 41 | **第八章:Vuex** 42 | 43 | 分析了 Vuex 的实现原理,深入分析了它的初始化过程,常用 API 以及插件部分的实现。 44 | 45 | 46 | -------------------------------------------------------------------------------- /docs/compile/index.md: -------------------------------------------------------------------------------- 1 | # 编译 2 | 3 | 之前我们分析过模板到真实 DOM 渲染的过程,中间有一个环节是把模板编译成 `render` 函数,这个过程我们把它称作编译。 4 | 5 | 虽然我们可以直接为组件编写 `render` 函数,但是编写 `template` 模板更加直观,也更符合我们的开发习惯。 6 | 7 | Vue.js 提供了 2 个版本,一个是 Runtime + Compiler 的,一个是 Runtime only 的,前者是包含编译代码的,可以把编译过程放在运行时做,后者是不包含编译代码的,需要借助 webpack 的 `vue-loader` 事先把模板编译成 `render `函数。 8 | 9 | 这一章我们就来分析编译的过程,对编译过程的了解会让我们对 Vue 的指令、内置组件等有更好的理解。不过由于编译的过程是一个相对复杂的过程,我们只要求理解整体的流程、输入和输出即可,对于细节我们不必抠太细。有些细节比如对于 `slot` 的处理我们可以在之后去分析插槽实现的时候再详细分析。 -------------------------------------------------------------------------------- /docs/compile/optimize.md: -------------------------------------------------------------------------------- 1 | # optimize 2 | 3 | 当我们的模板 `template` 经过 `parse` 过程后,会输出生成 AST 树,那么接下来我们需要对这颗树做优化,`optimize` 的逻辑是远简单于 `parse` 的逻辑,所以理解起来会轻松很多。 4 | 5 | 为什么要有优化过程,因为我们知道 Vue 是数据驱动,是响应式的,但是我们的模板并不是所有数据都是响应式的,也有很多数据是首次渲染后就永远不会变化的,那么这部分数据生成的 DOM 也不会变化,我们可以在 `patch` 的过程跳过对他们的比对。 6 | 7 | 来看一下 `optimize` 方法的定义,在 `src/compiler/optimizer.js` 中: 8 | 9 | ```js 10 | /** 11 | * Goal of the optimizer: walk the generated template AST tree 12 | * and detect sub-trees that are purely static, i.e. parts of 13 | * the DOM that never needs to change. 14 | * 15 | * Once we detect these sub-trees, we can: 16 | * 17 | * 1. Hoist them into constants, so that we no longer need to 18 | * create fresh nodes for them on each re-render; 19 | * 2. Completely skip them in the patching process. 20 | */ 21 | export function optimize (root: ?ASTElement, options: CompilerOptions) { 22 | if (!root) return 23 | isStaticKey = genStaticKeysCached(options.staticKeys || '') 24 | isPlatformReservedTag = options.isReservedTag || no 25 | // first pass: mark all non-static nodes. 26 | markStatic(root) 27 | // second pass: mark static roots. 28 | markStaticRoots(root, false) 29 | } 30 | 31 | function genStaticKeys (keys: string): Function { 32 | return makeMap( 33 | 'type,tag,attrsList,attrsMap,plain,parent,children,attrs' + 34 | (keys ? ',' + keys : '') 35 | ) 36 | } 37 | ``` 38 | 39 | 我们在编译阶段可以把一些 AST 节点优化成静态节点,所以整个 `optimize` 的过程实际上就干 2 件事情,`markStatic(root)` 标记静态节点 ,`markStaticRoots(root, false)` 标记静态根。 40 | 41 | ## 标记静态节点 42 | 43 | ```js 44 | function markStatic (node: ASTNode) { 45 | node.static = isStatic(node) 46 | if (node.type === 1) { 47 | // do not make component slot content static. this avoids 48 | // 1. components not able to mutate slot nodes 49 | // 2. static slot content fails for hot-reloading 50 | if ( 51 | !isPlatformReservedTag(node.tag) && 52 | node.tag !== 'slot' && 53 | node.attrsMap['inline-template'] == null 54 | ) { 55 | return 56 | } 57 | for (let i = 0, l = node.children.length; i < l; i++) { 58 | const child = node.children[i] 59 | markStatic(child) 60 | if (!child.static) { 61 | node.static = false 62 | } 63 | } 64 | if (node.ifConditions) { 65 | for (let i = 1, l = node.ifConditions.length; i < l; i++) { 66 | const block = node.ifConditions[i].block 67 | markStatic(block) 68 | if (!block.static) { 69 | node.static = false 70 | } 71 | } 72 | } 73 | } 74 | } 75 | 76 | function isStatic (node: ASTNode): boolean { 77 | if (node.type === 2) { // expression 78 | return false 79 | } 80 | if (node.type === 3) { // text 81 | return true 82 | } 83 | return !!(node.pre || ( 84 | !node.hasBindings && // no dynamic bindings 85 | !node.if && !node.for && // not v-if or v-for or v-else 86 | !isBuiltInTag(node.tag) && // not a built-in 87 | isPlatformReservedTag(node.tag) && // not a component 88 | !isDirectChildOfTemplateFor(node) && 89 | Object.keys(node).every(isStaticKey) 90 | )) 91 | } 92 | ``` 93 | 94 | 首先执行 `node.static = isStatic(node)` 95 | 96 | `isStatic` 是对一个 AST 元素节点是否是静态的判断,如果是表达式,就是非静态;如果是纯文本,就是静态;对于一个普通元素,如果有 pre 属性,那么它使用了 `v-pre` 指令,是静态,否则要同时满足以下条件:没有使用 `v-if`、`v-for`,没有使用其它指令(不包括 `v-once`),非内置组件,是平台保留的标签,非带有 `v-for` 的 `template` 标签的直接子节点,节点的所有属性的 `key` 都满足静态 key;这些都满足则这个 AST 节点是一个静态节点。 97 | 98 | 如果这个节点是一个普通元素,则遍历它的所有 `children`,递归执行 `markStatic`。因为所有的 `elseif` 和 `else` 节点都不在 `children` 中, 如果节点的 `ifConditions` 不为空,则遍历 `ifConditions` 拿到所有条件中的 `block`,也就是它们对应的 AST 节点,递归执行 `markStatic`。在这些递归过程中,一旦子节点有不是 `static` 的情况,则它的父节点的 `static` 均变成 false。 99 | 100 | ## 标记静态根 101 | 102 | ```js 103 | function markStaticRoots (node: ASTNode, isInFor: boolean) { 104 | if (node.type === 1) { 105 | if (node.static || node.once) { 106 | node.staticInFor = isInFor 107 | } 108 | // For a node to qualify as a static root, it should have children that 109 | // are not just static text. Otherwise the cost of hoisting out will 110 | // outweigh the benefits and it's better off to just always render it fresh. 111 | if (node.static && node.children.length && !( 112 | node.children.length === 1 && 113 | node.children[0].type === 3 114 | )) { 115 | node.staticRoot = true 116 | return 117 | } else { 118 | node.staticRoot = false 119 | } 120 | if (node.children) { 121 | for (let i = 0, l = node.children.length; i < l; i++) { 122 | markStaticRoots(node.children[i], isInFor || !!node.for) 123 | } 124 | } 125 | if (node.ifConditions) { 126 | for (let i = 1, l = node.ifConditions.length; i < l; i++) { 127 | markStaticRoots(node.ifConditions[i].block, isInFor) 128 | } 129 | } 130 | } 131 | } 132 | ``` 133 | 134 | `markStaticRoots` 第二个参数是 `isInFor`,对于已经是 `static` 的节点或者是 `v-once` 指令的节点,`node.staticInFor = isInFor`。 135 | 接着就是对于 `staticRoot` 的判断逻辑,从注释中我们可以看到,对于有资格成为 `staticRoot` 的节点,除了本身是一个静态节点外,必须满足拥有 `children`,并且 `children` 不能只是一个文本节点,不然的话把它标记成静态根节点的收益就很小了。 136 | 137 | 接下来和标记静态节点的逻辑一样,遍历 `children` 以及 `ifConditions`,递归执行 `markStaticRoots`。 138 | 139 | 回归我们之前的例子,经过 `optimize` 后,AST 树变成了如下: 140 | 141 | ```js 142 | ast = { 143 | 'type': 1, 144 | 'tag': 'ul', 145 | 'attrsList': [], 146 | 'attrsMap': { 147 | ':class': 'bindCls', 148 | 'class': 'list', 149 | 'v-if': 'isShow' 150 | }, 151 | 'if': 'isShow', 152 | 'ifConditions': [{ 153 | 'exp': 'isShow', 154 | 'block': // ul ast element 155 | }], 156 | 'parent': undefined, 157 | 'plain': false, 158 | 'staticClass': 'list', 159 | 'classBinding': 'bindCls', 160 | 'static': false, 161 | 'staticRoot': false, 162 | 'children': [{ 163 | 'type': 1, 164 | 'tag': 'li', 165 | 'attrsList': [{ 166 | 'name': '@click', 167 | 'value': 'clickItem(index)' 168 | }], 169 | 'attrsMap': { 170 | '@click': 'clickItem(index)', 171 | 'v-for': '(item,index) in data' 172 | }, 173 | 'parent': // ul ast element 174 | 'plain': false, 175 | 'events': { 176 | 'click': { 177 | 'value': 'clickItem(index)' 178 | } 179 | }, 180 | 'hasBindings': true, 181 | 'for': 'data', 182 | 'alias': 'item', 183 | 'iterator1': 'index', 184 | 'static': false, 185 | 'staticRoot': false, 186 | 'children': [ 187 | 'type': 2, 188 | 'expression': '_s(item)+":"+_s(index)' 189 | 'text': '{{item}}:{{index}}', 190 | 'tokens': [ 191 | {'@binding':'item'}, 192 | ':', 193 | {'@binding':'index'} 194 | ], 195 | 'static': false 196 | ] 197 | }] 198 | } 199 | ``` 200 | 201 | 我们发现每一个 AST 元素节点都多了 `staic` 属性,并且 `type` 为 1 的普通元素 AST 节点多了 `staticRoot` 属性。 202 | 203 | ## 总结 204 | 205 | 那么至此我们分析完了 `optimize` 的过程,就是深度遍历这个 AST 树,去检测它的每一颗子树是不是静态节点,如果是静态节点则它们生成 DOM 永远不需要改变,这对运行时对模板的更新起到极大的优化作用。 206 | 207 | 我们通过 `optimize` 我们把整个 AST 树中的每一个 AST 元素节点标记了 `static` 和 `staticRoot`,它会影响我们接下来执行代码生成的过程。 -------------------------------------------------------------------------------- /docs/components/component-register.md: -------------------------------------------------------------------------------- 1 | # 组件注册 2 | 3 | 在 Vue.js 中,除了它内置的组件如 `keep-alive`、`component`、`transition`、`transition-group` 等,其它用户自定义组件在使用前必须注册。很多同学在开发过程中可能会遇到如下报错信息: 4 | 5 | ``` 6 | 'Unknown custom element: - did you register the component correctly? 7 | For recursive components, make sure to provide the "name" option.' 8 | ``` 9 | 10 | 一般报这个错的原因都是我们使用了未注册的组件。Vue.js 提供了 2 种组件的注册方式,全局注册和局部注册。接下来我们从源码分析的角度来分析这两种注册方式。 11 | 12 | ## 全局注册 13 | 14 | 要注册一个全局组件,可以使用 `Vue.component(tagName, options)`。例如: 15 | 16 | ```js 17 | Vue.component('my-component', { 18 | // 选项 19 | }) 20 | ``` 21 | 22 | 那么,`Vue.component` 函数是在什么时候定义的呢,它的定义过程发生在最开始初始化 Vue 的全局函数的时候,代码在 `src/core/global-api/assets.js` 中: 23 | 24 | ```js 25 | import { ASSET_TYPES } from 'shared/constants' 26 | import { isPlainObject, validateComponentName } from '../util/index' 27 | 28 | export function initAssetRegisters (Vue: GlobalAPI) { 29 | /** 30 | * Create asset registration methods. 31 | */ 32 | ASSET_TYPES.forEach(type => { 33 | Vue[type] = function ( 34 | id: string, 35 | definition: Function | Object 36 | ): Function | Object | void { 37 | if (!definition) { 38 | return this.options[type + 's'][id] 39 | } else { 40 | /* istanbul ignore if */ 41 | if (process.env.NODE_ENV !== 'production' && type === 'component') { 42 | validateComponentName(id) 43 | } 44 | if (type === 'component' && isPlainObject(definition)) { 45 | definition.name = definition.name || id 46 | definition = this.options._base.extend(definition) 47 | } 48 | if (type === 'directive' && typeof definition === 'function') { 49 | definition = { bind: definition, update: definition } 50 | } 51 | this.options[type + 's'][id] = definition 52 | return definition 53 | } 54 | } 55 | }) 56 | } 57 | ``` 58 | 函数首先遍历 `ASSET_TYPES`,得到 `type` 后挂载到 Vue 上 。`ASSET_TYPES` 的定义在 `src/shared/constants.js` 中: 59 | 60 | ```js 61 | export const ASSET_TYPES = [ 62 | 'component', 63 | 'directive', 64 | 'filter' 65 | ] 66 | ``` 67 | 所以实际上 Vue 是初始化了 3 个全局函数,并且如果 `type` 是 `component` 且 `definition` 是一个对象的话,通过 `this.opitons._base.extend`, 相当于 `Vue.extend` 把这个对象转换成一个继承于 Vue 的构造函数,最后通过 `this.options[type + 's'][id] = definition` 把它挂载到 `Vue.options.components` 上。 68 | 69 | 由于我们每个组件的创建都是通过 `Vue.extend` 继承而来,我们之前分析过在继承的过程中有这么一段逻辑: 70 | 71 | ```js 72 | Sub.options = mergeOptions( 73 | Super.options, 74 | extendOptions 75 | ) 76 | ``` 77 | 78 | 也就是说它会把 `Vue.options` 合并到 `Sub.options`,也就是组件的 `options` 上, 然后在组件的实例化阶段,会执行 `merge options` 逻辑,把 `Sub.options.components` 合并到 `vm.$options.components` 上。 79 | 80 | 然后在创建 `vnode` 的过程中,会执行 `_createElement` 方法,我们再来回顾一下这部分的逻辑,它的定义在 `src/core/vdom/create-element.js` 中: 81 | 82 | ```js 83 | export function _createElement ( 84 | context: Component, 85 | tag?: string | Class | Function | Object, 86 | data?: VNodeData, 87 | children?: any, 88 | normalizationType?: number 89 | ): VNode | Array { 90 | // ... 91 | let vnode, ns 92 | if (typeof tag === 'string') { 93 | let Ctor 94 | ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag) 95 | if (config.isReservedTag(tag)) { 96 | // platform built-in elements 97 | vnode = new VNode( 98 | config.parsePlatformTagName(tag), data, children, 99 | undefined, undefined, context 100 | ) 101 | } else if (isDef(Ctor = resolveAsset(context.$options, 'components', tag))) { 102 | // component 103 | vnode = createComponent(Ctor, data, context, children, tag) 104 | } else { 105 | // unknown or unlisted namespaced elements 106 | // check at runtime because it may get assigned a namespace when its 107 | // parent normalizes children 108 | vnode = new VNode( 109 | tag, data, children, 110 | undefined, undefined, context 111 | ) 112 | } 113 | } else { 114 | // direct component options / constructor 115 | vnode = createComponent(tag, data, context, children) 116 | } 117 | // ... 118 | } 119 | ``` 120 | 这里有一个判断逻辑 `isDef(Ctor = resolveAsset(context.$options, 'components', tag))`,先来看一下 `resolveAsset` 的定义,在 `src/core/utils/options.js` 中: 121 | 122 | ```js 123 | /** 124 | * Resolve an asset. 125 | * This function is used because child instances need access 126 | * to assets defined in its ancestor chain. 127 | */ 128 | export function resolveAsset ( 129 | options: Object, 130 | type: string, 131 | id: string, 132 | warnMissing?: boolean 133 | ): any { 134 | /* istanbul ignore if */ 135 | if (typeof id !== 'string') { 136 | return 137 | } 138 | const assets = options[type] 139 | // check local registration variations first 140 | if (hasOwn(assets, id)) return assets[id] 141 | const camelizedId = camelize(id) 142 | if (hasOwn(assets, camelizedId)) return assets[camelizedId] 143 | const PascalCaseId = capitalize(camelizedId) 144 | if (hasOwn(assets, PascalCaseId)) return assets[PascalCaseId] 145 | // fallback to prototype chain 146 | const res = assets[id] || assets[camelizedId] || assets[PascalCaseId] 147 | if (process.env.NODE_ENV !== 'production' && warnMissing && !res) { 148 | warn( 149 | 'Failed to resolve ' + type.slice(0, -1) + ': ' + id, 150 | options 151 | ) 152 | } 153 | return res 154 | } 155 | ``` 156 | 这段逻辑很简单,先通过 `const assets = options[type]` 拿到 `assets`,然后再尝试拿 `assets[id]`,这里有个顺序,先直接使用 `id` 拿,如果不存在,则把 `id` 变成驼峰的形式再拿,如果仍然不存在则在驼峰的基础上把首字母再变成大写的形式再拿,如果仍然拿不到则报错。这样说明了我们在使用 `Vue.component(id, definition)` 全局注册组件的时候,id 可以是连字符、驼峰或首字母大写的形式。 157 | 158 | 那么回到我们的调用 `resolveAsset(context.$options, 'components', tag)`,即拿 `vm.$options.components[tag]`,这样我们就可以在 `resolveAsset` 的时候拿到这个组件的构造函数,并作为 `createComponent` 的钩子的参数。 159 | 160 | ## 局部注册 161 | 162 | Vue.js 也同样支持局部注册,我们可以在一个组件内部使用 `components` 选项做组件的局部注册,例如: 163 | 164 | ```js 165 | import HelloWorld from './components/HelloWorld' 166 | 167 | export default { 168 | components: { 169 | HelloWorld 170 | } 171 | } 172 | ``` 173 | 174 | 其实理解了全局注册的过程,局部注册是非常简单的。在组件的 Vue 的实例化阶段有一个合并 `option` 的逻辑,之前我们也分析过,所以就把 `components` 合并到 `vm.$options.components` 上,这样我们就可以在 `resolveAsset` 的时候拿到这个组件的构造函数,并作为 `createComponent` 的钩子的参数。 175 | 176 | 注意,局部注册和全局注册不同的是,只有该类型的组件才可以访问局部注册的子组件,而全局注册是扩展到 `Vue.options` 下,所以在所有组件创建的过程中,都会从全局的 `Vue.options.components` 扩展到当前组件的 `vm.$options.components` 下,这就是全局注册的组件能被任意使用的原因。 177 | 178 | ## 总结 179 | 180 | 通过这一小节的分析,我们对组件的注册过程有了认识,并理解了全局注册和局部注册的差异。其实在平时的工作中,当我们使用到组件库的时候,往往更通用基础组件都是全局注册的,而编写的特例场景的业务组件都是局部注册的。了解了它们的原理,对我们在工作中到底使用全局注册组件还是局部注册组件是有这非常好的指导意义的。 181 | -------------------------------------------------------------------------------- /docs/components/index.md: -------------------------------------------------------------------------------- 1 | # 组件化 2 | 3 | Vue.js 另一个核心思想是组件化。所谓组件化,就是把页面拆分成多个组件 (component),每个组件依赖的 CSS、JavaScript、模板、图片等资源放在一起开发和维护。组件是资源独立的,组件在系统内部可复用,组件和组件之间可以嵌套。 4 | 5 | 我们在用 Vue.js 开发实际项目的时候,就是像搭积木一样,编写一堆组件拼装生成页面。在 Vue.js 的官网中,也是花了大篇幅来介绍什么是组件,如何编写组件以及组件拥有的属性和特性。 6 | 7 | 那么在这一章节,我们将从源码的角度来分析 Vue 的组件内部是如何工作的,只有了解了内部的工作原理,才能让我们使用它的时候更加得心应手。 8 | 9 | 接下来我们会用 Vue-cli 初始化的代码为例,来分析一下 Vue 组件初始化的一个过程。 10 | 11 | ```js 12 | import Vue from 'vue' 13 | import App from './App.vue' 14 | 15 | var app = new Vue({ 16 | el: '#app', 17 | // 这里的 h 是 createElement 方法 18 | render: h => h(App) 19 | }) 20 | ``` 21 | 这段代码相信很多同学都很熟悉,它和我们上一章相同的点也是通过 `render` 函数去渲染的,不同的这次通过 `createElement` 传的参数是一个组件而不是一个原生的标签,那么接下来我们就开始分析这一过程。 -------------------------------------------------------------------------------- /docs/components/lifecycle.md: -------------------------------------------------------------------------------- 1 | # 生命周期 2 | 3 | 每个 Vue 实例在被创建之前都要经过一系列的初始化过程。例如需要设置数据监听、编译模板、挂载实例到 DOM、在数据变化时更新 DOM 等。同时在这个过程中也会运行一些叫做生命周期钩子的函数,给予用户机会在一些特定的场景下添加他们自己的代码。 4 | 5 | 6 | 7 | 在我们实际项目开发过程中,会非常频繁地和 Vue 组件的生命周期打交道,接下来我们就从源码的角度来看一下这些生命周期的钩子函数是如何被执行的。 8 | 9 | 源码中最终执行生命周期的函数都是调用 `callHook` 方法,它的定义在 `src/core/instance/lifecycle` 中: 10 | 11 | ```js 12 | export function callHook (vm: Component, hook: string) { 13 | // #7573 disable dep collection when invoking lifecycle hooks 14 | pushTarget() 15 | const handlers = vm.$options[hook] 16 | if (handlers) { 17 | for (let i = 0, j = handlers.length; i < j; i++) { 18 | try { 19 | handlers[i].call(vm) 20 | } catch (e) { 21 | handleError(e, vm, `${hook} hook`) 22 | } 23 | } 24 | } 25 | if (vm._hasHookEvent) { 26 | vm.$emit('hook:' + hook) 27 | } 28 | popTarget() 29 | } 30 | ``` 31 | 32 | `callHook` 函数的逻辑很简单,根据传入的字符串 `hook`,去拿到 `vm.$options[hook]` 对应的回调函数数组,然后遍历执行,执行的时候把 `vm` 作为函数执行的上下文。 33 | 34 | 在上一节中,我们详细地介绍了 Vue.js 合并 `options` 的过程,各个阶段的生命周期的函数也被合并到 `vm.$options` 里,并且是一个数组。因此 `callhook` 函数的功能就是调用某个生命周期钩子注册的所有回调函数。 35 | 36 | 了解了生命周期的执行方式后,接下来我们会具体介绍每一个生命周期函数它的调用时机。 37 | 38 | ## beforeCreate & created 39 | 40 | `beforeCreate` 和 `created` 函数都是在实例化 `Vue` 的阶段,在 `_init` 方法中执行的,它的定义在 `src/core/instance/init.js` 中: 41 | 42 | ```js 43 | Vue.prototype._init = function (options?: Object) { 44 | // ... 45 | initLifecycle(vm) 46 | initEvents(vm) 47 | initRender(vm) 48 | callHook(vm, 'beforeCreate') 49 | initInjections(vm) // resolve injections before data/props 50 | initState(vm) 51 | initProvide(vm) // resolve provide after data/props 52 | callHook(vm, 'created') 53 | // ... 54 | } 55 | ``` 56 | 57 | 可以看到 `beforeCreate` 和 `created` 的钩子调用是在 `initState` 的前后,`initState` 的作用是初始化 `props`、`data`、`methods`、`watch`、`computed` 等属性,之后我们会详细分析。那么显然 `beforeCreate` 的钩子函数中就不能获取到 `props`、`data` 中定义的值,也不能调用 `methods` 中定义的函数。 58 | 59 | 在这俩个钩子函数执行的时候,并没有渲染 DOM,所以我们也不能够访问 DOM,一般来说,如果组件在加载的时候需要和后端有交互,放在这俩个钩子函数执行都可以,如果是需要访问 `props`、`data` 等数据的话,就需要使用 `created` 钩子函数。之后我们会介绍 vue-router 和 vuex 的时候会发现它们都混合了 `beforeCreatd` 钩子函数。 60 | 61 | ## beforeMount & mounted 62 | 63 | 顾名思义,`beforeMount` 钩子函数发生在 `mount`,也就是 DOM 挂载之前,它的调用时机是在 `mountComponent` 函数中,定义在 `src/core/instance/lifecycle.js` 中: 64 | 65 | ```js 66 | export function mountComponent ( 67 | vm: Component, 68 | el: ?Element, 69 | hydrating?: boolean 70 | ): Component { 71 | vm.$el = el 72 | // ... 73 | callHook(vm, 'beforeMount') 74 | 75 | let updateComponent 76 | /* istanbul ignore if */ 77 | if (process.env.NODE_ENV !== 'production' && config.performance && mark) { 78 | updateComponent = () => { 79 | const name = vm._name 80 | const id = vm._uid 81 | const startTag = `vue-perf-start:${id}` 82 | const endTag = `vue-perf-end:${id}` 83 | 84 | mark(startTag) 85 | const vnode = vm._render() 86 | mark(endTag) 87 | measure(`vue ${name} render`, startTag, endTag) 88 | 89 | mark(startTag) 90 | vm._update(vnode, hydrating) 91 | mark(endTag) 92 | measure(`vue ${name} patch`, startTag, endTag) 93 | } 94 | } else { 95 | updateComponent = () => { 96 | vm._update(vm._render(), hydrating) 97 | } 98 | } 99 | 100 | // we set this to vm._watcher inside the watcher's constructor 101 | // since the watcher's initial patch may call $forceUpdate (e.g. inside child 102 | // component's mounted hook), which relies on vm._watcher being already defined 103 | new Watcher(vm, updateComponent, noop, { 104 | before () { 105 | if (vm._isMounted) { 106 | callHook(vm, 'beforeUpdate') 107 | } 108 | } 109 | }, true /* isRenderWatcher */) 110 | hydrating = false 111 | 112 | // manually mounted instance, call mounted on self 113 | // mounted is called for render-created child components in its inserted hook 114 | if (vm.$vnode == null) { 115 | vm._isMounted = true 116 | callHook(vm, 'mounted') 117 | } 118 | return vm 119 | } 120 | ``` 121 | 在执行 `vm._render()` 函数渲染 VNode 之前,执行了 `beforeMount` 钩子函数,在执行完 `vm._update()` 把 VNode patch 到真实 DOM 后,执行 `mouted` 钩子。注意,这里对 `mouted` 钩子函数执行有一个判断逻辑,`vm.$vnode` 如果为 `null`,则表明这不是一次组件的初始化过程,而是我们通过外部 `new Vue` 初始化过程。那么对于组件,它的 `mounted` 时机在哪儿呢? 122 | 123 | 之前我们提到过,组件的 VNode patch 到 DOM 后,会执行 `invokeInsertHook` 函数,把 `insertedVnodeQueue` 里保存的钩子函数依次执行一遍,它的定义在 `src/core/vdom/patch.js` 中: 124 | 125 | ```js 126 | function invokeInsertHook (vnode, queue, initial) { 127 | // delay insert hooks for component root nodes, invoke them after the 128 | // element is really inserted 129 | if (isTrue(initial) && isDef(vnode.parent)) { 130 | vnode.parent.data.pendingInsert = queue 131 | } else { 132 | for (let i = 0; i < queue.length; ++i) { 133 | queue[i].data.hook.insert(queue[i]) 134 | } 135 | } 136 | } 137 | ``` 138 | 该函数会执行 `insert` 这个钩子函数,对于组件而言,`insert` 钩子函数的定义在 `src/core/vdom/create-component.js` 中的 `componentVNodeHooks` 中: 139 | 140 | ```js 141 | const componentVNodeHooks = { 142 | // ... 143 | insert (vnode: MountedComponentVNode) { 144 | const { context, componentInstance } = vnode 145 | if (!componentInstance._isMounted) { 146 | componentInstance._isMounted = true 147 | callHook(componentInstance, 'mounted') 148 | } 149 | // ... 150 | }, 151 | } 152 | ``` 153 | 我们可以看到,每个子组件都是在这个钩子函数中执行 `mouted` 钩子函数,并且我们之前分析过,`insertedVnodeQueue` 的添加顺序是先子后父,所以对于同步渲染的子组件而言,`mounted` 钩子函数的执行顺序也是先子后父。 154 | 155 | ## beforeUpdate & updated 156 | 157 | 顾名思义,`beforeUpdate` 和 `updated` 的钩子函数执行时机都应该是在数据更新的时候,到目前为止,我们还没有分析 Vue 的数据双向绑定、更新相关,下一章我会详细介绍这个过程。 158 | 159 | `beforeUpdate` 的执行时机是在渲染 Watcher 的 `before` 函数中,我们刚才提到过: 160 | 161 | ```js 162 | export function mountComponent ( 163 | vm: Component, 164 | el: ?Element, 165 | hydrating?: boolean 166 | ): Component { 167 | // ... 168 | 169 | // we set this to vm._watcher inside the watcher's constructor 170 | // since the watcher's initial patch may call $forceUpdate (e.g. inside child 171 | // component's mounted hook), which relies on vm._watcher being already defined 172 | new Watcher(vm, updateComponent, noop, { 173 | before () { 174 | if (vm._isMounted) { 175 | callHook(vm, 'beforeUpdate') 176 | } 177 | } 178 | }, true /* isRenderWatcher */) 179 | // ... 180 | } 181 | 182 | ``` 183 | 注意这里有个判断,也就是在组件已经 mounted 之后,才会去调用这个钩子函数。 184 | 185 | `update` 的执行时机是在`flushSchedulerQueue` 函数调用的时候, 它的定义在 `src/core/observer/scheduler.js` 中: 186 | 187 | ```js 188 | function flushSchedulerQueue () { 189 | // ... 190 | // 获取到 updatedQueue 191 | callUpdatedHooks(updatedQueue) 192 | } 193 | 194 | function callUpdatedHooks (queue) { 195 | let i = queue.length 196 | while (i--) { 197 | const watcher = queue[i] 198 | const vm = watcher.vm 199 | if (vm._watcher === watcher && vm._isMounted) { 200 | callHook(vm, 'updated') 201 | } 202 | } 203 | } 204 | ``` 205 | 206 | `flushSchedulerQueue` 函数我们之后会详细介绍,可以先大概了解一下,`updatedQueue` 是 更新了的 `wathcer` 数组,那么在 `callUpdatedHooks` 函数中,它对这些数组做遍历,只有满足当前 `watcher` 为 `vm._watcher` 以及组件已经 `mounted` 这两个条件,才会执行 `updated` 钩子函数。 207 | 208 | 我们之前提过,在组件 mount 的过程中,会实例化一个渲染的 `Watcher` 去监听 `vm` 上的数据变化重新渲染,这断逻辑发生在 `mountComponent` 函数执行的时候: 209 | 210 | ```js 211 | export function mountComponent ( 212 | vm: Component, 213 | el: ?Element, 214 | hydrating?: boolean 215 | ): Component { 216 | // ... 217 | // 这里是简写 218 | let updateComponent = () => { 219 | vm._update(vm._render(), hydrating) 220 | } 221 | new Watcher(vm, updateComponent, noop, { 222 | before () { 223 | if (vm._isMounted) { 224 | callHook(vm, 'beforeUpdate') 225 | } 226 | } 227 | }, true /* isRenderWatcher */) 228 | // ... 229 | } 230 | ``` 231 | 那么在实例化 `Watcher` 的过程中,在它的构造函数里会判断 `isRenderWatcher`,接着把当前 `watcher` 的实例赋值给 `vm._watcher`,定义在 `src/core/observer/watcher.js` 中: 232 | 233 | ```js 234 | export default class Watcher { 235 | // ... 236 | constructor ( 237 | vm: Component, 238 | expOrFn: string | Function, 239 | cb: Function, 240 | options?: ?Object, 241 | isRenderWatcher?: boolean 242 | ) { 243 | this.vm = vm 244 | if (isRenderWatcher) { 245 | vm._watcher = this 246 | } 247 | vm._watchers.push(this) 248 | // ... 249 | } 250 | } 251 | ``` 252 | 253 | 同时,还把当前 `wathcer` 实例 push 到 `vm._watchers` 中,`vm._watcher` 是专门用来监听 `vm` 上数据变化然后重新渲染的,所以它是一个渲染相关的 `watcher`,因此在 `callUpdatedHooks` 函数中,只有 `vm._watcher` 的回调执行完毕后,才会执行 `updated` 钩子函数。 254 | 255 | ## beforeDestroy & destroyed 256 | 257 | 顾名思义,`beforeDestroy` 和 `destroyed` 钩子函数的执行时机在组件销毁的阶段,组件的销毁过程之后会详细介绍,最终会调用 `$destroy` 方法,它的定义在 `src/core/instance/lifecycle.js` 中: 258 | 259 | ```js 260 | Vue.prototype.$destroy = function () { 261 | const vm: Component = this 262 | if (vm._isBeingDestroyed) { 263 | return 264 | } 265 | callHook(vm, 'beforeDestroy') 266 | vm._isBeingDestroyed = true 267 | // remove self from parent 268 | const parent = vm.$parent 269 | if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) { 270 | remove(parent.$children, vm) 271 | } 272 | // teardown watchers 273 | if (vm._watcher) { 274 | vm._watcher.teardown() 275 | } 276 | let i = vm._watchers.length 277 | while (i--) { 278 | vm._watchers[i].teardown() 279 | } 280 | // remove reference from data ob 281 | // frozen object may not have observer. 282 | if (vm._data.__ob__) { 283 | vm._data.__ob__.vmCount-- 284 | } 285 | // call the last hook... 286 | vm._isDestroyed = true 287 | // invoke destroy hooks on current rendered tree 288 | vm.__patch__(vm._vnode, null) 289 | // fire destroyed hook 290 | callHook(vm, 'destroyed') 291 | // turn off all instance listeners. 292 | vm.$off() 293 | // remove __vue__ reference 294 | if (vm.$el) { 295 | vm.$el.__vue__ = null 296 | } 297 | // release circular reference (#6759) 298 | if (vm.$vnode) { 299 | vm.$vnode.parent = null 300 | } 301 | } 302 | ``` 303 | `beforeDestroy` 钩子函数的执行时机是在 `$destroy` 函数执行最开始的地方,接着执行了一系列的销毁动作,包括从 `parent` 的 `$children` 中删掉自身,删除 `watcher`,当前渲染的 VNode 执行销毁钩子函数等,执行完毕后再调用 `destroy` 钩子函数。 304 | 305 | 在 `$destroy` 的执行过程中,它又会执行 ` vm.__patch__(vm._vnode, null)` 触发它子组件的销毁钩子函数,这样一层层的递归调用,所以 `destroy` 钩子函数执行顺序是先子后父,和 `mounted` 过程一样。 306 | 307 | ## activated & deactivated 308 | 309 | `activated` 和 `deactivated` 钩子函数是专门为 `keep-alive` 组件定制的钩子,我们会在介绍 `keep-alive` 组件的时候详细介绍,这里先留个悬念。 310 | 311 | ## 总结 312 | 313 | 这一节主要介绍了 Vue 生命周期中各个钩子函数的执行时机以及顺序,通过分析,我们知道了如在 `created` 钩子函数中可以访问到数据,在 `mounted` 钩子函数中可以访问到 DOM,在 `destroy` 钩子函数中可以做一些定时器销毁工作,了解它们有利于我们在合适的生命周期去做不同的事情。 314 | 315 | -------------------------------------------------------------------------------- /docs/cyarn使用手册.md: -------------------------------------------------------------------------------- 1 | ### CYarn使用手册 2 | `yarn install` 命令用来安装工程的所有依赖,这是下载工程代码常做的事情,也可能是其他的开发人员在工程里新增加的功能,你需要去down下来 3 | 4 | 如果你曾经使用npm的话,你可能会经常使用到--save或--save-dev, 这些命令在yarn中已经被yarn add和yarn add --dev所取代,参考更多信息[https://yarnpkg.com/en/docs/cli/add](https://yarnpkg.com/en/docs/cli/add "documentation") 5 | 6 | 1. yarn install 7 | 8 | 安装package.json的所有依赖列表到node-modules目录 9 | 2. yarn install --flat 10 | 仅仅允许包有一个版本, 第一次运行会提醒为每一个依赖包选择一个版本 11 | -------------------------------------------------------------------------------- /docs/data-driven/create-element.md: -------------------------------------------------------------------------------- 1 | # createElement 2 | 3 | Vue.js 利用 createElement 方法创建 VNode,它定义在 `src/core/vdom/create-elemenet.js` 中: 4 | 5 | ```js 6 | // wrapper function for providing a more flexible interface 7 | // without getting yelled at by flow 8 | export function createElement ( 9 | context: Component, 10 | tag: any, 11 | data: any, 12 | children: any, 13 | normalizationType: any, 14 | alwaysNormalize: boolean 15 | ): VNode | Array { 16 | if (Array.isArray(data) || isPrimitive(data)) { 17 | normalizationType = children 18 | children = data 19 | data = undefined 20 | } 21 | if (isTrue(alwaysNormalize)) { 22 | normalizationType = ALWAYS_NORMALIZE 23 | } 24 | return _createElement(context, tag, data, children, normalizationType) 25 | } 26 | ``` 27 | 28 | `createElement` 方法实际上是对 `_createElement` 方法的封装,它允许传入的参数更加灵活,在处理这些参数后,调用真正创建 VNode 的函数 `_createElement`: 29 | 30 | ```js 31 | export function _createElement ( 32 | context: Component, 33 | tag?: string | Class | Function | Object, 34 | data?: VNodeData, 35 | children?: any, 36 | normalizationType?: number 37 | ): VNode | Array { 38 | if (isDef(data) && isDef((data: any).__ob__)) { 39 | process.env.NODE_ENV !== 'production' && warn( 40 | `Avoid using observed data object as vnode data: ${JSON.stringify(data)}\n` + 41 | 'Always create fresh vnode data objects in each render!', 42 | context 43 | ) 44 | return createEmptyVNode() 45 | } 46 | // object syntax in v-bind 47 | if (isDef(data) && isDef(data.is)) { 48 | tag = data.is 49 | } 50 | if (!tag) { 51 | // in case of component :is set to falsy value 52 | return createEmptyVNode() 53 | } 54 | // warn against non-primitive key 55 | if (process.env.NODE_ENV !== 'production' && 56 | isDef(data) && isDef(data.key) && !isPrimitive(data.key) 57 | ) { 58 | if (!__WEEX__ || !('@binding' in data.key)) { 59 | warn( 60 | 'Avoid using non-primitive value as key, ' + 61 | 'use string/number value instead.', 62 | context 63 | ) 64 | } 65 | } 66 | // support single function children as default scoped slot 67 | if (Array.isArray(children) && 68 | typeof children[0] === 'function' 69 | ) { 70 | data = data || {} 71 | data.scopedSlots = { default: children[0] } 72 | children.length = 0 73 | } 74 | if (normalizationType === ALWAYS_NORMALIZE) { 75 | children = normalizeChildren(children) 76 | } else if (normalizationType === SIMPLE_NORMALIZE) { 77 | children = simpleNormalizeChildren(children) 78 | } 79 | let vnode, ns 80 | if (typeof tag === 'string') { 81 | let Ctor 82 | ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag) 83 | if (config.isReservedTag(tag)) { 84 | // platform built-in elements 85 | vnode = new VNode( 86 | config.parsePlatformTagName(tag), data, children, 87 | undefined, undefined, context 88 | ) 89 | } else if (isDef(Ctor = resolveAsset(context.$options, 'components', tag))) { 90 | // component 91 | vnode = createComponent(Ctor, data, context, children, tag) 92 | } else { 93 | // unknown or unlisted namespaced elements 94 | // check at runtime because it may get assigned a namespace when its 95 | // parent normalizes children 96 | vnode = new VNode( 97 | tag, data, children, 98 | undefined, undefined, context 99 | ) 100 | } 101 | } else { 102 | // direct component options / constructor 103 | vnode = createComponent(tag, data, context, children) 104 | } 105 | if (Array.isArray(vnode)) { 106 | return vnode 107 | } else if (isDef(vnode)) { 108 | if (isDef(ns)) applyNS(vnode, ns) 109 | if (isDef(data)) registerDeepBindings(data) 110 | return vnode 111 | } else { 112 | return createEmptyVNode() 113 | } 114 | } 115 | ``` 116 | 117 | `_createElement` 方法有 5 个参数,`context` 表示 VNode 的上下文环境,它是 `Component` 类型;`tag` 表示标签,它可以是一个字符串,也可以是一个 `Component`;`data` 表示 VNode 的数据,它是一个 `VNodeData` 类型,可以在 `flow/vnode.js` 中找到它的定义,这里先不展开说;`children` 表示当前 VNode 的子节点,它是任意类型的,它接下来需要被规范为标准的 VNode 数组;`normalizationType` 表示子节点规范的类型,类型不同规范的方法也就不一样,它主要是参考 `render` 函数是编译生成的还是用户手写的。 118 | 119 | `createElement` 函数的流程略微有点多,我们接下来主要分析 2 个重点的流程 —— `children` 的规范化以及 VNode 的创建。 120 | 121 | ## children 的规范化 122 | 123 | 由于 Virtual DOM 实际上是一个树状结构,每一个 VNode 可能会有若干个子节点,这些子节点应该也是 VNode 的类型。`_createElement` 接收的第 4 个参数 children 是任意类型的,因此我们需要把它们规范成 VNode 类型。 124 | 125 | 这里根据 `normalizationType` 的不同,调用了 `normalizeChildren(children)` 和 `simpleNormalizeChildren(children)` 方法,它们的定义都在 `src/core/vdom/helpers/normalzie-children.js` 中: 126 | 127 | ```js 128 | // The template compiler attempts to minimize the need for normalization by 129 | // statically analyzing the template at compile time. 130 | // 131 | // For plain HTML markup, normalization can be completely skipped because the 132 | // generated render function is guaranteed to return Array. There are 133 | // two cases where extra normalization is needed: 134 | 135 | // 1. When the children contains components - because a functional component 136 | // may return an Array instead of a single root. In this case, just a simple 137 | // normalization is needed - if any child is an Array, we flatten the whole 138 | // thing with Array.prototype.concat. It is guaranteed to be only 1-level deep 139 | // because functional components already normalize their own children. 140 | export function simpleNormalizeChildren (children: any) { 141 | for (let i = 0; i < children.length; i++) { 142 | if (Array.isArray(children[i])) { 143 | return Array.prototype.concat.apply([], children) 144 | } 145 | } 146 | return children 147 | } 148 | 149 | // 2. When the children contains constructs that always generated nested Arrays, 150 | // e.g.