如何构建一个MVVM框架


如何构建⼀个 MVVM框架 ⾃我介绍 ⺫前是饿了么的前端⼯程师 。 5年 UI组件开发经验 ,在前司从事开发⼀个类似 ExtJS的框架。 SIMPLE MVVM https://github.com/furybean/simple-mvvm AGENDA • 介绍 MVVM • MVVM的组成 • 实现 Compiler • 实现 ViewModel • 实现 Directive MVVM • https://en.wikipedia.org/wiki/Model_View_ViewModel ViewModelView Model DataBinding BusinessLogic ObjectPresentation and Presentation Logic WELL KNOWN MVVM • AngularJs: Controller + HTML • Ember.js: Controller + Handlebars + Model • KnockoutJS: Observable + HTML • Vue.js: ViewModel + HTML MVVM FOR WEB ViewModelHTML / DOM POJO DataBinding BusinessLogic ObjectPresentation and Presentation Logic MVVM组成 PROBLEM ViewModelHTML / DOM DataBinding I/O ViewModel DOM Template Directives POJO DOM Compiler 实现 COMPILER MAIN $compile(element, context) TODOS CONTEXT var context = { newTodo: "",
 todos: [
 { title: 'first', done: true },
 { title: 'second', done: false }
 ],
 add: function() {
 this.todos.push({ title: this.newTodo, done: false });
 this.newTodo = '';
 },
 remove: function(item) {
 this.todos.splice(this.todos.indexOf(item), 1);
 }
 }; HTML
  • {{item.title}}Remove
DOM WALK function walk(node, callback) {
 if (node.nodeType === 1 || node.nodeType === 3) {
 var returnValue = callback(node);
 if (returnValue === false) {
 return;
 }
 }
 
 if (node.nodeType === 1) {
 var current = node.firstChild;
 while (current) {
 walk(current, callback);
 current = current.nextSibling;
 }
 }
 } SUB CONTEXT
  • {{item.title}}Remove
context sub context CONTEXT的继承 function newContext(context) {
 var empty = function() {};
 empty.prototype = context;
 
 return new empty();
 } Object.create(context); WALK NODE 1. 节点不存在 d-repeat属性,把表达式的值映射到 DOM,继续 WALK⼦节点。 2. 节点存在 d-repeat属性,结束 WALK⼦节点: 1. Clone当前节点后把 d-repeat属性删除,作为数组 元素使⽤的模板 childTemplate。 2. 对数组中每个元素创建⼀个 Sub Context,执⾏ $compile(childTemplate, subContext) 从 CONTEXT取值 (function() {
 return this.item.title;
 }).bind(context); item.title CODE TO FUNCTION var compileFn = function(body, context) {
 var fn;
 if (body) {
 fn = new Function('return ' + body + ';');
 }
 if (fn && context) {
 return fn.bind(context);
 }
 return fn;
 }; PARSE TYPES • Repeat Expression(d-repeat): item in todos • Expression(attribute value): newTodo • Text(text node): {{item.title}} • Pair(d-class): done: item.done EXPRESSION • newTodo • item.title • add() • remove(item) EXPRESSION OUTPUT • newTodo => this.newTodo • item.title => this.item.title • add() => this.add() • remove(item) => this.remove(this.item) OPEN SOURCE PARSER • esprima: http://esprima.org/ Esprima is a high performance, standard-compliant ECMAScript parser written in ECMAScript • jsep: https://github.com/soney/jsep JavaScript Expression Parser JSEP PARSE RESULT type === 'premium' ? 5 : 0 {
 type: "ConditionalExpression",
 test: {
 type: "BinaryExpression",
 operator: "===",
 left: {
 type: “Identifier", name: "type"
 },
 right: {
 type: “Literal", value: "premium"
 }
 },
 consequent: {
 type: “Literal", value: 5
 },
 alternate: {
 type: “Literal", value: 0
 }
 } Conditional Expression Binary Expression Literal 5 Literal 0 Identifier type === Literal 'premium' consequent alternate test left operator right type === 'premium' ? 5 : 0 AST TO CODE function astToCode(ast) {
 if (ast.type === 'Literal') {
 return typeof ast.value === 'string' ? '"' + ast.value + '"' : '' + ast.value;
 } else if (ast.type === 'ThisExpression') {
 return 'this';
 } else if (ast.type === 'UnaryExpression') {
 return ast.operator + astToCode(ast.argument);
 } else if (ast.type === 'BinaryExpression' || ast.type === 'LogicalExpression') {
 return astToCode(ast.left) + ' ' + ast.operator + ' ' + astToCode(ast.right);
 } else if (ast.type === 'ConditionalExpression') {
 return '(' + astToCode(ast.test) + ' ? (' + astToCode(ast.consequent) + ') : (' + astToCode(ast.alternate) + '))';
 } else if (ast.type === 'Identifier') {
 return 'this.' + ast.name;
 }
 //...
 } PARSE TEXT(INTERPOLATE) text{{expression}}text{{expression}} 使⽤正则会遇到的边界情况: • {{、 }}在字符串或者在表达式 {}()[]⾥ 遍历字符串,定义两个变量: inString和 level。 PARSE PAIR key: expression, key: expression 实现 VIEWMODEL WATCH • Dirty Check: Angular.js 1.x • defineProperty: Ember.js / Vue.js • Object.observe OBJECT.OBSERVE var obj = {
 foo: 0
 };
 
 Object.observe(obj, function(changes) {
 console.log(changes);
 });
 
 obj.foo = 2; 
 //[{type: 'update', object: { foo: 2 }, name: 'foo', oldValue: 0 }] ⽅法 • $watch: Object.observe • $unwatch • $extend: Object.create • $destroy 实现 DIRECTIVE DIRECTIVE TYPE • attr => setAttribute • event => addEventListener • text => innerText / nodeValue • class => className • model => value / addEventListener • repeat => [ Element ] DIRECTIVE 属性 • element • context • expression • attr/className/eventName DIRECTIVE ⽅法 • bind • update • unbind • destroy DIRECTIVE BIND var directive = this;
 if (directive.element && directive.expression && directive.context){
 directive.valueFn = compileExpr(directive.expression, directive.context);
 
 var depends = getDepends(directive.expression);
 var context = directive.context;
 
 depends.forEach(function(depend) {
 context.$watch(depend, directive);
 });
 
 directive.update();
 } EXPRESSION DEPENDS • 从 AST提取,只关注两种类型: • Identifier: newTodo • MemberExpression: item.done REPEAT DIRECTIVE item in todos track by id • track by的作⽤是对 Array中的元素进⾏ hash • Array的 diff就简化成了有序集合的 diff 1 2 3 54 6 128 54 7 • 遍历集合 A,如果元素在集合 A中存在,集合 B中不存在,则该元素被删除 • 遍历集合 B,如果元素在 集合 B中存在,集合 A中不存在,则该元素为新增 如果元素在集合 B中存在,集合 A中存在,但是元素的前⼀个 元素不同,则该元素被移动 REVIEW • DOM TREE WALK • AST WALK • ViewModel => EVENT EMITTER • ARRAY DIFF => SET DIFF THANKS
还剩44页未读

继续阅读

下载pdf到电脑,查找使用更方便

pdf的实际排版效果,会与网站的显示效果略有不同!!

需要 8 金币 [ 分享pdf获得金币 ] 0 人已下载

下载pdf

pdf贡献者

cw63

贡献于2015-09-25

下载需要 8 金币 [金币充值 ]
亲,您也可以通过 分享原创pdf 来获得金币奖励!
下载pdf