基础知识
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div>
<input type="text" ng-bind="name" />
<button type="button" ng-click="increment">increment</button>
<div ng-bind="name"></div>
</div>
<script>
class Watcher {
constructor(name, last, exp, listener) {
this.name = name; // 数据变量名
this.last = last; // 数据变量旧值
this.newVal = exp; // 返回数据变量新值的函数
this.listener = listener || function () {}; // 监听回调函数,变量“脏”时触发
this.listener(this.last, this.newVal());
}
}
class Scope {
constructor() {
// 观察者数组
this.watchers = [];
}
// 添加数据观察者
watch(name, exp, listener) {
this.watchers.push(new Watcher(name, "", exp, listener));
}
// 对监视器的新旧值进行对比
// 当新旧值不同时,调用listener函数进行相应操作
// 并将旧值更新为新值。它将不断重复这一过程,直到所有数据变量的新旧值相等:
digest() {
let dirty = true;
while (dirty) {
dirty = false;
this.watchers.forEach(watcher => {
let newVal = watcher.newVal();
var oldVal = watcher.last;
if (newVal !== oldVal) {
dirty = true;
watcher.listener(oldVal, newVal);
watcher.last = newVal;
}
});
}
}
}
class App extends Scope {
name = "Ajanuw";
constructor() {
super();
}
increment() {
this.name += "+";
}
}
const app = new App();
run(app);
function run(app) {
document // 绑定依赖观察者
.querySelectorAll("[ng-bind]")
.forEach(it => {
const nodeName = it.nodeName.toLowerCase();
const bindKey = it.getAttribute("ng-bind");
if (bindKey in app) {
app.watch(
bindKey,
() => app[bindKey],
(oldVal, newVal) => {
if (nodeName === "input") {
it.value = newVal;
} else {
it.textContent = newVal;
}
}
);
}
});
// 绑定事件
document.querySelectorAll("[ng-click]").forEach(it => {
const bindKey = it.getAttribute("ng-click");
it.addEventListener("click", e => {
if (app[bindKey] && typeof app[bindKey] === "function") {
app[bindKey]();
app.digest();
}
});
});
// 双向绑定
document.querySelectorAll("input[ng-bind]").forEach(it => {
const bindKey = it.getAttribute("ng-bind");
it.addEventListener("input", e => {
app[bindKey] = it.value;
app.digest();
});
});
}
</script>
</body>
</html>
监听object和array的修改
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div>
<input type="text" ng-bind="name" />
<button type="button" ng-click="increment">increment</button>
<div ng-bind="name"></div>
<hr />
<div ng-bind="obj"></div>
<button ng-click="changeValue">改变object的值</button>
</div>
<script>
function equal(obj, other) {
const objectTag = "[object Object]";
const arrayTag = "[object Array]";
const _tostring = value => Object.prototype.toString.call(value);
const emptyp = value => JSON.stringify(value).length === 2;
function Equal(obj, other) {
let objTag = _tostring(obj);
let otherTag = _tostring(other);
// 非集合,使用===判断
if (
objTag !== objectTag &&
objTag !== arrayTag &&
otherTag !== objectTag &&
otherTag !== arrayTag
) {
return obj === other;
}
// 集合类型不一样
if (objTag !== otherTag) return false;
// 集合元素数量不一样
if (
Object.getOwnPropertyNames(obj).length !==
Object.getOwnPropertyNames(other).length
)
return false;
// 类型一样的空集合,永远相等。
if (emptyp(obj) && emptyp(other)) return true;
let rsult = false;
for (const k in obj) {
if (k in other) {
const obj_value = obj[k];
const other_value = other[k];
rsult = Equal(obj_value, other_value);
} else {
return false;
}
}
return rsult;
}
return Equal(obj, other);
}
function copytree(tree, all = true) {
const objectTag = "[object Object]";
const arrayTag = "[object Array]";
const _tostring = value => Object.prototype.toString.call(value);
// 记录所有的对象
const map = new WeakMap();
function copyTree(tree, all = true) {
const treeTag = _tostring(tree);
const res =
treeTag === objectTag ? {} : treeTag === arrayTag ? [] : tree;
if (treeTag !== objectTag && treeTag !== arrayTag) return res;
// 判断是否有此对象
if (map.has(tree)) {
// 直接返回
return tree;
} else {
map.set(tree, true);
}
const t = all ? Object.getOwnPropertyNames(tree) : tree;
if (all) {
for (const i in t) {
const k = t[i];
res[k] = copyTree(tree[k], all);
}
} else {
for (const k in t) {
res[k] = copyTree(tree[k], all);
}
}
return res;
}
return copyTree(tree, all);
}
function evalFun(bindKey, data) {
try {
const r = Function(`with(this){ return ${bindKey} }`).apply(
data,
arguments
);
return r === "" ? undefined : r;
} catch (error) {}
}
function setData(key, newValue, context) {
return Function(`return function(d) {
with(this){
${key} = d;
}
}`)().call(context, newValue);
}
class Watcher {
constructor(last, exp, listener, valueEq) {
this.last = last; // 数据变量旧值
this.newVal = exp; // 返回数据变量新值的函数
this.listener = listener || function () {}; // 监听回调函数,变量“脏”时触发
this.valueEq = valueEq;
this.listener(this.last, this.newVal());
}
}
class Scope {
constructor() {
// 观察者数组
this.watchers = [];
}
// 添加数据观察者
// valueEq检查值,而不是引用
watch(v, exp, listener, valueEq = false) {
this.watchers.push(
new Watcher(valueEq ? copytree(v) : v, exp, listener, valueEq)
);
}
// 对监视器的新旧值进行对比
// 当新旧值不同时,调用listener函数进行相应操作
// 并将旧值更新为新值。它将不断重复这一过程,直到所有数据变量的新旧值相等:
digest() {
let dirty = true;
// while (dirty) {
// dirty = false;
this.watchers.forEach(watcher => {
const newVal = watcher.newVal();
const oldVal = watcher.last;
if (!this.valueEqual(newVal, oldVal, watcher.valueEq)) {
dirty = true;
watcher.listener(oldVal, newVal);
watcher.last = watcher.valueEq ? copytree(newVal) : newVal;
}
});
// }
}
valueEqual(newValue, oldValue, valueEq) {
if (this.valueEq) {
return equal(newValue, oldValue);
} else {
return newValue === oldValue;
}
}
}
class App extends Scope {
name = "Ajanuw";
obj = {
value: "hello world"
};
constructor() {
super();
}
increment() {
this.name += "+";
}
changeValue() {
this.obj.value = "hello ajanuw";
}
}
const app = new App();
run(app);
function run(app) {
document // 绑定依赖观察者
.querySelectorAll("[ng-bind]")
.forEach(it => {
const nodeName = it.nodeName.toLowerCase();
const bindKey = it.getAttribute("ng-bind");
const v = evalFun(bindKey, app);
if (v) {
app.watch(
v,
() => evalFun(bindKey, app),
(oldVal, newVal) => {
if (nodeName === "input") {
it.value = newVal;
} else {
if (typeof newVal === "object") {
it.textContent = JSON.stringify(newVal);
} else {
it.textContent = newVal;
}
}
},
typeof v === "object" && v !== null
);
}
});
// 绑定事件
document.querySelectorAll("[ng-click]").forEach(it => {
const bindKey = it.getAttribute("ng-click");
const fn = evalFun(bindKey, app);
if (fn && typeof fn === "function") {
it.addEventListener("click", e => {
fn.call(app);
app.digest();
});
}
});
// 双向绑定
document.querySelectorAll("input[ng-bind]").forEach(it => {
const bindKey = it.getAttribute("ng-bind");
it.addEventListener("input", e => {
setData(bindKey, it.value, app);
app.digest();
});
});
}
</script>
</body>
</html>