函数柯里化
把一次传递两个参数, 变成需要调用两次但每次只传递一个参数 利用了闭包, 把第一次传递的参数保存下来(延长生命周期)
- 正则验证密码
const reg = /^\w{6,12}$/;
const res = reg.test("asdvkashas");
console.log(res);
- 封装为函数
function fn1(reg, str) {
return reg.test(str);
}
console.log(fn1(/^\w{6,12}$/, "abcdefg"));
console.log(fn1(/^\w{6,12}$/, "abcdefghijkl"));
console.log(fn1(/^\w{6,12}$/, "abcdefghijklmnopq"));
- 封装为柯里化函数
function test(reg) {
return function (str) {
return reg.test(str);
};
}
const testPwd = test(/^\w{6,12}$/);
const testName = test(/^\w{6,8}$/);
const testPhone = test(/^\d{11}$/);
console.log(testPwd("abcdefg"));
console.log(testPwd("QF666"));
console.log(testPwd(12345678901));
函数柯里化封装
/**
* 函数柯里化
* 内层函数负责处理功能
* 外层函数负责处理参数 (当参数满足指定数量时在执行)
*
* 功能: 拼接地址栏字符串
* http://localhost:8080/index.html
* 传输协议: http https
* 域名: localhost 127.0.0.1
* 端口号: 0~65535
* 地址: /a /a/b /a/b/index
*
* http://localhost:8080/地址1
* http://localhost:8080/地址2
* http://localhost:8080/地址3
* http://localhost:443/地址1
* http://localhost:445/地址2
* http://localhost:7777/地址3
* http://127.0.0.1:7777/地址1
* http://127.0.0.2:7778/地址2
* http://127.0.0.3:7779/地址3
*
*/
function fn(a, b, c, d) {
return a + "://" + b + ":" + c + d;
}
// let res = fn('http', 'localhost', '8080', '/a/b/d')
// console.log(res)
// 外层函数: 负责接受功能参数, 与基础参数
function currying(callback, ...arg) {
// 利用扩展运算符以数组的形式拿到所有的 基础参数, 没有的话为 空数组
// 内层函数负责判断参数是否满足指定数量, 满足调用函数, 不满足继续接收参数
return function (..._arg) {
// 将来内层函数也有可能接收参数
_arg = [...arg, ..._arg];
if (_arg.length === callback.length) {
// callback.length 拿到函数形参的数量
return callback(..._arg);
} else {
return currying(callback, ..._arg);
}
};
}
let r1 = currying(fn, "https", "localhost");
let r2 = currying(fn, "https");
let r3 = currying(fn);
let res1 = r1("8080", "/a/b/c");
let res2 = r1("7777", "/index");
console.log(res1);
console.log(res2);
函数的防抖与节流
- 防抖:
- 短时间内快速触发同一个事件
- 每一次都用下一次干掉上一次, 永远执行最后一次
// <input type="text" class="inp">
let inp = document.querySelector(".inp");
inp.oninput = (function (timer) {
return function (e) {
clearInterval(timer);
timer = setTimeout(() => {
console.log("经过了 300 ms 搜索到了", e.target.value, "相关的新闻");
}, 300);
};
})(0);
- 节流:
- 短时间内快速触发同一个事件
- 第一次执行过程中, 不能重复触发, 等到第一次执行完毕后, 才可以触发
// <input type="text" class="inp">
let inp = document.querySelector(".inp");
inp.oninput = ((type) => {
return (e) => {
if (type == false) return;
type = false;
setTimeout(() => {
type = true;
console.log("经过了 300 ms 搜索到了", e.target.value, "相关的新闻");
}, 300);
};
})(true);
数据劫持
将来在框架中我们通常都是 数据驱动视图 也就是说: 修改完毕数据, 视图自动更新
- 数据劫持
- 以原始数据为基础, 对数据进行一份复刻
- 复刻出来的数据是不允许修改的, 值是从原始数据里面获取的
- 语法:
Object.defineProperty(哪一个对象, 属性名, { 配置项 })- 配置项:
- value: 该属性对应的值
- writable: 该属性是否可以被重写, 默认是 false
- emunerable: 该属性是否可被美剧, 默认是 false
- get: 是一个函数, 叫做 getter 获取器, 可以来决定该属性的值
- get 函数的返回值, 就是当前这个属性的值
- 注意: 不能和 value 与 writable 一起使用, 会报错
- set: 是一个函数, 叫做 setter 设置器, 当你需要修改该属性的值的时候会触发该函数
- 配置项:
const obj = {};
obj.name = "Jack";
Object.defineProperty(obj, "age", {
// value: 20,
// writable: true,
enumerable: true,
get() {
return 100;
},
set(val) {
console.log("你想修改 age 的值, 你想修改为", val);
},
});
// // console.log(obj)
// obj.name = 'Jack001'
obj.age = 50;
console.log(obj);
// for (let k in obj) {
// console.log(k)
// }
- 实现数据劫持
const obj = {
name: "QF001",
age: 18,
};
const res = {};
Object.defineProperty(res, "name", {
get() {
return obj.name;
},
set(val) {
// 设置的时候, 直接去修改原对象 obj 中对应属性的值, 就能够影响到 res对象中对应的值
obj.name = val;
box.innerHTML = `名字: ${res.name}; 年龄: ${res.age}`;
},
});
Object.defineProperty(res, "age", {
get() {
return obj.age;
},
set(val) {
// 设置的时候, 直接去修改原对象 obj 中对应属性的值, 就能够影响到 res对象中对应的值
obj.age = val;
box.innerHTML = `名字: ${res.name}; 年龄: ${res.age}`;
},
});
// console.log(res)
// console.log(res.age)
// console.log(res.name)
box.innerHTML = `名字: ${res.name}; 年龄: ${res.age}`;
- 封装数据劫持
function observer(origin, fn) {
// origin 劫持的原数据, fn 你想做的事
// 劫持目标
const target = {};
for (let k in origin) {
Object.defineProperty(target, k, {
get() {
return origin[k];
},
set(val) {
// 设置的时候, 直接去修改原对象 obj 中对应属性的值, 就能够影响到 res对象中对应的值
origin[k] = val;
fn(target);
},
});
}
// 首次调用一下, 让页面有数据
fn(target);
// 返回一个被劫持后的数据
return target;
}
const obj = {
name: "QF001",
age: 18,
};
function fn(res) {
box.innerHTML = `名字: ${res.name}; 年龄: ${res.age}`;
}
const app = observer(obj, fn);
console.log(app);
inp.oninput = function (e) {
app.age = e.target.value;
};
模拟数据劫持 + 渲染
- 封装操作
/**
* 为了将来使用者考虑, 需要制定一些规则
* + 把 渲染页面这件事, 直接书写在 html 结构内
* + 让我来捕获, 我来替你进行渲染
*
* 约定一些渲染规则
* + 因为我们已经确定了根元素
* + 把我的约定放在了根元素内就可以了
* + 当你需要渲染一个基本数据的时候
* => 约定一个自己认识的符号
* => {{}}
*/
/**
* options 是一个配置项
* 所有参数都以配置型的方式传递给我
* el: 参数, 需要一个选择器 用来锁定一个 页面结构
* data: 你需要在页面上渲染的数据
*/
function fn(options = {}) {
// 1. 确保传递了 el 参数
if (options.el === undefined) {
throw new Error("您的 el 选项没有传递, 请您传递");
}
// 2. 确保传递了 data 对象
if (options.data == undefined || options.data.constructor !== Object) {
throw new Error("您没有按照规定传递一个 对象形式的 data");
}
// 3. 确保能正确拿到根节点
const root = document.querySelector(options.el);
if (root === null) {
throw new Error("您传递的 el 参数有误, 我没有获取到对应节点");
}
// 4. 劫持 options.data 内的数据
const _data = {};
// 4.1 留存一份 data 的数据 (可选, 有了更好)
_data.origin = options.data;
// 4.2 保存原始基础结构 (不存的话, 首次渲染完毕页面结构将没有 {{}}, 那么render函数就没办法找到对应的位置赋值, 先写后续演示问题)
const rootStr = root.innerHTML;
// 4.3 开始劫持
for (let k in options.data) {
Object.defineProperty(_data, k, {
get() {
return options.data[k];
},
set(val) {
options.data[k] = val;
// 重新修改数据时需要重新渲染页面, 封装一个 rander 函数
rander(root, _data, rootStr);
},
});
}
// 5. 一开始的时候先渲染一些页面
rander(root, _data, rootStr);
// 6. 返回劫持后的对象
return _data;
}
function rander(root, _data, str) {
// 正则
const reg = /{{ *(\w+) *}}/g;
// 从 str 内捕获到所有的内容
const res = str.match(reg);
res.forEach((item) => {
const key = reg.exec(str)[1];
console.log(key);
str = str.replace(/{{ *(\w+) *}}/, _data[key]);
});
// 替换完毕以后, 去更新 root 内的数据
root.innerHTML = str;
}
- 实际使用
const app = fn({
el: "#root",
data: {
name: "QF001",
age: 18,
message: "醒醒, 别睡了, 就说你呢!",
},
});
- DOM 结构
<div id="root">
<p>你好 想使用我的name属性 : {{ name }}</p>
<p>你好 想使用我的age属性 : {{ age }}</p>
<p>你好 想使用我的message属性 : {{ message }}</p>
</div>
数据劫持升级
-
语法: Object.defineProperties(对象, {配置项})
-
弊端: 数据劫持后新增的数据无法被劫持
Object.defineProperties(对象, {
属性1: { 配置项 },
属性2: { 配置项 },
});
// 原先: Object.defineProperty(对象, 属性, {配置项})
const obj = {
name: "QF001",
age: 18,
};
console.log("原始 obj: ", obj);
for (let k in obj) {
Object.defineProperties(obj, {
["_" + k]: {
value: obj[k],
writable: true,
enumerable: false,
},
[k]: {
get() {
return obj["_" + k];
},
set(val) {
obj["_" + k] = val;
root.innerHTML = `名字: ${obj.name}; 年龄: ${obj.age}`;
},
},
});
}
root.innerHTML = `名字: ${obj.name}; 年龄: ${obj.age}`;
console.log("劫持后的数据", obj);
数据代理
- proxy (数据代理是官方给的, 但是大家还是习惯叫 数据劫持)
- ES6 提供的语法
- 内置构造函数 Proxy(代理原始对象, {配置项})
const obj = {
name: "QF001",
age: 18,
};
console.log("原始的对象: ", obj);
// 开始代理
const res = new Proxy(obj, {
// 配置 get 来进行代理设置
get(target, property) {
// target, 就是你要代理的目标对象, 当前案例中 为 obj
// property, 就是该对象内的每一个属性, 自动遍历
return target[property];
},
// 配置 set 来进行修改
set(target, property, val) {
// target, 就是你要代理的目标对象, 我们当前案例就是 obj
// property, 就是你要修改的 对象属性
// val, 就是你要修改那个属性的值
target[property] = val;
console.log(
"你想要修改",
property,
"属性, 你想修改为: ",
val,
"我需要根据你修改的内容重新渲染页面"
);
// 注意!!! 简单代理需要返回 true (返回 true 代表属性设置成功; 后续详细讲解)
return true;
},
});
console.log("代理后的对象: ", res);
res.name = "100";
res.gender = "男";