Last active
May 8, 2017 04:27
-
-
Save bravelincy/071f2ed5ea2f4f69c9eed8db65eef3a3 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const isArray = Array.isArray; | |
const slice = Array.prototype.slice; | |
const toString = Object.prototype.toString; | |
function isObject(value) { | |
return value !== null && typeof value === 'object'; | |
} | |
function isDate(value) { | |
return toString.call(value) === '[object Date]'; | |
} | |
function isRegExp(value) { | |
return toString.call(value) === '[object RegExp]'; | |
} | |
function clone(value, deep) { | |
if (isObject(value)) { | |
let copy; | |
switch (toString.call(value)) { | |
case '[object Array]': | |
copy = value.slice(); | |
break; | |
case '[object RegExp]': | |
copy = new RegExp(value); | |
break; | |
case '[object Date]': | |
copy = new Date(+value); | |
break; | |
default: | |
copy = {}; | |
} | |
for (let k in value) { | |
let v = value[k]; | |
copy[k] = deep ? clone(v, true) : v; | |
} | |
return copy; | |
} | |
return value; | |
} | |
function extend(deep, dst, src) { | |
if (typeof deep !== 'boolean') { | |
src = slice.call(arguments, 1); | |
dst = deep; | |
deep = false; | |
} else { | |
src = slice.call(arguments, 2); | |
} | |
src.forEach(srcObj => { | |
for (let k in srcObj) { | |
let v = srcObj[k]; | |
dst[k] = deep ? clone(v, true) : v; | |
} | |
}); | |
return dst; | |
} | |
function equals(v1, v2) { | |
if (v1 === v2) return true; | |
if (v1 !== v1 && v2 !== v2) return true; // NaN | |
let t1 = typeof v1, t2 = typeof v2; | |
if (t1 === t2 && t1 === 'object') { | |
if (isArray(v1)) { | |
if (!isArray(v2)) return false; | |
if (v1.length === v2.length) { | |
return v1.every((item, index) => { | |
return equals(item, v2[index]); | |
}); | |
} | |
} else if(isDate(v1)) { | |
if (!isDate(v2)) return false; | |
return +v1 === +v2; | |
} else if(isRegExp(v1)) { | |
if (!isRegExp(v2)) return false; | |
return v1.toString() === v2.toString(); | |
} else { | |
for (let k in v1) { | |
let vv1 = v1[k], vv2 = v2[k]; | |
if (!equals(vv1, vv2)) return false; | |
} | |
return true; | |
} | |
} | |
return false; | |
} | |
function Scope() { | |
this.$$watchers = []; | |
this.$$asyncQueue = []; | |
this.$$postDigestQueue = []; | |
this.$$phase = null; | |
} | |
Scope.prototype = { | |
constructor: Scope, | |
$watch(watchFn, listenerFn, valueEq) { | |
let $$watchers = this.$$watchers; | |
let watcher = { | |
watchFn, | |
listenerFn, | |
valueEq: !!valueEq | |
}; | |
if (!listenerFn) { | |
watcher.listenerFn = function() {}; | |
} | |
$$watchers.push(watcher); | |
return function() { | |
let index = $$watchers.indexOf(watcher); | |
if (index > -1) { | |
$$watchers.splice(index, 1); | |
} | |
}; | |
}, | |
$digestOnce() { | |
let dirty; | |
this.$$watchers.forEach(watcher => { | |
let oldValue = watcher.last; | |
let newValue = watcher.watchFn(this); | |
if (!this.$$areEqual(newValue, oldValue, watcher.valueEq)) { | |
watcher.listenerFn(oldValue, newValue, this); | |
// 有可能listenerFn里修改了其他监控的值,需要再次digest | |
dirty = true; | |
} | |
watcher.last = watcher.valueEq ? clone(newValue, true) : newValue; | |
}); | |
return dirty; | |
}, | |
$digest() { | |
let ttl = 10; | |
let dirty; | |
this.$$beginPhase("$digest"); | |
do { | |
while (this.$$asyncQueue.length) { | |
let asyncTask = this.$$asyncQueue.shift(); | |
this.$eval(asyncTask.expression); | |
} | |
dirty = this.$digestOnce(); | |
if (dirty && !(ttl--)) { | |
this.$$clearPhase(); | |
throw '10 digest iterations reached.' | |
} | |
} while (dirty); | |
this.$$clearPhase(); | |
while (this.$$postDigestQueue.length) { | |
this.$$postDigestQueue.shift()(); | |
} | |
}, | |
$$areEqual(newValue, oldValue, valueEq) { | |
if (valueEq) { | |
return equals(newValue, oldValue); | |
} else { | |
return newValue === oldValue || (newValue !== newValue && oldValue !== oldValue); | |
} | |
}, | |
$eval(expr, locals) { | |
return expr(this, locals); | |
}, | |
$apply(expr) { | |
try { | |
this.$$beginPhase('$apply'); | |
return this.$eval(expr); | |
} finally { | |
this.$$clearPhase(); | |
this.$digest(); | |
} | |
}, | |
$evalAsync(expr) { | |
if (!this.$$phase && !this.$$asyncQueue.length) { | |
setTimeout(() => { | |
if (this.$$asyncQueue.length) { | |
this.$digest(); | |
} | |
}, 0); | |
} | |
this.$$asyncQueue.push({ | |
scope: this, | |
expression: expr | |
}); | |
}, | |
$$beginPhase(phase) { | |
if (this.$$phase) { | |
throw this.$$phase + ' already in progress.'; | |
} | |
this.$$phase = phase; | |
}, | |
$$clearPhase() { | |
this.$$phase = null; | |
}, | |
$$postDigest(fn) { | |
this.$$postDigestQueue.push(fn); | |
} | |
}; | |
let scope = new Scope(); | |
scope.user = null; | |
scope.counter = 0; | |
let unwatchUser = scope.$watch($scope => $scope.user, function(oldV, newV, $scope) { | |
$scope.counter++; | |
console.log('user changed: ', oldV, newV) | |
}, true); | |
scope.$watch($scope => $scope.counter, function(oldV, newV) { | |
console.log('counter changed: ', oldV, newV); | |
}); | |
scope.$apply(function($scope) { | |
$scope.user = {}; | |
}); | |
scope.$evalAsync(function($scope) { | |
console.log('evalAsync user.name result: ', $scope.user.name) | |
}); | |
scope.$$postDigest(function() { | |
console.log('digest done!'); | |
}); | |
scope.user.name = 'joenil'; | |
scope.$digest(); | |
unwatchUser(); | |
scope.user.age = 24; | |
scope.$digest(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment