一、柯里化函数
概念:将一个接受多个参数的函数(假如有三个函数 funA funB funC),更改为需要调用多次 每次只传入一个参数的函数,(funA里传入一个形参a funB里传入一个形参b funC里传入一个形参c 利用return返回函数)。
假设我们要计算的是a、b、c的和。
语法形式:
方法一:是直接在函数传入全部参数后,进行打印所有值之和
function funA(a){
return function funB(b){
return function funC(c){
console.log(a + b + c);
}
}
}
方法二:利用闭包的方法将内部的和在外部调用打印
function funA(a){
return function funB(b){
return function funC(c){
return a+b+c;
}
}
}
let res = funA(10)(10)(10);
console.log(res);
利用了闭包 延长了外部函数的参数使用时间。
return 的可以是一个匿名函数也可以是一个普通函数 实质上没有什么区别 一般都是写匿名函数 优化代码 。
实际项目中会结合模块化开发的语法形式 执行柯里化函数。
/*
求和
*/
// 普通函数求和
function fun1(a, b) {
console.log(a + b);
}
// 如果有相同的参数 会多次调用 代码会比较冗余
fun1(10, 20);
fun1(10, 30);
fun1(10, 40);
// 利用柯里化解决相同的参数多次调用的问题
function fun2(a) {
return function(b){
return a + b;
}
}
/*
第一种调用方法:分两次两用
*/
// 先将全局函数赋值a为10
let res1 = fun2(10);
// 返回的是里面的函数体
console.log(res1);
// 再次调用全局函数 返回局部函数的值之和
res1(20);
res1(30);
res1(40);
/*
第二种调用方法:直接一次性调用
*/
console.log(fun2(20)(30));
console.log(fun2(20)(46));
/*
利用柯里化求正则
*/
// 普通函数求正则
function fun3(reg , str){
console.log(reg.test(str));
}
fun3(/^\w{5,10}$/ , 'abc');
fun3(/^\w{5,10}$/ , 'abcdef');
fun3(/^\d{1,5}$/ , 'abcdef');
fun3(/^\d{1,5}$/ , '123');
// 利用柯里化函数求正则
function fun4(reg){
return function(str){
return reg.test(str);
}
}
console.log(fun4(/^\w{5,10}$/)('123'));
console.log(fun4(/^\w{5,10}$/)('123456'));
console.log(fun4(/^\d{7,10}$/)('123456'));
二、封装柯里化函数
外层函数负责收集参数
内层函数负责 在 参数收集完毕的时候 执行功能 www.baidu.com:8080/index.html www.baidu.com:8080/a.html
协议: https http
域名: www.baidu.com www.taobao.com 127.0.0.1
端口号: 0~65535 80 443 7777
地址: index.html a.html /a/b/c.html
function fn(a, b, c, d) {
return a + '://' + b + ':' + c + d
}
// let res = fn('http', '127.0.0.1', '80', '/a.html')
// console.log(res)
// let res1 = fn('http', '127.0.0.1', '443', '/b.html')
// console.log(res1)
// let res2 = fn('http', '127.0.0.1', '8080', '/c.html')
// console.log(res2)
// 外层函数 负责接接收参数
function currying(callback, ...arg) {
// ...arg 将后续所有实参, 以数组的形式存放在 arg 形参内, 如果没有传递的话, 是一个空数组
// console.log(callback) // 功能函数, 首次调用必传
// console.log(arg) // 基本参数, 首次调用可传可不传
// console.log(callback.length) // 函数名.length 能获取到函数的形参数量
// 内层函数负责 当前是否接受够参数了
return function (...iArg) {
iArg = [...arg, ...iArg] // 开始执行共能前, 先将两次函数调用接受的参数合并为一个数组, 后续用于计算传递的参数是否足够
if (iArg.length === callback.length) { // 如果传递的参数数量, 刚好是功能函数需要的数量, 代表此时参数足够, 直接执行函数即可
return callback(...iArg) // 执行功能函数, 此时会得到一个拼接好的字符串, 我们将这个字符串返回出去, 就能得到功能函数的执行结果
} else { // else 分支执行时表明此时函数参数仍未接受足够, 此时需要继续接受参数, 根据函数功能, 我们应该调用 currying并将之前接收的功能函数与之前传递所有的参数全部传递进去
return currying(callback, ...iArg) // callback => 功能函数 ...iArg => 之前传递进来的所有的参数
// 这里是返回了一个 currying的调用结果, 所以相当于是返回了内层函数, 并且外层函数是接受了对应的功能函数与实际参数
}
}
}
// 无注释
function currying(callback, ...arg) {
return function (...iArg) {
iArg = [...arg, ...iArg]
if (iArg.length === callback.length) {
return callback(...iArg)
} else {
return currying(callback, ...iArg)
}
}
}
let res = currying(fn, 'https') // 此处传递了功能函数与一个基本参数, 按照函数规则, 后续起码应该再传递够三个参数, 才能正常执行功能
let res1 = res('127.0.0.1', '80') // 此时传递了两个参数, 加上首次调用的一个参数, 现在接收到了 3个字符串, 所以应该在传递一个参数, 才能够正常执行
let str1 = res1('/a.html') // 此时传递了 一个 参数, 加上之前的三个字符串参数, 所以现在正好满足 4个参数, 所以现在就可能正常执行功能函数
console.log(str1) // https://127.0.0.1:80/a.html
let res2 = currying(fn) // 此处传递了 功能函数, 没有传递基本参数, 按照函数规则, 后续起码应该传递四个参数, 才能正常执行功能
let str2 = res2('https', 'www.baidu.com', '8080', '/a.html') // 此时传递了四个参数, 因为首次没有传递字符串, 这里就是有4个参数, 所以正常执行函数
console.log(str2) // https://www.baidu.com:8080/a.html
let res3 = currying(fn, 'https', 'www.baidu.com', '8080', '/a.html') // 此处传递了 功能函数, 与 四个后续的参数, 根据函数规则, 后续不需要传递参数即可
let str3 = res3() // 因为之前传递的参数已足够, 所以此处可以不调用
console.log(str3) // https://www.baidu.com:8080/a.html
// let str2 = res('127.0.0.1', '443', '/a.html')
// let str3 = res('www.taobao.com', '8080', '/c.html')
// let res2 = currying(fn, 'https', '127.0.0.1')
// let str4 = res2('8080', '/q.html')
// let res3 = currying(fn)
// let str5 = res3('http', '127.0.0.1', '8080', '/c.html')
三、函数的防抖和节流
防抖和节流
防抖:在一定时间内 快速触发同一事件
每次重新触发 都取消前一次事件 以后一次事件为主
防抖使用的是清除定时器来实现
节流:
在一定时间内 快速触发同一件事件
在规定时间内 只能触发一次 下一次必须等到规定时间结束以后才能执行
使用的是开关变量flag和自执行函数来执行的
// 普通操作
// 每一次输入都会很快的触发一件事件发生
// const oIpt = document.querySelector('.ipt');
// oIpt.oninput = function(){
// console.log(`输入的参数是${oIpt.value}`);
// }
// 节流操作
// 使用的是开关变量或者自执行函数实现
const oIpt = document.querySelector('.ipt');
// // 定义开关变量 来控制函数防抖的产生
// // 一开始输入事件的时候 允许输出 开启开关变量
// let flag = true;
// oIpt.oninput = function (e) {
// // console.log(this); // input
// // 如果开关变量为false 就返回不执行函数
// if (flag == false) return;
// // 再重新将开关变量赋值为false
// // 也就是当点击输入框的时候输入第一次过后 就不可以再输出了 尽管输入很多次
// flag = false;
// console.log(`输入的参数是${e.target.value}`);
// // 设置定时器 控制输入框输出 当输入框输出第一次过后
// // 中途在输入的时候 倒计时3s过后再将开关变量赋值为true 在进行打印
// // 有效防止节流
// setTimeout(() => {
// flag = true;
// }, 3000)
// }
// // 使用自执行函数
// const oIpt = document.querySelector('.ipt');
// oIpt.oninput = (function (flag) {
// return function(e){
// // 如果开关变量为false 就返回不执行函数
// if (flag == false) return;
// // 再重新将开关变量赋值为false
// // 也就是当点击输入框的时候输入第一次过后 就不可以再输出了 尽管输入很多次
// flag = false;
// console.log(`输入的参数是${e.target.value}`);
// // 设置定时器 控制输入框输出 当输入框输出第一次过后
// // 中途在输入的时候 倒计时3s过后再将开关变量赋值为true 在进行打印
// // 有效防止节流
// setTimeout(() => {
// flag = true;
// }, 3000)
// }
// })(true);
// 自执行函数
// 一定要写分号 两个或多个自执行函数过后
fun1();
function fun1() {
console.log(888);
}
(function () {
console.log(999);
})();
(function () {
console.log(123523);
})();
// 防抖操作
// 使用的是自执行函数
oIpt.oninput = (function (timer) {
return function (e) {
// 先清除定时器
clearInterval(timer);
timer = setTimeout(function () {
console.log(`${e.target.value}内容`);
}, 3000)
}
})(0)
</script>
四、数据劫持
1、数据劫持1
const box = document.querySelector('.box')
// 数据劫持
// const obj = {
// name: 'QF666',
// age: 18
// }
// box.innerHTML = `名字: ${obj.name}; 年龄: ${obj.age}`
// obj.age = 99
// box.innerHTML = `名字: ${obj.name}; 年龄: ${obj.age}`
// obj.name = 'QF999'
// box.innerHTML = `名字: ${obj.name}; 年龄: ${obj.age}`
// console.log(obj)
/**
* 数据驱动视图
*
* 1. 数据劫持
* 将原始数据 劫持出一份一摸一样, 听起来有点像 浅拷贝
*
* 劫持出来的数据, 默认是不可以修改的
*
* 语法: Object.defineProperty(那个对象, '对象的key', {配置项})
* 配置项:
* 1. value 访问这个值 之后, 得到结果
* 2. writable 决定当前这个属性能否被修改, 默认是 false
* 3. enumerable 决定当前这个属性能否被枚举, 决定当前这个属性能否被遍历到
* 4. getter 是一个函数, 是一个获取器, 当访问这个属性时, 会执行这个函数
* + getter 不能和 value writable 一起使用
* 5. setter 是一个函数, 是一个设置器, 当设置这个属性是, 会执行这个函数
*/
const obj = {}
obj.name = 'QF666'
console.log(obj)
// let str = 'age'
// Object.defineProperty(obj, str, {配置项})
Object.defineProperty(obj, 'age', {
// value: 'QF999',
// writable: true,
enumerable: true,
get() {
// console.log('你当前访问了 这个 age 属性, 触发了 get 函数')
return 'qwer'
},
set(val) {
console.log('你当前想要修改这个 age 属性, 修改的值 是: ', val)
}
})
// console.log(obj)
// for (let k in obj) {
// console.log(k)
// }
console.log(obj.age)
obj.age = 100
// obj.age = 999
// console.log(obj.age)
</script>
2、数据劫持2
<body>
<div class="box"></div>
<script>
const oBox = document.querySelector('.box');
/*
劫持一部分obj中的数据到res中去
*/
// 定义数据劫持
const obj = {
name:'qqqq',
age:18,
}
console.log('原始对象:' , obj);
const res = {};
Object.defineProperty(res , 'name' , {
get(){
return obj.name;
},
set(val){
obj.name = val;
oBox.innerHTML = `劫持后的数据为:${res.age} , ${res.name}`;
}
})
Object.defineProperty(res , 'age' , {
get(){
return obj.age;
},
set(val){
// 将年龄重新赋值 将val只赋值给obj中的age
obj.age = val;
oBox.innerHTML = `劫持后的数据为:${res.age} , ${res.name}`;
}
})
console.log('数据劫持后的:' , res);
console.log(res.name); // qqqq
// 如果只劫持一个
// console.log(res.age); // undefined
// 如果劫持两个
console.log(res.age); // 18
// 修改其中的值
res.name = 'wwwww';
res.age = 222;
console.log(res);
console.log(res.name);
console.log(res.age);
// 将数据写入到页面中去
oBox.innerHTML = `劫持后的数据为:${res.age} , ${res.name}`;
// 写入到页面过后 再进行修改值
// res里面的数据修改了 但是页面的数据没有修改 所以要在循环的时候 也就是set里面进行重新赋值写入
res.name = 'hack';
res.age = '100';
console.log(res);
</script>
3、封装数据劫持
<body>
<input type="text" class="ipt">
<div class="box"></div>
<script>
/*
数据劫持
自己将劫持的数据封装成函数 这个封装的函数在框架中是已经封装好了的
*/
const oIpt = document.querySelector('.ipt');
const oBox = document.querySelector('.box');
const obj = {
name: 'rose',
age: 18,
}
console.log(obj);
// 这里回调函数是调用fn函数
function hiJack(origin, callback) {
const res = {};
for (let key in origin) {
Object.defineProperty(res, key, {
get() {
// 返回原始对象中的每一个值
return origin[key];
},
set(val) {
// 设置里面的属性 将val值赋值给原始数组
origin[key] = val;
callback(res);
},
})
}
callback(res);
return res;
}
// 将写入页面的代码单独封装成一个 以便后面每次都要修改
function fn(res) {
oBox.innerHTML = `劫持后的数据为:${res.name} , ${res.age}`;
}
// 打印出定义的res
const app = hiJack(obj , fn);
oIpt.oninput = function(e){
app.age = e.target.value;
}
</script>