js高阶编程技巧

391 阅读5分钟

js高阶编程技巧

js高阶编程技巧的核心就是利用闭包,实现一些高阶编程的方式 那么,属于js高阶编程方式都有哪些呢?

模块化思想

模块化思想的发展经过了单例模式->AMD->CMD->CommonJS->ES6Moudle几个里程碑 在js中,模块化思想的好处就是便于封装,在协同工作的模式下,保护了各个人员之间的代码与全局作用域中的变量不互相污染。

(function(){
    let time=0;
    function changeTime(){
        time++;
    }
})();

上面的代码使用闭包的方式实现了对其的保护,不会污染全局变量

(function(){
    let time=0;
    function changeTime(){
        time++;
    }
    window.changeTime=changeTime;
})();

window.changeTime=changeTime;将方法changeTime暴露给全局作用域,以供他人调用。但是如果挂载的过多,就会可能造成污染

js对象的特点是,每一个对象都是一个单独的堆内存空间

仿照其他后台语言,其实obj1,obj2不仅仅是对象名,还被成为命名空间

每一个对象都是一个单独的实例,用来管理自己的私有信息,即使名字相同,也互不影响,其实这就是js中的单例模式

var obj1={
    name:"rose",
    age:18,
    fn:function(){
        //.....
    }
}
var obj2={
    name:"jack",
    age:18,
    fn:function(){
        //.....
    }
}

前面的解决

var Time = (function(){
    let time=0;
    function changeTime(){
        time++;
    }
    return{
        changeTime
    }
})();
//自执行函数执行之后返回一个对象被Time引用,其开辟的栈内存空间不能释放,形成了一个闭包。外界想要调用changeTime方法的时候,直接使用Time.changeTime即可,这样就不会污染全局环境。

以上模式就是高级单例设计模式(闭包+单例)也是最早期的js模块化思想。

惰性函数

惰性函数就是懒的意思,想要代码尽量少,执行尽量少,内存消耗尽量少。

例如:

返回样式对象的api window.getComputedStyle(),但是这个api并不兼容ie,ie中有自己的api dom.currentStyle()来获取样式对象。

function getCss(element,attr){
    if("getComputedStyle" in window){
         return window.getComputedStyle(element)[attr];
    }
    return element.currentStyle[attr];
}

但是,我们在每次使用 getCss()这个函数的时候,就会执行一次getComputedStyle" in window,我们知道,我们没有换浏览器,所以每次执行的结果都相同,再次执行getComputedStyle" in window,没有必要,惰性思想要做的就是能一次解决的事情,不会再做第二次。

优化:

let flag="getComputedStyle" in window
function getCss(element,attr){
    if(flag){
         return window.getComputedStyle(element)[attr];
    }
    return element.currentStyle[attr];
}

但是这个flag变量进入全局变量空间了,通过学习前面的单例模式,我们知道这样写其实不太好。接下来,看下面的代码。

再优化

let getCss=function (element,attr){
    if("getComputedStyle" in window){
        getCss = function(element,attr){
            return window.getComputedStyle(element,attr);
        }	
    }else{
        getCss = function(element,attr){
        	return element.currentStyle[attr];
    	}
    }
}

这样我们发现第一次执行并不会出效果。再优化

let getCss=function (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)
}

结束!

柯里化函数

是一种预先处理的思想,形成一个闭包,将一些值(未来会使用)保存起来。所有符合这种模式的,都叫做柯里化函数。

下面我们以一道题展开:

// 普通的add函数
function add(x, y) {
    return x + y
}
add(1, 2)           // 3
curryingAdd(1)(2)()   // 3

要实现上面代码中的效果,curryingAdd函数要如何编写呢

上面我们说到,函数柯里化是用闭包将一些值保存起来以供未来使用。

function curryingAdd(...params) {
  let args = [...params];
  return function inner(...arguments) {
    args = args.concat(arguments);
    return eval(args.join("+"));
  };
}
console.log(curryingAdd(1, 2)());//3
console.log(curryingAdd(1, 1)(1, 1);//4

这里,我们将传进来的值放进数组args中保存,返回一个函数inner,这样就可以二次调用。那么如何实现三次甚至四次多次连续不定次调用呢?比如

console.log(curryingAdd(1, 1)(1, 1)(2, 2)());//8

这时我们就需要再inner函数中做一些处理,如下所示:

function curryingAdd(...params) {
  let args = [...params];
  return function inner(...arguments) {
    args = args.concat(arguments);
    if (arguments.length == 0) {
      return eval(args.join("+"));
    } else {
      return inner;
    }
  };
}
console.log(curryingAdd(1, 2)());
console.log(curryingAdd(1, 1)(1, 1)());//4
console.log(curryingAdd(1, 1)(1, 1)(2, 2)());//8

我们通过判断inner函数值的有无来判断是返回结果还是继续返回函数。如果arguments参数长度为0,说明已经没有参数可以使用了,那么返回args中值相加的结果,如果arguments中仍旧有参数,那么就返回函数inner,以供接着向args中追加参数。

来一道题吧

问题:

let res=add(1)(2)(3);
console.log(res)//6;
let res2=add(1,2,3)(3);
console.log(res2)//9;

实现:

function add(...args){
	const proxy=(...params)=>{args=args.concat(params);return proxy};
    proxy.toString=function(){
    	return eval(args.join("+"));
    }
    return proxy;
}

利用的是函数输出的时候会隐式转换,调用其toString()方法。

compose组合函数

在函数式编程中有一个很重要的概念就是函数组合,实际上就是把处理函数像管道一样连接起来。然后让数据穿过管道得到最终的结果。例如:

const add1=(x)=>x+1;
const mul3=(x)=>x*3;
const div2=(x)=>x/2;
div2(mul3(add1(add1(0))))//=>3

上面的那中写法可读性明显太差了,我们可以构建一个compose函数,它接收任意多个函数作为参数(这些函数都只接受一个参数),然后compose函数返回的也是一个函数,达到以下的效果。

const operate=compose(div2,mul3,add1,add1);
operate(0)//相当于div2(mul3(add1(add1(0))))//=>3
operate(2)//相当于div2(mul3(add1(add1(2))))//=>3

那么compose函数应该怎样写呢?

const add1 = (x) => x + 1;
const mul3 = (x) => x * 3;
const div2 = (x) => x / 2;
function compose(...funs) {
  return function(arg) {
    for (let i = funs.length - 1; i >= 0; i--) {
      arg = funs[i](arg);
    }
    return arg;
  };
}
let operate = compose(div2, mul3, add1, add1);
console.log(operate(0));//3

同样是使用闭包的思想,将存储进去的函数保存起来,待operate()调用的时候就可以使用保存的函数进行运算。返回运算结果。

优化:

const add1 = (x) => x + 1;
const mul3 = (x) => x * 3;
const div2 = (x) => x / 2;
function compose(...funs) {
  return function(arg) {
    if (funs.length === 0) return arg;
    for (let i = funs.length - 1; i >= 0; i--) {
      if (typeof funs[i] != "function") continue;
      arg = funs[i](arg);
    }
    return arg;
  };
}
let operate = compose(div2, mul3, add1, add1);
console.log(operate(0));

另外一种实现方案

const add1 = (x) => x + 1;
const mul3 = (x) => x * 3;
const div2 = (x) => x / 2;
function compose(...funs) {
  if (funs.length === 0) {
    return (arg) => arg;
  }
  if (funs.length == 1) {
    return funs[0];
  }
  //a:初始值或者计算结束后返回的值,b当前值
  return funs.reduce((a, b) => {
    return (...args) => a(b(...args));
  });
}
let operate = compose(div2, mul3, add1, add1);
console.log(operate(0));//3