Skip to content

Instantly share code, notes, and snippets.

@bravelincy
Last active May 8, 2017 04:27
Show Gist options
  • Save bravelincy/071f2ed5ea2f4f69c9eed8db65eef3a3 to your computer and use it in GitHub Desktop.
Save bravelincy/071f2ed5ea2f4f69c9eed8db65eef3a3 to your computer and use it in GitHub Desktop.
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