JavaScript学习笔记(十):函数式编程

  • 高阶函数
    • 在数学和计算机科学中,高阶函数是指至少满足下列一个条件的函数(1)接受一个或多个函数作为输入(2)输出一个函数
  • 函数链式调用
    • 在设计函数(作为对象方法)返回值时,将处理后的当前对象(上下文)返回,以便进行紧凑的方法调用
  • 偏函数
    • 固定一个函数的一个或者多个参数,返回一个新的函数,这个函数用于接收剩余的参数
  • 函数柯里化
    • 转换一个调用函数比如fun(a,b,c)为fun(a)(b)©的方式调用

一、函数闭包

JavaScript采用词法作用域,也就是静态作用域
动态作用域:代码执行后的运行环境,如this

  • 函数的执行依赖于变量作用域,这个作用域是在函数定义时决定的,而不是函数调用时决定的
  • 为了实现这种词法作用域,JavaScript函数对象的内部状态不仅包含函数的代码逻辑,还必须引用当前的作用域链

匿名函数经常被人误认为是闭包,闭包是指那些引用了另一个函数作用域中变量的函数,通常是在嵌套函数中实现的。
函数对象可以通过作用域链相互关联起来,函数体内部的变量都可以保存在函数作用域内,这种特性在计算机科学文献中称为闭包。从技术的角度讲,所有的JavaScript函数都是闭包,它们都是对象,都关联到作用域链

{
    var scope = "globle scope";
    function checkscope() {
        var scope = "local scope";
        function f() {
            return scope; // 闭包,获取到局部变量
        }
        return f();
    }
    console.log(checkscope()); // local scope
}

闭包的原理

  • 在JavaScript中,如果一个对象不再被引用,那么这个对象就会被垃圾回收机制回收
  • 每次调用JavaScript函数的时候,都会为之创建一个新的对象(活动对象Activation Object)用来保存局部变量,把这个对象添加至作用域链中
  • 当函数返回的时候,就从作用域链中将这个绑定变量的对象删除
    • 如果不存在嵌套的函数,也没有其他引用指向这个绑定对象,它就会被当做垃圾回收掉
    • 如果定义了嵌套的函数,每个嵌套的函数都各自对应一个作用域链,并且这个作用域链指向一个变量绑定对象
  • 如果这个函数定义了嵌套的函数,并将它作为返回值返回或者存储在某处的属性里,这时就会有一个外部引用指向这个嵌套的函数
    • 它就不会被当做垃圾回收,并且它所指向的变量绑定对象也不会被当做垃圾回收

利用闭包

{
	let cart = [
		{name:"xiaomi",price:1999},
		{name:"iphone",price:3999},
		{name:"huawei",price:2999}
	]
	function between(a,b){
		return function(value){
			return value.price >= a && value.price <= b;
		};
	}
	console.log(cart.filter(between(1500,3000))); // [ { name: 'xiaomi', price: 1999 }, { name: 'huawei', price: 2999 } ]
}

通过闭包between函数中的子函数能够访问到between函数的参数,数组的filter方法中的回调函数每次调用时都会创建一个活动对象来保存局部变量,因此可以获取到变量来进行处理并把结果返回出来
另一个例子:

{
	let items = document.querySelectorAll("li");
	function bindClick(nodes){
		for(var i = 0; i< nodes.length; i++){
			nodes[i].onclick = (function(index){
				return function(){
					console.log(index,nodes[index].textContent);
				}
			})(i)
		}
	}
	bindClick(items);
}
{
	// 第二种方式,使用块作用域
	let items = document.querySelectorAll("li");
	function bindClick(nodes) {
		for (let i = 0; i < nodes.length; i++) {
			nodes[i].onclick = function () {
				console.log(i, nodes[i].textContent);
			};
		}
	}
	bindClick(items)
}

利用立即执行函数嵌套事件处理函数形成闭包,用于保留每次循环时的i。用let声明的变量会识别块作用域,块作用域中声明的函数就形成了闭包,每次循环都会创建一个新的函数,则这些函数每一个都会形成闭包,因此每次调用特定函数时所访问的i就是其闭包中引用了外部函数作用域上的i

闭包的缺陷

闭包的缺点就是常驻内存会增大内存使用量,并且使用不当很容易造成内存泄露,如果不是因为某些特殊任务而需要闭包,在没有必要的情况下,在其它函数中创建函数是不明智的,因为闭包对脚本性能具有负面影响,包括处理速度和内存消耗

二、偏函数

偏函数是指固定一个函数的一个或者多个参数,返回一个新的函数,这个函数用于接受剩余的参数。

{
	let sum = function(x,y){
		return x + y;
	}
	let part = sum.bind(null,1)
	console.log(part(2)); // 3
}

偏函数的好处:通过创建一个名称易懂的独立函数,调用时无需每次传入第一个参数,因为第一个参数通过bind提供了固定值
如果想固定一些参数,但不绑定this,bind不允许这样做,因为必须指定第一个参数(要么指定对象,要么null)不能忽略上下文并跳转到参数

{
	// 实现一个自定义偏函数生成器
	function partial(func, ...argsBound) {
        return function (...args) {
            return func.call(this, ...argsBound, ...args);
        };
    }

	let jack = {
        firstName: "Jack",
        say(time, phrase) {
            console.log(`[${time}] ${this.firstName}: ${phrase}`);
        },
    };
    let tom = {
        firstName: "Tom",
    };
    jack.sayNow = partial(
        jack.say,
        new Date().getHours() + ":" + new Date().getMinutes()
    );
    tom.sayNow = jack.sayNow;
    jack.sayNow("Hello"); // [10:46] Jack: Hello
    tom.sayNow("World"); // [10:46] Tom: World
}

三、函数柯里化

转换一个调用函数比如fun(a,b,c)为fun(a)(b)©的方式调用

{
	// 简单示例,针对两个参数的柯里化
	function curry(func){
		return function(a){
			return function(b){
				return func(a,b);
			}
		}
	}
	function sum(a,b){
		return a + b;
	}
	let currySum = curry(sum);
	console.log(currySum(1)(2)) // 3
}
{
	// 实现高级柯里化函数,针对N个参数的函数
	function curry(func) {
        return function curried(...args) {
            if (args.length >= func.length) {
                return func.apply(this, args);
            } else {
                return function pass(...args2) {
                    return curried.apply(this, args.concat(args2));
                };
            }
        };
    }

	function sum(a, b, c) {
        return a + b + c;
        }
    let curriedSum = curry(sum);
    console.log(curriedSum(1)(2)(3)); // 6
    console.log(curriedSum(1)(2, 3)); // 6
}

如果传递args数与原函数已经定义的参数个数一样或更长,那么直接调用func函数,否则不调用func函数,返回另一个包装器pass,提供连接之前的参数一起作为新参数重新应用curried。然后再次执行一个新调用,返回一个新偏函数(如果参数不够)或最终结果

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
JavaScript 函数是一种可重复使用的代码块,它可以接收参数、执行特定任务,并返回值。函数的基本语法是使用关键字 function,后跟函数名和一对圆括号,其中可以包含参数列表,紧接着是一对大括号,用于包含函数体的代码。 函数可以通过调用来使用,调用函数时,需要提供相应的参数。当函数被调用时,它将执行函数体内的代码,并可以返回一个值给调用者。 在 JavaScript 中,函数可以有或没有参数,我们可以在定义函数时指定参数名,以便在函数体内使用参数的值。在调用函数时,可以为参数提供实际的值,这些值将传递给函数并在函数体内使用。 函数中的代码可以通过 return 语句来返回一个值给调用者,返回值可以是任何数据类型。如果函数没有 return 语句,或者 return 后面没有表达式,函数将返回 undefined。 函数也可以作为值进行赋值和传递。我们可以将函数赋值给一个变量,并通过变量名来调用函数。还可以将函数作为参数传递给其他函数,甚至将函数作为返回值返回。 JavaScript 也支持匿名函数,即没有名称的函数。匿名函数可以用作回调函数,或者在一次性使用时使用。 函数还可以嵌套在其他函数内部,形成嵌套函数或内部函数。内部函数可以访问外部函数的变量和参数。 总之,JavaScript 函数是非常重要且强大的功能,它使我们能够将代码模块化、提高代码重用性,并实现更高级的编程功能。熟练掌握函数的使用将大大提升我们的JavaScript编程能力。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值