The source learning notes of this project are based on Vue version 1.0.9, which is also the earliest tag version. The reason for choosing this version is that it is the most original version without too much functional expansion, which is conducive to a better view of the original skeleton and context of Vue and the author's original ideas. Comparing with the subsequent version 1.x.x, we find many interesting improvements and even retreats made by the author to fix bug s, such as the version iteration of vue nextTick experienced updates, retreats and re-updates.
Original address
Project address
A Source Parsing Article by Lux
Vue.js is a typical MVVM framework, the whole program is divided into the top level.
1 Global Design: Including Global Interface, Default Options
2 vm case design: including interface design (vm prototype), case initialization process design (vm constructor)
The core of constructor:
The key to the initialization process of the whole instance is to establish the relationship between the data (Model) and the view.
Monitoring data through observer and providing the ability to subscribe to changes in a data item
-
The template is parsed into a document fragment, and then the directive is parsed to get the data items and updating methods that each directive depends on. For example, after v-text="message" is parsed (for illustration only, the actual program logic will be more rigorous and complex):
The dependent data item is this.$data.message, and
The corresponding view update method node.textContent = this.$data.message
watcher combines the above two parts, that is, subscribing data dependency in directive to the observer of corresponding data, so that when the data changes, observer will be triggered, and then the corresponding view update method of dependency will be triggered. Finally, the original association effect of template will be achieved.
So the core of the whole vm is how to implement observer, directive (parser), watcher.
Above is an excerpt from an article on Lux's blog.
Beginning with v1.0.9
The project structure at this time is as follows:
The source code is in src, build is packaged and compiled code, dist is where the packaged code is placed, and test is the test code directory.
From package.json, you can see the dependency packages used by the project and the way the project is developed and run, where the compiled code is:
"build": "node build/build.js",
So we go to the corresponding file:
var fs = require('fs') var zlib = require('zlib') var rollup = require('rollup') var uglify = require('uglify-js') var babel = require('rollup-plugin-babel') var replace = require('rollup-plugin-replace') var version = process.env.VERSION || require('../package.json').version var banner = '/*!\n' + ' * Vue.js v' + version + '\n' + ' * (c) ' + new Date().getFullYear() + ' Evan You\n' + ' * Released under the MIT License.\n' + ' */' // CommonJS build. // this is used as the "main" field in package.json // and used by bundlers like Webpack and Browserify. rollup.rollup({ entry: 'src/index.js', plugins: [ babel({ loose: 'all' }) ] }) ...
You can see that rollup is used to package and compile at this time. The entry file is _src/index.js_. The code of index.js is very concise:
import Vue from './instance/vue' import directives from './directives/public/index' import elementDirectives from './directives/element/index' import filters from './filters/index' import { inBrowser } from './util/index' Vue.version = '1.0.8' /** * Vue and every constructor that extends Vue has an * associated options object, which can be accessed during * compilation steps as `this.constructor.options`. * * These can be seen as the default options of every * Vue instance. */ Vue.options = { directives, elementDirectives, filters, transitions: {}, components: {}, partials: {}, replace: true } export default Vue // devtools global hook /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production') { if (inBrowser && window.__VUE_DEVTOOLS_GLOBAL_HOOK__) { window.__VUE_DEVTOOLS_GLOBAL_HOOK__.emit('init', Vue) } }
It can be seen from this that the implementation of instance Vue in src/instance/vue also involves directives which should be used for instruction parsing and filter filters. This function is no longer available in 2.0 but frequently used in 1.0. At the same time, inBrowser should be used to determine whether the browser environment is browser environment, indicating that src/util is a catalog of tool classes. Verification
Tool class method inBrowser
First, look at in Browser_ and find that _util/index.js is just a tool function entry file:
export * from './lang' export * from './env' export * from './dom' export * from './options' export * from './component' export * from './debug' export { defineReactive } from '../observer/index'
Literally, we can know that there are languages and environments involved in tool classes. Dom operation, options? Componentization, development class, real-time definition? These types of tools, and inBrowser should belong to Env or dom, found its implementation in util/env:
... // Browser environment sniffing export const inBrowser = typeof window !== 'undefined' && Object.prototype.toString.call(window) !== '[object Object]' ...
In this paper, we use the browser's global object window to distinguish, because there is no global object in nodejs environment, so we can judge whether the typeof window is not'undefined'and not a common object created by the user himself. If so, Object. prototype. toString. call (window) /== [object object object object]
In the browser environment, this is the case:
typeof window // "object" Object.prototype.toString.call(window) // "[object Window]"
Implementation of Vue instance constructor
Then look at _src/instance/vue_, which should be the initialization function of the instance of vue. From the code, we can know that it is the constructor of an instance, and it is also the top-level implementation. The bottom code is located in the api and internal of the subdirectory, which implements the public method and private method variables respectively.
import initMixin from './internal/init' import stateMixin from './internal/state' import eventsMixin from './internal/events' import lifecycleMixin from './internal/lifecycle' import miscMixin from './internal/misc' import globalAPI from './api/global' import dataAPI from './api/data' import domAPI from './api/dom' import eventsAPI from './api/events' import lifecycleAPI from './api/lifecycle' /** * The exposed Vue constructor. * * API conventions: * - public API methods/properties are prefixed with `$` * - internal methods/properties are prefixed with `_` * - non-prefixed properties are assumed to be proxied user * data. * * @constructor * @param {Object} [options] * @public */ function Vue (options) { this._init(options) } // install internals initMixin(Vue) ... // install APIs globalAPI(Vue) ... export default Vue
The catalogue is as follows:
As you can see from the annotations, the prefix $is used to mark public methods and variables, and the prefix is used to mark private methods and variables. Variables without prefix may be used to proxy user data.
From the files introduced, we can see that private methods and variables are lifecycle Mixin life cycle, event Mixin event mechanism, state Mixin state, miscMixin filter, and common method API of instances: global API, data binding API, DOM operation DOM API, event operation API, lifecycle API.
Add a prototype method to the prototype of Vue by initMixin(Vue):
export default function (Vue) { Vue.prototype.Method = function(options) { ... } }
How to implement it is in the two folders of api and internal, so src/instance is the implementation of vue instance constructor
directives, filter s and elementDirectives
// src/index.js import Vue from './instance/vue' import directives from './directives/public/index' import elementDirectives from './directives/element/index' import filters from './filters/index' import { inBrowser } from './util/index' Vue.options = { directives, elementDirectives, filters, transitions: {}, components: {}, partials: {}, replace: true } export default Vue // devtools global hook /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production') { if (inBrowser && window.__VUE_DEVTOOLS_GLOBAL_HOOK__) { window.__VUE_DEVTOOLS_GLOBAL_HOOK__.emit('init', Vue) } }
The remaining three variables in index.js exist as variables in Vue.options. We have known the implementation of the Vue constructor before. We know whether we are in the browser by using the tool class inBrowser and whether there exists the variable _VUE_DEVTOOLS_GLOBAL_HOOK_ in the window s. If there exists, then we will call this variable when we install the debugging plug-in of Vue on behalf of the browser. The init method of one variable tells the plug-in that the Vue object has been initialized.
from 1.0 official website document custom-directive You can see that directive is a directive that allows developers to develop their own instructions. Examples are as follows
and element-directive Similar to directive, it only exists formally as an element and cannot be transmitted to element data, but it can manipulate the attributes of elements.
It's a powerful function that allows developers to decide how to render data into views when they change. There's also a lot of powerful functional code. There are only 20 files in directives alone.
From the entry file src/directives/public/index, you can see that custom directive contains methods and attributes:
// text & html import text from './text' import html from './html' // logic control import vFor from './for' import vIf from './if' import show from './show' // two-way binding import model from './model/index' // event handling import on from './on' // attributes import bind from './bind' // ref & el import el from './el' import ref from './ref' // cloak import cloak from './cloak' // must export plain object export default { text, html, 'for': vFor, 'if': vIf, show, model, on, bind, el, ref, cloak }
You can see that directive includes text operations, logical operations (loops, conditions), bidirectional bindings (this is an interesting and important quota), event bindings, data bindings, dom bindings, and one more cloak for Unrendered Styles
two-way binding, the v-model attribute in vue, binds elements of form input types such as textarea, select, and input elements of different types in both directions. The other types of elements do not support this binding.
// src/directives/public/index.js import { warn, resolveAsset } from '../../../util/index' import text from './text' import radio from './radio' import select from './select' import checkbox from './checkbox' const handlers = { text, radio, select, checkbox } export default { priority: 800, twoWay: true, handlers: handlers, params: ['lazy', 'number', 'debounce'], /** * Possible elements: * <select> * <textarea> * <input type="*"> * - text * - checkbox * - radio * - number */ bind () { // friendly warning... this.checkFilters() if (this.hasRead && !this.hasWrite) { process.env.NODE_ENV !== 'production' && warn( 'It seems you are using a read-only filter with ' + 'v-model. You might want to use a two-way filter ' + 'to ensure correct behavior.' ) } var el = this.el var tag = el.tagName var handler if (tag === 'INPUT') { handler = handlers[el.type] || handlers.text } else if (tag === 'SELECT') { handler = handlers.select } else if (tag === 'TEXTAREA') { handler = handlers.text } else { process.env.NODE_ENV !== 'production' && warn( 'v-model does not support element type: ' + tag ) return } el.__v_model = this handler.bind.call(this) this.update = handler.update this._unbind = handler.unbind }, /** * Check read/write filter stats. */ checkFilters () { var filters = this.filters if (!filters) return var i = filters.length while (i--) { var filter = resolveAsset(this.vm.$options, 'filters', filters[i].name) if (typeof filter === 'function' || filter.read) { this.hasRead = true } if (filter.write) { this.hasWrite = true } } }, unbind () { this.el.__v_model = null this._unbind && this._unbind() } }
Determining which binding and update to use by determining the element name of the element is the same for textarea processing as input with type as text, and type as number as test.
priority here is not sure what to do.
Choose the text handle which is more common:
import { _toString } from '../../util/index' export default { bind () { this.attr = this.el.nodeType === 3 ? 'data' : 'textContent' }, update (value) { this.el[this.attr] = _toString(value) } }
It uses the node type nodeType to determine whether it is text or element. When nodeType is 3, it is a text node. The binding method is this.attr['data']. If it is an element node, it is this.attr['textContent'] to get all the text in the element. If it takes textcontent from the whole html, it will get all the text content.
Node type:
(excerpts from http://www.w3school.com.cn/js...