当前位置: 首页 > news >正文

c 网站开发案例详解百度云/爱链工具

c 网站开发案例详解百度云,爱链工具,网站建设的一些专业术语,网站域名自己做高阶函数英文叫 Higher-order function,它的定义很简单,就是至少满足下列一个条件的函数:接受一个或多个函数作为输入输出一个函数一、高阶函数实现AOPAOP(面向切面编程)的主要作用就是把一些和核心业务逻辑模块无关的功能抽取出来&#xff0…

高阶函数英文叫 Higher-order function,它的定义很简单,就是至少满足下列一个条件的函数:

  • 接受一个或多个函数作为输入
  • 输出一个函数

一、高阶函数实现AOP

AOP(面向切面编程)的主要作用就是把一些和核心业务逻辑模块无关的功能抽取出来,然后再通过“动态织入”的方式掺到业务模块种。这些功能一般包括日志统计,安全控制,异常处理等。AOP是Java Spring架构的核心。下面我们就来探索一下再Javascript种如何实现AOP
在JavaScript种实现AOP,都是指把一个函数“动态织入”到另外一个函数中,具体实现的技术有很多,我们使用Function.prototype来做到这一点。代码如下

/**
* 织入执行前函数
* @param {*} fn 
*/
Function.prototype.aopBefore = function(fn){console.log(this)// 第一步:保存原函数的引用const _this = this// 第四步:返回包括原函数和新函数的“代理”函数return function() {// 第二步:执行新函数,修正thisfn.apply(this, arguments)// 第三步 执行原函数return _this.apply(this, arguments)}
}
/**
* 织入执行后函数
* @param {*} fn 
*/
Function.prototype.aopAfter = function (fn) {const _this = thisreturn function () {let current = _this.apply(this,arguments)// 先保存原函数fn.apply(this, arguments) // 先执行新函数return current}
}
/**
* 使用函数
*/
let aopFunc = function() {console.log('aop')
}
// 注册切面
aopFunc = aopFunc.aopBefore(() => {console.log('aop before')
}).aopAfter(() => {console.log('aop after')
})
// 真正调用
aopFunc()

二、currying(柯里化)

curring又称部分求值。一个curring的函数首先会接受一些参数,接受了这些参数之后,该函数并不会立即求值,而是继续返回另外一个函数,刚才传入的参数在函数形成的闭包中被保存起来。待到函数中被真正的需要求值的时候,之前传入的所有参数被一次性用于求值。

例子: 我们需要一个函数来计算一年12个月的消费,在每个月月末的时候我们都要计算消费了多少钱。正常代码如下

// 未柯里化的函数计算开销
let totalCost = 0
const cost = function(amount, mounth = '') {console.log(`第${mounth}月的花销是${amount}`)totalCost += amountconsole.log(`当前总共消费:${totalCost}`)
}
cost(1000, 1) // 第1个月的花销
cost(2000, 2) // 第2个月的花销
// ...
cost(3000, 12) // 第12个月的花销

如果我们要计算一年的总消费,没必要计算12次。只需要在年底执行一次计算就行,接下来我们对这个函数进行部分柯里化的函数帮助我们理解

// 部分柯里化完的函数
const curringPartCost = (function() {// 参数列表let args = []return function (){/*** 区分计算求值的情况* 有参数的情况下进行暂存* 无参数的情况下进行计算*/if (arguments.length === 0) {let totalCost = 0args.forEach(item => {totalCost += item[0]})console.log(`共消费:${totalCost}`)return totalCost} else {// argumens并不是数组,是一个类数组对象let currentArgs = Array.from(arguments)args.push(currentArgs)console.log(`暂存${arguments[1] ? arguments[1] : '' }月,金额${arguments[0]}`)}}
})()
curringPartCost(1000,1)
curringPartCost(100,2)
curringPartCost()

接下来我们编写一个通用的curring, 以及一个即将被curring的函数。代码如下

// 通用curring函数
const curring = function(fn) {let args = []return function () {if (arguments.length === 0) {console.log('curring完毕进行计算总值')return fn.apply(this, args)} else {let currentArgs = Array.from(arguments)[0]console.log(`暂存${arguments[1] ? arguments[1] : '' }月,金额${arguments[0]}`)args.push(currentArgs)// 返回正被执行的 Function 对象,也就是所指定的 Function 对象的正文,这有利于匿名函数的//递归或者保证函数的封装性return arguments.callee}}
}
// 求值函数
let costCurring = (function() {let totalCost = 0return function () {for (let i = 0; i < arguments.length; i++) {totalCost += arguments[i]}console.log(`共消费:${totalCost}`)return totalCost}
})()
// 执行curring化
costCurring = curring(costCurring)
costCurring(2000, 1)
costCurring(2000, 2)
costCurring(9000, 12)
costCurring()

三、函数节流

JavaScript中的大多数函数都是用户主动触发的,一般情况下是没有性能问题,但是在一些特殊的情况下不是由用户直接控制。大量的调用容易引起性能问题。毕竟DOM操作的代价是非常昂贵的。下面将列举一些这样的场景:

  • window.resize事件
  • mouse, input等事件
  • 上传进度
  • ...

下面通过高阶函数的方式我们来实现函数节流

/**
* 节流函数
* @param {*} fn 
* @param {*} interval 
*/
const throttle = function (fn, interval = 500) {let timer = null, // 计时器 isFirst = true // 是否是第一次调用return function () {let args = arguments, _me = this// 首次调用直接放行if (isFirst) {fn.apply(_me, args)return isFirst = false}// 存在计时器就拦截if (timer) {return false}// 设置timertimer = setTimeout(function (){console.log(timer)window.clearTimeout(timer)timer = nullfn.apply(_me, args)}, interval)}
}
// 使用节流
window.onresize = throttle(function() {console.log('throttle')
},600)

四、分时函数

节流函数为我们提供了一种限制函数被频繁调用的解决方案。下面我们将遇到另外一个问题,某些函数是用户主动调用的,但是由于一些客观的原因,这些操作会严重的影响页面性能,此时我们需要采用另外的方式去解决。

如果我们需要在短时间内在页面中插入大量的DOM节点,那显然会让浏览器吃不消。可能会引起浏览器的假死,所以我们需要进行分时函数,分批插入。

/**
* 分时函数
* @param {*创建节点需要的数据} list 
* @param {*创建节点逻辑函数} fn 
* @param {*每一批节点的数量} count 
*/
const timeChunk = function(list, fn, count = 1){let insertList = [], // 需要临时插入的数据timer = null // 计时器const start = function(){// 对执行函数逐个进行调用for (let i = 0; i < Math.min(count, list.length); i++) {insertList = list.shift()fn(insertList)}}return function(){timer = setInterval(() => {if (list.length === 0) {return window.clearInterval(timer)}start()},200)}
}
// 分时函数测试
const arr = []
for (let i = 0; i < 94; i++) {arr.push(i)
}
const renderList = timeChunk(arr, function(data){let div =document.createElement('div')div.innerHTML = data + 1document.body.appendChild(div)
}, 20)
renderList()

五、惰性加载函数

因为浏览器的差异性,我们要常常做各种各样的兼容,举一个非常简单常用的例子:在各个浏览器中都能够通用的事件绑定函数。

常见的写法是这样的:

// 常用的事件兼容
const addEvent = function(el, type, handler) {if (window.addEventListener) {return el.addEventListener(type, handler, false)}// for IEif (window.attachEvent) {return el.attachEvent(`on${type}`, handler)}
}

这个函数存在一个缺点,它每次执行的时候都会去执行if条件分支。虽然开销不大,但是这明显是多余的,下面我们优化一下, 提前一下嗅探的过程:

const addEventOptimization = (function() {if (window.addEventListener) {return (el, type, handler) => {el.addEventListener(type, handler, false)}}// for IEif (window.attachEvent) {return (el, type, handler) => {el.attachEvent(`on${type}`, handler)}}
})()

这样我们就可以在代码加载之前进行一次嗅探,然后返回一个函数。但是如果我们把它放在公共库中不去使用,这就有点多余了。下面我们使用惰性函数去解决这个问题:

// 惰性加载函数
let addEventLazy = function(el, type, handler) {if (window.addEventListener) {// 一旦进入分支,便在函数内部修改函数的实现addEventLazy = function(el, type, handler) {el.addEventListener(type, handler, false)}} else if (window.attachEvent) {addEventLazy = function(el, type, handler) {el.attachEvent(`on${type}`, handler)}}addEventLazy(el, type, handler)
}
addEventLazy(document.getElementById('eventLazy'), 'click', function() {console.log('lazy ')
})

一旦进入分支,便在函数内部修改函数的实现,重写之后函数就是我们期望的函数,在下一次进入函数的时候就不再存在条件分支语句。

六、数组高阶函数reduce

6.1 reduce数组去重:遍历数组每一项,若值为数组则递归遍历,否则concat。

function flatten(arr) {  return arr.reduce((result, item)=> {return result.concat(Array.isArray(item) ? flatten(item) : item);}, []);
}

6.2 数值运算

假设有如下数组:

const arr = [1,2,3,4,5];

求和:

const sum = arr.reduce((pre, cur) => pre + cur);
sum // 15

求积:

const prod = arr.reduce((pre, cur) => pre * cur);
prod // 120

求平均数:

const avrg = arr.reduce((pre, cur, i, a) => ( // 这里使用大括号{的话,不能省略return关键字i < a.length - 1 ? pre + cur : (pre + cur) / a.length
));
avrg // 3 

如果这里的arr不是一个数值数组而是一个对象数组,每个对象包含一个值为数值类型的属性呢?我们只需要在回调函数中访问对象的对应属性并相加就可以了。需要注意的是初始值需要定义为与回调函数中使用pre参数时的默认类型相匹配,即数值类型的0, 否则可能得到意料之外的结果。

const objArr = [{name: "A",score: 80,
}, {name: "B",score: 75,
}, {name: "C",score: 90,
}];const scoreSum = objArr.reduce((pre, cur) => pre + cur.score, 0);
scoreSum // 245objArr.reduce((pre, cur) => pre + cur.score); // "[object Object]7590"

也可以先对对象数组执行map函数得到数值数组,然后执行reduce求和:

const scoreSum1 = objArr.map(o => o.score).reduce((pre, cur) => pre + cur); // 245

6.3 公倍数和公约数

首先明确这两个概念:对于a, b两个非零整数,a和b的最小公倍数(Least Common Multiple)是指可以被a和b整除的最小正整数;a和b的最大公约数(Greatest Common Divisor)是指能同时整除a和b的最大正整数。

一般求多个数之间的最大公约数,可以先求两个数之间的最大公约数,然后用此结果继续与下一个数求最大公约数,直到遍历所有数值;求多个数之间的最小公倍数也是相似的过程。但求两个数之间的最小公倍数,需要先确定最大公约数后,用它们的乘积除以它得到结果。

333b318bc6bb96b4e8176778de252866.png

求两个数a, b的最大公约数和最小公倍数可以分别如下简单实现:

// 求两个数的最大公约数(欧几里得算法)
function maxDenom(a, b) {return b ? maxDenom(b, a % b) : a;
}// 求两个数的最小公倍数
function minMulti(a, b) {return a * b / maxDenom(a, b);
}

求数组中多个数值的最大公约数和最小公倍数:

const data = [12, 15, 9, 6]const GCD = data.reduce(maxDenom)
CGD // 3const LCM = data.reduce(minMulti)
LCM // 180

6.4 添加千位分隔符或四位空格

思路: 一串数字要从末尾开始向前数,每3个数字就加一个逗号,第一个数字前面一定不加逗号(有效的整数数值)。

function addSeparator(num) {const arr = [...String(num)]; // 数字转为数组const len = arr.length;return arr.reduceRight((tail, cur, i) => i === 0 || (len -  i) % 3 !== 0 ? `${cur + tail}` : `,${cur + tail}`, "");
}addSeparator(12345678901) // "12,345,678,901"

6.5 “大数”相加

这里的“大数”是我自己的叫法,是指数据本身位数很多,计算机的数值范围无法表示所以表示为字符串的一种“数值”。

为了保留完整结果,每一位的计算结果依然要作为字符串整合在一起,但是当前运算结果是否进位也需要传给下一个迭代,所以可以借助解构赋值,传递两个信息:[digit, tail], digit为1或0,表示后面的值相加后是否进位;tail表示已确定的各个位的计算结果。为了计算方便可以先把两个字符串倒序排列。

const s1 = '712569312664357328695151392';
const s2 = '8100824045303269669937';// 将字符串倒序并输出数值数组
function strToArrRvs(str) {return str.split("").map(x => +x).reverse();
}function addStr(a, b) {const [h, l] = (a.length > b.length ? [a, b] : [b, a]).map(strToArrRvs);// 用相对位数更多的字符串调用reducereturn h.reduce(([digit, tail], cur, idx, arr) => {const sum = cur + digit + (l[idx] || 0); // 如果遍历完成 直接输出结果, 否则输出数组用于下一次迭代return idx === arr.length - 1? sum + tail: [+(sum >= 10), sum % 10 + tail];}, [0, ""]);
}addStr('712569312664357328695151392','8100824045303269669937');
// "712577413488402631964821329"

6.6 与位运算结合查找特征项

这里说的位运算包括按位与、按位或、按位异或这种二元运算符。在有一组数的情况下,因为它们满足“交换律”和“结合律”,使用reduce有时可以很方便地求解它们按位运算的结果,根据它们本身所具有的特性可能很容易地找到某些特征元素。

按位异或(对应位相异则返回1,否则返回0)a ^ b运算:

  • 一个数与它自己按位异或将会得到0,因为它们每个对应位都是相同的,都会返回0,所有位都是0最后也会得到0;
  • 一个数与0按位异或,则会得到这个数本身,因为对应位是0的还是0,对应位是1的还是1,相当于把这个数复制了一个。

案例:

一个整数数组中,只有一个数出现了奇数次,其他数都出现了偶数次,找到这个出现了奇数次的数。(类似变形题目如 有一个数出现了1次,其他数都出现了2次)

根据交换律和结合律,x ^ y ^ x ^ y ^ n等于(x ^ x) ^ (y ^ y) ^ n; 对所有数依次进行按位异或运算,所有出现两次的数运算结果最终还是0,而那个只出现一次的数和0按位异或得到它本身:

function findOnlyOne(arr) {return arr.reduce((pre, cur) => pre ^ cur);
}const array = [2,2,3,4,5,6,7,6,6,6,3,4,5];
findOnlyOne(array) // 7

如果换成有一个数出现了5次,其他数都出现了3次呢?那就换另一种思路,如果把每个数都看作是二进制数字,它们最多不超过32位;如果能确定出现了3次的那个数在每个对应位上是0还是1,那也就确定了这个数。所以我们可以从低位到高位依次判断。

这里根据“按位与”运算的特征,两数在某位上都为1,该位返回1,否则返回0. 我们先确定一个仅在某位是1,其他位均为0的数作为标识数,然后每个数与它按位与之后再相加;假如出现了5次的数在这一位上是0,那结果一定是3的倍数(或0);否则对3取余一定为2(即5-3);

// 得到从0到31组成的数组
const iStore = (Array.from(new Array(32), (x, i) => i));
// 求解给定某特定标志数时的结果
function checkBit(flagNum, srcArr) {const bitSum = srcArr.reduce((sum, cur) => sum + (cur & flagNum), 0);return bitSum % 3 === 0 ? 0 : 1;
}// 对每一位执行求解
function checkArr(array) {const binaryStr = iStore.reduce((str, i) => checkBit(1 << i, array) + str, "");return parseInt(binaryStr, 2);
}checkArr([12,12,12,5,5,5,32,32,32,9,9,9,4,4,4,4,4]);
// 4

6.7 构建数组或对象

例如以下对象,我们希望改造成{name: value}的形式的对象

const info = [{name: "A",value: 4,}, {name: "B",value: 7,}, {name: "C",value: 10,}
];// 期望结果
{A: 4,B: 7,C: 10,
}

我们使用reduce,只需要一行就可以完成, 目的也会更明确:

const result = info.reduce((res, cur) => ({...res, [cur.name]: cur.value}), {});
result // {A: 4, B: 7, C: 10}

构建一个新数组也是同样的道理,把空数组作为初始值,然后通过迭代向数组中添加元素,最终得到的就是想要的结果数组。

// result为上面得到的{A: 4, B: 7, C: 10}
const arrResult = Object.keys(result).reduce((accu, cur) => [...accu, {key: cur,value: result[cur]}], []);
arrResult // [{key: "A", value: 4}, {key: "B", value: 7}, {key: "C", value: 10}]

6.8 数组元素去重

const sample = ["a", "b", "c", "a", "b", "d", "c"];
sample.reduce((acc, cur) => {if (!acc.includes(cur)) {acc.push(cur);}return acc;
}, []);

参考:JavaScript中高阶函数的魅力 - 掘金

《Javascript设计模式》

http://www.jmfq.cn/news/4873537.html

相关文章:

  • 手机网站 php/阿里云云服务平台
  • 勒索做钓鱼网站的人/百度软文推广怎么做
  • 做视频网站需要哪些证/百度知道官网入口
  • 兰州学校网站建设/百度seo技术
  • 做网站宝鸡/最新新闻国内大事件
  • 做变态手术视频网站/聊城seo优化
  • 成都网站建设sntuu/搜索引擎技术基础
  • 女装网站建设规划书怎么写/长沙网站推广有哪些啊
  • 网站建设选择什么系统好/湖南好搜公司seo
  • 台州建设局网站信息价/亚马逊跨境电商开店流程及费用
  • 在建工程/广州seo优化排名推广
  • 那个网站教宝妈做辅食/seo外包 杭州
  • 品牌推广网站怎么做/网站空间租用
  • 深圳网站建设antnw/seo网站系统
  • 生物医药基地网站建设/品牌推广软文
  • 华为免费企业网站建设/买卖交易网
  • 基于搜索引擎的网站推广方式/我要下载百度
  • 网站设计概述/爱用建站官网
  • 免费做网站建设/广州竞价托管公司
  • 怎样在网站上做营业执照公示/教育培训网站模板
  • 合肥专业做网站的/聊城seo
  • 营销型网站建设标准/百度一下电脑版网页
  • 网站开发公司杭州网站建设/网站推广常用方法
  • 网站开发实习计划模板/销售网站有哪些
  • 杭州学校网站建设/深圳百度seo优化
  • 网站推广连接怎么做的/潮州网站建设
  • 做试用网站的原理/互联网营销师证
  • 哈尔滨有多少家网站建设公司/百度搜索一下就知道
  • 仲恺企业网站建设/友情链接交易网站源码
  • 个人域名 公司网站/百度下载应用