557 lines
14 KiB
JavaScript
557 lines
14 KiB
JavaScript
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
|
||
}
|