仿vue - safe
2019-01-21 本文已影响0人
Viker_bar
本文主要对学习Vuejs源码做总结,理解双向绑定,以及学习框架设计思想。
源码参考版本:V2.6.10 总共11945行,去除复杂逻辑以及异常处理后为945行
精简版:
(function(g,f){
g.Vue = f();
}(this,function(){
/********************************************************************************
*
* @Author zpw
* @Description 静态变量(类变量)
*
********************************************************************************/
var queue = [],
timerFunc,
activeInstance = null,
identity = function(_){ return _; },
config = ({
parsePlatformTagName: identity,
async: true,
}),
isUsingMicroTask = false,
callbacks = [];
/********************************************************************************
*
* @Author zpw
* @Description 工具方法
*
********************************************************************************/
function toString(val){
return String(val);
}
function isNative (Ctor) {
return typeof Ctor === 'function' && /native code/.test(Ctor.toString())
}
function isDef (v) {
return v !== undefined && v !== null
}
function isUndef (v) {
return v === undefined || v === null
}
function query(el) {
if (typeof el === 'string') {
var selected = document.querySelector(el);
if(!selected){
return document.createElement('div')
}
return selected
}else{
return el
}
}
function createFunction(code){
try{
return new Function(code);
}catch(err) {
console.log(err);
}
}
//浅拷贝
function extend (to, _from) {
for(var key in _from) {
to[key] = _from[key];
}
return to
}
function createTextVNode (val) {
//VNode是JavaScript对象
return new VNode(undefined, undefined, undefined, String(val))
}
function getOuterHTML(el){
if(el.outerHTML){
return el.outerHTML
}else{
var container = document.createElement('div');
container.appendChild(el.cloneNode(true));
return container.innerHTML
}
}
function def (obj, key, val, enumerable) {
Object.defineProperty(obj, key, {
value: val,
enumerable: !!enumerable,
writable: true,
configurable: true
});
}
//非IO的异步回调兼容处理(https://zhuanlan.zhihu.com/p/33090541)
if (typeof Promise !== 'undefined' && isNative(Promise)) {
var p = Promise.resolve();
timerFunc = function () {
p.then(flushCallbacks); //触发这里的flushCallbacks函数
};
isUsingMicroTask = true;
}else if(!isIE && typeof MutationObserver !== 'undefined' && (
isNative(MutationObserver) ||
MutationObserver.toString() === '[object MutationObserverConstructor]'
)){
var counter = 1;
var observer = new MutationObserver(flushCallbacks);
var textNode = document.createTextNode(String(counter));
observer.observe(textNode, {
characterData: true
});
timerFunc = function () {
counter = (counter + 1) % 2;
textNode.data = String(counter);
};
isUsingMicroTask = true;
}else if(typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
timerFunc = function () {
setImmediate(flushCallbacks);
};
}else{
timerFunc = function(){
setTimeout(flushCallbacks, 0);
};
}
/********************************************************************************
*
* @Author zpw
* @Description 实例方法
*
********************************************************************************/
function createElement$1 (tagName, vnode) {
var elm = document.createElement(tagName);
if (tagName !== 'select') {
return elm
}
}
function createElementNS (namespace, tagName) {
return document.createElementNS(namespaceMap[namespace], tagName)
}
function createTextNode (text) {
return document.createTextNode(text)
}
function createComment (text) {
return document.createComment(text)
}
function insertBefore (parentNode, newNode, referenceNode) {
parentNode.insertBefore(newNode, referenceNode);
}
function removeChild (node, child) {
node.removeChild(child);
}
function appendChild (node, child) {
node.appendChild(child);
}
function parentNode (node) {
return node.parentNode
}
function nextSibling (node) {
return node.nextSibling
}
function tagName (node) {
return node.tagName
}
function setTextContent (node, text) {
node.textContent = text;
}
function setStyleScope (node, scopeId) {
node.setAttribute(scopeId, '');
}
var nodeOps = _nos = Object.freeze({
createElement: createElement$1,
createElementNS: createElementNS,
createTextNode: createTextNode,
createComment: createComment,
insertBefore: insertBefore,
removeChild: removeChild,
appendChild: appendChild,
parentNode: parentNode,
nextSibling: nextSibling,
tagName: tagName,
setTextContent: setTextContent,
setStyleScope: setStyleScope
});
function VNode(
tag,
data,
children,
text,
elm,
context,
componentOptions
){
this.tag = tag;
this.data = data;
this.children = children;
this.text = text;
this.elm = elm;
this.context = context;
this.key = data && data.key;
this.componentOptions = componentOptions;
};
function installRenderHelpers (target) {
target._s = toString;
target._v = createTextVNode;
}
function noop(a, b, c){};
function setActiveInstance(vm) {
var prevActiveInstance = activeInstance;
activeInstance = vm;
return function () {
activeInstance = prevActiveInstance;
}
}
function mergeOptions(options){
return options
}
var sharedPropertyDefinition = {
enumerable: true,
configurable: true,
get: noop,
set: noop
};
function proxy (target, sourceKey, key) {
sharedPropertyDefinition.get = function proxyGetter () {
return this[sourceKey][key]
};
sharedPropertyDefinition.set = function proxySetter (val) {
this[sourceKey][key] = val;
};
Object.defineProperty(target, key, sharedPropertyDefinition);
}
function flushCallbacks () {
var copies = callbacks.slice(0);
callbacks.length = 0;
for (var i = 0; i < copies.length; i++){
copies[i]();
}
}
function nextTick (cb, ctx) {
callbacks.push(function () {
cb.call(ctx);
});
timerFunc();
}
function flushSchedulerQueue () {
for (index = 0; index < queue.length; index++) {
watcher = queue[index];
watcher.run();
}
}
function queueWatcher (watcher) {
queue.push(watcher);
nextTick(flushSchedulerQueue);
}
function renderMixin(Vue){
installRenderHelpers(Vue.prototype);
Vue.prototype._render = function(){
var vm = this;
var ref = vm.$options;
var render = ref.render;
vm.$vnode = undefined;
var vnode;
try{
// 解析: vnode = render.call(vm._renderProxy)
//1:用javascript对象模拟虚拟Dom;即:with(this){return _c("div",{attrs:{"id":"app"}},[_v(""+_s(message)+"")])}
//2:_c函数由虚拟Dom树转化为真实Dom
//2.1: 把_c函数分解运行,如下:
// var v1 = vm._v,
// s1 = vm._s;
// var o1 = {
// attrs:{"id":"app"}
// },
// a2 = [v1(s1("self message 'Examples'"))];
//3:挂载虚拟Dom到Vm上下文
// vnode = vm._c("div",o1,a2);
//3:挂载虚拟Dom到Vm上下文
vnode = render.call(vm._renderProxy);
}catch(err){
console.log(err);
}
return vnode
};
}
function lifecycleMixin(Vue){
Vue.prototype._update = function (vnode, hydrating) {
var vm = this;
var prevEl = vm.$el;
var prevVnode = vm._vnode;
var restoreActiveInstance = setActiveInstance(vm);
vnode.elm = vm.$el;
vm._vnode = vnode;
var oldVnode = prevEl,
vnode = vnode;
if (!prevVnode) {
// initial render
createPatchFunction(nodeOps,oldVnode,vnode);
// vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false);
} else {
// updates render
createPatchFunction(prevVnode,vnode);
// vm.$el = vm.__patch__(prevVnode, vnode);
}
restoreActiveInstance();
};
}
function mountComponent(vm,el){
vm.$el = el;
// beforeMount hook
var updateComponent = function () {
vm._update(vm._render());
}
new Watcher(vm, updateComponent, noop, {}, true);
hydrating = false;
if(vm.$vnode == null) {
vm._isMounted = true;
}
return vm
}
Vue.prototype.$mount = function(el){
el = el && query(el);
return mountComponent(this, el)
};
function mountComponent(vm,el){
vm.$el = el;
var updateComponent = function () {
vm._update(vm._render());
}
new Watcher(vm, updateComponent, noop, {}, true);
hydrating = false;
if(vm.$vnode == null){
vm._isMounted = true;
}
return vm
}
Vue.prototype.$mount = function(el){
el = el && query(el);
return mountComponent(this, el)
};
function baseCompile(){
function parse(template){
var ast = '抽象语法树(Abstract Syntax Tree)';
return ast
}
function genText(ast){
/*
*
*
* 这里的变量决定初始化的时候 dep订阅器中有几个订阅者(Watcher)
* eg :
* _s(testMessage,testMsg) dep 里面就是2个订阅者
* _s(testMessage) dep 里面就是1个订阅者
*
*/
var txt = '_s(testMessage)' //写死的变量
return ("_v(" + txt+ ")")
}
function genNode(ast) {
return genText(ast)
}
function genChildren(ast){
var txt = genNode(ast);
return ("[" + txt + "]")
}
function genElement(ast){
var code,
tag = 'div',
data = '{attrs:{"id":"app"}}',
children = genChildren(ast);
code = "_c('" + tag + "'" + "," + data + "," + children + ")";
return code
}
function generate (ast,options){
var code = ast ? genElement(ast) : '_c("div")';
return {
render: ("with(this){return " + code + "}") //"with(this){return _c('div',{attrs:{"id":"app"}},[_v("\n "+_s(message)+"\n ")])}"
}
}
function init(template){
var ast = parse(template),
code = generate(ast);
return {
ast: ast,
render: code.render
}
}
return init(template);
}
function compileToFunctions(template,options,vm){
var compiled = baseCompile(template);
var res = {};
res.render = createFunction(compiled.render);
return res;
}
function createElement (
context,
tag,
data,
children,
normalizationType,
alwaysNormalize
){
return _createElement(context, tag, data, children, normalizationType)
}
function _createElement (
context,
tag,
data,
children,
normalizationType
) {
var vnode;
if(typeof tag === 'string') {
vnode = new VNode(
config.parsePlatformTagName(tag), data, children,
undefined, undefined, context
);
}
return vnode
}
function createPatchFunction(nodeOps,oldVnode,vnode) {
function emptyNodeAt(elm){
return new VNode(nodeOps.tagName(elm).toLowerCase(), {}, [], undefined, elm)
}
function insert (parent, elm, ref$$1) {
nodeOps.insertBefore(parent, elm, ref$$1);
}
function createChildren (vnode, children, insertedVnodeQueue) {
for (var i = 0; i < children.length; ++i) {
createElm(children[i], insertedVnodeQueue, vnode.elm, null, true, children, i);
}
}
function removeVnodes (parentElm, vnodes, startIdx, endIdx) {
removeAndInvokeRemoveHook(vnodes[0]);
}
function removeAndInvokeRemoveHook (vnode, rm) {
removeNode(vnode.elm); //childElm为#app元素
}
function removeNode(el) {
// parent :body 元素
// el: #app元素
var parent = nodeOps.parentNode(el);
nodeOps.removeChild(parent, el);
}
function patchVnode (
oldVnode,
vnode,
insertedVnodeQueue,
ownerArray,
index,
removeOnly
) {
var elm = vnode.elm = oldVnode.elm; //#app
var i;
var data = vnode.data;
var oldCh = oldVnode.children;
var ch = vnode.children;
if(isUndef(vnode.text)) {
if(oldCh !== ch){
updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly);
}
}else if(oldVnode.text !== vnode.text) {
_nos.setTextContent(elm, vnode.text);
}
}
function createElm(vnode,insertedVnodeQueue,parentElm,refElm,nested,ownerArray,index){
var data = vnode.data;
var children = vnode.children;
var tag = vnode.tag;
if(isDef(tag)){
vnode.elm = nodeOps.createElement(tag, vnode);
createChildren(vnode, children, insertedVnodeQueue);
insert(parentElm, vnode.elm, refElm);
}else{
vnode.elm = nodeOps.createTextNode(vnode.text);
insert(parentElm, vnode.elm, refElm);
}
}
function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {
var oldStartVnode = oldCh[0],
newStartVnode = newCh[0],
newStartIdx = 0;
patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx);
}
function patch(){
var isInitialPatch = false;
var insertedVnodeQueue = [];
var isRealElement = isDef(oldVnode.nodeType);
var removeOnly = undefined;
if(!isRealElement){
patchVnode(nodeOps, oldVnode, insertedVnodeQueue, null, null, removeOnly); //导致无法正常使用
}else{
oldVnode = emptyNodeAt(oldVnode);
}
var oldElm = oldVnode.elm;
// var parentElm = document.querySelector('body');
var parentElm = nodeOps.parentNode(oldElm);
vnode.elm = undefined;
createElm(
vnode,
insertedVnodeQueue,
parentElm,
nodeOps.nextSibling(oldElm)
);
removeVnodes(parentElm, [oldVnode], 0, 0);
// invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch);
return vnode.elm
}
return patch();
}
var mount = Vue.prototype.$mount;
Vue.prototype.$mount = function(el){
el = el && query(el);
if(el === document.body || el === document.documentElement) return this;
var options = this.$options;
template = getOuterHTML(el);
var ref = compileToFunctions(template, {}, this);
var render = ref.render;
options.render = render;
//mount.call(this, el)
return mount.call(this,el)
};
var Dep = function Dep () {
this.subs = [];
};
Dep.prototype.addSub = function(sub) {
this.subs.push(sub);
};
Dep.prototype.removeSub = function(sub) {
remove(this.subs, sub);
};
Dep.prototype.depend = function(){
Dep.target.addDep(this); //Dep.target 就是Watcher对象
};
Dep.prototype.notify = function() {
var subs = this.subs.slice();
for (var i = 0, l = subs.length; i < l; i++) {
subs[i].update();
}
};
Dep.target = null;
var targetStack = [];
function pushTarget (target) {
targetStack.push(target);
Dep.target = target;
}
function popTarget () {
targetStack.pop();
Dep.target = targetStack[targetStack.length - 1];
}
var Observer = function(value) {
this.value = value;
this.dep = new Dep();
this.vmCount = 0;
def(value, '__ob__', this);
if(Array.isArray(value)) {
this.observeArray(value);
}else{
this.walk(value);
}
};
Observer.prototype.walk = function(obj) {
var keys = Object.keys(obj);
for (var i = 0; i < keys.length;i++) {
defineReactive$$1(obj, keys[i]);
}
};
Observer.prototype.observeArray = function(items) {
for (var i = 0, l = items.length; i < l; i++) {
observe(items[i]);
}
};
function observe(value) {
if(typeof value !== 'object') return;
var ob = new Observer(value);
ob.vmCount++;
return ob
}
function defineReactive$$1(
obj,
key,
val
){
// var o = { bar: 42 },
// d = Object.getOwnPropertyDescriptor(o, "bar");
/**
*
* Input:
*
* configurable: true //属性是否可以被修改和删除
* enumerable: true //属性是否可以被枚举
* value: 42 //属性值(仅针对数据属性描述符有效)
* writable: true //属性的值是否可以被改变(仅针对数据属性描述有效)
*
*/
// console.dir(d);
//instanceof 某个对象是不是另一个对象的实例(原型链上有也算对象的一个实例)
var dep = new Dep(),
property = Object.getOwnPropertyDescriptor(obj, key),
val ;
if(property && property.configurable === false) return;
var getter = property && property.get, //存储getter构造器
setter = property && property.set; //存储setter构造器
val = obj[key];
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function() {
var value = getter ? getter.call(obj) : val; //如果有getter构造器,this指向obj,否则直接取值
if(Dep.target){
console.log("触发获取了--");
dep.depend(); //添加订阅者
}
return value
},
set: function(newVal) {
var value = getter ? getter.call(obj) : val;
if(newVal === value || (newVal !== newVal && value !== value)) return;
if(setter){
console.log("触发设置数据了-----");
setter.call(obj, newVal);
}else{
console.log("触发设置数据了+++++");
val = newVal;
}
dep.notify();
}
});
}
function createWatcher (
vm,
expOrFn,
handler,
options
) {
return vm.$watch(expOrFn, handler, options)
}
function Watcher(
vm,
expOrFn,
cb,
options,
isRenderWatcher
){
this.vm = vm;
if(isRenderWatcher) {
vm._watcher = this;
}
vm._watchers.push(this);
this.cb = cb;
this.active = true;
this.deps = [];
this.newDeps = [];
this.expression = expOrFn.toString();
if(typeof expOrFn === 'function') {
this.getter = expOrFn;
}
this.value = this.get(); //将自己添加到dep订阅器
};
Watcher.prototype.get = function(){
pushTarget(this);
var value;
var vm = this.vm;
try {
value = this.getter.call(vm, vm); //触发dep.depend(),且告诉dep订阅器 把自己收集到dep订阅器里面,为之后的数据变化做准备
}catch (e){
}finally {
popTarget();
}
return value
};
Watcher.prototype.addDep = function(dep) {
dep.addSub(this);
};
Watcher.prototype.update = function update () {
queueWatcher(this);
};
Watcher.prototype.run = function() {
var value = this.get();
};
Watcher.prototype.depend = function(){
var i = this.deps.length;
while (i--) {
this.deps[i].depend();
}
};
function initProxy(vm){
vm._renderProxy = vm;
};
function initRender(vm){
//将createElement fn绑定到此实例,得到合适的渲染上下文
vm._c = function (a, b, c, d) {
return createElement(vm, a, b, c, d, false);
};
}
function initMixin (Vue) {
Vue.prototype._init = function (options) {
var vm = this;
//合并对象
vm.$options = mergeOptions(options);
initProxy(vm);
initRender(vm);
//beforeCreate Hook
initState(vm); //为所有data属性添加getter,setter方法
//created Hook
if(vm.$options.el){
vm.$mount(vm.$options.el);
}
};
}
function initState(vm){
vm._watchers = [];
initData(vm);
}
function initData(vm){
var data = vm.$options.data,
keys = Object.keys(data),
i = keys.length;
data = vm._data = data || {};
while (i--) {
var key = keys[i];
proxy(vm, "_data", key);
}
observe(data);
}
function Vue(options){
this._init(options);
}
initMixin(Vue);
lifecycleMixin(Vue);
renderMixin(Vue);
return Vue;
}))
分析图:
<html>
<head>
<meta charset="utf-8">
<title>safe</title>
<script src="./js/Mvue.js?v=1.0.0"></script>
</head>
<body>
<div id="app">
{{ testMessage }}
</div>
<script>
var app = new Vue({
el: '#app',
data: {
testMessage : 'Hello Vue!'
}
})
app._data.testMessage = "Vue Hello!"
</script>
</body>
</html>