vue源码引的什么库

MVVM大比拼之vue.js源码精析
我的图书馆
MVVM大比拼之vue.js源码精析
MVVM大比拼之vue.js源码精析
Vue 是 MVVM 框架中的新贵,如果我没记错的话作者应该毕业不久,现在在google。vue 如作者自己所说,在api设计上受到了很多来自knockout、angularjs等大牌框架影响,但作者相信 vue 在性能、易用性方面是有优势。同时也自己做了和其它框架的性能对比,在这里。
今天以版本 0.10.4 为准
Vue 的入口也很直白:
var demo = new Vue({ el: '#demo', data: { message: 'Hello Vue.js!' } })
和 ko 、avalon 不同的是,vue 在一开始就必须指定 el 。个人认为这里设计得不是很合理,因为如果一份数据要绑定到两个不同dom节点上,那就不得不指定一个同时包含了这两个dom节点的祖先dom节点。
接下来去找 Vue 的定义。翻开源码,vue 用 grunt。build命令中用了作者自己写的gulp-component来组合代码片段。具体请读者自己看看,这里不仔细说了。
从 /src/main.js 里看到,Vue 的定义就是 ViewModal 的定义。打开 ViewModel,发现它的定义中只是实例化了一个 Compiler,把自己作为参数传给构造函数。同时看到 ViewModel 原型上定义了一些方法,基本上是跟内部事件、dom 操作有关。那接下来我们就主要看看这个 compiler了。不要忘了我们第一个目的是找到它双工绑定的主要原理。
翻到 compiler 的定义,代码太长。犹豫了一下决定还是删掉一些注释贴出来,因为基本上大部分值得看的都在这里,愿深入的读者最好看源文件。
function Compiler (vm, options) {
& & var compiler = this,
& & & & key, i
& & compiler.init & & & = true
& & compiler.destroyed &= false
& & options = compiler.options = options || {}
& & utils.processOptions(options)
& & extend(compiler, options.compilerOptions)
& & compiler.repeat & = compiler.repeat || false
& & compiler.expCache = compiler.expCache || {}
& & var el = compiler.el = compiler.setupElement(options)
& & utils.log('\nnew VM instance: ' + el.tagName + '\n')
& & compiler.vm & & & = el.vue_vm = vm
& & compiler.bindings = utils.hash()
& & compiler.dirs & & = []
& & compiler.deferred = []
& & compiler.computed = []
& & compiler.children = []
& & compiler.emitter &= new Emitter(vm)
& & if (options.methods) {
& & & & for (key in options.methods) {
& & & & & & compiler.createBinding(key)
& & if (options.computed) {
& & & & for (key in options.computed) {
& & & & & & compiler.createBinding(key)
& & // VM ---------------------------------------------------------------------
& & vm.$ & & & & = {}
& & vm.$el & & & = el
& & vm.$options &= options
& & vm.$compiler = compiler
& & vm.$event & &= null
& & var parentVM = options.parent
& & if (parentVM) {
& & & & compiler.parent = parentVM.$compiler
& & & & parentVM.$compiler.children.push(compiler)
& & & & vm.$parent = parentVM
& & vm.$root = getRoot(compiler).vm
& & // DATA -------------------------------------------------------------------
& & compiler.setupObserver()
& & var data = compiler.data = options.data || {},
& & & & defaultData = options.defaultData
& & if (defaultData) {
& & & & for (key in defaultData) {
& & & & & & if (!hasOwn.call(data, key)) {
& & & & & & & & data[key] = defaultData[key]
& & & & & & }
& & var params = options.paramAttributes
& & if (params) {
& & & & i = params.length
& & & & while (i--) {
& & & & & & data[params[i]] = utils.checkNumber(
& & & & & & & & compiler.eval(
& & & & & & & & & & el.getAttribute(params[i])
& & & & & & & & )
& & & & & & )
& & extend(vm, data)
& & vm.$data = data
& & compiler.execHook('created')
& & data = compiler.data = vm.$data
& & var vmProp
& & for (key in vm) {
& & & & vmProp = vm[key]
& & & & if (
& & & & & & key.charAt(0) !== '$' &&
& & & & & & data[key] !== vmProp &&
& & & & & & typeof vmProp !== 'function'
& & & & ) {
& & & & & & data[key] = vmProp
& & compiler.observeData(data)
& & // COMPILE ----------------------------------------------------------------
& & if (options.template) {
& & & & this.resolveContent()
& & while (i--) {
& & & & compiler.bindDirective(compiler.deferred[i])
& & compiler.deferred = null
& & if (this.computed.length) {
& & & & DepsParser.parse(this.computed)
& & compiler.init = false
& & compiler.execHook('ready')
注释就已经写明了 compiler 实例化分为四个阶段,第一阶段是一些基础的设置。两个值得注意的点:一是在 compiler 里面定义一个 vm 属性来保存对传入的 ViewModel 的引用;二是对 method 和 computed 的每一个成员都调用了 createBinding 。跳到 createBinding:
CompilerProto.createBinding = function (key, directive) {
& & /*省略*/
& & var compiler = this,
& & & & methods &= compiler.options.methods,
& & & & isExp & &= directive && directive.isExp,
& & & & isFn & & = (directive && directive.isFn) || (methods && methods[key]),
& & & & bindings = compiler.bindings,
& & & & computed = compiler.options.computed,
& & & & binding &= new Binding(compiler, key, isExp, isFn)
& & if (isExp) {
& & & & /*省略*/
& & } else if (isFn) {
& & & & bindings[key] = binding
& & & & binding.value = compiler.vm[key] = methods[key]
& & } else {
& & & & bindings[key] = binding
& & & & if (binding.root) {
& & & & & & /*省略*/
& & & & & & if (computed && computed[key]) {
& & & & & & & & // computed property
& & & & & & & & compiler.defineComputed(key, binding, computed[key])
& & & & & & } else if (key.charAt(0) !== '$') {
& & & & & & & & /*省略*/
& & & & & & } else {
& & & & & & & & /*省略*/
& & & & & & }
& & & & } else if (computed && computed[utils.baseKey(key)]) {
& & & & & & /*省略*/
& & & & } else {
& & & & & & /*省略*/
& & return binding
它做了两件事情:一是实例化了一个叫做 Bingding 的东西,二是将 method 和 computed 成员的 bingding 进行了一些再处理。凭直觉和之前看过的代码,我们可以大胆猜测这个实例化的 bingding 很可能就是用来保存数据和相应地"更新回调函数"的集合。点进 /src/binding 里。果然,看到其中的 update 、pub 等函数和 sub 、dir 等对象成员,基本证明猜对了。
到这里,实例化的对象已经有点多了。后面还会更多,为了让各位不迷失,请提前看看这张关键对象图:
看完 bingding,我们继续回到 createBinding 中,刚才还说到对 method 和 computed 成员的 bingding 做了一些再处理。对 method,就直接在 vm 上增加了一个同名的引用,我们可以把 vm 看做一个公开的载体,在上面做引用就相当于把自己公开了。对 computed 的成员,使用defineComputed 做的处理是:在vm上定义同名属性,并将 getter/setter 对应到相应computed成员的$get和$set。
至此,compiler 的第一部分做完,基本上把数据的架子都搭好了。我们看到 bingding 的 pub 和 sub, 知道了 vue 也是就与 observe 模式,那接下来就看看它是如何把把视图编译成数据更新函数,并注册到bingding里。
回到compiler里,第二部分处理了一下vm,增加了一些引用。 第三部分关键的来了,一看就知道最重要的就是第一句 compiler.setupObserver() 和最后一句compiler.observeData(data) 。直接看源码的读者,注释里已经很清楚了。第一句是用来注册一些内部事件的。最后一句是用来将数据的成员转化成 getter/setter。并和刚刚提到的bingding 相互绑定。值得注意的是,如果遇到数据成员是对象或者数组,vue 是递归式将它们转化成 getter/setter 的,所以你嵌套多深都没关系,直接替换掉这些成员也没关系,它对新替换的对象重新递归式转化。
这里的代码都很易懂,读者可以自己点进去看。我只想说一点,就是 vue 在内部实现中使用了很多事件派发器,也就是 /src/emitter。比如对数据的 set 操作。在 set 函数只是触发一个 set 事件,后面的视图更新函数什么都是注册这个事件下的。这个小小的设计让关键的几个模块解耦得非常好,能够比较独立地进行测试。同时也为框架本身的扩展提供了很多很多的空间。下面这张图展示了对data的成员进行修改时内部的事件派发:
视图渲染和扩展
看到最后一部分视图渲染,这里值得注意的是,vue 支持的是 angular 风格的可复用的directive。directive 的具体实现和之前的 ko 什么的没太大区别,都是声明 bind、update等函数。
至于扩展方面,vue已有明确的 component 和 plugin 的概念,很好理解,读者看看文档即可。 另外注意下,vue 是到最后才处理 computed 和普通数据的依赖关系的。
总体来说,vue 在内核架构上很精巧。精指的是没有像ko一样先实现一些强大但复杂的数据结构,而是需要什么就实现什么。巧指的是在代码架构上既完整实现了功能,又尽量地解耦,为扩展提供了很大的空间。比如它使用了 binding 这样一个中间体,而不是将试图更新函数直接注册到数据的set函数中等等,这些设计都是值得学习了。 当然我们也看到了一些有异议的地方: 比如是否考虑将数据的转化和视图编译明确分成两个过程?这样容易实现数据的复用,也就是最开始讲的问题。这样改的话,compiler 的实例化的代码也可以稍微更优雅一些:先处理数据和依赖关系,再建立bingding并绑定各种事件,最后处理视图。
馆藏&25768
TA的最新馆藏
喜欢该文的人也喜欢3.6k 人阅读
标签:至少1个,最多5个
源码版本:2.0.5
构造器、实例、选项
让我们用一段demo展示一下这三个概念:
&div id="app"&
{{ message }}
var vm = new Vue({
el: '#app',
message: 'Hello Vue!'
Vue: Vue的构造器
vm : 实例
(实例名可以任意取,这里我们便于理解保持和vue文档一致)
new Vue(options):
选项(options)即为传入构造器里的配置选项。(data, methods,computed,created...)
当我们了解这三个概念,将有助于我们去理解vue的api文档
Vue 的开放api
以 Vue.config.xx
的形式去访问和修改
全局API: 以Vue.xx 的形式去访问和修改
以 var vm = new Vue(options) 的形式将options传入构造器
实例属性/方法:
以vm.$xx的方式去访问
(前缀$,为了避免用户data/methods等解析后绑定的api 和 默认api冲突)
从api文档中我们可以了解到,当我们引入vue.js, 我们仅仅引入了一个构造函数(Vue)引入了构造函数后,我们有几种使用方式
最常见的使用方式:
var vm = new Vue(options)
将我们自定义的选项,传入构造器。 当new Vue(options)时,会自动运行vm._init方法
解析各种选项
调用beforeCreate 和created 上绑定的钩子函数
将数据项(data,computed,props)和methods等绑到实例上
调用vm.$mount方法,来执行模板渲染
返回一个实例对象
实际上,我们使用vue.js来开发时,主要就是配置不同的options提供Vue构造器解析,实现不同的业务功能。
通过更改Vue.config来进行全局配置
在选项内部可以使用 Vue 和 vm 来调用构造器和实例上的方法
Vue源码是怎么开放这些api的
src/core/index.js
import Vue from './instance/index'
import { initGlobalAPI } from './global-api/index'
import { isServerRendering } from 'core/util/env'
initGlobalAPI(Vue)
Object.defineProperty(Vue.prototype, '$isServer', {
get: isServerRendering
Vue.version = '__VERSION__'
export default Vue
Vue源码的主入口主要做三件事1.引用 ./instance/index 中暴露的Vue构造器2.调用initGlobalAPI方法,定义全局资源3.暴露Vue
initGlobalAPI
src/core/global-api/index.js
//源码有点长,我去掉了引用部分和一些注释。
export function initGlobalAPI (Vue: GlobalAPI) {
const configDef = {}
configDef.get = () =& config
if (process.env.NODE_ENV !== 'production') {
configDef.set = () =& {
util.warn(
'Do not replace the Vue.config object, set individual fields instead.'
Object.defineProperty(Vue, 'config', configDef)
Vue.util = util
Vue.set = set
Vue.delete = del
Vue.nextTick = util.nextTick
Vue.options = Object.create(null)
config._assetTypes.forEach(type =& {
Vue.options[type + 's'] = Object.create(null)
Vue.options._base = Vue
util.extend(Vue.options.components, builtInComponents)
initUse(Vue)
initMixin(Vue)
initExtend(Vue)
initAssetRegisters(Vue)
initGlobal的代码就是对Vue进行各种方法和属性定义
【Vue.config】
各种全局配置项
【Vue.util】
各种工具函数,还有一些兼容性的标志位(哇,不用自己判断浏览器了,Vue已经判断好了)
【Vue.set/delete】
这个你文档应该见过
【Vue.nextTick】
【Vue.options】 这个options和我们上面用来构造实例的options不一样。这个是Vue默认提供的资源(组件指令过滤器)。
【Vue.use】 通过initUse方法定义
【Vue.mixin】 通过initMixin方法定义
【Vue.extend】通过initExtend方法定义
这些定义的全局api可好玩了,平常我们多是用实例上的方法。其实构造器上也绑了不少好用的方法。有兴趣的同学,可以用下方代码去探究一下
在你的vue项目里,谷歌命令行键入
Object.getOwnPropertyNames(Vue)
//可以看定义在对象上的所有属性名/方法名
Vue.config
Vue.set.toString()
//我们平常在控制台上是看不了一个函数到底源码怎么样的,用toString()就可以啦
Vue构造器的定义
src/core/instance/index.js
//构造函数,当new Vue(options) 会自动执行这个函数
function Vue (options) {
if (process.env.NODE_ENV !== 'production' &&
!(this instanceof Vue)) {
warn('Vue is a constructor and should be called with the `new` keyword')
this._init(options)
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
这里就不一个一个函数展开了构造函数里其实就一句话,this._init(options)
initMixin之类的方法,定义了实例上的方法,下面给出一个探索地图,以供探究源码
我们可以看到 以 "_"为开头的方法,多半是Vue内部使用,但不公开的api。以“$” 为开头的方法,是文档中公开给用户使用的默认api
至此,我们对Vue的结构有了个初步的了解,以及相关api的原始出处有了初步了解。
在学习的过程中,参考了两位大牛的文章,收益良多
13 收藏&&|&&89
你可能感兴趣的文章
你可能感兴趣的文章
331 收藏,
分享到微博?
我要该,理由是:
在 SegmentFault,学习技能、解决问题
每个月,我们帮助 1000 万的开发者解决各种各样的技术问题。并助力他们在技术能力、职业生涯、影响力上获得提升。vue源码入口文件分析(推荐)
转载 &更新时间:日 09:22:23 & 作者:zhanglearning
这篇文章主要介绍了vue源码入口文件分析(推荐),小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
开发vue项目有段时间了, 之前用angularjs 后来用 reactjs 但是那时候一直没有时间把自己看源码的思考记录下来,现在我不想再浪费这 来之不易的思考, 我要坚持!!
看源码我个人感觉非常开心,每每看上一段,自己就充实许多,不知道你是否和我一样。
vue 源码是众多module(模块)用 rollup 工具合并而成, 从package.json 中能够看到。现在让我们从github上下载vue项目,开始我们今天的“思考”。
我下载的源码版本是:"version": "2.5.7",
源码起始位置从这里可以看到
"dev": "rollup -w -c build/config.js --environment TARGET:web-full-dev"
// 从build/config.js 中找到 TARGET: web-full-dev 这是运行和编译(支持现在的浏览器,由于里面大量应用了ES6-7)后的
// Runtime+compiler development build (Browser)
'web-full-dev': {
entry: resolve('web/entry-runtime-with-compiler.js'),
dest: resolve('dist/vue.js'),
format: 'umd',
env: 'development',
alias: { he: './entity-decoder' },
找到了开始文件就是 "web/entry-runtime-with-compiler.js", 然后我们一路找 Vue 对象 终于在 “instance/index.js” 中找到了:
// 这是Vue 的开始位置
function Vue (options) {
// 判断如果是不是生产环境,且不是通过new关键字来创建对象的话,就在控制台打印一个warning
if (process.env.NODE_ENV !== 'production' &&
!(this instanceof Vue)
warn('Vue is a constructor and should be called with the `new` keyword')
this._init(options)
看似到这里都结束了,因为我们目的就是找到开始位置,但是我有个疑问,为什么Vue需要这么多层 ?
entry-runtime-with-compiler.js
runtime/index.js
core/index.js
instance/index.js
当我仔细看了源码后恍然大悟,我们先看看他们这些文件都做了什么:
(1)instance/index.js
从Vue 模块命名中能看出一些端倪, instance (实例) 。
这个文件是Vue 对象的开始,同时也是Vue 原型链(prototype) 方法的集中文件
initMixin(Vue)
// $set、$delete、$watch
stateMixin(Vue)
// $on、$once、$off、$emit
eventsMixin(Vue)
// _update、$forceUpdate、$destroy
lifecycleMixin(Vue)
// $nextTick、_render、以及多个内部调用的方法
renderMixin(Vue)
这些方法只有实例化了才能调用。
(2)core/index.js
这个文件在Instance/index.js 创建和初步加工后,再次加工。 那他主要做了什么呢? 我们不考虑运行环境
initGlobalAPI(Vue)
对,就调用了这个方法,很简单明了吧 --- "初始化全局接口",
让我们走进initGlobalAPI 方法
export function initGlobalAPI (Vue: GlobalAPI) {
const configDef = {}
configDef.get = () =& config
// 在 非生产环境,如何修改了配置文件config里面的内容会提示警告
if (process.env.NODE_ENV !== 'production') {
configDef.set = () =& {
'Do not replace the Vue.config object, set individual fields instead.'
// 定义config 属性, 监听变化
Object.defineProperty(Vue, 'config', configDef)
// exposed util methods.
// NOTE: these are not considered part of the public API - avoid relying on
// them unless you are aware of the risk.
Vue.util = {
mergeOptions,
defineReactive
Vue.set = set
Vue.delete = del
Vue.nextTick = nextTick
Vue.options = Object.create(null)
// 给vue 创建 ASSET_TYPES 的 空对象
ASSET_TYPES.forEach(type =& {
Vue.options[type + 's'] = Object.create(null)
// this is used to identify the "base" constructor to extend all plain-object
// components with in Weex's multi-instance scenarios.
Vue.options._base = Vue
extend(Vue.options.components, builtInComponents)
// Vue.use
initUse(Vue)
// Vue.mixin
initMixin(Vue)
// Vue.extend
initExtend(Vue)
// Vue.component, Vue.directive, Vue.filter
initAssetRegisters(Vue)
这里面基本都是 静态方法,即:用 Vue. xxx 的形式调用。
(3)runtime/index.js
这里就加一些扩展和 在 Vue.prototype上添加了__patch__和$mount(挂载元素)。
// Vue.options.directives(model和show)和 Vue.options.components(Transition和TransitionGroup)
extend(Vue.options.directives, platformDirectives)
extend(Vue.options.components, platformComponents)
// install platform patch function
Vue.prototype.__patch__ = inBrowser ? patch : noop
// public mount method
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && inBrowser ? query(el) : undefined
return mountComponent(this, el, hydrating)
(4)entry-runtime-with-compiler.js
就干了一件事就是重写$mount, Vue根据不同运行环境,重写不同$mount
const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
return mount.call(this, el, hydrating)
到此我们找到了开始执行的文件,和每个文件有什么用,具体怎么做的,做了什么我会下次再写。不过我们刚开始不要太在乎每个细节,不要非得弄懂每一行代码,如果那样,真的太累了,而且可能没有勇气坚持下去。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。
大家感兴趣的内容
12345678910
最近更新的内容
常用在线小工具没有更多推荐了,
加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!

我要回帖

更多关于 vue 引入第三方js库 的文章

 

随机推荐