This repository has been archived on 2023-08-29. You can view files and clone it, but cannot push or open issues/pull-requests.
yutent
/
anot.js
Archived
1
0
Fork 0
anot.js/src/06-vm.js

557 lines
14 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

function $watch(expr, binding) {
var $events = this.$events || (this.$events = {}),
queue = $events[expr] || ($events[expr] = [])
if (typeof binding === 'function') {
var backup = binding
backup.uuid = '_' + ++bindingID
binding = {
element: root,
type: 'user-watcher',
handler: noop,
vmodels: [this],
expr: expr,
uuid: backup.uuid
}
binding.wildcard = /\*/.test(expr)
}
if (!binding.update) {
if (/\w\.*\B/.test(expr) || expr === '*') {
binding.getter = noop
var host = this
binding.update = function() {
var args = this.fireArgs || []
if (args[2]) binding.handler.apply(host, args)
delete this.fireArgs
}
queue.sync = true
Anot.Array.ensure(queue, binding)
} else {
Anot.injectBinding(binding)
}
if (backup) {
binding.handler = backup
}
} else if (!binding.oneTime) {
Anot.Array.ensure(queue, binding)
}
return function() {
binding.update = binding.getter = binding.handler = noop
binding.element = DOC.createElement('a')
}
}
function $emit(key, args) {
var event = this.$events
var _parent = null
if (event && event[key]) {
if (args) {
args[2] = key
}
var arr = event[key]
notifySubscribers(arr, args)
if (args && event['*'] && !/\./.test(key)) {
for (var sub, k = 0; (sub = event['*'][k++]); ) {
try {
sub.handler.apply(this, args)
} catch (e) {}
}
}
_parent = this.$up
if (_parent) {
if (this.$pathname) {
$emit.call(_parent, this.$pathname + '.' + key, args) //以确切的值往上冒泡
}
$emit.call(_parent, '*.' + key, args) //以模糊的值往上冒泡
}
} else {
_parent = this.$up
if (this.$ups) {
for (var i in this.$ups) {
$emit.call(this.$ups[i], i + '.' + key, args) //以确切的值往上冒泡
}
return
}
if (_parent) {
var p = this.$pathname
if (p === '') p = '*'
var path = p + '.' + key
arr = path.split('.')
args = (args && args.concat([path, key])) || [path, key]
if (arr.indexOf('*') === -1) {
$emit.call(_parent, path, args) //以确切的值往上冒泡
arr[1] = '*'
$emit.call(_parent, arr.join('.'), args) //以模糊的值往上冒泡
} else {
$emit.call(_parent, path, args) //以确切的值往上冒泡
}
}
}
}
function collectDependency(el, key) {
do {
if (el.$watch) {
var e = el.$events || (el.$events = {})
var array = e[key] || (e[key] = [])
dependencyDetection.collectDependency(array)
return
}
el = el.$up
if (el) {
key = el.$pathname + '.' + key
} else {
break
}
} while (true)
}
function notifySubscribers(subs, args) {
if (!subs) return
if (new Date() - beginTime > 444 && typeof subs[0] === 'object') {
rejectDisposeQueue()
}
var users = [],
renders = []
for (var i = 0, sub; (sub = subs[i++]); ) {
if (sub.type === 'user-watcher') {
users.push(sub)
} else {
renders.push(sub)
}
}
if (kernel.async) {
buffer.render() //1
for (i = 0; (sub = renders[i++]); ) {
if (sub.update) {
sub.uuid = sub.uuid || '_' + ++bindingID
var uuid = sub.uuid
if (!buffer.queue[uuid]) {
buffer.queue[uuid] = '__'
buffer.queue.push(sub)
}
}
}
} else {
for (i = 0; (sub = renders[i++]); ) {
if (sub.update) {
sub.update() //最小化刷新DOM树
}
}
}
for (i = 0; (sub = users[i++]); ) {
if ((args && args[2] === sub.expr) || sub.wildcard) {
sub.fireArgs = args
}
sub.update()
}
}
//一些不需要被监听的属性
var kernelProps = oneObject(
'$id,$watch,$fire,$events,$model,$active,$pathname,$up,$ups,$track,$accessors'
)
//如果浏览器不支持ecma262v5的Object.defineProperties或者存在BUG比如IE8
//标准浏览器使用__defineGetter__, __defineSetter__实现
function modelFactory(source, options) {
options = options || {}
options.watch = true
return observeObject(source, options)
}
function isSkip(k) {
return
k.charAt(0) === '$' || k.slice(0, 2) === '__' || kernelProps[k]
}
//监听对象属性值的变化(注意,数组元素不是数组的属性),通过对劫持当前对象的访问器实现
//监听对象或数组的结构变化, 对对象的键值对进行增删重排, 或对数组的进行增删重排,都属于这范畴
// 通过比较前后代理VM顺序实现
function Component() {}
function observeObject(source, options) {
if (
!source ||
(source.$id && source.$accessors) ||
(source.nodeName && source.nodeType > 0)
) {
return source
}
//source为原对象,不能是元素节点或null
//options,可选,配置对象,里面有old, force, watch这三个属性
options = options || nullObject
var force = options.force || nullObject
var old = options.old
var oldAccessors = (old && old.$accessors) || nullObject
var $vmodel = new Component() //要返回的对象, 它在IE6-8下可能被偷龙转凤
var accessors = {} //监控属性
var hasOwn = {}
var skip = []
var simple = []
var userSkip = {}
// 提取 source中的配置项, 并删除相应字段
var state = source.state
var computed = source.computed
var methods = source.methods
var props = source.props
var watches = source.watch
var mounted = source.mounted
delete source.state
delete source.computed
delete source.methods
delete source.props
delete source.watch
if (source.skip) {
userSkip = oneObject(source.skip)
delete source.skip
}
// 基础数据
if (state) {
if (source.$id) {
// 直接删除名为props的 字段, 对于主VM对象, props将作为保留关键字
// 下面的计算属性,方法等, 作同样的逻辑处理
delete state.props
}
for (name in state) {
var value = state[name]
if (!kernelProps[name]) {
hasOwn[name] = true
}
if (
typeof value === 'function' ||
(value && value.nodeName && value.nodeType > 0) ||
(!force[name] && (isSkip(name) || userSkip[name]))
) {
skip.push(name)
} else if (isComputed(value)) {
log('warning:计算属性建议放在[computed]对象中统一定义')
// 转给下一步处理
computed[name] = value
} else {
simple.push(name)
if (oldAccessors[name]) {
accessors[name] = oldAccessors[name]
} else {
accessors[name] = makeGetSet(name, value)
}
}
}
}
//处理计算属性
if (computed) {
delete computed.props
for (var name in computed) {
hasOwn[name] = true
;(function(key, value) {
var old
if (typeof value === 'function') {
value = { get: value, set: noop }
}
if (typeof value.set !== 'function') {
value.set = noop
}
accessors[key] = {
get: function() {
return (old = value.get.call(this))
},
set: function(x) {
var older = old,
newer
value.set.call(this, x)
newer = this[key]
if (this.$fire && newer !== older) {
this.$fire(key, newer, older)
}
},
enumerable: true,
configurable: true
}
})(name, computed[name]) // jshint ignore:line
}
}
// 方法
if (methods) {
delete methods.props
for (var name in methods) {
hasOwn[name] = true
skip.push(name)
}
}
if (props) {
hideProperty($vmodel, 'props', {})
hasOwn.props = !!source.$id
for (var name in props) {
$vmodel.props[name] = props[name]
}
}
Object.assign(source, state, methods)
accessors['$model'] = $modelDescriptor
$vmodel = Object.defineProperties($vmodel, accessors, source)
function trackBy(name) {
return hasOwn[name] === true
}
skip.forEach(function(name) {
$vmodel[name] = source[name]
})
/* jshint ignore:start */
// hideProperty($vmodel, '$ups', null)
hideProperty($vmodel, '$id', 'anonymous')
hideProperty($vmodel, '$up', old ? old.$up : null)
hideProperty($vmodel, '$track', Object.keys(hasOwn))
hideProperty($vmodel, '$active', false)
hideProperty($vmodel, '$pathname', old ? old.$pathname : '')
hideProperty($vmodel, '$accessors', accessors)
hideProperty($vmodel, '$events', {})
hideProperty($vmodel, '$refs', {})
hideProperty($vmodel, '$children', [])
hideProperty($vmodel, '$components', [])
hideProperty($vmodel, 'hasOwnProperty', trackBy)
hideProperty($vmodel, '$mounted', mounted)
if (options.watch) {
hideProperty($vmodel, '$watch', function() {
return $watch.apply($vmodel, arguments)
})
hideProperty($vmodel, '$fire', function(path, a) {
if (path.indexOf('all!') === 0) {
var ee = path.slice(4)
for (var i in Anot.vmodels) {
var v = Anot.vmodels[i]
v.$fire && v.$fire.apply(v, [ee, a])
}
} else if (path.indexOf('child!') === 0) {
var ee = 'props.' + path.slice(6)
for (var i in $vmodel.$children) {
var v = $vmodel.$children[i]
v.$fire && v.$fire.apply(v, [ee, a])
}
} else {
$emit.call($vmodel, path, [a])
}
})
}
/* jshint ignore:end */
//必须设置了$active,$events
simple.forEach(function(name) {
var oldVal = old && old[name]
var val = ($vmodel[name] = state[name])
if (val && typeof val === 'object' && !Date.isDate(val)) {
val.$up = $vmodel
val.$pathname = name
}
$emit.call($vmodel, name, [val, oldVal])
})
// 属性的监听, 必须放在上一步$emit后处理, 否则会在初始时就已经触发一次 监听回调
if (watches) {
delete watches.props
for (var key in watches) {
if (Array.isArray(watches[key])) {
var tmp
while ((tmp = watches[key].pop())) {
$watch.call($vmodel, key, tmp)
}
} else {
$watch.call($vmodel, key, watches[key])
}
}
}
$vmodel.$active = true
if (old && old.$up && old.$up.$children) {
old.$up.$children.push($vmodel)
}
return $vmodel
}
/*
新的VM拥有如下私有属性
$id: vm.id
$events: 放置$watch回调与绑定对象
$watch: 增强版$watch
$fire: 触发$watch回调
$track:一个数组,里面包含用户定义的所有键名
$active:boolean,false时防止依赖收集
$model:返回一个纯净的JS对象
$accessors:放置所有读写器的数据描述对象
$pathname:返回此对象在上级对象的名字,注意,数组元素的$pathname为空字符串
=============================
skip:用于指定不可监听的属性,但VM生成是没有此属性的
*/
function isComputed(val) {
//speed up!
if (val && typeof val === 'object') {
for (var i in val) {
if (i !== 'get' && i !== 'set') {
return false
}
}
return typeof val.get === 'function'
}
}
function makeGetSet(key, value) {
var childVm,
value = NaN
return {
get: function() {
if (this.$active) {
collectDependency(this, key)
}
return value
},
set: function(newVal) {
if (value === newVal) return
var oldValue = value
childVm = observe(newVal, value)
if (childVm) {
value = childVm
} else {
childVm = void 0
value = newVal
}
if (Object(childVm) === childVm) {
childVm.$pathname = key
childVm.$up = this
}
if (this.$active) {
$emit.call(this, key, [value, oldValue])
}
},
enumerable: true,
configurable: true
}
}
function observe(obj, old, hasReturn, watch) {
if (Array.isArray(obj)) {
return observeArray(obj, old, watch)
} else if (Anot.isPlainObject(obj)) {
if (old && typeof old === 'object') {
var keys = Object.keys(obj)
var keys2 = Object.keys(old)
if (keys.join(';') === keys2.join(';')) {
for (var i in obj) {
if (obj.hasOwnProperty(i)) {
old[i] = obj[i]
}
}
return old
}
old.$active = false
}
return observeObject(
{ state: obj },
{
old: old,
watch: watch
}
)
}
if (hasReturn) {
return obj
}
}
function observeArray(array, old, watch) {
if (old && old.splice) {
var args = [0, old.length].concat(array)
old.splice.apply(old, args)
return old
} else {
for (var i in newProto) {
array[i] = newProto[i]
}
hideProperty(array, '$up', null)
hideProperty(array, '$pathname', '')
hideProperty(array, '$track', createTrack(array.length))
array._ = observeObject(
{
state: { length: NaN }
},
{
watch: true
}
)
array._.length = array.length
array._.$watch('length', function(a, b) {
$emit.call(array.$up, array.$pathname + '.length', [a, b])
})
if (watch) {
hideProperty(array, '$watch', function() {
return $watch.apply(array, arguments)
})
}
Object.defineProperty(array, '$model', $modelDescriptor)
for (var j = 0, n = array.length; j < n; j++) {
var el = (array[j] = observe(array[j], 0, 1, 1))
if (Object(el) === el) {
//#1077
el.$up = array
}
}
return array
}
}
function hideProperty(host, name, value) {
Object.defineProperty(host, name, {
value: value,
writable: true,
enumerable: false,
configurable: true
})
}
Anot.hideProperty = hideProperty
function toJson(val) {
var xtype = Anot.type(val)
if (xtype === 'array') {
var array = []
for (var i = 0; i < val.length; i++) {
array[i] = toJson(val[i])
}
return array
} else if (xtype === 'object') {
var obj = {}
for (i in val) {
if (val.hasOwnProperty(i)) {
var value = val[i]
obj[i] = value && value.nodeType ? value : toJson(value)
}
}
return obj
}
return val
}
var $modelDescriptor = {
get: function() {
return toJson(this)
},
set: noop,
enumerable: false,
configurable: true
}
Anot 是Anot not only templateEngine的缩写。 它是一款迷你,易用、高性能的前端MVVM框架, fork于avalon。进行了大量的重构,精简部分冗余的API, 同时针对组件拓展进行了优化。
JavaScript 100%