原创的~Vue.js

8.最俗学习之-Vue源码学习-数据篇(下)

2018-02-05  本文已影响5人  木子tar

源码地址

new Watcher(vm, expOrFn, cb, options),对于这个对应的文件在src/observer/watcher.js
关于这个也看了很多的文章,自己也有写了学习的笔记,不过最后还是决定引用一篇文章,因为大概
的思路也就是这样子,然后再Vue的实现里面还有很多复杂的东西,我也没怎么看懂,但是那些都是一
些辅助的东西,并不是主要的核心功能,看完下面这篇文章即可明白dep和watch和observer的关系

<p style="font-weight: bold;margin-bottom: 10px;color: #FF0000">看这里</p>
<p style="font-weight: bold;margin-bottom: 10px;color: #FF0000">看这里</p>
<p style="font-weight: bold;margin-bottom: 10px;color: #FF0000">看这里</p>

深入浅出Vue基于“依赖收集”的响应式原理

之前还有几个问题剩下的,今天把它看一下

Vue.set = set // 涉及到Vue的数据响应式系统,先保留

Vue.delete = del // 涉及到Vue的数据响应式系统,先保留


<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Vue.set和Vue.delete</title>
</head>
<body>
  <div id="app">
    <h2>{{msg}}</h2>
    <p v-for="v in list">{{v}}</p>
    <button v-text="'click me'" @click="changeList()"></button>
    <button v-text="'click me'" @click="deleteList()"></button>
  </div>
</body>
<script type="text/javascript" src="vue.js"></script>
<script>

/**
 * Set a property on an object. Adds the new property and
 * triggers change notification if the property doesn't
 * already exist.
 */
function set$1 (obj, key, val) {
  if (Array.isArray(obj)) {  // 如果是数组,则使用splice方法,简单粗暴
    obj.length = Math.max(obj.length, key);
    obj.splice(key, 1, val);
    return val
  }
  if (hasOwn(obj, key)) {  // 如果是obj并且有这个key,直接赋值
    obj[key] = val;
    return
  }
  var ob = obj.__ob__;  // 获取这个obj的__ob__对象,这个也就是之前说的,经过observer之后会有这个东东挂载在上面
  if (obj._isVue || (ob && ob.vmCount)) {  // 一些操作上的判断
    "development" !== 'production' && warn(
      'Avoid adding reactive properties to a Vue instance or its root $data ' +
      'at runtime - declare it upfront in the data option.'
    );
    return
  }
  if (!ob) {  // 如果有这个ob,那么则是被observer过的,直接赋值
    obj[key] = val;
    return
  }
  defineReactive$$1(ob.value, key, val);  // 对这个值进行observer
  ob.dep.notify();   // 触发dep的notify方法,就是对应的计算属性和watch的数据更新
  return val
}

/**
 * Delete a property and trigger change if necessary.
 */
function del (obj, key) {
  var ob = obj.__ob__;
  if (obj._isVue || (ob && ob.vmCount)) {
    "development" !== 'production' && warn(
      'Avoid deleting properties on a Vue instance or its root $data ' +
      '- just set it to null.'
    );
    return
  }
  if (!hasOwn(obj, key)) {  // 如果没有则返回
    return
  }
  delete obj[key];  // 删除这个key
  if (!ob) {  // 没有ob过的则返回,不需要下面的notify了
    return
  }
  ob.dep.notify();
}


// ------------上面是这两个方法对应的源码,这里用的是构建后的源码---------------

/*

vm.$set( target, key, value )

参数:

{Object | Array} target
{string | number} key
{any} value
返回值:设置的值。

用法:

这是全局 Vue.set 的别名。

参考:Vue.set

vm.$delete( target, key )

参数:

{Object | Array} target
{string | number} key
用法:

这是全局 Vue.delete 的别名。

*/

// -----------------上面是官方文档的api的使用说明----------------------------


var vm = new Vue({
  el: '#app',
  data () {
    return {
      msg: 'hello Vue',
      list: [1,2,3,4,5]
    }
  },
  created () {

  },
  methods: {
    changeList () {
      // this.list[2] = 999;    // 用这种方法是不会更新视图的
      Vue.set(this.list, 2, 999)    // 用这种方法ok
    },
    deleteList () {
      // this.list[2] = null;    // 用这种方法是不会更新视图的
      Vue.delete(this.list, 2)    // 用这种方法ok
    }
    // 在2.1.7版本的Vue中,这两个方法会有一个bug,不过经过测试,在最新版本(2.5.13)中
    // 已经修复了,这里就不说出来咯,对应的方法在上面
  }
})
</script>
</html>


然后到了这个问题:initExtend(Vue),对应的源码在src/core/global-api/extend.js


// 这里我们用官网的例子来说

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Vue.extend</title>
</head>
<body>
  <div id="mount-point">

  </div>
</body>
<script type="text/javascript" src="vue.js"></script>
<script>

// 创建构造器
var Profile = Vue.extend({
  template: '<p>{{firstName}} {{lastName}} aka {{alias}}</p>',
  data: function () {
    return {
      firstName: 'Walter',
      lastName: 'White',
      alias: 'Heisenberg'
    }
  }
})

// 创建 Profile 实例,并挂载到一个元素上。

// new Profile().$mount('#mount-point')

new Profile({
  el: '#mount-point',
  data: function () {
    return {
      firstName: 'Walter from self',
      lastName: 'White from self'
    }
  }
})


// 这里我们看到,冲突的数据会被覆盖了的,但是这种感觉有点奇怪,感觉就是把new Vue变成
// 了new Profile的样子,到现在还是不太明白其中的奥义所在,唯一的一种理解就是这样


// 比如组件化的功能,我们在很多页面要用到Alert组件,Toast组件,那么我们可以事先定义
// 然后在有需要的页面直接的使用new Alert和new Toast即可,但感觉这个功能应该不仅仅如此

</script>
</html>


// --------------下面是方法对应的源码-----------------------------------


export function initExtend (Vue: GlobalAPI) {
  /**
   * Each instance constructor, including Vue, has a unique
   * cid. This enables us to create wrapped "child
   * constructors" for prototypal inheritance and cache them.
   */
  Vue.cid = 0
  let cid = 1

  /**
   * Class inheritance
   */
  Vue.extend = function (extendOptions: Object): Function {
    extendOptions = extendOptions || {}
    const Super = this  // 重点,这个this是Vue的构造函数
    const SuperId = Super.cid  // 一个编号
    const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {})  // 缓存的值
    if (cachedCtors[SuperId]) {  // 是否有缓存过的
      return cachedCtors[SuperId]
    }
    const name = extendOptions.name || Super.options.name  // 获取name,不是重点
    if (process.env.NODE_ENV !== 'production') {  // 一些操作的判断和提示
      if (!/^[a-zA-Z][\w-]*$/.test(name)) {
        warn(
          'Invalid component name: "' + name + '". Component names ' +
          'can only contain alphanumeric characters and the hyphen, ' +
          'and must start with a letter.'
        )
      }
    }
    const Sub = function VueComponent (options) {  // 这个Sub就是最后返回的方法
      this._init(options)  // 就是我们初始化的Vue的步骤,很熟悉的东西
    }
    Sub.prototype = Object.create(Super.prototype)  // 以Vue的构造函数为原型的原型
    Sub.prototype.constructor = Sub  // 自身的constructor
    Sub.cid = cid++  // 静态属性cid,一个编号
    Sub.options = mergeOptions(  // 合并,很熟悉的东西,就是我们最开始new Vue的时候数据合并的步骤
      Super.options,
      extendOptions
    )
    Sub['super'] = Super  // 获取Super上面的东西
    // allow further extension/mixin/plugin usage
    Sub.extend = Super.extend  // 同理,获取Super上面的东西
    Sub.mixin = Super.mixin  // 同理,获取Super上面的东西
    Sub.use = Super.use  // 同理,获取Super上面的东西
    // create asset registers, so extended classes
    // can have their private assets too.
    config._assetTypes.forEach(function (type) {  // 这个也很熟悉,组件注册,过滤器,自定义指令的赋值
      Sub[type] = Super[type]
    })
    // enable recursive self-lookup
    if (name) {
      Sub.options.components[name] = Sub
    }
    // keep a reference to the super options at extension time.
    // later at instantiation we can check if Super's options have
    // been updated.
    Sub.superOptions = Super.options  获取各自的options
    Sub.extendOptions = extendOptions  获取各自的options
    // cache constructor
    cachedCtors[SuperId] = Sub  缓存下来
    return Sub  // 返回这个构造函数
  }
}

// 看到了这里,有种感觉就是这个东西把Vue上面的东西都继承下来了,最后返回它,然后new它又
// 调用了Vue的init方法初始化,好吧,懵逼,鉴于官方文档也没说的太详细,这个暂时就只能这么
// 理解了,不过这个应该还有更大的用处的


到了这里大概就剩下渲染的事情了,也就是initRender方法


initLifecycle(vm)
initEvents(vm)
callHook(vm, 'beforeCreate')
initState(vm)
callHook(vm, 'created')
initRender(vm)


主要就是执行vm.$mount方法,这里大概涉及的有:

模板的编译,
生成ast,
生成render,
生成Virtual DOM,
通过snabbdom的方法实现优化,

等等,就是Vue的渲染的操作了


<p style="font-weight: bold;margin-bottom: 10px;color: #FF0000">剩下的几个问题</p>


Vue.nextTick = util.nextTick        // 水平有限,看不懂 - -#,dom的异步更新的问题

extend(Vue.options.directives, platformDirectives)  // 水平有限,看不懂 - -#,内置的指令directives,v-bind,v-model,v-show
extend(Vue.options.components, platformComponents)  // 水平有限,看不懂 - -#,内置的组件,transition组件
Vue.prototype.__patch__                             // 水平有限,看不懂 - -#,渲染的方法
compileToFunctions                                  // 水平有限,看不懂 - -#,模板编译的方法


const extendsFrom = child.extends                   // 水平有限,看不懂 - -#

initProxy(vm)                                       // 水平有限,看不懂 - -#

上一篇下一篇

猜你喜欢

热点阅读