# 慕课vue源码学习

flow (opens new window)

vue源码目录 (opens new window)

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找到对应源码位置

vue入口 (opens new window)

数据驱动 (opens new window)

new Vue (opens new window)

  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)
}

render (opens new window)

虚拟dom (opens new window)

createElement (opens new window)

update (opens new window)

组件化 (opens new window)

observer与watcher (opens new window)

createComponent (opens new window)

patch (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)

最后更新: 11/14/2021, 2:18:08 PM