VueRouter
class VueRouter {
constructor(Vue, options) {
this.$options = options;
this.routeMap = {};
this.app = new Vue({
data: {
current: '#/'
}
});
this.init();
this.createRouteMap(this.$options);
this.initComponent(Vue);
}
init() {
window.addEventListener('load', this.onHashChange.bind(this), false);
window.addEventListener('hashchange', this.onHashChange.bind(this), false);
}
createRouteMap(options) {
options
.routes
.forEach(item => {
this.routeMap[item.path] = item.component;
});
}
initComponent(Vue) {
Vue.component('router-link', {
props: {
to: String
},
template: '<a :href="to"><slot></slot></a>'
});
const _this = this;
Vue.component('router-view', {
render(h) {
var component = _this.routeMap[_this.app.current];
return h(component);
}
});
}
getHash() {
return window
.location
.hash
.slice(1) || '/';
}
onHashChange() {
this.app.current = this.getHash();
}
}
vuex
export class Store {
constructor(options = {}) {
this.getters = {};
this._mutations = {};
this._wrappedGetters = {};
this._actions = {};
this._modules = new ModuleCollection(options)
const { dispatch, commit } = this;
this.commit = (type) => {
return commit.call(this, type);
}
this.dispatch = (type) => {
return dispatch.call(this, type);
}
const state = options.state;
const path = [];
this._vm = new Vue({
data: {
state: state
}
});
installModule(this, state, path, this._modules.root);
}
get state() {
return this._vm._data.state;
}
commit(type) {
this._mutations[type].forEach(handler => handler());
}
dispatch(type) {
return this._actions[type][0]();
}
}
class ModuleCollection {
constructor(rawRootModule) {
this.register([], rawRootModule)
}
register(path, rawModule) {
const newModule = {
_children: {},
_rawModule: rawModule,
state: rawModule.state
}
if (path.length === 0) {
this.root = newModule;
} else {
const parent = path.slice(0, -1).reduce((module, key) => {
return module._children(key);
}, this.root);
parent._children[path[path.length - 1]] = newModule;
}
if (rawModule.modules) {
forEachValue(rawModule.modules, (rawChildModule, key) => {
this.register(path.concat(key), rawChildModule);
})
}
}
}
function installModule(store, rootState, path, module) {
if (path.length > 0) {
const parentState = rootState;
const moduleName = path[path.length - 1];
Vue.set(parentState, moduleName, module.state)
}
const context = {
dispatch: store.dispatch,
commit: store.commit,
}
const local = Object.defineProperties(context, {
getters: {
get: () => store.getters
},
state: {
get: () => {
let state = store.state;
return path.length ? path.reduce((state, key) => state[key], state) : state
}
}
})
if (module._rawModule.actions) {
forEachValue(module._rawModule.actions, (actionFn, actionName) => {
registerAction(store, actionName, actionFn, local);
});
}
if (module._rawModule.getters) {
forEachValue(module._rawModule.getters, (getterFn, getterName) => {
registerGetter(store, getterName, getterFn, local);
});
}
if (module._rawModule.mutations) {
forEachValue(module._rawModule.mutations, (mutationFn, mutationName) => {
registerMutation(store, mutationName, mutationFn, local)
});
}
forEachValue(module._children, (child, key) => {
installModule(store, rootState, path.concat(key), child)
})
}
function registerMutation(store, mutationName, mutationFn, local) {
const entry = store._mutations[mutationName] || (store._mutations[mutationName] = []);
entry.push(() => {
mutationFn.call(store, local.state);
});
}
function registerAction(store, actionName, actionFn, local) {
const entry = store._actions[actionName] || (store._actions[actionName] = [])
entry.push(() => {
return actionFn.call(store, {
commit: local.commit,
state: local.state,
})
});
}
function registerGetter(store, getterName, getterFn, local) {
Object.defineProperty(store.getters, getterName, {
get: () => {
return getterFn(
local.state,
local.getters,
store.state
)
}
})
}
function forEachValue(obj, fn) {
Object.keys(obj).forEach(key => fn(obj[key], key));
}
export function install(_Vue) {
Vue = _Vue;
Vue.mixin({
beforeCreate: function vuexInit() {
const options = this.$options;
if (options.store) {
this.$store = options.store;
} else if (options.parent && options.parent.$store) {
this.$store = options.parent.$store;
}
}
})
}
虚拟dom
- 用JS对象模拟DOM(虚拟DOM)
- 把此虚拟DOM转成真实DOM并插入页面中(render)
- 如果有事件发生修改了虚拟DOM,比较两棵虚拟DOM树的差异,得到差异对象(diff)
- 把差异对象应用到真正的DOM树上(patch)
function patchVnode(oldVnode, vnode, insertedVnodeQueue) {
const elm = vnode.elm = oldVnode.elm
let oldCh = oldVnode.children
let ch = vnode.children
if (oldVnode === vnode) return
if (vnode.data) {
for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode)
}
if (vnode.text === undefined) {
if (oldCh && ch) {
if (oldCh !== ch) {
updateChildren(elm, oldCh, ch, insertedVnodeQueue)
}
}
else if (ch) {
if (oldVnode.text) api.setTextContent(elm, '')
addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)
}
else if (oldCh) {
removeVnodes(elm, oldCh, 0, oldCh.length - 1);
}
else if (oldVnode.text) {
api.setTextContent(elm, '');
}
}
else if (oldVnode.text !== vnode.text) {
api.setTextContent(elm, vnode.text)
}
}
function updateChildren(parentElm, oldCh, newCh, insertedVnodeQueue) {
let oldStartIdx = 0, newStartIdx = 0
let oldEndIdx = oldCh.length - 1
let oldStartVnode = oldCh[0]
let oldEndVnode = oldCh[oldEndIdx]
let newEndIdx = newCh.length - 1
let newStartVnode = newCh[0]
let newEndVnode = newCh[newEndIdx]
let oldKeyToIdx
let idxInOld
let elmToMove
let before
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
if (oldStartVnode == null) {
oldStartVnode = oldCh[++oldStartIdx]
} else if (oldEndVnode == null) {
oldEndVnode = oldCh[--oldEndIdx]
} else if (newStartVnode == null) {
newStartVnode = newCh[++newStartIdx]
} else if (newEndVnode == null) {
newEndVnode = newCh[--newEndIdx]
}
else if (isSameVnode(oldStartVnode, newStartVnode)) {
patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue)
oldStartVnode = oldCh[++oldStartIdx]
newStartVnode = newCh[++newStartIdx]
}
else if (isSameVnode(oldEndVnode, newEndVnode)) {
patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue)
oldEndVnode = oldCh[--oldEndIdx]
newEndVnode = newCh[--newEndIdx]
}
else if (isSameVnode(oldStartVnode, newEndVnode)) {
patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue)
api.insertBefore(parentElm, oldStartVnode.elm, api.nextSibling(oldEndVnode.elm))
oldStartVnode = oldCh[++oldStartIdx]
newEndVnode = newCh[--newEndIdx]
}
else if (isSameVnode(oldEndVnode, newStartVnode)) {
patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue)
api.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)
oldEndVnode = oldCh[--oldEndIdx]
newStartVnode = newCh[++newStartIdx]
}
else {
if (oldKeyToIdx === undefined) {
oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
}
idxInOld = oldKeyToIdx[newStartVnode.key]
if (idxInOld == null) {
api.insertBefore(parentElm, createElm(newStartVnode, insertedVnodeQueue), oldStartVnode.elm)
newStartVnode = newCh[++newStartIdx]
}
else {
elmToMove = oldCh[idxInOld]
if (elmToMove.type !== newStartVnode.type) {
api.insertBefore(parentElm, createElm(newStartVnode, insertedVnodeQueue), oldStartVnode.elm)
}
else {
patchVnode(elmToMove, newStartVnode, insertedVnodeQueue)
oldCh[idxInOld] = undefined
api.insertBefore(parentElm, elmToMove.elm, oldStartVnode.elm)
}
newStartVnode = newCh[++newStartIdx]
}
}
}
if (oldStartIdx > oldEndIdx) {
before = newCh[newEndIdx+1] == null ? null : newCh[newEndIdx+1].elm
addVnodes(parentElm, before, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)
}
else if (newStartIdx > newEndIdx) {
removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx)
}
}
模板
enum State {
EVAL,
ESCAPED,
RAW,
COMMENT,
LITERAL
}
const REGEXP = /(<%%|%%>|<%=|<%-|<%#|<%|%>)/;
const start = "<%";
const end = "%>";
const escpStart = "<%%";
const escpEnd = "%%>";
const escpout = "=";
const unescpout = "-";
const comt = "#";
export default class Template {
public template: string;
private tokens: string[] = [];
private source: string = "";
private state?: State;
private fn?: Function;
public constructor(template: string) {
this.template = template;
}
public compile() {
this.parseTemplateText();
this.transformTokens();
this.wrapit();
}
public render(local: object) {
return this.fn.call(null, local, escape);
}
private wrapit() {
this.source = `\
const __out = [];
const __append = __out.push.bind(__out);
with(local||{}) {
${this.source}
}
return __out.join('');\
`;
this.fn = new Function("local", "escapeFn", this.source);
}
private parseTemplateText() {
let str = this.template;
const arr = this.tokens;
let res = REGEXP.exec(str);
let index;
while (res) {
index = res.index;
if (index !== 0) {
arr.push(str.substring(0, index));
str = str.slice(index);
}
arr.push(res[0]);
str = str.slice(res[0].length);
res = REGEXP.exec(str);
}
if (str) {
arr.push(str);
}
}
private transformTokens() {
if (!this.tokens.length) {
return;
}
this.tokens.forEach((tok, idx) => {
let closing: string;
if (tok.includes(start) && !tok.includes(escpStart)) {
closing = this.tokens[idx + 2];
if (closing == null || !closing.includes(end)) {
throw new Error(`${tok} 未找到对应的标签`);
}
}
switch (tok) {
case start:
this.state = State.EVAL;
break;
case start + escpout:
this.state = State.ESCAPED;
break;
case start + unescpout:
this.state = State.RAW;
break;
case start + comt:
this.state = State.COMMENT;
break;
case escpStart:
this.state = State.LITERAL;
this.source += `;__append('<%');\n`;
break;
case escpEnd:
this.state = State.LITERAL;
this.source += `;__append('%>');\n`;
break;
case end:
this.state = undefined;
break;
default:
if (this.state != null) {
switch (this.state) {
case State.EVAL:
this.source += `;${tok}\n`;
break;
case State.ESCAPED:
this.source += `;__append(escapeFn(${stripSemi(tok)}));\n`;
break;
case State.RAW:
this.source += `;__append(${stripSemi(tok)});\n`;
break;
case State.LITERAL:
this.source += `;__append('${transformString(tok)}');\n`;
break;
case State.COMMENT:
break;
}
} else {
this.source += `;__append('${transformString(tok)}');\n`;
}
}
});
}
}
function transformString(str: string) {
str = str.replace(/\\/g, "\\\\");
str = str.replace(/\n/g, "\\n");
str = str.replace(/\r/g, "\\r");
str = str.replace(/'/g, "\\'");
return str;
}
function stripSemi(str) {
return str.replace(/;(\s*$)/, "$1");
}
const _MATCH_HTML = /[&<>'"]/g;
const _ENCODE_HTML_RULES = {
"&": "&",
"<": "<",
">": ">",
'"': """,
"'": "'"
};
function encode_char(c) {
return _ENCODE_HTML_RULES[c] || c;
}
function escape(markup: string) {
return markup == null ? "" : String(markup).replace(_MATCH_HTML, encode_char);
}
;__append('\n<html>\n <head>');
;__append(escapeFn( title ));
;__append('</head>\n <body>\n ');
;__append('<%');
;__append(' 转义 ');
;__append('%>');
;__append('\n ');
;__append('\n ');
;__append( before );
;__append('\n ');
; if (show) {
;__append('\n <div>root</div>\n ');
; }
;__append('\n </body>\n</html>\n');
模板2
var TemplateEngine = function(html, options) {
var re = /<%([^%>]+)?%>/g
var reExp = /(^( )?(if|for|else|switch|case|break|{|}))(.*)?/g
var code = 'var r=[];\n', cursor = 0
var match;
var add = function(line, js) {
js? (code += line.match(reExp) ? line + '\n' : 'r.push(' + line + ');\n') :
(code += line != '' ? 'r.push("' + line.replace(/"/g, '\\"') + '");\n' : '');
return add;
}
while(match = re.exec(html)) {
add(html.slice(cursor, match.index))(match[1], true);
cursor = match.index + match[0].length;
}
add(html.substr(cursor, html.length - cursor));
code += 'return r.join("");';
return new Function(code.replace(/[\r\t\n]/g, '')).apply(options);
}