vuejs教程

我们都知道 Vue 是一个非常典型的 MVVM 框架,它的核心功能:

  1. 双向数据绑定系统
  2. 组件化开发系统

本文我们就聊聊双向数据绑定,不管你是学过或者没学过,我相信看完本文你都会对 vue 有一个比较简单明确的了解。不过如果哪块有错误,还望指出。

很多朋友说自己读不懂,索性就“不敢”去读。要我说凡事均要去尝试,不尝试永远没有读懂的机会,如果你试着读了,并且坚持了,那你就真的能读懂。

读源码也是有技巧的,我的技巧就是: 抓住主线,从宏观到微观。

我们不能一开始就要求自己读懂所有的细节,基本不现实;最好是能找到一条主线,先把大体流程结构摸清楚,再深入到细节,逐项击破,形成对源码整体的认识。

比如,我们都知道 Vue 中更新数据后会采用 virtual DOM(虚拟dom)的方式更新 dom。

这个时候,如果你不了解 virtual DOM,那么听我一句“暂且不要去研究内部具体实现,因为这会使你丧失主线”,而你仅仅需要知道 virtual DOM 分为三个步骤:

  1. createElement( ): 用 JavaScript 对象(虚拟树) 描述 真实 DOM 对象(真实树)
  2. diff(oldNode, newNode) : 对比新旧两个虚拟树的区别,收集差异
  3. patch( ) : 将差异应用到真实 DOM 树

回过头我们再去研究这个分支,仅此而已。

上图对于学习过 Vue 的朋友来说应该不陌生吧,来自 Vue 官网深入响应式原理,建议先看图一分钟。

为了说明原理,我们会把虚拟 dom 这块用 fragment 来代替(这个是1.x版本的实现)。

并且只考虑数据为对象的情况。记住今天的主线:搞清楚响应式原理,实现一个简单的 MVVM 框架。

由一个例子开始:

template:

javascript:

我们要解决的问题有:

如何将 data 中的数据渲染到真实的宿主环境中?

template 是如何被编译成真实环境中可用的 HTML 的?

如何通过“响应式”修改数据?

计算属性 getWeChatblog 如何和 data 中的数据绑定的?

带着这些问题开始我们 Vues 的开发。尽量和 Vue 代码保持一致。下面是目录结构:

入口文件:

export 构造函数 Vues,不清楚 ES6 中 module 可以可以点这里

初始化

进入到 ./instance/index.js 就可以看到 Vues 构造函数

其实就是调用 this._init(options),我们先不看这个函数是做什么的,这里有一个疑惑点,我们并没有在 Vues 构造函数内部申明 _init() 函数呀,那是因为我们调用了一个 initMixin 函数,我们来看看此函数:

ok,原来 _init() 是在这里定义的,在 Vues 的原型上扩展了此方法。Vue也用了这种形式。

在 _init() 首先调用了 initState(this) :

但是这里有个问题,从代码中可看出监听的数据对象是 $options.data,每次需要更新视图,则必须通过 vm._data.dsx= ‘前端开发大师兄’;这样的方式来改变数据。

这显然不符合 Vue 中的赋值方式,我们所期望的调用方式应该是这样的: vm.dsx = ‘前端开发大师兄’;

所以这里需要给 Vues 实例添加一个属性代理的方法 _proxyData(),使访问 vm 的属性代理为访问 vm._data 的属性,方法代码如下:

我们初始化计算属性 computed,具体就是调用了函数 _initComputed(vm),来看看代码:

我们可以看出我们已经两次用到同一个方法——Object.defineProperty(),这就是 Vue 实现响应式数据的利器之一。举个栗子来说明。

我们为对象a 通过该方法定义了一个b属性,然后定义了 set 和 get 属性,这样我们给b 赋值就会触发它的 set 属性,我们获取值就会触发它的 get 属性。

这样我们就可以劫持数据,然后执行我们的操作。详细点可以看这里。

observe

这是我们第一个重点,observe 很明显我们会用到观察者模式,事实上Vue也是这么干的。我们看一下 observe() 做了什么?

就是做了类型判断,之后就直接实例化 Oberver ,参数为 data 对象。

官网的 Reactivity in Depth 上有这么句话:

When you pass a plain JavaScript object to a Vue instance as its data

option, Vue.js will walk through all of its properties and convert

them to getter/setters

The getter/setters are invisible to the user, but under the hood they

enable Vue.js to perform dependency-tracking and change-notification

when properties are accessed or modified

observe 使 data 变成“发布者”,watcher 是订阅者,订阅 data 的变化。那如何使 data 变为“发布者”呢?

当然是我们的利器—-Object.defineProperty() ,将数据对象 data 的属性转换为访问器属性。看看我们的代码:

我们遍历 data 对象的所有可配置属性,最终调用了 defineReactive() 函数。

将需要 observe 的数据对象进行递归遍历,包括子属性对象的属性,都加上 set 和 get 。

先看看 defineReactive() 的我们是怎么实现的:

这个地方有一个值得思考的点,如果修改一个数组的成员,该成员是一个对象,那只需要递归对数组的成员进行双向绑定即可。

但这时候出现了一个问题,如果我们进行 pop、push 等操作的时候,push 进去的对象根本没有进行过双向绑定,更别说 pop 了,那么我们如何监听数组的这些变化呢?

Vue.js 提供的方法是重写 push、pop、shift、unshift、splice、sort、reverse 这七个数组方法。

修改数组原型方法的代码可以参考observer/array.js 以及 observer/index.js。

还有就是利用 vue.set() ,借用官方的 API

get 的方法主要用来进行依赖收集,就是添加订阅者。

所以我们只要在最开始进行一次 render,那么所有被渲染所依赖的 data 中的数据就会被 getter 收集到 Dep 的 subs 中去。

set 方法会在对象被修改的时候触发(不存在添加属性的情况,添加属性请用Vue.set),这时候 set 会通知闭包中的 Dep,Dep 中有一些订阅了这个对象改变的 Watcher 观察者对象,Dep 会通知 Watcher 对象更新视图。

我们用一个简单的栗子来说明观察者模式:

那应用到我们这里就是:每个 data 属性值在 defineReactive 函数监听处理的时候,添加一个主题对象,当 data 属性发生改变,通过 set 函数去通知所有的观察者们。

那么如何添加观察者们呢,就是在 complie 函数编译 template 时,通过初始化 value 值,触发 set 函数,在 set 函数中为主题对象添加观察者。有点难理解?直接看代码就明白了。

ok,我们继续。看看我们的 Dep :

那么你可能有疑问了。。谁是订阅者。。对,没错就是 Watcher。。一旦 dep.notify()

就遍历订阅者,也就是 Watcher,并调用他的 update() 方法。

Watcher

如何实现一个 Watcher,通过上面的分析我可以确定得要一个 update() 方法。见下图:

很关键的一个地方就是 this.value = this.get() ,这个就是我们之前说的最开始要进行一次 render,我们看 get() 实现:

这个最关键了,主要做了以下几件事:

  1. 把当前 watcher 赋值给 Dep.target
  2. 获取 value 值就会触发 Oberver 中定义的 get
  3. 执行 dep.append() 添加订阅者
  4. 重新将 Dep.target 赋值为 null
  5. 返回 vaule 值

关于 notify

当监听的数据赋值就会被 set 拦截,然后执行 dep.notify() ,遍历订阅者(watcher)执行其 update() 方法,update 调用的其实是 this.run() 自己的 run 方法。我们看看:

有一个值得注意的地方,this.cb.call(this.vm, value, oldVal); 这个 cb 是什么?没错就是我们在编译 template 的时候为每一个指令绑定的更新 dom 的函数 。

最后对 watcher 做一个总结:

  1. 每次调用 run() 的时候会触发相应属性的 get
  2. get 里面会触发 dep.depend(),继而触发这里的 addDep
  3. 假如相应属性的 dep.id 已经在当前 watcher 的 depIds 里,说明不是一个新的属性,仅仅是改变了其值而已。

    则不需要将当前 watcher 添加到该属性的 dep 里

  4. 假如相应属性是新的属性,则将当前watcher添加到新属性的dep里,如通过 vm.child = {name: ‘a’} 改变了 child.name 的值,child.name 就是个新属性。

    则需要将当前watcher(child.name)加入到新的 child.name 的 dep 里,因为此时 child.name 是个新值,之前的 set,dep 都已经失效,如果不把 watcher 加入到新的 child.name 的dep中。

    通过 child.name = xxx 赋值的时候,对应的 watcher 就收不到通知,等于失效了。

  5. 每个子属性的 watcher 在添加到子属性的 dep 的同时,也会添加到父属性的 dep。
  6. 监听子属性的同时监听父属性的变更,这样,父属性改变时,子属性的 watcher 也能收到通知进行 update。

    这一步是在 this.get() –> this.getVMVal() 里面完成,forEach 时会从父级开始取值,间接调用了它的 get,触发 addDep(),在整个 forEach 过程,当前 wacher 都会加入到每个父级过程属性的 dep。

    例如:当前 watcher 的是 ‘child.child.name’, 那么 child, child.child, child.child.name 这三个属性的 dep 都会加入当前watcher。

到时候看看 Compile 了。

Compile

还记得在 _init() 函数最后那行代码吗?

new Compile ,看看做了什么?

注释说的很明白,就不做解释。看看如何转化 fragment :

就是遍历子节点添加到 fragment 。

接下来我们就会对 fragment 节点包括子节点遍历,判断其节点类型,然后调用对应的解析函数解析其中的指令。

我们只看一个 compile:

内置的指令处理方法:

最终都调用同一个方法bind() ,我们先看看它是做了什么?

  1. 获取对应指令的更新方法,并执行
  2. new Watcher,在回调函数执行 updaterFn

对于第一条,在执行 updaterFn 的时候会调用 this._getVMVal(vm, exp) :

很简单,就是获取对应的数据返回。我们看一个text类型的更新函数:

这个也是没毛病吧?ok。

第二条,new Watcher,在回调函数执行 updaterFn。还记得之前讲Watcher时提过一句:

还没记住,我就再贴一次图了

哈哈哈,明白了吧。那这个cb啥时执行呢?

当我们修改了数据就会触发对应的set ,然后就会调用 dep.notify();,通知订阅者,再调用订阅者的 update() 方法,update() 方法就会调用 this.run( ) ,run() 就会执行下面这一句:

最后补充,input 的 v-model 双向数据绑定,其实就是监听了 input 事件,还是贴代码:

在 input 事件回调执行 _setVMVal() 方法重新设置一次值。最后再贴一次代码,感觉我贴了好多,不过都是为了把事情说清楚。

到这我们就完成了一个缩减版的 Vue,当然 Vue 功能远远不止这些。我们这只是凤毛麟角。

学会这个对于你看真正的 Vue 源码帮助绝对很大,因为逻辑都是相似的。最后再看看下图,回味一下整个过程。

篇幅比较长,有时还很罗嗦,当然我只是想更清楚的讲解,真怕漏掉那个难点。

原文:大专栏  vuejs教程

原文地址:https://www.cnblogs.com/chinatrump/p/11615051.html

时间: 2024-10-11 09:56:16

vuejs教程的相关文章

JavaScript - 收藏集 - 掘金

Angular 中的响应式编程 -- 浅淡 Rx 的流式思维 - 掘金第一节:初识Angular-CLI第二节:登录组件的构建第三节:建立一个待办事项应用第四节:进化!模块化你的应用第五节:多用户版本的待办事项应用第六节:使用第三方样式库及模块优化用第七节:给组件带来活力Rx--隐藏在 Angular 中的利剑Redux你的 A... Electron 深度实践总结 - 前端 - 掘金思维导图 前言: Electron 从最初发布到现在已经维护很长一段时间了,但是去年才开始慢慢升温.笔者个人恰好

20200409 Vue 视频学习【归档】

Vue 视频学习[归档] 学习历程 时间 开始时间:2020-3-20 结束时间:2020-4-9 背景 公司的前端技术采用了 Vue ,作为后端开发,不需要完全掌握 Vue 开发,但是平时调试时,如果前后端同时使用,会很方便. Vue 作为前端三大框架之一,且作者是国人,有文档文档,方便学习. 作为开发,不应该局限于后端,对前端即使不掌握,也需要起码的了解. 学后感 老师讲解的很仔细,不仅包括 Vue 的相关知识,还学习到了 ES6 的一些语法以及其他相关知识. 学习中我跳过了项目相关的视频(

2017-11-20 中文代码示例之Vuejs入门教程(一)问题后续

"中文编程"知乎专栏原文 第一个issue: Error compiling template if using unicode naming as v-for alias · Issue #6971 · vuejs/vue 多谢尤大搞定, 貌似是把标识符的正则表达式匹配检测去掉了. 经测试已经不再在控制台报警告. 第二个issue: Unicode naming for methods seems unsupported. Some warning will be appreciat

VueJS简明教程(一)之基本使用方法

如果说是JQuery是手工作坊,那么Vue.js就像是一座工厂,虽然Vue.js做的任何事情JQuery都可以做,但无论是代码量还是流程规范性都是前者较优. Vue.js的官方中文教程其实也是一个不错的教程,不过相比于一次性把所有概念掌握,我更倾向于先会用,之后再在实际应用中把未涉及到的知识点逐步补全. 就像开车,不是非要知道发动机的工作原理才能上路的,甚至你可能一辈子也不用知道. 好了,开始吧 准备 首先,以下几点希望你已经知道或者做到了: 你会用html+css+javascript 写一些

vuejs + vuex

vuejs 的数据是双向绑定的,而这些数据只是在父组件中,如果各个组件公用的数据就要通过一个容器去管理起来, vuex是不错的选择, 今天看了下vuex的教程: 总结下遇到的问题: vue-cli 了一个项目 import的时候发现了报错:node的版本太低,升级版本就好了. import { mapState, mapActions, mapGetters, map } from "vuex";//注意大括号. https://github.com/lin-xin/notepad/

Vuejs——Vue生命周期,数据,手动挂载,指令,过滤器

版权声明:出处http://blog.csdn.net/qq20004604 目录(?)[+] 原教程: http://cn.vuejs.org/guide/instance.html http://cn.vuejs.org/guide/syntax.html 本博文是在原教程的基础上加上实例,并尝试说明的更详细. (十)Vue实例的生命周期 如图:(我自己翻译的中文版,英文版请查看本博文顶部的,第一个链接) (八)传入的数据绑定 先创建一个对象(假如是obj),然后将他传入Vue实例中,作为d

vuejs从安装开始一起飞~

要写一个基于vuejs的项目,首先安装vue~这个可以在安装nodejs时一并安装 一.安装nodejs 1.去nodejs官网对应下载合适的nodejs,然后安装 2.在菜单栏搜索cmd,右键以管理员身份打开必须是管理员身份 3.输入  node -v    出现版本号说明nodejs安装成功 3.再输入   npm install vue  ,开始安装vue,结束后输入 npm -v,出现vue的版本号,则说明安装成功,否则安装失败! 4.安装失败的盆友需要  百度搜索npm淘宝镜像网站,使

Vuejs——(2)Vue生命周期,数据,手动挂载,指令,过滤器

版权声明:出处http://blog.csdn.net/qq20004604 目录(?)[+] 原教程: http://cn.vuejs.org/guide/instance.html http://cn.vuejs.org/guide/syntax.html 本博文是在原教程的基础上加上实例,并尝试说明的更详细. (十)Vue实例的生命周期 如图:(我自己翻译的中文版,英文版请查看本博文顶部的,第一个链接) (八)传入的数据绑定 先创建一个对象(假如是obj),然后将他传入Vue实例中,作为d

Vue基础---->VueJS的使用(一)

Vue.js是一个构建数据驱动的web界面的库.它的目标是通过尽可能简单的API 实现响应的数据绑定和组合的视图组件,今天我们就开始vue.js的学习. vue的安装及使用 一.vue的下载地址:http://vuejs.org/js/vue.js 二.vue的第一个例子: 项目的结构如下,引入vue.js vue1.html的代码: <!DOCTYPE html> <html lang="en"> <head> <meta charset=&