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

源码地址

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)                                       // 水平有限,看不懂 - -#

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容