js 脏检测
基础知识
<!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>
js 脏检测的更多相关文章
- 我的angularjs源码学习之旅3——脏检测与数据双向绑定
前言 为了后面描述方便,我们将保存模块的对象modules叫做模块缓存.我们跟踪的例子如下 <div ng-app="myApp" ng-controller='myCtrl ...
- Device.js – 快速检测平台、操作系统和方向信息
在 Web 项目中,有时候我们需要根据程序运行的环境采取特定操作.Device.js 是一个很小的 JavaScript 库,它简化了编写和平台,操作系统或浏览器相关的条件 CSS 或 JavaScr ...
- Sublime text3 JS语法检测工具安装及使用
Sublime text3 JS语法检测工具安装及使用 工具/原料 sublime text3 nodejs sublimeLinter sublimeLinter-jshint 方法/步骤 首先ct ...
- angular1.x 脏检测
写在前面 双向绑定是angular的大亮点,然后支撑它的就是脏检测.一直对脏检测都有一些理解,却没有比较系统的概念. 以下是我阅读网上博文以及angular高级程序设计的理解与总结. 接收指导与批评. ...
- 手写面试编程题- 数组去重 深拷贝 获取文本节点 设置奇数偶数背景色 JS中检测变量为string类型的方法 第6题闭包 将两个数组合并为一个数组 怎样添加、移除、移动、复制、创建和查找节点? 继承 对一个数组实现随机排序 让元素水平 垂直居中的三种方式 通过jQuery的extend方法实现深拷贝
第1题==>实现数组去重 通过 new Set(数组名) // var arr = [12, 12, 3, 4, 5, 4, 5, 6, 6]; // var newarr1 = new Set ...
- js代码检测设备问题:为什么在移动端检测设备的时候会出现pc的页面
为了在手机上也能正常显示页面,所以为之前写的页面又重写了一遍,专门用来在移动端显示,用js代码检测设备,如果是pc就显示pc的页面,如果是移动就显示移动的页面,但遇到一个问题就是在移动端打开会有一个延 ...
- JS 中检测数组的四种方法
今天和大家分享一下 JS 中检测是不是数组的四种方法,虽然篇幅不长,不过方法应该算是比较全面了. 1. instanceof 方法 instanceof 用于检测一个对象是不是某个类的实例,数组也是一 ...
- JS正则检测密码强度
今天遇到个需求,使用JS检测密码强度:密码长度最短为8,必须同时包含字母.数字.特殊符号. 代码如下: /* * 检测密码复杂度 */ function ...
- js如何检测打开窗口是否存在的三个方法?
js打开窗口一般也就是使用window.open方法: win = window.open(CHILD_WINDOW_URL, CHILD_WINDOW_NAME, CHILD_WINDOW_ATTR ...
随机推荐
- loj黑暗城堡
黑暗城堡 题目描述 你知道黑暗城堡有\(N\)个房间,M 条可以制造的双向通道,以及每条通道的长度. 城堡是树形的并且满足下面的条件: 设\(D_i\)为如果所有的通道都被修建,第i号房间与第1号房间 ...
- C++类组合问题
#include <iostream> using namespace std; class Vehicle { public: Vehicle(float speed=0,int tot ...
- Spring boot 异步线程池
package com.loan.msg.config; import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandle ...
- Java 复习整理day07
package com.it.demo05_innerclass; /* 案例: 演示内部类入门. 概述: 所谓的内部类指的是类里边还有一个类, 里边那个类叫: 内部类, 外边那个类, 叫外部类. 分 ...
- dedecms万能SQL标签使用方法大全
注意:dede_archives这是系统默认的数据库表,如果你修改过表前缀dede_,请自行更改表名.在以下示例的标签中,有一部分只写出了SQL语句,具体的完整标签写法请参考:织梦SQL标签调用方法. ...
- 浅谈Webpack模块打包工具一
为什么要使用模块打包工具 1.模块化开发ES Modules存在兼容性问题 打包之后成产阶段编译为ES5 解决兼容性问题 2.模块文件过多 网络请求频繁 开发阶段把散的模块打包成一个模块 解决网络请 ...
- zjnu1762 U (线段树)
Description Mirko is hungry as a bear, scratch that, programmer and has stumbled upon a local restau ...
- 2020 年百度之星·程序设计大赛 - 初赛一Dec 简单dp
题意: Problem Description 初始有 a, ba,b 两个正整数,每次可以从中选一个大于 1 的数减 1,最后两个都会减到 1,我们想知道在过程中两个数互质的次数最多是多少. Inp ...
- poj3087 Shuffle'm Up
Description A common pastime for poker players at a poker table is to shuffle stacks of chips. Shuff ...
- 解决关闭ssh后网页停止服务的方法,利用nohup
上一篇文章提到宝塔面板无法运行,只能用ssh运行app.py. 关闭ssh时,app.py会被杀死.因为app.py的父进程就是ssh,关掉ssh会造成进程被杀死. 解决方法参考 https://w ...