写了一半关机了,又得重新写,好气。

  上一节讲到initData函数,其中包含格式化、代理、监听。

    // Line-3011
function initData(vm) {
var data = vm.$options.data;
//data = vm._data = ... 格式化data
// ...proxy(vm, "_data", keys[i]); 代理
// 监听
observe(data, true /* asRootData */ );
}

  这一节重点开始跑observe函数,该函数接受2个参数,一个是数据,一个布尔值,代表是否是顶层根数据。

    // Line-899
function observe(value, asRootData) {
if (!isObject(value)) {
return
}
var ob;
// 判断是否有__ob__属性 即是否已被监听
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__;
}
// 若无进行条件判断
else if (
observerState.shouldConvert && // 是否应该被监听 默认为true
!isServerRendering() && // 是否是服务器渲染
(Array.isArray(value) || isPlainObject(value)) && // 数据必须为数组或对象
Object.isExtensible(value) && // 是否可扩展 => 能添加新属性
!value._isVue // vue实例才有的属性
) {
// 生成一个观察者
ob = new Observer(value);
}
// 根数据记数属性++
if (asRootData && ob) {
ob.vmCount++;
}
return ob
}

  observe函数除去大量的判断,关键部分就是new了一个观察者来进行数据监听,所以直接跳进该构造函数:

    // Line-833
var Observer = function Observer(value) {
// data
this.value = value;
// 依赖收集
this.dep = new Dep();
this.vmCount = 0;
// 通过Object.defineProperty定义__ob__属性 this指向Observer实例
def(value, '__ob__', this);
// 根据类型调用不同的遍历方法
if (Array.isArray(value)) {
var augment = hasProto ?
protoAugment :
copyAugment;
augment(value, arrayMethods, arrayKeys);
this.observeArray(value);
} else {
this.walk(value);
}
};

  这个构造函数给实例绑了3个属性,分别为data对象的value、记数用的vmCount、依赖dep,接着根据数据类型调用不同的遍历方法进行依赖收集。  

  Dep对象比较简单,包含2个属性和4个对应的原型方法,如下:

    // Line-720
// 超级简单
var Dep = function Dep() {
this.id = uid++;
this.subs = [];
};
// 原型方法
Dep.prototype.addSub = function addSub(sub) {
this.subs.push(sub);
};
Dep.prototype.removeSub = function removeSub(sub) {
remove(this.subs, sub);
};
Dep.prototype.depend = function depend() {
if (Dep.target) {
Dep.target.addDep(this);
}
};
Dep.prototype.notify = function notify() {
// stabilize the subscriber list first
var subs = this.subs.slice();
for (var i = 0, l = subs.length; i < l; i++) {
subs[i].update();
}
};

  可见,new出来的dep实例只有2个属性,一个是每次+1的计数uid,还有一个是依赖收集数组。

  原型上的4个方法分别对应增、删、添加依赖、广播,由于暂时用不到update函数,所以先放着。

  

  接着,用自定义的def方法把__ob__属性绑到了生成的observe实例上,该属性引用了自身。

  最后,根据value是类型是数组还是对象,调用不同的方法进行处理。案例中传进来的value是一个object,所以会跳到walk方法中。

  这里不妨看看如果是数组会怎样。

    // Line-838
if (Array.isArray(value)) {
// 判断是否支持__proto__属性
var augment = hasProto ?
protoAugment :
copyAugment;
// 原型扩展
augment(value, arrayMethods, arrayKeys);
// 数组监听方法
this.observeArray(value);
}

  其中,根据环境是否支持__proto__分别调用protoAugment或copyAugment,这两个方法比较简单,上代码就能明白。

    // Line-876
function protoAugment(target, src) {
// 直接指定原型
target.__proto__ = src;
} // Line-887
function copyAugment(target, src, keys) {
// 遍历keys
// 调用def(tar,key,value) => (tar[key] = (value => src[key]))
for (var i = 0, l = keys.length; i < l; i++) {
var key = keys[i];
def(target, key, src[key]);
}
}

  选择了对应的方法就开始调用,传进的参数除了value还是两个奇怪的值:arrayMethods、arrayKeys。

    // Line-767
// 创建一个对象 原型为数组对象的原型
var arrayProto = Array.prototype;
var arrayMethods = Object.create(arrayProto);
console.log(Array.isArray(arrayMethods)); //false
console.log('push' in arrayMethods); //true // Line-814
var arrayKeys = Object.getOwnPropertyNames(arrayMethods);

  简单来讲,arrayMethods就是一个对象,拥有数组的方法但不是数组。

  Object.getOwnPropertyNames方法以数组形式返回对象所有可枚举与不可枚举方法,所以arrayKeys直接在控制台打印可以看到:

  最后,不管选择哪个方法,都会将“改造过”的数组方法添加到value对象上,由于代码跑不到,等下次给出具体值吧。这里接着会调用observeArray方法,将数组value穿进去。

    // Line-865
Observer.prototype.observeArray = function observeArray(items) {
// 遍历分别调用observe方法
for (var i = 0, l = items.length; i < l; i++) {
observe(items[i]);
}
};

  绕了一圈,最后还是遍历value,挨个调用observe方法,并指向了walk方法。

    // Line-855
Observer.prototype.walk = function walk(obj) {
// 获取对象的键
var keys = Object.keys(obj);
for (var i = 0; i < keys.length; i++) {
// 核心方法
defineReactive$$1(obj, keys[i], obj[keys[i]]);
}
};

  这个方法比较简单,获取传进来的对象键,遍历后调用defineReactive$$1方法。看名字也就差不多明白了,这是响应式的核心函数,双绑爸爸。

  下节再来说这个,完结完结!  

 

补充tips:

  之前有一段代码,我说将改造过的数组方法添加到数组value上,这个改造是什么意思呢?其实关于arrayMethods代码没有全部贴出来,这里做简单的解释。

    // Line-767
var arrayProto = Array.prototype;
var arrayMethods = Object.create(arrayProto);
// 以下数组方法均会造成破坏性操作
[
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
.forEach(function(method) {
// 缓存原生方法
var original = arrayProto[method];
// 修改arrayMethods上的数组方法
def(arrayMethods, method, function mutator() {
// 将argument转换为真数组
var arguments$1 = arguments;
var i = arguments.length;
var args = new Array(i);
while (i--) {
args[i] = arguments$1[i];
}
// 首先执行原生方法
var result = original.apply(this, args);
var ob = this.__ob__;
var inserted;
// 值添加方法
// push和unshift会添加一个值 即args
// splice(a,b,c,..)方法只有c后面是添加的值 所以用slice排除前两个参数
switch (method) {
case 'push':
inserted = args;
break
case 'unshift':
inserted = args;
break
case 'splice':
inserted = args.slice(2);
break
}
// 对添加的值调用数组监听方法
if (inserted) {
ob.observeArray(inserted);
}
// 广播变化 提示DOM更新及其他操作
ob.dep.notify();
return result
});
});

  完整的arrayMethods如上所述,解释大部分都写出来了,这也是vue通过对数组方法的劫持来达到变化监听的原理,对象的劫持下节再来分析。

  惯例,来一张图:

  快撒花!

.3-Vue源码之数据劫持(1)的更多相关文章

  1. Vue源码解析---数据的双向绑定

    本文主要抽离Vue源码中数据双向绑定的核心代码,解析Vue是如何实现数据的双向绑定 核心思想是ES5的Object.defineProperty()和发布-订阅模式 整体结构 改造Vue实例中的dat ...

  2. .4-Vue源码之数据劫持(2)

    开播了开播了! vue通过数据劫持来达到监听和操作DOM更新,上一节简述了数组变化是如何监听的,这一节先讲讲对象属性是如何劫持的. // Line-855 Observer.prototype.wal ...

  3. [Vue源码]一起来学Vue双向绑定原理-数据劫持和发布订阅

    有一段时间没有更新技术博文了,因为这段时间埋下头来看Vue源码了.本文我们一起通过学习双向绑定原理来分析Vue源码.预计接下来会围绕Vue源码来整理一些文章,如下. 一起来学Vue双向绑定原理-数据劫 ...

  4. vue源码之响应式数据

    分析vue是如何实现数据响应的. 前记 现在回顾一下看数据响应的原因. 之前看了vuex和vue-i18n的源码, 他们都有自己内部的vm, 也就是vue实例. 使用的都是vue的响应式数据特性及$w ...

  5. vue 快速入门 系列 —— 侦测数据的变化 - [vue 源码分析]

    其他章节请看: vue 快速入门 系列 侦测数据的变化 - [vue 源码分析] 本文将 vue 中与数据侦测相关的源码摘了出来,配合上文(侦测数据的变化 - [基本实现]) 一起来分析一下 vue ...

  6. Vue源码解析之数组变异

    力有不逮的对象 众所周知,在 Vue 中,直接修改对象属性的值无法触发响应式.当你直接修改了对象属性的值,你会发现,只有数据改了,但是页面内容并没有改变. 这是什么原因? 原因在于: Vue 的响应式 ...

  7. Vue框架核心之数据劫持

    本文来自网易云社区. 前瞻 当前前端界空前繁荣,各种框架横空出世,包括各类mvvm框架横行霸道,比如Angular.Regular.Vue.React等等,它们最大的优点就是可以实现数据绑定,再也不需 ...

  8. Vue源码--解读vue响应式原理

    原文链接:https://geniuspeng.github.io/2018/01/05/vue-reactivity/ Vue的官方说明里有深入响应式原理这一节.在此官方也提到过: 当你把一个普通的 ...

  9. 【Vuejs】350- 学习 Vue 源码的必要知识储备

    前言 我最近在写 Vue 进阶的内容.在这个过程中,有些人问我看 Vue 源码需要有哪些准备吗?所以也就有了这篇计划之外的文章. 当你想学习 Vue 源码的时候,需要有扎实的 JavaScript 基 ...

随机推荐

  1. Hibernate映射乱码

    1.修改数据库字符集:将数据库默认编码设置为UTF-8 CHARSET=utf8 2.配置Hibernate环境时将数据库URL设置成: jdbc:mysql://localhost:3306/dbN ...

  2. MapReduce极简教程

    一个有趣的例子 你想数出一摞牌中有多少张黑桃.直观方式是一张一张检查并且数出有多少张是黑桃?   MapReduce方法则是: 给在座的所有玩家中分配这摞牌 让每个玩家数自己手中的牌有几张是黑桃,然后 ...

  3. AngularJS指南文档

    点击查看AngularJS系列目录 转载请注明出处:http://www.cnblogs.com/leosx/ 核心概念 模板 在Angular应用当中,我们的工作就是将服务器的数据填充到客户端页面模 ...

  4. 黑马程序员Java基础班+就业班课程笔记全发布(持续更新)

    正在黑马学习,整理了一些课程知识点和比较重要的内容分享给大家,也是给自己拓宽一些视野,仅供大家交流学习,大家有什么更好的内容可以发给我 ,现有黑马教程2000G  QQ 1481135711 这是我总 ...

  5. TCP/IP(三)数据链路层~2

    一.局域网 1.1.局域网和以太网的区别和联系 局域网:前面已经介绍了,其实就是学校里面.各个大的公司里,自己组件的一个小型网络,这种就属于局域网. 以太网:以太网(Ethernet)指的是由Xero ...

  6. HDFS概述(1)————HDFS架构

    概述 Hadoop分布式文件系统(HDFS)是一种分布式文件系统,用于在普通商用硬件上运行.它与现有的分布式文件系统有许多相似之处.然而,与其他分布式文件系统的区别很大.HDFS具有高度的容错能力,旨 ...

  7. mvc的filter

    如果想要记录ajax的请求和输出信息.内部发生异常记录日志.需要登录认证.需要权限判断:那mvc的各种filter可以帮助你实现你想要的.Mvc框架支持5种不同类型的过滤器:我会按照执行顺序进行简单的 ...

  8. CSS盒子模型之详解

    前言:        盒子模型是css中最核心的基础知识,理解了这个重要的概念才能更好的排版,进行页面布局.一.css盒子模型概念    CSS盒子模型 又称框模型 (Box Model) ,包含了元 ...

  9. 【特效】单选按钮和复选框的美化(只用css)

    表单的默认样式都是比较朴素的,实际页面中往往需要美化他们.这里先说说单选按钮和复选框,有了css3,这个问题就变的好解决了.利用input与label相关联,对label进行美化并使其覆盖掉原本的in ...

  10. 用Python来实现列举某个文件夹内所有的文件列表

    用Python来实现列举某个文件夹内所有的文件列表.吾八哥我动手写代码之前分析了下,遍历一个文件夹,肯定是需要用到os模块了,查阅模块帮助信息,可知os.listdir()方法可以列举某个文件夹内的所 ...