函数的柯里化
将一个 接收多个参数的函数 更改为需要调用多次的函数 ,每次只传入一个参数,外层函数负责收集参数,内层函数负责 在参数收集完毕时 执行功能
- 通过一个函数计算 a、b的和
function fun(a,b){
return a+b; // 返回值 => a+b
}
console.log(fun(10,20)); // 30
我们可以看到,想要计算 a、b 的和,我们必须要传入两个参数,而通过函数柯里化后我们只需要传入一个参数即可实现 计算 a、b 的和
- 函数柯里化 => 计算a 、 b的和
function fun(a){
return function(b){
console.log(a+b); // 打印 a+b
}
}
let res = fun(10); // 调用外部函数 参数传入a => a=10
let res1 = res(20); // 调用内部函数 参数传入b => b=20
/*
当调用外部函数时 参数 10 传入 a , a=10, 当调用内部函数时 参数 20 传入 b, b=20,
利用了 闭包, 延长了 外部函数的参数使用时间 此时 a=10
*/
- 例如: 咱们通过正则表达式,制定一项密码规则,利用函数柯里化来判断传入的密码是否正确
function fun(reg) {
return function (str) {
return reg.test(str); // 检验传入参数
}
}
let test1 = fun(/^\d{4,8}$/); // 将规则传入参数
let boo1 = test1('123456');
console.log(boo1); // true
let boo2 = test1('qwe123456');
console.log(boo2); // false
- 封装柯里化函数 每次输入不同的实参,实现一个网址的输出 => 例: www.baidu.com/
// https://www.baidu.com/
function fun(a,b,c,d){
return a + '://' + b + '.' + c + '.' + d
}
// 外层函数 负责接收参数
function currying(callback, ...arg){ //callback 功能函数 首次调用必传 ...arg 将存入参数以数组形式存储 如果首次调用没有传入参数 那么 ...arg 为空数组
// const len = callback.length; // 传入参数的个数
// console.log(...arg);
// 内层函数 负责参数执行完毕后 执行功能
return function(...iArg){
iArg = [...arg,...iArg]; // 执行功能前 先将两次函数调用传入的形参从数组中拿出来 存储在 iArg 中
// console.log(iArg.length);
if(iArg.length === callback.length){ // iArg.length === len => 4 fun()形参长度
return callback(...iArg); // 如果传入实参 个数 满足形参个数 以字符串的形式返回
}else{
return currying(callback,...iArg); // 如果传入实参不满足 形参个数 将继续传入 并将之前传入的实参拼接 返回
}
}
}
let res = currying(fun,'https'); // 首次调用函数currying 此时参数只有一个 不满足形参个数 将继续调用 加上之前传入的实参 直到 满足形参个数 fun => 为功能函数实参
let res1 = res('www','baidu'); // 此时不满足形参个数 将继续调用 加上之前传入的实参 直到满足形参个数
let res2 = res1('com'); // 加上之前传入参数 满足形参个数
console.log(res2); // 打印功能函数的返回值 得到传入参数的字符串拼接
函数的防抖与节流
防抖: 在一定时间内, 快速触发同一事件,每次重新触发, 都覆盖掉前一次事件, 以最后一次事件触发为主
节流: 在一定时间内, 快速触发同一事件,在规定时间内, 只能触发一次, 下一次必须等到 规定时间结束以后才能执行
-
常见的节流
在输入框输入时,输入的值不会在开始时输入事件不会打印或者返回结果,达到规定时间时才会触发事件,返回结果
// 输入框:<input type="text">
// 节流
const inp = document.querySelector('input'); // 获取 input 输入框
let flag = true; // 定义开关变量
inp.oninput = function(e){
if(!flag) return; // 输入开始后未到达时间 不再触发事件
flag = false; // 开始后 触发事件失效
console.log(e.target.value); // 打印输出 输入到输入框的值 e.target.value => 输进输入框的值
setInterval(function(){ // 计时器
flag = true; // 满足时间 开关打开 触发事件
},300); // 触发时间 输入开始300毫秒后触发
}
引入 => 自执行函数
见面知意, 自执行函数,就是不调用也可以执行的函数, 多个自执行函数连在一起时, 需要用 ' ; ' 隔开,则就会报错,不能正常执行.(分隔号 ' ; ' 加在函数前后都可以)
- 自执行函数的写法
自执行函数, 第一个小括号内写 函数体,第二个小括号内写 实参(会传递给第一个小括号内部的函数)
// 自执行函数的写法 : ()()
(function () {
console.log(999); // 999
})();
(function (num){ // num 为形参
console.log(num)
})(666); // 666 实参 写在第二个括号里面
- 函数的节流 (自执行函数)
// 输入框:<input type="text">
inp.oninput = (function (flag) { // flag 为形参 => 开关变量
return function (e) {
if(!flag) return; //
flag = false; // 输入开始 开关关闭 不触发事件
console.log('e.target.value');
setTimeout(() => {
flag = true; // 规定时间到达 开关打开 触发事件
}, 300);
}
})(true); // true 实参 => 传入 flag
- 防抖 => 输入框一直输入 以最后一次输入的为准 前面输入的会被后面的覆盖
// 输入框:<input type="text">
// 防抖 => 输入框一直输入 以最后一次输入的为准 前面输入的会被后面的覆盖
inp.oninput = (function(time){
return function(e){
clearTimeout(time); // 清除0 => 当倒计时器为0时 清除到计时器 开始输入时
time = setTimeout(function(){
console.log(e.target.value); // 拿到键盘输入的值
},300);
}
})(0); //time === 0
数据劫持
- 将原数据 劫持一份一模一样的数据出来 存储到一个空对象中,劫持出来的数据默认默认是不能修改的
- 语法: Object.defineProperty(那个对象, '对象的key', {配置项})
- 配置项
- value 访问这个值 之后, 得到结果
- writable 决定当前这个属性能否被修改, 默认是 false
- enumerable 决定当前这个属性能否被枚举, 决定当前这个属性能
- getter 是一个函数, 是一个获取器, 当访问这个属性时, 会执行这个函数 ( getter 不能和 value writable 一起使用 )
- setter 是一个函数, 是一个设置器, 当设置这个属性是, 会执行这个函数
const obj = {
name:'QF100',
age:18
};
console.log(obj);
Object.defineProperty(obj,'age',{
// value:'QF100',
// writable:true, // value writable 不能与 get 一起用
enumerable:true,
get(){
return 'qwer'; // 访问时返回的值
},
set(val){
console.log(`修改后:${val}`); // val 被修改的值
}
})
console.log(obj.name); // QF100
obj.age = 40; // 修改 age
console.log(obj.age); // 40
obj.name = 'QF111';
console.log(obj.name); // QF111
-
由上述代码看来 我们在修改name时 并没有返回值 ,说明并不能实现多个值的修改,如果我们将劫持语法写为多个呢? 是否可以修改多个值?
-
定义多个数据劫持虽然可以修改 截取到全部数据 但代码量过多 , 如果我们有一百个这样的数据要修改,要写这样的一百个?
// <div id="box"></div>
const box = document.querySelector('#box');
const obj = {
name:'qf100',
age:18
}
console.log('原对象:',obj);
const res = {}; // 将劫持到的数据 存储到res 中
Object.defineProperty(res,'name',{
get(){
return obj.name ; // 访问 读取时返回 name
},
set(val){
obj.name = val; // 修改名字
box.innerHTML = `名字:${res.name}; 年龄:${res.age}`
}
})
Object.defineProperty(res,'age',{
get(){
return obj.age; // 访问 读取时返回 age
},
set(val){
obj.age = val; // 修改年龄
box.innerHTML = `名字:${res.name}; 年龄:${res.age}` // 将修改的数据渲染页面
}
})
res.name = 'QF111'; // 修改name
res.age = 99; // 修改age
- 数据劫持 优化1
语法: Object.defineProperties(到那个对象, {
属性1: 配置项,
属性2: 配置项
})
// <div id="box"></div>
const obj = {
name: 'QF100',
age: 18
}
const res = {}; // 定义空对象 用于存储 劫持到的数据
// 基础版
Object.defineProperties(res, {
name: { // name 项
get() {
return obj.name;
},
set(val) {
obj.name = val;
}
},
age:{ // age 项
get(){
return obj.age;
},
set(val){
obj.age = val;
}
}
})
console.log(res); // 获取劫持到的数据
console.log(res.age); // 获取劫持到的age
res.name = 'QF1234'; // 修改 name
console.log(res.name); // 获取修改后的name
- 数据劫持 优化2
通过自己劫持自己,将自己的数据全部劫持存储在自身内部
const obj = {
name: 'QF001',
age: 18
}
// 2. 升级版(plus)
for (let k in obj) { // 循环遍历对象
Object.defineProperties(obj, {
['_' + k]: { // 我在我这个对象内把所有属性复制一份, 放在自己这个对象内部
value: obj[k],
writable: true
},
[k]: {
get () {
return obj['_' + k]; // 读取时返回劫持的数据
},
set(val) {
obj['_' + k] = val; // 修改劫持后的数据
document.querySelector('#root').innerHTML = `name: ${obj.name}`
}
}
})
}
obj.newName = 'QF999'; // 添加数据
console.log(obj);
obj.abc = 'www'; // 增加一个key 时 可以加上 但加上的key 没有被劫持
console.log(obj);
document.querySelector('#root').innerHTML = `name: ${obj.name}`
-
数据代理
上述数据劫持中 当新增 obj 一个key 时 发现新增的key 并没有被劫持,劫持的数据仍是之前数据,所以,在此我们可以使用数据代理优化数据劫持
语法: new Proxy(参数一: 代理那个对象)
const obj = {
name:'QF111',
age:18
}
const res = new Proxy(obj,{
get(target,key){
return target[key]; // target 是当前代理对象,在当前代码中 也就时obj 而key 就是obj 中的全部数据
},
set(target,key,val){
target[key] = val;
console.log('修改成功');
return true; // 代表修改成功返回true
}
})
console.log(res);
obj.abc = 999; // 当增加obj的key值时 新增key会被劫持
console.log(obj); // 打印对象 查看新增key
console.log(res); // 打印对象 查看新增key 是否被劫持
res.def = 666; // 新增到已经完成劫持的对象中
console.log(res);
- 封装数据劫持
// <input type="text">
// <div class="box"></div>
const box = document.querySelector('.box');
const obj = {
name:'QF100',
age:18
}
function observer(origin, callback){
const target = {}; // target 空对象 用于存储劫持后的数据
for(let key in origin){ // 通过对象遍历方法 拿到 origin 中的每一项 origin => 形参 obj传入对象
Object.defineProperty(target,key,{
get(){
return origin[key]; // 返回每一项
},
set(val){
origin[key] = val; // 修改每一项
callback(target); // 调用渲染函数 target
}
})
}
callback(target);
return target; //返回劫持后的数据
}
function fun(res){
box.innerHTML = `名字:${res.name}; 年龄:${res.age}`
}
const app = observer(obj,fun);
document.querySelector('input').oninput = function(e){
app.age = e.target.value;
}