导图社区 前端开发学习整理笔记
前端开发学习整理笔记,主要包括Vue.js 框架源码与进阶,JavaScript核心原理、前端工程化实战三部分内容。
编辑于2021-11-30 02:07:22学习
Part1JavaScript深度剖析、
函数式编程 与 JS异步编程、Promise
任务一(函数式编程范式)
函数
函数是一等公民
函数可以存储在变量中
函数作为参数
函数作为返回值
函数的定义方式
1. 声明式函数定义:function() { }
2. 函数表达式:let fun = function(){ }
总结:第一种和第二种函数的定义的方式其实是第三种new Function 的语法糖,当我们定义函数时候都会通过 new Function 来创建一个函数,只是前两种为我们进行了封装,我们看不见了而已,js 中任意函数都是Function 的实例。2、ECMAScript 定义的 函数实际上是功能完整的对象。
1. function() { } 这种定义方式,会将函数声明提升到该函数所在作用域的最开头,也是就无论你在这个函数的最小作用域的那儿使用这种方式声明的函数,在这个作用域内,你都可以调用这个函数为你所用。 2. let fun = function(){ } 方式定义的函数,只能在该作用域中,这段赋值代码执行之后才能通过fun()调用函数,否则,由于变量声明提升,fun === undefined。 3. new Function -> var fun1 = new Function (arg1 , arg2 ,arg3 ,…, argN , body );
3. new Function
注意:千万不要使用字面量方式来定义属性和方法,否则原有属性和方法会被重写
函数(ES5)调用形式
func(p1,p2)
obj.child.method(p1, p2)
语法糖
func(p1, p2) 等价于 func.call(undefined, p1, p2) obj.child.method(p1, p2) 等价于 obj.child.method.call(obj.child, p1, p2)
正常调用形式: func.call(context, p1, p2)
箭头函数
实际上箭头函数里并没有 this,如果你在箭头函数里看到 this,你直接把它当作箭头函数外面的 this 即可。外面的 this 是什么,箭头函数里面的 this 就还是什么,因为箭头函数本身不支持 this。 总结 1. this 就是你 call 一个函数时,传入的第一个参数。(请务必背下来「this 就是 call 的第一个参数」) 2. 如果你的函数调用形式不是 call 形式,请按照「转换代码」将其转换为 call 形式。
this
var o = { user:"追梦子", fn:function(){ console.log(this.user); } } o.fn(); 这里的this指向的是对象o,因为你调用这个fn是通过o.fn()执行的,那自然指向就是对象o,这里再次强调一点,this的指向在函数创建的时候是决定不了的,在调用的时候才能决定,谁调用的就指向谁,一定要搞清楚这个。 分情况: 情况1:如果一个函数中有this,但是它没有被上一级的对象所调用,那么this指向的就是window,这里需要说明的是在js的严格版中this指向的不是window,但是我们这里不探讨严格版的问题,你想了解可以自行上网查找。 情况2:如果一个函数中有this,这个函数有被上一级的对象所调用,那么this指向的就是上一级的对象。 情况3:如果一个函数中有this,这个函数中包含多个对象,尽管这个函数是被最外层的对象所调用,this指向的也只是它上一级的对象
特殊示例
知识点补充: Javascript 是一个文本作用域的语言, 就是说, 一个变量的作用域, 在写这个变量的时候确定. this 关键字是为了在 JS 中加入动态作用域而做的努力. 所谓动态作用域, 就是说变量的作用范围, 是根据函数调用的位置而定的. 从这个角度来理解 this, 就简单的多. this 是 JS 中的动态作用域机制, 具体来说有四种, 优先级有低到高分别如下: 1. 默认的 this 绑定, 就是说 在一个函数中使用了 this, 但是没有为 this 绑定对象. 这种情况下, 非严格默认, this 就是全局变量 Node 环境中的 global, 浏览器环境中的 window.在严格版中的默认的this不再是window,而是undefined 2. 隐式绑定: 使用 obj.foo() 这样的语法来调用函数的时候, 函数 foo 中的 this 绑定到 obj 对象. 3. 显示绑定: foo.call(obj, ...), foo.apply(obj,[...]), foo.bind(obj,...) 4. 构造绑定: new foo() , 这种情况, 无论 foo 是否做了绑定, 都要创建一个新的对象, 然后 foo 中的 this 引用这个对象. var o = { a:10, b:{ a:12, fn:function(){ console.log(this.a); //undefined console.log(this); //window } } } var j = o.b.fn; j(); 这里this指向的是window this永远指向的是最后调用它的对象,也就是看它执行的时候是谁调用的, 例子中虽然函数fn是被对象b所引用, 但是在将fn赋值给变量j的时候并没有执行所以最终指向的是window ===================================== function fn(){ this.num = 1; } var a = new fn(); console.log(a.num); //1 为什么this会指向a?首先new关键字会创建一个空的对象,然后会自动调用函数apply方法或者call,将this指向这个空对象,这样的话函数内部的this就会被这个空的对象替代。
函数式编程
高阶函数
可以把函数作为参数传递给另外一个函数
可以把函数作为另一个函数的返回结果
函数作为参数
纯函数
相同的输入永远会得到相同的输出
可缓存
可测试
并行处理
好处
闭包
函数在执行的时候会放到一个执行栈上当函数执行完毕之后会从执行栈上移除,但是堆上的作用域成员因为被外部引用不能释放,因此内部函数依然可以访问外部函数的成员
柯里化与副作用
副作用让一个函数变的不纯,纯函数的根据相同的输入返回相同的输出,如果函数依赖于外部的状态就无法保证输出相同,就会带来副作用。
1.柯里化可以让我们给一个函数传递较少的参数得到一个已经记住了某些固定参数的新函数,2.这是一种对函数参数的'缓存' 3.让函数变的更灵活,让函数的粒度更小 可以把多元函数转换成一元函数,可以组合使用函数产生强大的功能
组合函数
如果一个函数要经过多个函数处理才能得到最终值,这个时候可以把中间 过程的函数合并成一个函数
函数组合默认是从右到左执行
PonitFree(函数的组合)
不需要指明处理的数据
只需要合成运算过程
需要定义一些辅助的基本运算函数
Functor函子
控制副作用
处理异常
处理异步操作
函数式编程库
Lodash
lodash 是一个纯函数的功能库,提供了对数组、数字、对象、字符串、函数等操作的一些方法
Folktale
函数组合
处理异步任务
任务二(JavaSrcipt异步编程)
js异步编程
事件循环 - event loop
进程和线程
异步
ES新特性 与 TypeScript、js性能优化
ECMAScript
ES2015
ES6
对象
new操作符
1. new 创建并返回了一个新对象,是构造函数的实例 2. 对象的实例的构造函数属性其实是构造函数的原型对象的 constructor 属性 3. 对象实例的 __proto__ 关联到构造函数的原型对象 关键两步: (1)将新创建对象的原型链设置正确,这样我们才能使用原型链上的方法。 (2)将新创建的对象作为构造函数执行的上下文,这样我们才能正确地进行一些初始化操作。
1. 创建一个空对象obj({}); 2. 将obj的[[prototype]]属性指向构造函数constrc的原型(即obj.[[prototype]] = constrc.prototype)。 3. 将构造函数constrc内部的this绑定到新建的对象obj,执行constrc(也就是跟调用普通函数一样,只是此时函数的this为新创建的对象obj而已,就好像执行obj.constrc()一样); 4. 若构造函数没有返回非原始值(即不是引用类型的值),则返回该新建的对象obj(默认会添加return this)。否则,返回引用类型的值。 自己实现new操作符 function myNew(constrc, ...args) { // 1,2 创建一个对象obj,将obj的[[prototype]]属性指向构造函数的原型对象 // 即实现:obj.__proto__ === constructor.prototype const obj = Object.create(constrc.prototype) // 3.将constrc内部的this(即执行上下文)指向obj,并执行 const result = constrc.apply(obj, args); // 4. 如果构造函数返回的是对象,则使用构造函数执行的结果。否则,返回新创建的对象 return result instanceof Object ? result : obj; } // 使用的例子: function Person(name, age){ this.name = name; this.age = age; } const person1 = myNew(Person, 'Tom', 20) console.log(person1) // Person {name: "Tom", age: 20}
apply
bind
call
异同: 都可以改变函数 func 的 this 指向;call 和 apply 的区别在于,传参的写法不同:apply 的第 2 个参数为数组; call 则是从第 2 个至第 N 个都是给 func 的传参;而 bind 和这两个(call、apply)又不同,bind 虽然改变了 func 的 this 指向,但不是马上执行,而这两个(call、apply)是在改变了函数的 this 指向之后立马执行。
func.call(thisArg, param1, param2, ...) func.apply(thisArg, [param1,param2,...]) func.bind(thisArg, param1, param2, ...)
JS
基本
1. 但凡创建了对象(无论是函数对象还是普通对象),都自带一个__proto__属性,可称为隐式对象。一个对象的隐式原型指向构造该对象构造函数的原型,这也保证了实例能够访问在构造函数原型中的定义的属性和方法
通过字面量构造出来的对象,其__proto__指向Object.prototype
let obj = { name:"rays77", age:"17" } //通过字面量构造出来的对象,其[[prototype]]指向Object.prototype console.log('obj.prototype -> ',Object.prototype)
2.其中函数对象除了和其他对象一样有上述_proto_属性之外,还有自己特有的属性——原型属性(prototype),这个属性是一个指针,指向一个对象,这个对象的用途就是包含所有实例共享的属性和方法(我们把这个对象叫做原型对象)。原型对象也有一个属性,叫做constructor,这个属性包含了一个指针,指回原构造函数。
1. __proto__和constructor属性是对象所独有的; 2. prototype属性是函数所独有的。
继承
function inherit(Child, Parent) { // 继承原型上的属性 Child.prototype = Object.create(Parent.prototype) // 修复 constructor Child.prototype.constructor = Child // 存储超类 Child.super = Parent // 静态属性继承 if (Object.setPrototypeOf) { // setPrototypeOf es6 Object.setPrototypeOf(Child, Parent) } else if (Child.__proto__) { // __proto__ es6 引入,但是部分浏览器早已支持 Child.__proto__ = Parent } else { // 兼容 IE10 等陈旧浏览器 // 将 Parent 上的静态属性和方法拷贝一份到 Child 上,不会覆盖 Child 上的方法 for (var k in Parent) { if (Parent.hasOwnProperty(k) && !(k in Child)) { Child[k] = Parent[k] } } } }
构造函数
通过 new 函数名 来实例化对象的函数叫构造函数
new 做了哪些事情:new 申请内存, 创建对象,当调用new时,后台会隐式执行new Object()创建对象。所以,通过new创建的字符串、数字是引用类型,而是非值类型
constructor : 是每一个实例对象都拥有的属性
 constructor属性也是对象才拥有的,它是从一个对象指向一个函数,含义就是指向该对象的构造函数,每个对象都有构造函数(本身拥有或继承而来,继承而来的要结合__proto__属性查看会更清楚点,如下图所示),从上图中可以看出Function这个对象比较特殊,它的构造函数就是它自己(因为Function可以看成是一个函数,也可以是一个对象),所有函数A和对象最终都是由Function构造函数得来,所以constructor属性的终点就是Function这个函数 function Person(name, age) { this.name = name this.age = age } var person1 = new Person('rays77', 18) console.log( 'person1.__proto__ === Person.prototype => ', person1.__proto__ === Person.prototype ? '相等' : '不等' ) console.log( 'person1.constructor === Person => ', person1.constructor === Person ? '相等' : '不等' )
构造函数的主要 功能为 初始化对象,特点是和new 一起使用。new就是在创建对象,从无到有,构造函数就是在为初始化的对象添加属性和方法。构造函数定义时首字母大写
原型对象
它是从一个函数指向一个对象。它的含义是函数的原型对象,也就是这个函数(其实所有函数都可以作为构造函数)所创建的实例的原型对象
prototype的作用: 就是包含可以由特定类型的所有实例共享的属性和方法,也就是让该函数所实例化的对象们都可以找到公用的属性和方法。任何函数在创建的时候,其实会默认同时创建该函数的prototype对象。
原型链
通过__proto__属性来连接对象直到null的一条链即为我们所谓的原型链
prototype、__proto__与constructor
一、我们需要牢记两点: ①__proto__和constructor属性是对象所独有的; ② prototype属性是函数所独有的,因为函数也是一种对象,所以函数也拥有__proto__和constructor属性。 二、__proto__属性的作用就是当访问一个对象的属性时,如果该对象内部不存在这个属性,那么就会去它的__proto__属性所指向的那个对象(父对象)里找,一直找,直到__proto__属性的终点null,再往上找就相当于在null上取值,会报错。通过__proto__属性将对象连接起来的这条链路即我们所谓的原型链。 三、prototype属性的作用就是让该函数所实例化的对象们都可以找到公用的属性和方法,即f1.__proto__ === Foo.prototype。 四、constructor属性的含义就是指向该对象的构造函数,所有函数(此时看成对象了)最终的构造函数都指向Function。
TypeScript
强类型与弱类型
静态类型与动态类型
强类型的优势
错误更早暴露
代码更加只能,编码更加准确
重构更牢靠有保障
减少不必要的类型判断
Flow
一种工具
Typescript语法
性能优化
内存管理
子主题
垃圾回收与常见的GC算法
V8引擎的垃圾回收
Performance工具
代码优化实例
Part2 前端工程化实战
解决问题
使用ES6+新特性,有兼容性问题
使用Less / Sass 、PostCss增强Css的编程性,运行环境不能直接支持
使用模块化的方式提高项目的可维护性,但运行环境不能直接支持
部署上线前需要手动压缩代码以及资源文件、部署过程需要手动上传代码到服务器
多人协作开发,无法硬性统一大家的代码风格,从库中pull下来的代码质量无法保证
1. 传统语言或语法的弊端 2. 无法使用模块化、组件化 3. 重复的机械式的工作 4. 代码风格统一、质量保证 5. 依赖后端服务接口支持 6. 整体依赖后端项目
工程化步骤
创建项目
脚手架工具
相同的组织结构
相同的开发范式
相同的模块依赖
相同的工具配置
相同的基础代码
编码
a. 格式化代码 b. 检验代码风格 c. 编译、构建、打包
预览、测试
webServer / Mock
热更新 Live Reloading / HMR
Source Map 调试定位源代码
提交
Git Hooks 项目代码质量检查
Lint-staged
持续集成
部署
CI / CD
自动发布
自动化创建
模块化规范
模块
ADMJS
Reruire.js 实现了该规范、 异步加载模块,适合浏览器 缺点: 1. AMD使用起来相对复杂 2. 模块JS文件请求频繁
ES Modules
自动采用严格模式
每个ESM模块都是单独的私有作用域
ESM是通过CORS去请求外部JS模块的
ESM的script标签会延迟执行脚本
主流模块化标准 import/export
注意事项
ES Modules中可以导入CommonJS模块
CommonJS中不能导入ES Modules模块
CommonJS始终只会导出一个默认成员
注意:import不是解构导出对象
CommonJS
1. 一个文件就是一个模块 2. 每个模块都有独立的作用域 3. 通过module.exports导出成员 4. 通过require函数载入模块 5. 以同步模式加载模块
webpack5
功能
打包 :将不同类型的资源按模块处理进行打包
静态 :打包后最终产出静态资源
模块 :webpack支持不同规范的模块化开发
loader
css-loader: 例如处理import导入的css文件样式,将当前css模块解析。
style-loader:样式变化,呈现出来
less-loader:依赖less帮助,解析编译.less文件成css文件
postcss-loader:,利用javascript转换样式的工具,处理样式兼容
importLoader
file-loader :将资源拷贝至制定目录,分开请求
url-loader : base64 url 文件当中,减少请求次数,内部也可以调用file-loader, 有个limit属性控制超过这一设置,如果超过就使用 拷贝,否则就使用base64
转换特定类型的模块
asset module
01 asset/resource -> file-loader 拷贝到指定目录
02 asset/inline -> url-loader
03 asset/source -> raw-loader
04 asset 动态判断体积大小预值设置
plugin
clean-webpack-plugin : 清除dist目录
html-webpack-plugin
DefinePlugin : 自定义变量插件
CopyWebpackPlugin :将单个文件或整个目录复制到构建目录
babel
@babel/core
@babel/cli
@babel/plugin-transform-arrow-functions :处理箭头函数
@babel/plugin-transform-block-scoping :处理模块作用域
@babel/preset-env : 预设插件集合
babel-loader
@babel/preset-env: 使用插件来处理兼容性js
@babel/preset-typescript:编译ts->js
JSX TS ES6+ -> 浏览器平台不支持不能直接使用 需要经过babel进行转换,类似postcss。处理JS兼容
polyfill(填充):@babel/polyfill
core-js/stable
regenerator-runtime/runtime
webpack4集成了polyfill,打出的包太大,所以webpack5 将polyfill隔离了出来,因为它比较大,隔离出来可以按需加载;建议我们使用 -> core-js、regenerator-runtime
文件热更新
watch
live server
1. 所有源代码都会重新编译 2. 每次编译成功之后都需要进行文件读写 3. live server 4. 不能实现局部刷新
所以我们使用webpack-dev-server: 在内存处理数据,效率快
HMR
模块热替换,局部模块更新,不影响其他模块(webpack-dev-middleware)
Vue组件支持热更新
webpack.config.js
output
publicPath
index.html静态资源的引用路径。 域名+publicPath+filename 1. publicPath: '' (域名+filename) 2. publicPath: '/' (是反斜杠-> 相当于绝对路径) 3. publicPath:'./' (设置为'./' -> 相当于相对路径)
path : 打包的路径
devServer
publicPath : 指定本地服务所在的目录
contentBase : 打包之后的资源如果说依赖其他的资源,此时告知去哪找
watchContentBase : true,监听依赖的未打包文件是否变更
proxy : 代理设置
resolve : 配置模块解析规则
devtool : 到底要不要生成source-map,以及以什么方式生成source-map
webpack.common.js : 合并开发、生产环境配置
拆分打包
多入口entry
dependOn
splitchunks配置
import动态导入配置
optimization->chunkIds
name(适用于开发),natural,deterministic(适用于生产)
output->chunkFilename (chunk) + 魔法注释
import ( /*webpackChunkName:"main"*/ './main1')
优化配置
runtimeChunk
optimization -> runtimeChunk
设置为true时,会为每个入口添加一个只含有runtime的额外chunk. 为了浏览器做长期的缓存。runtime-index[hash值].bundle.js,重新打包时,只会变更做过修改的模块,这样打包就只会做最小的打包过程,如果有更新只会更新修改了的js部分。runtime中记录了打包require相关信息
代码懒加载
在触发事件中动态导入:import('./utils').then(res=>{ ... })
预获取与预加载
prefetch(预获取):将来某些导航下可能需要的资源
preload(预加载):当前导航下可能需要的资源
preload chunk会在父chunk加载时,以并行的方式开始加载(当前导航下),prefetch chunk会在父chunk加载结束后开始加载(加载时机:浏览器空闲时)。
打包dll库
css抽离和压缩
抽离:mini-css-extract-plugin
压缩:css-minimizer-webpack-plugin
TerserPlugin 压缩JS
scope hoisting : 作用域提升
摇树优化
JS
usedExports
optimization->minimize
optimization->usedExports
摇树优化。打包时webpack会使用魔法注释做标记未使用的函数,然后TerserPlugin会去将被标记好unuse的代码摇掉
sideEffects(数组,哪些副作用不需要进行摇树的) or false
CSS
purgecss-webpack-plugin
paths : 告诉webpack相应路径目录所有文件 对没有使用的css 进行摇树优化
safelist : 排除哪些css样式不做摇树
glob
资源压缩
http压缩(compression-webpack-plugin)
1.http资源压缩,交给webpack压缩,配置
2.告知服务当前支持哪些压缩
3. 服务端返回压缩好的资源
inlineChunkHtmlPlugin : 合成文件注入到hrml中。
webpack打包library
webpack4
Part3 Vue.js 框架源码与进阶
虚拟DOM
为什么要使用虚拟DOM?
在虚拟dom出现之前,MVVM框架解决视图和状态同步问题 模板引擎可以简化视图操作,但没有办法跟踪状态 虚拟dom带来的好处: 1. 虚拟dom可以维护视图与状态之间的关系,可以记录上一次状态的变化,跟踪状态变化 2. 通过比较前后两次状态的差异更新真实Dom
作用
虚拟DOM的作用: 1. 维护视图和状态的关系 2. 复杂视图情况下提升渲染性能 3. 跨平台 浏览器平台渲染DOM 服务端渲染SSR( Nuxt.js / Next.js ) 原生应用( Weex / React Native ) 小程序( mpvue / uni-app )等
开源库
Snabbom
Vuejs 2.*内部使用的虚拟DOM就是改造的Snabbdom 通过模板可扩展 源码使用typescript 最快的虚拟Dom之一 大约200行代码
核心
init函数:设置模块,创建patch()函数
h函数: 用来创建javasrcipt对象(一个虚拟节点)描述真实DOM
patch函数:
Patch函数 整体过程分析: patch ( oldVnode, newVnode ) 把新节点中变化的内容渲染到真实DOM,最后返回新节点作为下一次处理的旧节点 对比新旧Vnode是否相同节点(节点的key和sel相同) 如果是相同节点,删除之前的内容,重新渲染。 如果是相同节点,再判断新的Vnode是否有text,如果有并且和Vnode的text不同,直接更新文本内容、 如果新的Vnode有children, 判断子节点是否有变化。
对比新旧两个(vnode)虚拟节点,将两个虚拟节点之间的差异更新到真实dom上。如果patch函数第一个参数是真实DOM,会转换成vnode, 再进行对比。
patchVnode
触发 prepatch 钩子函数
触发 update 钩子函数
新节点有text属性,且不等于旧节点的text属性
如果老节点有children
移除老节点children对应的DOM元素
设置新节点对应DOM元素的textContent
新老节点都有children,且不相等
调用updateChildren()
对比子节点,并且更新子节点的差异
只有新节点有children属性
如果老节点有text属性
清空对应DOM元素的textContent
添加所有的子节点
只有老节点有children属性
移除所有的老节点
只有老节点有text属性
清空所有的老节点
触发 postpatch 钩子函数
使用Snabbdom步骤
h函数: 用来创建一个虚拟节点(调用vnode函数创建vnode对象) init函数: 创建patch函数 patch函数:对比两个虚拟节点,将两个虚拟节点之间的差异更新到真实dom上。
virtual-dom
Diff算法
Vue实现原理
Vue响应式
数据驱动
数据响应式:数据模型为js对象,修改数据时,视图会进行更新,避免繁琐的DOM操作,提高开发效率。
双向绑定:数据改变->视图改变,视图改变->数据也随之改变 可以使用v-model在表单元素上创建双向数据绑定
数据驱动:数据驱动是Vue最独特的特性之一,开发过程中仅需要关注数据本身,不需要关心数据是如何渲染到视图。
响应式的核心原理
Vue2
ES5中新增加:Object.defineproperty
Vue3
ES6新增加:Proxy
整体结构
Vue:把data中的成员注入到Vue实例,并且把data中的成员转换成getter和setter
Observer: 能够对数据对象的所有属性进行监听,如有变动可拿到最新值并通知Dep
Dep发布者:在Data的getter中收集依赖,在Setter中通知依赖
watcher观察者
Compiler解析指令
发布订阅模式和观察者模式
Vue Router
基于Hash模式实现
基于锚点,以及onhashChange事件,监听锚点的值是否发生变化,来触发onhashchange事件,根据路径决定页面呈现的内容
基于History模式实现
基于Html5的history API,history.pushState ie10以后才支持,有兼容性问题,history.replaceState
Vue中的虚拟DOM
h函数
vm.$createElement( tag , data , children , normalizeChildren) tag: 标签名称或者组件对象 data : 描述tag, 可以设置DOM的属性或者标签的属性 children : tag中的文本内容或者子节点
特点
避免直接操作DOM,提高开发效率
作为一个中间层可以跨平台
整体过程分析
vm._init()
vm.$mount()
mountComponent()
创建Watcher对象
updateComponent()
vm._update(vm._render(),hydrating)
vm._render()
vnode=render.call(vm._renderProxy,vm.$createElement)
vm.$createElement()
h函数,render()中调用,通过代码演示
createElement(vm,a,b,c,d,true)
_createElement(context, tag , data, children , normalizationType)
vm_createElement()
vnode=new VNode(config.parsePlatformTagName(tag),data,children,undefined,undefined,context)
vm._render()结束,返回vnode
vm._update()
负责把虚拟DOM.渲染成真实DOM
首次执行
vm.__patch__(vm, $el , vnode , hydrating , false )
数据更新
vm.__patch__(prevVnode , vnode )
vm.__patch__()
patchVnode
updateChildren
源码解析
入口文件
1. 导出config配置对象: scripts\config.js 2. 入口文件 src\platforms\web\entry-runtime-with-compiler.js
el
如果在初始化时指定了这个选项,实例将立即进入编译过程。否则,需要调用 vm.$mount(),手动开始编译。 提供的元素只能作为挂载点。不同于 Vue 1.x,所有的挂载元素会被 Vue 生成的 DOM 替换。因此不推荐挂载 root 实例到 <html> 或者 <body> 上。 如果 render 函数和 template 属性都不存在,挂载 DOM 元素的 HTML 会被提取出来用作模板,此时,必须使用 Runtime + Compiler 构建的 Vue 库。 el 的作用是用于指明 Vue 实例的挂载目标。我们重点关注上面两个部分, 总结一下就是:如果存在 render 函数或 template 属性,则挂载元素会被 Vue 生成的 DOM 替换;否则,挂载元素所在的 HTML 会被提取出来用作模版 例:render 函数渲染的 DOM 替换 <div id="ppp"></div> new Vue({ el: '#ppp', router, store, render: h => h(App) })
首次渲染过程
Vue初始化,实例成员,静态成员
(1) 实例成员(vm.$data,vm.$options 等) (2) 静态成员(如:set / delete / nextTick 等方法) (3) 还初始化了Vue.options对象,为它添加了扩展属性(如:filters, components等) (4) 注册了kepp-alive内置组件 (5) 注册了静态方法:Vue.use()、Vue.extend()、Vue.ximin()、 Vue.directive()、Vue.component()、Vue.filter()等方法
new Vue()
this._init() 入口
vm.$mount()
src\platform\web\entry-runtime-with-compiler.js
入口文件,作用:把模板编译成render函数
如果没有传递render(如果没有的话,它会获取Template选项,如果template也没有的话,会把el传入的内容作为模板),把模板编译成render函数,
compilerToFunctions()把模板编译生成render()渲染函数
options.render = render
vm.$mount()
src\platforms\web\runtime\index.js
mountComponent()
这个方法中会重新获取el,因为如果是运行时版本的话, 是不会走entry−runtime−with−compiler.js这个入口中获取el, 所以如果是运行时版本的话,我们会在runtime/index.js的mount()中重新获取el。
会重新获取el
mountComponent(this,el)
src\core\instance\lifecycle.js
判断是否有render选项,如果没有,但是传入了模板,并且当前是开发环境的话,会发送警告
mountComponent(this,el)在src\core\instance\lifecycle.js中定义, 首先判断是否有render选项,如果没有另外还传入了模板, 并且当前是开发环境,则发出警告(运行时版本不支持编译器), 触发beforeMount钩子函数(开始挂载之前),定义updateComponents函数但是并未调用, 这个函数中调用render()和update()两个方法, render是生成虚拟dom,update是将虚拟dom转化为真实dom并挂载到页面上。 之后创建了Watcher实例对象,创建时,传递函数updateComponents, 然后调用get方法,创建完毕后,触发钩子函数mounted(),挂载结束,返回vue实例。
如果当前是运行时版本
触发beforeMount
定义updateComponent
vm._update(vm._render(),...)
vm._render()渲染,渲染虚拟DOM
vm._update()更新,将虚拟DOM转换成真实DOM
创建Watcher实例
updateComponent传递
调用get()方法
触发mounted
return vm
watcher.get()
创建完watcher会调用一次get
调用updateComponent()
调用vm._render()创建VNode
调用render.call(vm._renderProxy.vm.$createElement)
调用实例化时Vue传入的render()
或者编译template生成render()
返回Vnode
调用vm._update(vnode,....)
调用vm.__patech__(vm.$el,vnode)挂在真实dom
记录vm.$el
响应式处理过程
initState()-->initData() -->observe()
observe(value)
位置
src/core/observer/index.js
功能
判断value是否为对象,如果不是对象直接返回
判断value对象是否有__ob__,如果有(说明已经做过响应化处理)直接返回
如果没有,创建observer对象
返回observer对象
Observer类
位置
src/core/observer/index.js
功能
给value对象定义不可枚举的__ob__属性,记录当前的observer对象
数组的响应式处理
设置数组特有的方法例如:push...这些方法会改变数组
对象的响应式处理,调用walk方法(遍历对象的每一个属性,为每一个属性调用defineReactive方法)
defineReactive
位置
src/core/observer/index.js
功能
为每一个属性创建dep对象
如果当前属性的值是对象,调用observe
定义getter
收集依赖
返回属性的值
定义setter
保存新值
如果新值是对象,调用observe
派发更新(发送通知)调用dep.notify()
依赖收集
在watcher对象的get方法中调用pushTarget记录Dep.target属性
Dep.target用来存放目前正在使用的watcher 全局唯一,并且一次也只能有一个watcher被使用 每一个组件会对应一个watcher对象,每一个组件都会有一个mountComponent,在mountComponent函数中创建了watcher对象
访问data中的成员的时候收集依赖,defineReactive的getter收集依赖
把属性对应的watcher对象添加到dep的sub数组中
给childOb收集依赖,目的是子对象添加和删除成员时发送通知
Watcher
dep.notify()在调用watcher对象的update()方法
queueWatcher()判断watcher是否被处理,如果没有的话添加queue队列中,并调用flushSchedulerQueue()
flushSchedulerQueue()
触发beforeUpdate钩子函数
调用watcher.run()
run()-->get()-->getter()-->updateComponenrt(如果是渲染watcher,则此时的getter就是updateComponenrt函数)
清空上一次的依赖
触发actived钩子函数
触发updated钩子函数