# 慕课vue源码学习
Vue.js 源码构建 (opens new window)
如果本机调试源码的话:
- 调试环境搭建,从官网下载vue源码
- npm i
- 安装rollup: npm i -g rollup,如果有,直接忽略
- 修改dev脚本,添加sourcemap,package.json,这样可以映射出源码结构,更方便调试
"dev": "rollup -w -c scripts/config.js --sourcemap --environment TARGET:webfull-dev",
- 运行开发命令: npm run dev
- 实例调试,在new Vue这可以打断点进入,然后理由review in sidebar找到对应源码位置
export default function Skt(){
console.log('Skt')
this._init()
}
function init(){
console.log('init')
Skt.prototype._init = function (){
console.log('_init')
this._render()
}
}
function render(Skt){
console.log('render')
Skt.prototype._render=function(){
console.log('_render')
}
}
init()
render(Skt)
import Skt from './react.js'
// 这里只引入了Skt这个函数,但是init 和 render因为在原文件中调用了,也会执行。
//这就像Vue里的_init会被添加的Vue的原型上一样可以调用
Vue 实例挂载的实现 (opens new window)
function F(){
console.log(this)
}
let f1 =new F()
F.prototype.k=function(){
console.log(1)
console.log(this)
}
f1.k()
//1
// 保留当前内k方法逻辑
let ff = F.prototype.k
// 扩展重写k方法
// 增强复用性
F.prototype.k =function(){
console.log(2)
ff.call(this)
}
f1.k()
//1 2
//重写扩展$mount
const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && query(el)
/* istanbul ignore if */
if (el === document.body || el === document.documentElement) {
process.env.NODE_ENV !== 'production' && warn(
`Do not mount Vue to <html> or <body> - mount to normal elements instead.`
)
return this
}
const options = this.$options
console.log(options,1)
// resolve template/el and convert to render function
if (!options.render) {
let template = options.template
if (template) {
//如果有模板怎么处理
//typeof template === 'string'
// template.nodeType
} else if (el) {
//如果是el怎么处理
template = getOuterHTML(el)
}
}
return mount.call(this, el, hydrating)
}
createElement (opens new window)
observer与watcher (opens new window)
createComponent (opens new window)
# vue2源码中的dep
地址:/vue2/src/core/observer/index.js
- Observer中的dep暂时定义为大管家,每当有个对象的时候,就会有一个大管家,比如vue中的data,然后data里的person
- 函数defineReactive中生成的dep是小管家,默认有$attrs和$listeners,然后去data遍历key,每一个key都会生成一个dep与之对应
- 大小管家都是根据先后顺序产生
- 一个组件会在E:\company\vue2\src\core\instance\lifecycle.js中的mountComponent方法中实例化一个Wacther,当然,如果该页面有自定义的watcher的话就不止一个了
- vue2/src/core/instance/state.js中的$watch就是平时手写的,挂载在vue的原型上
- 子组件不要直接去改父组件传来的props如a1,改了虽然会生效,但是破坏了数据的 安全性和复用性 ,且父组件定义的watch监听不到子组件对原本数据a1的改变
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="../../dist/vue.js"></script>
</head>
<body>
<div id='app'>
{{person.name}}
{{person.name}}
{{person.name}}
</div>
</body>
</html>
<script>
let app =new Vue({
el:'#app',
data:{
var1:0,
person:{
name:'java'
}
},
template:`
<div >
{{person.name}}
{{person.name}}
{{person.name}}
{{person.name}}
<counter :count='var1'/>
</div>
`,
watch:{
var1:function(newval,oldval){
}
},
components:{
'counter':{
props: ['count'],
mounted(){
setInterval(()=>{
this.count=this.count+4
},50000)
},
template: `<div @click="count += 1">{{count}}</div>`
}
},
mounted(){
}
})
</script>
- var1所在的闭包Dep会关注三个watcher,ss-computed watcher,$watcher ,组件watcher
- 首次执行后完成后进入vue中的watcher的cleanupDeps方法,将deps替换成newdeps
- 删除后deps就会清除该数据的dep
- 添加后会新建一个dep,uid从原来的++
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="../../dist/vue.js"></script>
</head>
<body>
<div id='app'>
<button @click='var1++'>var1</button>
<button @click='var2++'>var2</button>
{{ss}}
{{var1}}---{{var2}}--- {{var1}}
{{obj}}
<button @click='del'>删除</button>
<button @click='change'>改变</button>
<button @click='delobj'>delobj</button>
<button @click='addobj'>addobj</button>
</div>
</body>
</html>
<script>
let app =new Vue({
el:'#app',
data:{
var1:0,
var2:1000,
test:'ceshi',
obj:{
f1:'f1'
},
person:{
name:'java',
test:{
a:1,
b:2
}
},
arr:[1,2,3,4,5]
},
computed:{
ss(){
return this.var1+111
}
},
watch:{
var1:function(newval,oldval){
console.log(newval)
this.var2=100
},
},
methods:{
del(){
this.$delete(this.person,'name')
},
change(){
this.person.name+='A'
this.test.a=1000
},
delobj(){
this.$delete(this.obj,'f1')
},
addobj(){
this.$set(this.obj,'f1')
}
}
})
</script>
- watcher深度监听
- watcher每个component一般会有一个,如果用户自定义的话,可以是多个,包括$watcher和computed的watcher
- 会在new Watcher 中获取到vm实例,也就是当前的组件,然后添加到vm._watcher.push(this),当前的watcher会被添加到
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<script src="../../dist/vue.js"></script>
</head>
<body>
<div id='app'>
</div>
</body>
</html>
<script>
let app =new Vue({
el:'#app',
data:{
var1:0,
test:'ceshi',
person:{
name:'java'
}
},
template:`
<div >
<button @click='var1++'>1111</button>
{{person.name}}
{{person.name}}
</div>
`,
watch:{
person:{
handler(newName, oldName) {
console.log('obj.a changed');
},
// 开启深度监听
deep: true
},
var1:function(newval,oldval){
// console.log(newval)
},
test(){
}
}
})
</script>
Vue {_uid: 0, _isVue: true, $options: {…}, _renderProxy: Proxy, _self: Vue, …}
//watcher部分源码
constructor (
vm: Component,
expOrFn: string | Function,
cb: Function,
options?: ?Object,
isRenderWatcher?: boolean
) {
this.vm = vm
if (isRenderWatcher) {
vm._watcher = this
}
vm._watchers.push(this)
console.log(vm._watchers)
}
。。。。。。
闭包dep和__ob__.dep (opens new window)
depIds newDepIds (opens new window)
watcher和dep的关系
watcher 和 dep 是互相持有的关系,可以理解成一个多对多的关系,在定义响应式对象的时候会创建 dep,在对象被访问触发 getter 的时候就会触发 dep.depend 做依赖收集,作为依赖收集到当前正在计算的 watcher 中,这个 watcher 可以是 render watcher,可以是 computed watcher,也可以是 user watcher。
watcher 内部维护了 deps 和 newDeps 也是为了可以让 watcher 可以在每次计算完成清空不需要要的 dep 依赖了。
deps&newDeps
deps 是维护 watcher 中所有收集到的依赖,而 newDeps 是新添加的依赖。在每次 watcher 执行 get 方法做完求值后,会做一个对比,遍历 deps 中的 dep,如果发现 dep 不在新添加的依赖 newDeps 中的话,则把这个 dep 从依赖中删除。这个是一种优化手段。
- dep.js
depend () {
//Dep.target === 当前的watcher
if (Dep.target) {
Dep.target.addDep(this)
}
}
notify () {
// stabilize the subscriber list first
const subs = this.subs.slice()
if (process.env.NODE_ENV !== 'production' && !config.async) {
// subs aren't sorted in scheduler if not running async
// we need to sort them now to make sure they fire in correct
// order
subs.sort((a, b) => a.id - b.id)
}
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
}
- watcher.js
addDep (dep: Dep) {
console.log(dep)
const id = dep.id
if (!this.newDepIds.has(id)) {
this.newDepIds.add(id)
this.newDeps.push(dep)
if (!this.depIds.has(id)) {
dep.addSub(this)
}
}
}
cleanupDeps () {
let i = this.deps.length
while (i--) {
const dep = this.deps[i]
if (!this.newDepIds.has(dep.id)) {
dep.removeSub(this)
}
}
let tmp = this.depIds
this.depIds = this.newDepIds
this.newDepIds = tmp
this.newDepIds.clear()
tmp = this.deps
this.deps = this.newDeps
this.newDeps = tmp
this.newDeps.length = 0
}
watcher 和 dep (opens new window)
Vue响应式原理-理解Observer、Dep、Watcher (opens new window)
vuejs构建使用rollup进行构建:调试源码 (opens new window)
← 企业组件库开发 封装elementplus →