高级编程,我们主要讲解,在JavaScript中,高级编程思想有哪些,在项目中有哪些实际用途?单例设计模式使用一个单独的实例来管理当前事物的相关特性,一般指的是属性和方法,类似于实现分组的特性,将一个实例的所有特性描述绑定到一个组中。让我们来看看一个简单的单例设计模式:letmodule1=(function(){functiontools(){}functionshare(){}return{name:'house',tools};})();module1.tools();这里我们可以暴露module1模块定义的方法供外部模块使用。还有一种基于闭包实现的单例模式叫做:高级单例设计模式,在vue/react出来之前,是团队协作最常用的模块化思想,也经常被用来划分这个模块。闭包形式的单例模式如下:letA=(function(){functionfn(){B.getXxx();}functionquery(){}return{query}})();letB=(function(){functionfn(){}functiongetXxx(){}A.query();return{getXxx}})();在上面的例子中,我们可以定义两个模块A和B,先在自己的模块中声明函数,然后暴露出去给对方的模块使用。这是最早的模块化思想,也是一种高级的单例设计模式。惰性函数我们先来看这样一个例子,封装了一个函数来获取元素的样式。获取样式,我们通常会想到两个API,window.getComputedStyle和element.currentStyle,但是它们的执行也是有条件的。例如:前者只兼容谷歌浏览器,后者兼容IE浏览器。functiongetCss(element,attr){if('getComputedStyle'inwindow){returnwindow.getComputedStyle(element)[attr];}returnelement.currentStyle[attr];}getCss(document.body,'margin');getCss(document.身体,'填充');getCss(document.body,'宽度');从这个例子我们可以看出,如果需要三次获取元素的样式,显然每次进入函数都需要判断方法是否兼容。这造成了不必要的浪费。最好的解决方案——惰性函数思维。通俗的说,lazy函数是在第一次渲染初始化的时候执行的。如果第二次执行还是一样的效果,我们就需要使用闭包的思想来解决这个问题。functiongetCss(element,attr){if('getComputedStyle'inwindow){getCss=function(element,attr){returnwindow.getComputedStyle(element)[attr];};}else{getCss=function(element,attr){returnelement.currentStyle[attr];};}//为了第一次获取值returngetCss(element,attr);}getCss(document.body,'margin');getCss(document.body,'padding');getCss(document.body,'宽度');在这个函数中,我们第一次执行getCss函数的时候,已经可以判断getComputedStyle是否兼容,所以不需要第二次判断,根据第一次返回的结果判断一次后,直接判断第二个和第三个函数执行使用哪个API。这样每执行一次函数就少一次判断,一定程度上提高了js的运行速度。那么我们来看看,这种惰性体现在哪里呢?过程:其实是第一次执行getCss函数,创建一个全局作用域下的私有执行上下文,在里面重新生成getCss函数,重新创建它的引用地址。赋值给全局函数getCss,导致全局getCss不能释放,形成闭包。以后每次执行的时候,都会执行里面的小函数getCss。Kelihua函数Kelihua函数的意思是一步步给函数传递参数,每次传递一些参数,返回一个比较具体的函数来接收剩下的参数,接收一些参数的函数可以嵌套在函数中的多层中间,直到返回最后一个结果。最基本的柯里化拆分//原函数functionadd(a,b,c){returna+b+c;}//柯里化函数functionaddCurrying(a){returnfunction(b){returnfunction(c){returna+b+c;}}}//调用原函数add(1,2,3);//6//调用柯里化函数addCurrying(1)(2)(3)//6被柯里化函数addCurrying的返回值为每次一个函数,并使用下一个参数作为形式参数。传入三个参数后,最后返回的函数内部进行求和运算,实际上是充分利用了闭包特性实现的。封装柯里化通式上面的柯里化函数不涉及高阶函数,也不具有通用性,不能转换形式参数为任意数或未知数的函数。让我们封装一个通用的柯里化转换函数,它可以将任何函数转换为柯里化。//add的参数不固定,看有多少个数加在一起??functionadd(a,b,c,d){returna+b+c+d}functioncurrying(fn,...args){//fn.length回调函数的参数之和//args.lengthcurrying函数后面的参数之和//如:add(a,b,c,d)currying(add,1,2,3,4)if(fn.length===args.length){returnfn(...args)}else{//继续一步步传递参数newArgsreturnfunctionanonymous(...newArgs){//将先传递的参数和后面一起传递的参数letallArgs=[...args,...newArgs]returncurrying(fn,...allArgs)}}}letfn1=currying(add,1,2)//3letfn2=fn1(3)//6letfn3=fn2(4)//10理化函数的思想利用闭包保存机制提前存储一些信息(预处理的思想)。我们用一个求和问题来描述:functioncurrying(...outerArgs){returnfunctionanonymous(...innerArgs){letargs=outerArgs.concat(innerArgs)returnargs.reduce((previousValue,currentValue)=>previousValue+currentValue)}}letfn1=currying(1,2)letfn2=fn1(3)letfn3=fn2(4)补充什么是预处理的思想:第一次执行fn1函数时,会创建一个函数执行上下文,和1,2将存储在上下文中的局部变量中。后面执行fn2和fn3时,需要先获取fn1返回结果,使用存储的1、2参数,这种机制形成了一个闭包。不懂reduce的朋友看看:情况一:当reduce没有第二个参数时letArray=[10,20,30,40,50];Array.reduce(previousValue,currentValue,currentIndex,arr){returnpreviousValue+currentValue;}第一次触发函数时,previousValue为第一项(10),currentValue为第二项(20)。第二次触发函数时,previousValue为回调函数的返回值(30),currentValue为第二项三项(30)当第X次触发函数时,previousValue为回调函数的返回值回调函数,currentValue为第X+1项currentIndex:对应currentValue的索引值arr:本身为常量数组情况2:reduce有第二个当参数为letArray=[10,20,30,40,50];Array.reduce((previousValue,currentValue,currentIndex,arr){returnpreviousValue+currentValue;},0)函数第一次触发时,previousValue为第二个参数(0),currentValue为第一项(10)第二次触发函数时,previousValue为回调函数(10)的返回值,currentValue为第二项(20)第X次触发函数时,previousValue为回调函数返回值,currentValue是它的第X个emcurrentIndex:对应currentValue的索引值arr:是常量数组本身compose函数compose组合函数:嵌套多层函数,调用扁平化的compose函数往往是基于reduce和合理化函数的思想来求解函数调用扁平化的问题:在项目中,我们经常会遇到这样的问题:constfn1=x=>x+10;constfn2=x=>x-10;constfn3=x=>x*10;constfn4=x=>x/10;console.log(fn3(fn1(fn2(fn1(4)))))在上面的例子中,可以看出嵌套的层次很深。这时候就需要调用函数扁平化的思路了。:如果是多层嵌套调用函数,调用一个函数,作为另一个函数的实参传递给下一个函数。函数(数组)=>[fn1,fn3,fn2,fn4]*@paramargs存放第一个函数需要传递的实际参数信息(数组)=>[5]*/functioncompose(...funcs){returnfunctionanonymous(...args){if(funcs.length===0)returnargs;if(funcs.length===1)returnfuncs[0](...args);//当funcs中有多个函数时returnfuncs.reduce((n,func)=>{//th一次执行://n:第一个函数执行的实参func:第一个函数//第二次执行://n的值:上一次func执行后的返回值,传递给下一个作为实际参数一个函数执行func:第二个函数returnArray.isArray(n)?func(...n):func(n);},args)}}letres=compose(fn1,fn3,fn2,fn4)(5)//执行过程:console.log(compose()(5));//=>5console.log(compose(fn1)(5));//=>5+10=15console.log(compose(fn1,fn3)(5));//=>fn1(5)=15fn3(15)=150console.log(compose(fn1,fn3,fn2)(5));//=>fn1(5)=15fn3(15)=150fn2(150)=140console.log(compose(fn1,fn3,fn2,fn4)(5));//=>fn1(5)=15fn3(15)=150fn2(150)=140fn4(140)=14compose函数的扁平化执行过程:compose()(5),如果compose()函数中没有传入函数参数,则直接返回以下参数5compose(fn1,fn3,fn2,fn4)(5),先执行fn1(5),再将fn1(5)的结果传递给fn3,如fn3(fn1(5))=>fn3(15),以此类推fn2(fn3(fn1(5)))=>fn4(150),然后每次函数调用的参数都是上一个函数返回的结果,所以很自然的我们就想到了Array.prototype.reduce方法设置reduce的第二个参数是为了统一n作为各个函数的返回结果(第一次n为5,func为fn1),正好需要执行fn1(5)函数,所以第二个参数可以看出回调函数的必要性,就是把每个回调函数的返回结果统一为数字函数编程和命令式编程回调函数:将一个函数作为值传递给另一个函数,在另一个函数中执行这个函数(这是实现函数式编程的重要知识)函数式编程:关注结果,不关心过程,把过程交给别人,体现函数封装的思想(提倡)命令式编程:关注过程,你需要自己实现流程函数式编程:把逻辑如何实现封装成一个API方法,我们以后只需要调用API方法就可以得到想要的结果letarr=[10,20,30,40,50];letres=arr.reduce((n,item)=>{returnn+item;});命令式编程:用代码实现主要逻辑,注意流程letres=0;for(leti=0;i
