Vue.js 早期源码阅读(版本号154861f71d4886251e0057c74f07c786f5262081),关于ViewModel的一点分析,这一次代码来的比上一次复杂的多,完全不谦虚的说,以我的能力可谓是勉强往下看。总体感觉代码写的非常好(和自己平时写的一坨代码相比,完全不是一个档次),悟性太低看了只有一点点小收获而已。代码太长粘在最下面。
一、依赖关系
main.js
require('./directive')
require('./directives')
directive
require('./directives')
require('./filters')
二、导出函数和作用域内函数
main.js
1.module.exports.create
1)Seed.constructor
2)Seed.prototype.compileNode
3)Seed.prototype.bind
4)Seed.prototype.createBinding
5)Seed.prototype.dump
6)Seed.prototype.destroy
7)scope.cloneAttributes
2.module.exports.directive
3.module.exports.filter
directive.js
1.module.exports.parse
1)Directive.constructor
2)Directive.prototype.update
3)Directive.prototype.applyFilters
directives.js
1.module.exports.text
2.module.exports.show
3.module.exports.class
4.module.exports.on
1)module.exports.on.update
2)module.exports.on.unbind
5.module.exports.each
6.scope.augmentArray
filters.js
1.module.exports.capitalize
2.module.exports.uppercase
3.module.exports.delegate
三、执行分析
1.Seed.create(此Seed是变量名)传入参数创建Seed实例(此Seed是main.js内部的函数名)
2.Seed做了3件事
以下root指#test,select指[sd-text],[sd-show],[sd-class],[sd-on],[sd-each]
1)遍历具有指令的元素(root.querySelectorAll(selector)),调用compileNode解析节点,此时的生命周期处于beforeCompile(按照官网文档参考,命名不一定准确)
2)对根节点root,调用compileNode解析节点
3)上面已经为劫持了data(绑定了getter和setter),并将具有指令的元素和setter建立了映射关系
四、函数分析
dev.html
<style type="text/css">
.red {
color: red;
}
</style>
<body>
<div id="test" sd-on-click="changeMessage | delegate .button">
<p sd-text="msg.wow | capitalize" sd-on-click="remove"></p>
<p sd-text="msg.wow | uppercase" class="button"></p>
<p sd-class-red="error" sd-text="hello"></p>
<div sd-each="todos">
<span sd-text="text"></span>
</div>
</div>
<script>
var Seed = require('seed')
var app = Seed.create({
id: 'test',
// template
scope: {
'msg.wow': 'wow',
hello: 'hello',
changeMessage: function () {
app.scope['msg.wow'] = 'hola'
},
remove: function () {
app.destroy()
},
todos: [
{
title: 'make this shit work',
done: false
},
{
title: 'make this shit kinda work',
done: true
}
]
}
})
</script>
</body>
main.js
var prefix = 'sd',
Directive = require('./directive'),
Directives = require('./directives'),
selector = Object.keys(Directives).map(function (d) {
return '[' + prefix + '-' + d + ']'
}).join()
function Seed (opts) {
var self = this,
root = this.el = document.getElementById(opts.id),
els = root.querySelectorAll(selector)
self.bindings = {}
self.scope = {}
// process nodes for directives
;[].forEach.call(els, this.compileNode.bind(this))
this.compileNode(root)
// initialize all variables by invoking setters
for (var key in self.bindings) {
self.scope[key] = opts.scope[key]
}
}
Seed.prototype.compileNode = function (node) {
var self = this
cloneAttributes(node.attributes).forEach(function (attr) {
var directive = Directive.parse(attr, prefix)
if (directive) {
self.bind(node, directive)
}
})
}
Seed.prototype.bind = function (node, directive) {
directive.el = node
node.removeAttribute(directive.attr.name)
var key = directive.key,
binding = this.bindings[key] || this.createBinding(key)
// add directive to this binding
binding.directives.push(directive)
// invoke bind hook if exists
if (directive.bind) {
directive.bind(node, binding.value)
}
}
Seed.prototype.createBinding = function (key) {
var binding = {
value: undefined,
directives: []
}
this.bindings[key] = binding
// bind accessor triggers to scope
Object.defineProperty(this.scope, key, {
get: function () {
return binding.value
},
set: function (value) {
binding.value = value
binding.directives.forEach(function (directive) {
directive.update(value)
})
}
})
return binding
}
Seed.prototype.dump = function () {
var data = {}
for (var key in this._bindings) {
data[key] = this._bindings[key].value
}
return data
}
Seed.prototype.destroy = function () {
for (var key in this._bindings) {
this._bindings[key].directives.forEach(unbind)
}
this.el.parentNode.remove(this.el)
function unbind (directive) {
if (directive.unbind) {
directive.unbind()
}
}
}
// clone attributes so they don't change
function cloneAttributes (attributes) {
return [].map.call(attributes, function (attr) {
return {
name: attr.name,
value: attr.value
}
})
}
module.exports = {
create: function (opts) {
return new Seed(opts)
},
directive: function () {
// create dir
},
filter: function () {
// create filter
}
}
directive.js
var Directives = require('./directives'),
Filters = require('./filters')
var KEY_RE = /^[^\|]+/,
FILTERS_RE = /\|[^\|]+/g
function Directive (def, attr, arg, key) {
if (typeof def === 'function') {
this._update = def
} else {
for (var prop in def) {
if (prop === 'update') {
this['_update'] = def.update
continue
}
this[prop] = def[prop]
}
}
this.attr = attr
this.arg = arg
this.key = key
var filters = attr.value.match(FILTERS_RE)
if (filters) {
this.filters = filters.map(function (filter) {
// TODO test performance against regex
var tokens = filter.replace('|', '').trim().split(/\s+/)
return {
apply: Filters[tokens[0]],
args: tokens.length > 1 ? tokens.slice(1) : null
}
})
}
}
Directive.prototype.update = function (value) {
// apply filters
if (this.filters) {
value = this.applyFilters(value)
}
this._update(value)
}
Directive.prototype.applyFilters = function (value) {
var filtered = value
this.filters.forEach(function (filter) {
filtered = filter.apply(filtered, filter.args)
})
return filtered
}
module.exports = {
// make sure the directive and value is valid
parse: function (attr, prefix) {
if (attr.name.indexOf(prefix) === -1) return null
var noprefix = attr.name.slice(prefix.length + 1),
argIndex = noprefix.indexOf('-'),
arg = argIndex === -1
? null
: noprefix.slice(argIndex + 1),
name = arg
? noprefix.slice(0, argIndex)
: noprefix,
def = Directives[name]
var key = attr.value.match(KEY_RE)
return def && key
? new Directive(def, attr, arg, key[0].trim())
: null
}
}
directives.js
module.exports = {
text: function (value) {
this.el.textContent = value || ''
},
show: function (value) {
this.el.style.display = value ? '' : 'none'
},
class: function (value) {
this.el.classList[value ? 'add' : 'remove'](this.arg)
},
on: {
update: function (handler) {
var event = this.arg
if (!this.handlers) {
this.handlers = {}
}
var handlers = this.handlers
if (handlers[event]) {
this.el.removeEventListener(event, handlers[event])
}
if (handler) {
handler = handler.bind(this.el)
this.el.addEventListener(event, handler)
handlers[event] = handler
}
},
unbind: function () {
var event = this.arg
if (this.handlers) {
this.el.removeEventListener(event, this.handlers[event])
}
}
},
each: {
update: function (collection) {
augmentArray(collection, this)
},
mutate: function (mutation) {
console.log(mutation)
}
}
}
var push = [].push,
slice = [].slice
function augmentArray (collection, directive) {
collection.push = function (element) {
push.call(this, arguments)
directive.mutate({
event: 'push',
elements: slice.call(arguments),
collection: collection
})
}
}
filters.js
module.exports = {
capitalize: function (value) {
value = value.toString()
return value.charAt(0).toUpperCase() + value.slice(1)
},
uppercase: function (value) {
return value.toUpperCase()
},
delegate: function (handler, selectors) {
return function (e) {
var match = selectors.every(function (selector) {
return e.target.webkitMatchesSelector(selector)
})
if (match) handler.apply(this, arguments)
}
}
}
网友评论