js分享16-函数柯里化与数据劫持(小白必看)

71 阅读1分钟

函数柯里化

把一次传递两个参数, 变成需要调用两次但每次只传递一个参数 利用了闭包, 把第一次传递的参数保存下来(延长生命周期)

  • 正则验证密码
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);

数据劫持

将来在框架中我们通常都是 数据驱动视图 也就是说: 修改完毕数据, 视图自动更新

  1. 数据劫持
    • 以原始数据为基础, 对数据进行一份复刻
    • 复刻出来的数据是不允许修改的, 值是从原始数据里面获取的
    • 语法: Object.defineProperty(哪一个对象, 属性名, { 配置项 })
      • 配置项:
        1. value: 该属性对应的值
        2. writable: 该属性是否可以被重写, 默认是 false
        3. emunerable: 该属性是否可被美剧, 默认是 false
        4. get: 是一个函数, 叫做 getter 获取器, 可以来决定该属性的值
          • get 函数的返回值, 就是当前这个属性的值
          • 注意: 不能和 value 与 writable 一起使用, 会报错
        5. 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)
// }
  1. 实现数据劫持
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}`;
  1. 封装数据劫持
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 = "男";