vue 1.0源代码重点难点分析
Posted pzhu1
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了vue 1.0源代码重点难点分析相关的知识,希望对你有一定的参考价值。
vue初始化根组件的入口代码:
对于没有路由的单页应用来说,入口就是new Vue(options),options就是根组件代码,对于有路由的项目来说,入口就是router.start(app),其中app是根组件。
function Router() {} // router构造函数
var router = new Router(); // new router实例
router.start(app); // 项目真正的入口
Router.prototype.start = function start(App, container, cb) {
this._appContainer = container; //把app和container保存在router实例,后面的Vuecomponent就是app。
var Ctor = this._appConstructor = typeof App === ‘function‘ ? App : Vue.extend(App);
this.history.start();
HashHistory.prototype.start = function start() {
self.onChange(path.replace(/^#!?/, ‘‘) + query); //当浏览器地址中hash部分变化时触发执行listener(),listener会格式化浏览器地址中hash部分(hash如果没有/则加/前缀),再执行onChange,传入path
onChange: function onChange(path, state, anchor) {
_this._match(path, state, anchor);
Router.prototype._match = function _match(path, state, anchor) {
var _this3 = this; // this是router实例
var route = new Route(path, this);
var transition = new RouteTransition(this, route, currentRoute); //new transition实例时传入router实例
if (!this.app) {
_this3.app = new _this3._appConstructor({ //new根组件实例,构造函数是function VueComponent (options) { this._init(options) },new根组件实例保存在router实例中,router.app就是根组件实例,router实例会保存在根组件实例中。
在new Vuecomponent()实例化时要执行Vuecomponent构造函数,Vuecomponent是继承Vue的,就是要执行Vue的_init初始化函数,就开始初始化根组件,编译根组件
template。
vue的钩子函数created()就是指new组件实例之后,而ready()就是指编译组件template之后,先new组件实例,再编译组件template,因为是在创建组件实例之后才
执行构造函数Vue才执行_init()开始初始化编译,编译完成之后插入网页生效,是异步过程,所以执行ready()时可能插入网页生效还没有真正完成,如果ready()有
代码需要在网页找组件元素,比如初始化轮播的代码,可能就需要延迟执行等插入网页生效完成,否则就会出现无法理解的异常现象。
router实例会保存在根组件实例中,传递给各个子组件,因此vue的各个组件访问router实例非常方便。
Vue.prototype._init就是vue组件入口/初始化函数,这是组件通用的初始化函数,是处理组件的核心函数,因为vue是从根组件开始递归编译所有的子组件,
所有的子组件都要跑一遍这个方法,所以这个方法就是vue的顶层核心方法,在底层有几十几百个方法进行各种处理。
先从_init开始把组件初始化过程从头到尾大概走一遍如下(只列举主要语句):
Vue.prototype._init = function (options) {
this._initState(); //之后el从id变为网页元素DOM(引用网页元素)
if (options.el) {
this.$mount(options.el); //如果根组件写了template,el就是根组件占位元素,否则el就是online template元素,从根组件开始递归编译所有的子组件子节点
}
Vue.prototype._initState = function () {
this._initProps();
this._initMeta();
this._initMethods();
this._initData();
this._initComputed();
};
Vue.prototype._initProps = function () {
// make sure to convert string selectors into element now
el = options.el = query(el);
}
Vue.prototype.$mount = function (el) {
this._compile(el); //从根组件开始递归编译所有的子组件子节点,然后插入网页生效
return this;
};
Vue.prototype._compile = function (el) {
var original = el;
el = transclude(el, options);
var rootLinker = compileRoot(el, options, contextOptions); // compile产生link函数
var rootUnlinkFn = rootLinker(this, el, this._scope); // 执行link函数,插入网页生效
var contentUnlinkFn = contentLinkFn ? contentLinkFn(this, el) : compile(el, options)(this, el); //编译产生link()再执行link()
if (options.replace) {
replace(original, el); //可能需要替换更新
}
function compileRoot(el, options, contextOptions) {
replacerLinkFn = compileDirectives(el.attributes, options); //root元素<div id=没什么要编译的
return function rootLinkFn(vm, el, scope) {
}
function compileDirectives(attrs, options) {}
function compile(el, options, partial) {
var nodeLinkFn = compileNode(el, options)
var childLinkFn = compileNodeList(el.childNodes, options)
return function compositeLinkFn(vm, el, host, scope, frag) {
function makeChildLinkFn(linkFns) {
return function childLinkFn(vm, nodes, host, scope, frag) { //var childLinkFn = 的内容
function compileNodeList(nodeList, options) { // 会递归调用自己,层层递归所有子节点
nodeLinkFn = compileNode(node, options); //每个子节点编译一遍
childLinkFn = compileNodeList(node.childNodes, options) //递归编译子节点的子节点
return linkFns.length ? makeChildLinkFn(linkFns) : null; //nodeLinkFn和childLinkFn在linkFns中
function compileNode(node, options) { // 编译一个节点
var type = node.nodeType;
if (type === 1 && !isScript(node)) {
return compileElement(node, options);
} else if (type === 3 && node.data.trim()) {
return compileTextNode(node, options);
function compileElement(el, options) { // 到这里要解析处理页面元素表达式中的指令属性
// check terminal directives (for & if)
if (hasAttrs) {
linkFn = checkTerminalDirectives(el, attrs, options);
}
// check element directives
if (!linkFn) {
linkFn = checkElementDirectives(el, options);
}
// check component
if (!linkFn) {
linkFn = checkComponent(el, options);
}
// normal directives
if (!linkFn && hasAttrs) {
linkFn = compileDirectives(attrs, options);
}
return linkFn;
function checkTerminalDirectives(el, attrs, options) { // 编译处理指令
def = resolveAsset(options, ‘directives‘, matched[1]); //每种每个属性指令都有一套方法找出来
return makeTerminalNodeLinkFn(el, dirName, value, options, termDef, rawName, arg, modifiers);
function makeTerminalNodeLinkFn(el, dirName, value, options, def, rawName, arg, modifiers) {
var parsed = parseDirective(value); //指令表达式字符串如果没有|,就无需解析,直接返回指令表达式字符串
var fn = function terminalNodeLinkFn(vm, el, host, scope, frag) {
if (descriptor.ref) {
defineReactive((scope || vm).$refs, descriptor.ref, null);
}
vm._bindDir(descriptor, el, host, scope, frag);
};
return fn;
Vue.prototype._bindDir = function (descriptor, node, host, scope, frag) {
this._directives.push(new Directive(descriptor, this, node, host, scope, frag));
};
至此解析编译节点的指令表达式结束,然后执行link函数:
function compositeLinkFn(vm, el, host, scope, frag) {
var dirs = linkAndCapture(function compositeLinkCapturer() {
if (nodeLinkFn) nodeLinkFn(vm, el, host, scope, frag);
if (childLinkFn) childLinkFn(vm, childNodes, host, scope, frag);
}, vm);
function linkAndCapture(linker, vm) {
linker(); //增加新解析的directive实例存储到vm._directives中
var dirs = vm._directives.slice(originalDirCount); //取最新增加的directive实例
sortDirectives(dirs);
for (var i = 0, l = dirs.length; i < l; i++) {
dirs[i]._bind(); //执行每个directive实例的_bind方法,比如v-for是一个directive实例,每个循环项又是一个directive实例,组件标签也是一个directive实例
}
return dirs;
}
linker()就是要执行编译阶段产生的link函数:
function childLinkFn(vm, nodes, host, scope, frag) {
nodeLinkFn(vm, node, host, scope, frag);
childrenLinkFn(vm, childNodes, host, scope, frag); //childrenLinkFn就是childLinkFn,子节点递归调用
nodeLinkFn()就是编译阶段产生的那个fn:
var fn = function terminalNodeLinkFn(vm, el, host, scope, frag) {
if (descriptor.ref) {
defineReactive((scope || vm).$refs, descriptor.ref, null);
}
vm._bindDir(descriptor, el, host, scope, frag);
};
要执行new Directive(descriptor, this, node, host, scope, frag),然后把directive实例保存到vm._directives中,
new Directive时只是把之前compile/link阶段产生的数据方法保存到directive实例中,并无其它处理。
Directive.prototype._bind = function () { //dirs[i]._bind(),元素节点每个属性指令都有一个directive实例
// setup directive params
this._setupParams();
this.bind(); //bind方法如下
var watcher = this._watcher = new Watcher(this.vm, this.expression, this._update, // 凡是要获取表达式/变量值都要创建watcher,会把watcher加入到在求值过程中依赖的变量属性的dep中。
this.update(watcher.value); //更新属性指令网页数据,比如id="$index"要把变量值插入,比如{{item.name}}要把变量值插入,网页数据更新之后就处理完了,更新网页是最后一步,每种属性指令的update方法都不一样。
以v-for指令为例来看一段指令的源代码:
var vFor = { //
bind: function bind() {
if (‘development‘ !== ‘production‘ && this.el.hasAttribute(‘v-if‘)) {
warn(‘<‘ + this.el.tagName.toLowerCase() + ‘ v-for="‘ + this.expression + ‘" v-if="‘ + this.el.getAttribute(‘v-if‘) + ‘">: ‘ + ‘Using v-if and v-for on the same element is not recommended - ‘ + ‘consider filtering the source Array instead.‘, this.vm);
} //不推荐同时使用v-for和v-if是在这儿检查告警的
this.start = createAnchor(‘v-for-start‘); //text节点
this.end = createAnchor(‘v-for-end‘);
replace(this.el, this.end); //先用占位元素替换掉v-for元素,等编译好v-for元素之后再插入网页到占位元素位置生效
before(this.start, this.end);
this.factory = new FragmentFactory(this.vm, this.el); //此过程会compile(this.el)生成linker保存在fragment实例中
function FragmentFactory(vm, el) {
linker = compile(template, vm.$options, true); // 编译指令template,v-for指令是一个数组循环产生一个列表
router切换路由组件时涉及transition过渡效果,所以相关代码是封装在transition中:
var startTransition = function startTransition() {
transition.start(function () {
_this3._postTransition(route, state, anchor);
RouteTransition.prototype.start = function start(cb) {
var view = this.router._rootView; //_rootView是<router-view>指令实例,里面有app组件构造器
activate(_view, transition, depth, cb); //transition里面有要切换的子页面路由和component构造函数对象,里面有options(组件代码)
function activate(view, transition, depth, cb, reuse) { //depth对应子路由
var handler = transition.activateQueue[depth];
if (!handler) {
view.setComponent(null);
component = view.build({ //view就是占位标签router-view指令实例,build方法就是var component={}中的build方法
build: function build(extraOptions) {
var child = new this.Component(options); //options就是之前准备好的组件构造器含组件代码,这就是new 路由组件实例化
return child;
Vue[type] = function (id, definition) { //这就是vue component的构造函数的构造函数
definition = Vue.extend(definition);
this.options[type + ‘s‘][id] = definition;
return definition;
view.transition(component);
component.$before(view.anchor, null, false);
//Actually swap the components
transition: function transition(target, cb) { //var component={}中的transition方法,target是组件实例,实例中el是已经编译产生的DOM对象,可以直接插入网页生效。
self.remove(current);
target.$before(self.anchor, cb);
Vue.prototype.$before = function (target, cb, withTransition) { //此时target变为anchor占位元素(text节点)
return insert(this, target, cb, withTransition, beforeWithCb, beforeWithTransition);
function insert(vm, target, cb, withTransition, op1, op2) {
function beforeWithCb(el, target, vm, cb) {
before(el, target);
if (cb) cb();
}
function beforeWithTransition(el, target, vm, cb) {
applyTransition(el, 1, function () {
before(el, target);
}, vm, cb);
有两个细节提一下:
function View (Vue) {
Vue.elementDirective(‘router-view‘, viewDef);
在View构造函数中定义了router-view这个指令/组件。
下面是vue的on方法,是用底层js事件方法:
function on(el, event, cb, useCapture) {
el.addEventListener(event, cb, useCapture);
}
vue没有再定义一套事件机制,就是从上层封装直接调用底层js方法来定义事件绑定,比肩简单。
要注意,cb中已经引用组件实例,所有的组件实例都是保存在根组件实例中,按id索引。当点击页面执行方法时,页面并没有作用域指引,方法也不在全局空间,
就是在js的events[]中找handler执行,关键关键关键是handler方法代码中已经引用组件实例,这就是作用域。
它如何引用组件实例呢?估计很少有人想过这个问题。
先new router实例,要么在全局空间创建router实例,要么把router实例保存在全局,再new 根组件实例,保存在router实例,再new 组件实例,保存在根组件,组件实例的方法用this引用组件实例,因此引用组件实例就是引用保存在全局的router实例中的属性,js对象引用机制会导致能引用
到组件实例,这个可是很关键的,否则就全乱套了。
其它的框架大都自己又定义了一套事件机制,就复杂了。
组件构造函数Vuecomponent用Vue.extend继承vue构造函数,每个组件只是options不同,源代码中组件的初始化方法:
var component = {
bind: function bind() {
this.setComponent(this.expression);
build: function build(extraOptions) { //入口参数就是组件的代码
extend(options, extraOptions);
var child = new this.Component(options); //就是new VueComponent(),就是new Vue()实例,只不过是子实例。
setComponent: function setComponent(value, cb) {
self.mountComponent(cb);
mountComponent: function mountComponent(cb) {
var newComponent = this.build();
路由组件与页面组件不同,页面组件在编译template时会创建一个directve指令实例,里面有组件的id和descriptor,而所有的组件定义代码都在components[]中可以找到。
路由组件是new Vuecomponent()创建组件实例时执行_init() -> $mount(el)开始编译template,指令组件是在编译template过程中层层递归扫描html元素节点找到指令标签再编译处理指令标签。
根组件实例只在执行入口初始化时创建一次,每次路由变化时,会执行一次_match,this.app指根组件/主模块,根组件实例创建之后一直存在,不会再执行new _appConstructor()初始化根组件。
vue处理的对象主要是组件和指令,主要用vue构造函数和directive构造函数来处理组件和指令,new实例时就是初始化组件和指令,编译组件时从根节点开始递归循环
扫描所有的子节点,只要有组件/指令都是用同样的通用代码处理,所以只要上述两个构造函数对象写ok了,把编译循环递归写ok了,就基本成功了,不管template有
多复杂有多少层节点嵌套,有多少层组件嵌套,都是用通用方法和循环递归处理。
还有watcher构造函数,是为了实现数据更新同步。
Vue内置指令router-view,slot,v-for,v-if,v-model,是重要指令,每个指令有一套方法比如bind,update,都已经设计好,如果自定义指令,需要自己写方法,最起码
要写bind方法(初始化方法)。
从初始化根组件开始执行vue._init,如果根元素页面有调用组件,则每个组件都再执行一遍vue._init,从根组件开始每个组件
都初始化一个组件实例,从根元素开始每个节点都要编译处理一遍,编译之后产生link函数再执行link函数。对于每个属性指令
则执行一遍new Directive()创建一个directive实例,相关数据方法都保存在实例中,主要是el和update方法,
如果属性指令写在组件标签元素内,则el是编译之后插入网页的<div>元素,执行update方法更新网页时是针对网页中这个<div>
进行属性修改,指令实例保存在所属的组件实例中,路由组件中定义/调用的子组件都保存在组件实例的options.components下。
页面每个表达式都都会创建watcher,组件中被页面绑定的每个属性变量都会创建set/get方法,包含所有依赖自己的watcher,
watcher中的update方法会调用指令的update方法更新页面,比如:class指令的update方法就是修改所在元素的class属性,
class="{{}}"也是指令,更新时也是要修改所在元素的class属性。
vue自带指令保存在options.directives中,对于<test>这样的自定义标签,是已经用component定义的组件,它是指令组件,
既是指令也是组件,在当前页面组件实例的options.components下有,在_directives下也有,指令组件要把component流程
和directive流程都走一遍。
编译一个节点/属性,只是准备数据,产生link函数,执行link函数才初始化生效,比如执行指令的初始化方法,
对于组件指令,初始化方法最后要执行mountComponent才把template元素插入网页生效,之前的所有处理都是在做数据准备
每个指令都是事先定义好的,有bind/update方法,自定义指令也有方法,在页面调用指令时,
编译时查指令集合,获取指令的相关数据,然后new directive创建一个指令实例,保存在当前子页面组件实例中,编译就
大功告成了,然后执行link函数,把当前页面组件中调用的所有指令_directives[]都执行一遍初始化方法即可,指令的初始化
方法也是更新方法,就是是设置/修改指令所在的元素的属性,对于组件指令,就更复杂,要new component()创建组件实例,
再执行组件指令的初始化方法,执行mountComponent完成最后一个环节,把组件template元素插入网页生效,所以组件指令
既是组件又是指令,component和directive两种流程都要走一遍,非常复杂。在组件指令写属性,处理起来是最复杂的,
要把<test>变成<div>,标签原始属性复制到<div>,但标签原始属性的作用域是调用标签的页面组件,也就是标签组件的父组件,
而<div>中replacer属性,也就是写在template中的<div>中的属性,作用域是标签组件本身,除此之外,所有组件/指令处理
流程都是一样的,只是不同的组件/指令,它们的数据方法不同。
再小结一下:
组件走Vue构造器流程,指令走Directive构造器流程,组件指令两个流程都走,看Vue和Directive代码,要清楚每个组件,每个指令,都要执行‘
一遍同样的代码,代码会被多次执行,每次执行都是针对一个不同的组件/指令。编译方法中compileNodesList要递归所有的子节点,包括看不见的文本节点,
compileNode编译每个节点时要遍历所有的属性,html属性,指令属性,props属性,处理方法是不同的。组件指令既是组件又是指令,两种流程都要走一遍。
组件/指令都是事先定义好的,数据方法都已经有了,都保存在根组件Vue实例中,再继承到各个子组件实例中,编译时就是要判断标签/属性是不是已知指令,
如果是,就把数据准备一下,然后new directive实例保存在当前组件实例中,再执行link函数,link函数会执行指令的初始化函数,初始化函数会最后完成
网页更新,对于组件指令就是要把template元素插入网页生效。
为了实现数据同步,还要针对组件的属性和页面绑定的变量包括方法创建watcher,在初始化指令时要进行watcher相关处理,这是另外一种主要的流程,贯穿在
所有主要流程中。
vuex/store是vue作者参考flux/mutation实现原理自己写了一套代码实现了缓存数据方法,并不是第三方插件,调用
vuex/store初始化缓存的方式就是:
new Vuex.Store({
modules: {
userInfo: {
state: {
userInfo : {
username: ‘‘
}
}
},
mutations: {
[‘SETUSERINFO‘] (state, data) {
state.userInfo.username = data.username;
}
}
}
},
组件调用vuex/store数据方法的方式是:
import {getMenus} from ‘../vuex/getters/user_getters‘
import {setUserInfo} from ‘../vuex/actions/user_actions‘
store : store,
vuex : {
getters : {
getMenus : (state) => {
return state.userInfo.globle.menus;
}
},
actions : {
setUserInfo : ({dispatch},data) => {
dispatch(‘SETUSERINFO‘,data);
}
}
}
vuex安装到vue的相关源代码:
function vuexInit() {
var actions = vuex.actions;
options.methods[_key] = makeBoundAction(this.$store, actions[_key], _key); // 给action方法增加一层封装,所以你debug看组件实例里面的action方法并不是你自己写的原始方法,而是一个封装方法,任何框架只要访问全局store数据本质上都是加一层封装,由封装方法再调用真正的action方法,由于封装方法在store相关的程序空间,可以访问store/dispatch,可以传递给原始action方法,因此你写的原始action方法无需涉及store/state,可以写形参dispatch,实际调用时会自动传递dispatch,这就是action方法的奥秘,react的reducer/action方法本质原理上也是一样的。
store.js:
Vue.use(Vuex); //初始化时会安装vuex插件到vue,也就是执行vuex的install程序。
vuex.js:
function install(_Vue) {
override(Vue); //改写vue
}
function override (Vue) {
var _init = Vue.prototype._init;
Vue.prototype._init = function () { //改写vue原来的_init方法,其实还是调用原来的_init,但给options增加init=vuexinit方法
var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
options.init = options.init ? [vuexInit].concat(options.init) : vuexInit;
_init.call(this, options);
Vue.prototype._init = function (options) {
this._callHook(‘init‘); //会调用options.init,也就是调用vuexinit方法
Vue.prototype._callHook = function (hook) {
this.$emit(‘pre-hook:‘ + hook);
var handlers = this.$options[hook];
if (handlers) {
for (var i = 0, j = handlers.length; i < j; i++) {
handlers[i].call(this);
因此vuex扩展到vue之后,在new Vue(options)创建组件实例时,会执行vuexinit方法处理options中的vuex:{}表达式,会在组件中创建setUserInfo方法,
创建的方法代码是在原始action方法加了一层封装。
vue用set/get方法实现数据响应,其关键代码在于:
function defineReactive (obj, key, val) {
var dep = new Dep()
var childOb = observe(val) //如果属性有层次,要递归处理每一层属性
Object.defineProperty(obj, key, { //这是针对数据对象和里面的属性创建set/get,是为了实现同步功能,
//组件定义的数据属性在new Vue()时复制到this._data。
enumerable: true,
configurable: true,
get: function(){
if(Dep.target){ // 在针对页面表达式创建new watcher()时会调用get方法取值,并把new watcher实例保存在Dep.target,Dep.target就是当前正在创建的watcher实例,这个地方挺不容易看懂的,因为要懂整体设计逻辑才能看懂,
dep.addSub(Dep.target); //把watcher实例保存到属性对应的dep中,因此依赖obj的属性的watcher都会保存到属性中
Dep.target = null;
}
return val
},
set: function(newVal){
var value = val
if (newVal === value) {
return
}
val = newVal
childOb = observe(newVal)
dep.notify() // 执行属性中保存的watcher的update方法更新组件/指令的页面
}
})
}
其中dep的方法层层封装非常绕,详细代码本文忽略。
vuex/store也是用get/set方法实现数据响应,是通过加了一层computed方法实现的,computed方法再调用getter方法访问store的属性数据,computed方法会创建
watcher,当调用vuex getter方法获取属性的值时会把自身watcher实例保存在属性中,当set属性时就会执行watcher的update方法更新data属性,页面可以绑定
data属性,当data属性变化时,页面会更新,因为vue已经针对页面绑定表达式创建了watcher,针对data属性创建了get/set方法,已经建立了数据响应机制。
所以当使用computed方法时,还是很复杂很绕的,当数据变化时,从store到页面是通过好几层watcher实现数据响应的,页面表达式有一层watcher,cb是指令的
update方法,比如对于{{title}}这样最简单的表达式其实就是一个文本指令,其update方法就是要取title的值插入做为文本内容,computed方法又有一层watcher,
cb=null,因为computed方法只是执行方法代码获取值,不同于指令,没有cb,无需处理网页元素。
下面是vue构造comptedGetter方法的代码:
function makeComputedGetter(store, getter) {
var id = store._getterCacheId;
if (getter[id]) {
return getter[id];
}
var vm = store._vm;
var Watcher = getWatcher(vm);
var Dep = getDep(vm);
var watcher = new Watcher(vm, function (vm) { // 创建watcher,在执行getter方法取值时把自身保存到属性中
return getter(vm.state);
}, null, { lazy: true });
var computedGetter = function computedGetter() { //computedGetter
if (watcher.dirty) {
watcher.evaluate();
}
if (Dep.target) {
watcher.depend();
}
return watcher.value;
};
getter[id] = computedGetter;
return computedGetter;
}
watcher实例里面,proto里面有一个update方法,这是watcher通用update方法,还有一个cb,这是指令通用update方法,
指令通用update方法会调用指令specific update方法(如果有的话)。
下面是关于在组件标签用v-on:绑定事件的代码细节:
//Register v-on events on a child component
function registerComponentEvents(vm, el) {
name = attrs[i].name;
if (eventRE.test(name)) {
handler = (vm._scope || vm._context).$eval(value, true); //v-on=后面的写法需要解析处理一下
handler._fromParent = true; //事件在当前初始化的组件,方法在父组件
vm.$on(name.replace(eventRE), handler); // 绑定逻辑事件,逻辑事件用$emit触发
事件用addeventlistener和$on两种方法绑定,如果是物理事件,比如物理click事件,前者有效,如果是逻辑事件,前者
无效,后者有效,后者是vue自己建立的_events[]数据,handler方法在父组件。
在标签写v-on只能绑定组件里面的逻辑事件,组件里面用$emit触发逻辑事件,执行父组件里面的handler方法,不能监听子组件里面的物理click事件,
在子组件里面才能写v-on绑定物理click事件。
下面是关于checkbox元素用v-model双向绑定的源代码分析,checkbox handler代码:
var checkbox = {
bind: function bind() {
this.listener = function () {
var model = self._watcher.get(); //获取v-model=表达式的值,也就是变量属性值option.checked,涉及v-for循环变量/scope
if (isArray(model)) {
} else {
self.set(getBooleanValue()); //获取元素checked属性值,传递给set方法
}
Directive.prototype.set = function (value) {
if (this.twoWay) {
this._withLock(function () {
this._watcher.set(value);
Watcher.prototype.set = function (value) {
this.setter.call(scope, scope, value); //调用变量属性的setter方法设置值
function (scope, val) { //watcher的setter
setPath(scope, path, val);
function setPath(obj, path, val) {
obj[key] = val;
set(obj, key, val);//如果属性不存在,就调Vue.set添加属性?
this.getValue = function () {
return el.hasOwnProperty(‘_value‘) ? el._value : self.params.number ? toNumber(el.value) : el.value;
};
function getBooleanValue() {
var val = el.checked;
if (val && el.hasOwnProperty(‘_trueValue‘)) {
return el._trueValue;
}
if (!val && el.hasOwnProperty(‘_falseValue‘)) {
return el._falseValue;
}
return val;
}
this.on(‘change‘, this.listener);
可见v-model底层就是绑定‘change‘事件,就是获取元素checked属性值,可以重新定义checked属性值,那就取定义的值,一般不。
当点击checkbox元素时,其checked属性会变化,v-model获取元素checked属性值同步到变量,反之则是根据变量值设置元素
的checked属性值,从而实现两个方向的数据同步。
vue初始化组件时处理template的代码:
function transcludeTemplate(el, options) {
var template = options.template;
var frag = parseTemplate(template, true);
function parseTemplate(template, shouldClone, raw) {
if (typeof template === ‘string‘) {
//template如果写#id,则去网页按id找元素,这已经是DOM对象了
//如果template是html字符串,则用innerHTML编译为DOM对象再插入到frag
frag = stringToFragment(template, raw);
function stringToFragment(templateString, raw) {
node.innerHTML = prefix + templateString + suffix; //前后缀是有可能包裹一层标签
while (child = node.firstChild) {
frag.appendChild(child);
//这是移动子元素,循环结果把所有子元素都移动插入到frag中去,template可以写多个<div>
function nodeToFragment(node) {
// script template,template可以写在网页的<script>标签中
if (node.tagName === ‘SCRIPT‘) {
return stringToFragment(node.textContent);
}
因此在组件中写template=只能是html代码字符串,或者#id指向网页中的元素,没有其它写法,不能写js表达式,
也不能用js代码修改template,按id找元素可以把template写在网页的<script>中,类似angular,也可以写在网页中的
<template>标签元素中:
<template id="t1">
<div>{{context}}</div>
</template>
如果构造<template>插入网页,<template>类似frag,本身是虚拟节点,在网页不占节点。
以上是关于vue 1.0源代码重点难点分析的主要内容,如果未能解决你的问题,请参考以下文章