温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

怎么掌握JS高阶编程技巧

发布时间:2021-10-28 17:56:48 来源:亿速云 阅读:86 作者:iii 栏目:web开发

这篇文章主要讲解了“怎么掌握JS高阶编程技巧”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“怎么掌握JS高阶编程技巧”吧!

单例设计模式

用单独的实例来管理当前事物的相关特征,泛指属性和方法,类似于实现分组的特点,把一个实例的所有特征描述绑定在一个分组里。

来看一下简单的单例设计模式:

let module1 = (function () {   function tools() {}   function share() {}    return {     name: 'house',     tools   }; })(); module1.tools();

这里我们可以把module1模块定义的方法暴露出来,给外部模块用。

还有一种基于闭包实现的单例模式称为:高级单例设计模式,在vue/react出来之前,是团队协作最常用的模块化思想,常用来以此模块划分。

闭包形式的单例模式如下:

let A = (function () {   function fn() {     B.getXxx();   }   function query() {}    return {     query   } })();  let B = (function () {   function fn() {}   function getXxx() {}      A.query();    return {     getXxx   } })();

在上面这个例子中,我们可以分别定义A、B两个模块,先在自己模块内声明函数,再暴露出去,给对方的模块使用,这便是最早的模块化思想,也是高级单例设计模式。

惰性函数

我们先来看这样一个例子,封装一个获取元素样式的函数。

获取样式,我们通常想到window.getComputedStyle和element.currentStyle这两个API,但他们的执行也是有条件的。

比如:前者只有Google浏览器兼容,后者IE浏览器兼容。

function getCss(element, attr) {   if ('getComputedStyle' in window) {     return window.getComputedStyle(element)[attr];   }   return element.currentStyle[attr]; }   getCss(document.body, 'margin'); getCss(document.body, 'padding'); getCss(document.body, 'width');

从这个例子中,我们可以看出来,如果需要3次获取元素的样式,明显每一次进入函数都需要判断该方法兼容与否,这就造成了不必要的浪费。最好的解决方案——惰性函数思想。

通俗的来说,惰性函数初始化第一次渲染的时候执行,如果第二遍执行还是一样的效果,我们需要用闭包的思想解决这一问题。

function getCss(element, attr) {   if ('getComputedStyle' in window) {     getCss = function (element, attr) {       return window.getComputedStyle(element)[attr];     };   } else {     getCss = function (element, attr) {       return element.currentStyle[attr];     };   }   // 为了第一次也能拿到值   return getCss(element, attr); } getCss(document.body, 'margin'); getCss(document.body, 'padding'); getCss(document.body, 'width');

在这个函数中,当我们第一次执行getCss函数的时候,就已经可以确定getComputedStyle兼容与否了,所以在第二次就没必要再判断了,根据第一次判断完返回的结果,直接确定第二次,第三次的函数执行到底用哪个API,这样以来每次函数执行,都将少了一层判断,一定程度上提高了js的运行速度。

那么我们来看,这个惰性体现在哪里呢?

过程:其实,是第一次getCss函数执行完创建了全局作用域下的私有执行上下文,而在其里面重新生成了getCss函数,将其引用地址重新赋值给全局函数getCss,导致全局下的getCss不能被释放,形成了一个闭包。在以后每次执行时,都执行里面的小函数getCss。

柯理化函数

柯理化函数含义是给函数分步传递参数,每次传递部分参数,并返回一个更具体的函数接收剩下的参数,这中间可嵌套多层这样的接收部分参数的函数,直至返回最后结果。

最基本的柯里化拆分

// 原函数 function add(a, b, c) {   return a + b + c; }  // 柯里化函数 function addCurrying(a) {   return function (b) {     return function (c) {       return a + b + c;     }   } }  // 调用原函数 add(1, 2, 3); // 6  // 调用柯里化函数 addCurrying(1)(2)(3) // 6

被柯里化的函数 addCurrying  每次的返回值都为一个函数,并使用下一个参数作为形参,直到三个参数都被传入后,返回的最后一个函数内部执行求和操作,其实是充分的利用了闭包的特性来实现的。

封装柯理化通用式

上面的柯里化函数没涉及到高阶函数,也不具备通用性,无法转换形参个数任意或未知的函数。

我们来封装一个通用的柯里化转换函数,可以将任意函数转换成柯里化。

// add的参数不固定,看有几个数字累计相加 function add (a,b,c,d) {   return a+b+c+d }  function currying (fn, ...args) {   // fn.length 回调函数的参数的总和   // args.length currying函数 后面的参数总和    // 如:add (a,b,c,d)  currying(add,1,2,3,4)   if (fn.length === args.length) {       return fn(...args)   } else {     // 继续分步传递参数 newArgs 新一次传递的参数     return function anonymous(...newArgs) {       // 将先传递的参数和后传递的参数 结合在一起       let allArgs = [...args, ...newArgs]       return currying(fn, ...allArgs)     }   } }  let fn1 = currying(add, 1, 2) // 3 let fn2 = fn1(3)  // 6 let fn3 = fn2(4)  // 10

柯理化函数思想

利用闭包保存机制,把一些信息预先存储下来(预处理的思想)

我们用一道求和的题来描述:

function currying(...outerArgs) {   return function anonymous (...innerArgs) {     let args = outerArgs.concat(innerArgs)     return args.reduce((previousValue, currentValue) => previousValue + currentValue)   } }  let fn1 = currying(1, 2) let fn2 = fn1(3) let fn3 = fn2(4)

补充 什么是预处理的思想:

在第一次fn1函数执行的时候,会创建一个函数执行上下文,将 1, 2 存储在该上下文中的局部变量里,在以后执行 fn2 fn3的时候,需要先取得 fn1  的返回结果,使用存储的 1, 2 参数,这种机制便形成了闭包

不理解reduce的小伙伴看过来:

情况1:reduce没有第二个参数时

let Array = [10, 20, 30, 40, 50]; Array.reduce(previousValue, currentValue, currentIndex, arr){   return previousValue + currentValue; }
  • 第一次触发函数时,previousValue为第一项(10),currentValue为第二项(20)

  • 第二次触发函数时,previousValue为回调函数的返回值(30),currentValue为第三项(30)

  • 第X次触发函数时,previousValue为回调函数的返回值,currentValue为第X+1项

  • currentIndex:对应的就是currentValue的索引值

  • arr:是个常量 数组本身

情况2:reduce有第二个参数时

let Array = [10, 20, 30, 40, 50]; Array.reduce((previousValue, currentValue, currentIndex, arr){   return previousValue + currentValue; }, 0)
  • 第一次触发函数时,previousValue为第二个参数(0),currentValue为第一项(10)

  • 第二次触发函数时,previousValue为回调函数的返回值(10),currentValue为第二项(20)

  • 第X次触发函数时,previousValue为回调函数的返回值,currentValue为第X项

  • currentIndex:对应的就是currentValue的索引值

  • arr:是个常量 数组本身

compose函数

compose组合函数:把多层函数嵌套调用扁平化

compose函数常基于reduce 柯理化函数思想解决 函数调用扁平化的问题:

在项目中,我们经常遇到这样一个问题:

const fn1 = x => x + 10; const fn2 = x => x - 10; const fn3 = x => x * 10; const fn4 = x => x / 10;  console.log(fn3(fn1(fn2(fn1(4)))))

上面这个例子中,明显看出层级嵌套较深,这时候就需要调用函数扁平化的思想

函数调用扁平化: 如果是多层级嵌套调用的函数,把一个函数调用完,当作另一个函数的实参传递到下一个函数中

解决方案:从左到右依次执行

/**  * @param  funcs 存储按照顺序执行的函数(数组) => [fn1, fn3, fn2, fn4]  * @param  args 存储第一个函数执行需要传递的实参信息(数组) => [5]  */ function compose(...funcs) {   return function anonymous(...args) {     if(funcs.length === 0) return args;     if(funcs.length === 1) return funcs[0](...args);     // funcs 里有 多个函数时     return funcs.reduce((n, func) => {       // 第一次执行:       // n:第一个函数执行的实参 func:第一个函数       // 第二次执行:       // n的值:上一次func执行后的返回值,作为实参传递给下一个函数执行 func:第二个函数       return Array.isArray(n) ? func(...n) : func(n);     }, args)   } }  let res = compose(fn1, fn3, fn2, fn4)(5)  // 执行过程: console.log(compose()(5)); //=>5 console.log(compose(fn1)(5)); //=>5+10 = 15 console.log(compose(fn1, fn3)(5)); //=>fn1(5)=15  fn3(15)=150 console.log(compose(fn1, fn3, fn2)(5)); //=>fn1(5)=15  fn3(15)=150 fn2(150)=140 console.log(compose(fn1, fn3, fn2, fn4)(5)); //=>fn1(5)=15  fn3(15)=150 fn2(150)=140 fn4(140)=14

compose函数扁平化的执行过程:

  1. 鸿蒙官方战略合作共建——HarmonyOS技术社区

  2. compose()(5),如compose()函数中无函数参数传递进来,则直接返回后面的参数 5

  3. compose(fn1, fn3, fn2, fn4)(5),先执行fn1(5),再将fn1(5)的结果传递到fn3中,如fn3(fn1(5))  => fn3(15),以此类推fn2(fn3(fn1(5))) =>  fn4(150),后来每一次函数调用的参数都是前一个函数返回的结果,自然我们就想到了Array.prototype.reduce方法

  4. 设置reduce的第二个参数,是为了统一n为每一次函数的返回结果(第一次n为5,func为fn1),刚好又需要让fn1(5)函数执行,可见第二个参数的必要性,是为了统一每次的回调函数返回结果都是number

函数式编程与命令式编程

回调函数:把一个函数作为值传递给另外一个函数,在另外一个函数中把这个函数执行(这是实现函数式编程重要的知识)

函数式编程:注重结果,不在乎过程,过程交给别人处理,体现函数封装性思想(提倡)

命令式编程:注重过程,需要自己去实现过程

函数式编程:把逻辑如何实现封装成为API方法,我们以后只要调取API方法,即可获取想要的结果即可

let arr = [10, 20, 30, 40, 50];  let res = arr.reduce((n, item) => {   return n + item; });

命令式编程:用代码实现主要的逻辑,注重过程

let res = 0; for (let i = 0; i < arr.length; i++) {   res += arr[i]; }

感谢各位的阅读,以上就是“怎么掌握JS高阶编程技巧”的内容了,经过本文的学习后,相信大家对怎么掌握JS高阶编程技巧这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是亿速云,小编将为大家推送更多相关知识点的文章,欢迎关注!

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

js
AI