AirJD 焦点
AirJD

没有录音文件
00:00/00:00
加收藏

如何构建一个MVVM框架 by FuryBean@饿了吗

发布者 FEer   简介 前端技术
发布于 1435629165946  浏览 7478 关键词 前端, JavaScript 
分享到

第1页

如何构建⼀一个MVVM框架



第2页

⾃自我介绍

⺫⽬目前是饿了么的前端⼯工程师。 5年UI组件开发经验,在前司从事开发⼀一个类似 ExtJS的框架。



第3页

SIMPLE MVVM

https://github.com/furybean/simple-mvvm



第5页

AGENDA

• 介绍MVVM • MVVM的组成 • 实现Compiler • 实现ViewModel • 实现Directive



第6页

MVVM

• https://en.wikipedia.org/wiki/Model_View_ViewModel



View



ViewModel

DataBinding



Presentation and Presentation Logic



Model

BusinessLogic Object



第7页

WELL KNOWN MVVM

• AngularJs: Controller + HTML • Ember.js: Controller + Handlebars + Model • KnockoutJS: Observable + HTML • Vue.js:ViewModel + HTML



第8页

MVVM FOR WEB



HTML / DOM



ViewModel



DataBinding



Presentation and Presentation Logic



POJO

BusinessLogic Object



第9页

MVVM组成



第10页

PROBLEM



HTML / DOM



ViewModel



DataBinding



第11页

DOM Template

POJO



I/O



Compiler



DOM

ViewModel Directives



第12页

实现COMPILER



第13页

MAIN

$compile(element, context)



第14页

TODOS



第15页

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); }

};



第16页

HTML

<div id="demo"> <input d-model="newTodo" placeholder="Input your new todo"/> <button d-click="add()">Add</button> <ul> <li d-repeat="item in todos"> <input type="checkbox" d-model=“item.done"/> <span d-class="done:item.done">{{item.title}}</span> <a href="Javascript:" d-click="remove(item)">Remove</a> </li> </ul>

</div>



第17页

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; } }

}



第18页

SUB CONTEXT



<div id="demo">



<input d-model="newTodo" placeholder="Input your new todo"/>



<button d-click="add()">Add</button>

<ul> context



<li d-repeat="item in todos">



<input type="checkbox" checked="{{item.done}}"/>



<span d-class="done:item.done">{{item.title}}</span>



<a href="Javascript:" d-click="remove(item)">Remove</a>



</li>



</ul> </div>



sub context



第19页

CONTEXT的继承



function newContext(context) { var empty = function() {}; empty.prototype = context;

 return new empty();

}



Object.create(context);



第20页

WALK NODE

1. 节点不存在d-repeat属性,把表达式的值映射到 DOM,继续WALK⼦子节点。

2. 节点存在d-repeat属性,结束WALK⼦子节点:

1. Clone当前节点后把d-repeat属性删除,作为数组 元素使⽤用的模板childTemplate。

2. 对数组中每个元素创建⼀一个Sub Context,执⾏行 $compile(childTemplate, subContext)



第21页

从CONTEXT取值



item.title



(function() { return this.item.title;

}).bind(context);



第22页

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;

};



第23页

PARSE TYPES

• Repeat Expression(d-repeat): item in todos • Expression(attribute value): newTodo • Text(text node): {{item.title}} • Pair(d-class): done: item.done



第24页

EXPRESSION

• newTodo • item.title • add() • remove(item)



第25页

EXPRESSION OUTPUT

• newTodo => this.newTodo • item.title => this.item.title • add() => this.add() • remove(item) => this.remove(this.item)



第26页

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



第27页

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 }

}



第28页

type === 'premium' ? 5 : 0



consequent



Literal 5



Conditional Expression

test

Binary Expression



alternate



Literal 0



right



left



Identifier type



operator



===



Literal 'premium'



第29页

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; } //...

}



第30页

PARSE TEXT(INTERPOLATE)

text{{expression}}text{{expression}} 使⽤用正则会遇到的边界情况:

• {{、}}在字符串或者在表达式{}()[]⾥里 遍历字符串,定义两个变量:inString和level。



第31页

PARSE PAIR

key: expression, key: expression



第32页

实现VIEWMODEL



第33页

WATCH

• Dirty Check: Angular.js 1.x • defineProperty: Ember.js / Vue.js • Object.observe



第34页

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 }]



第35页

⽅方法

• $watch: Object.observe • $unwatch • $extend: Object.create • $destroy



第36页

实现DIRECTIVE



第37页

DIRECTIVE TYPE

• attr => setAttribute • event => addEventListener • text => innerText / nodeValue • class => className • model => value / addEventListener • repeat => [ Element ]



第38页

DIRECTIVE 属性

• element • context • expression • attr/className/eventName



第39页

DIRECTIVE ⽅方法

• bind • update • unbind • destroy



第40页

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(); }



第41页

EXPRESSION DEPENDS

• 从AST提取,只关注两种类型: • Identifier: newTodo • MemberExpression: item.done



第42页

REPEAT DIRECTIVE

item in todos track by id

• track by的作⽤用是对Array中的元素进⾏行hash • Array的diff就简化成了有序集合的diff



第43页

12 3 4







821 4 5





• 遍历集合A,如果元素在集合A中存在,集合B中不存在,则该元素被删除

• 遍历集合B,如果元素在集合B中存在,集合A中不存在,则该元素为新增

如果元素在集合B中存在,集合A中存在,但是元素的前⼀一个 元素不同,则该元素被移动



第44页

REVIEW

• DOM TREE WALK • AST WALK • ViewModel => EVENT EMITTER • ARRAY DIFF => SET DIFF



第45页

THANKS



支持文件格式:*.pdf
上传最后阶段需要进行在线转换,可能需要1~2分钟,请耐心等待。