闭包与回调函数

闭包(closure)

闭包定义:闭包在函数被定义时被创建,能否形成闭包在于内部函数是否能够直接访问到外界的作用域,与外界的作用域环境捆绑在一起形成的一个包就叫做闭包;

广义闭包:每当创建一个函数就形成一个闭包。

实用闭包:函数环境捆绑外部环境形成一个闭包,形不形成闭包,关键在于这个函数能够之后访问到外部函数的作用域。(一个函数和对其周围状态的引用捆绑在一起)

闭包:在函数被定义时就已经形成闭包。(函数环境捆绑外部环境形成一个包 --> 闭包 --> 这个函数 --> 闭包函数)

高阶函数: 闭包和回调函数任意一个使用在函数上,都是高阶函数。

高阶函数: 具有更强的封装性和集成性,高阶函数要比普通函数处理更多的问题。

闭包的形式

此时function fn函数也是一个闭包函数;为什么呢?因为MDN上介绍闭包的定义是:一个函数和对其周围状态(词法环境 lexical environment)的引用捆绑在一起形成的一个包就叫做闭包;也就是说,闭包可以让你在一个内层函数中访问到其外层函数的作用域;
那么此时function fn函数存在全局作用域中,fn函数能够访问当全局作用域,并且与全局作用域的引用捆绑在一起,fn函数可以任意访问到全局作用域;所以此时fn函数就是一个闭包函数,只是一种广义上的闭包形式;

function fn() {}

相比对上面的例子,下面的例子就能更形象的解释广义上的闭包形式;此时test函数是fn函数的内部函数,test函数与fn函数作用域的引用捆绑在一起,并且test函数能够直接访问到fn函数的作用域,所以此时test函数也是一个广义上的闭包函数,这种形式利于开发功能的集成与开发;

function fn() {
  function test() {}
}

函数test中参数fn是一个回调函数,判断函数是否是一个闭包,其实也是很简单;MDN上指明:在JavaScript中,每当创建一个函数,闭包就会在函数创建的同时被创建出来;也就是说闭包是在函数被声明的时候就已经确定,并且闭包能够让一个内层函数访问到其外层函数的作用域;
所以test函数中fn回调函数能够访问到test函数的作用域吗?与test函数作用域的引用捆绑在一起吗?很显然是fn回调函数是访问不到test函数的作用域的**,因为对于fn函数来说,它的作用域链上保存着自己函数的AO执行期上下文环境和全局执行期上下文环境GO;所以fn回调函数并不能够访问到test函数的作用域,所以并不符合闭包的定义,所以此时fn回调函数并不是一个闭包函数**;通过实验,fn函数也并不能访问到test函数中的变量a,所以fn回调函数并不是闭包;

function test(fn) {
  var a  = 1;
  fn();
}
test(function() {
  console.log(a);
});

实用的闭包(抛出函数API)

实用的闭包,也体现出闭包的现象;此时fn函数中声明一个函数fn2,根据广义性闭包的定义,我们可以知道fn2函数就是一个闭包函数,fn2函数与fn函数作用域的引用捆绑在一起,并且fn2函数能够访问到fn函数的作用域,所以fn2函数是一个闭包函数;但是呢?知道这些还不够。。。
fn函数执行完成后,通过return关键字终止fn函数的执行,并且将函数fn2返回出去(fn2函数此时并未执行,并且返回的不仅是fn2函数,因为fn2函数是闭包函数,所以返回的是fn2函数 + fn函数作用域[[scope]]);在全局环境下,声明变量fn3,将fn函数执行后的返回值赋值给全局变量fn3。那么全局变量fn3保存的是什么呢?变量fn3其实保存的就是fn2函数的引用,所以执行fn3,此时执行的就是fn2闭包函数,打印变量a其实打印的就是闭包函数fn2捆绑fn函数作用域引用中的变量a
可能中间有个疑问,就是fn函数执行完成,那么它的作用域链和作用域不都销毁的吗?为什么fn2能够依旧能够访问到fn函数的作用域呢?MDN上说:**一个函数和对其周围状态(当前作用域 lexical environment)的引用捆绑在一起形成的一个包就叫做闭包;**闭包是一种现象,虽然fn函数执行完成后作用域销毁,但是fn2函数是闭包函数与fn函数的作用域引用捆绑在一起,导致fn函数AO执行期上下文对象被fn2函数牢牢捆绑,使得函数fn函数AO执行期上下文对象不释放,所以在全局环境下执行fn3变量,依然可以访问到fn作用域中变量a;这种现象只是发挥了闭包的功能;

function fn() {
  var a = 1;
  function fn2() {
    a++;
    console.log(a);
  }
  return fn2;
}

var fn3 = fn();
fn3();

如果说你不通过关键字return将fn2函数返回出去,那么fn2函数现在也是闭包函数,只是没有发挥闭包函数的功能,展现出不闭包函数的能力;

function fn() {
  var a = 1;
  function fn2() {
    a++;
    console.log(a);
  }
}
fn();

公有变量、私有变量

公有变量: public variable
私有变量: private variable

  1. 公有变量

虽然功能上完成,但是有一个小的瑕疵;此时变量a应该是属于Compute类的私有变量,不能够被外界访问的到;但是呢,现在的这种操作,导致变量a可以被外界访问的到,变量a现在属于公有变量;那么怎么操作才能变成Compute类的私有变量呢?

class Compute {
  constructor() {
    this.a = 1;
  }
  add(number) {
    return this.a + number; 
  }
  miuns(number) {
    return this.a - number;
  }
}
var computed = new Compute();
console.log(computed.a); // 1
console.log(computed.add(200)); // 201
  1. 私有变量,闭包模拟面向对象的私有化

借助闭包函数;立即执行函数执行后返回Compute类,立即执行函数的作用就是创建一个私有的作用域;此时变量a是立即函数作用域中的变量,而Compute类中的add、miuns函数都是闭包函数,与立即执行函数形成作用域引用捆绑在一起;当在全局环境中实例化Computed对象时,此时打印compute.a为undefined,是因为变量a是Compute类的私有变量,外界访问不到,只有Compute类内部访问的到;而由于add、minus函数是闭包函数,所以在全局环境下依旧能够访问到变量a,并且实现功能;

var Compute = (function() {
  var a = 1;
  class Compute {
    add(number) {
      return a + number;
    }
    minus(number) {
      return a - number;
    }
  }
  return Compute;
})();
var computed = new Compute();
console.log(computed.a); // undefined
console.log(computed.add(200)); // 201

闭包例题

javaScript位运算符

// 写一个函数,参数是初始数字,函数提供加减方法和获取值的方法,加减方法参数是每一次都要加减的值

function Calculate(initialNumber) {
  // var _initialNumber = Number(initialNumber) >>> 0; 看javaScript位运算符
  var _initialNumber = Number(initialNumber);
  function add(a){
    _initialNumber += a;
  }
  function minus(b) {
    _initialNumber -= b;
  }
  function getValue(){
    return _initialNumber;
  }
  
  return {
    add:add,
    minus:minus,
    getValue:getValue
  }
}

回调函数

回调函数: a函数的参数是b函数 ---> b函数就是a函数的回调函数(callback function);

callback: 将任务分为两个部分 ---> 先进行一部分任务, 后进行一部分任务。

函数的调用叫做call, 回调函数的调用callback。

回调的函数意义在于将所有任务分为主函数任务和回调函数任务。

回调函数形式

/**
* 回调函数fn --> test ---> 在什么时候都可以执行, fn回调函数不受test函数内部程序的影响。
* 主函数test和回调函数fn完美隔离
* test函数在执行的时候(未执行的回调函数),fn函数在实际参数给形式参数赋值的时候被定义
* test函数执行期 --> 定义另一个函数
**/
function test(num, callback) {
	num += 1;
	callback && callback(num);
}
test(1, function fn(){
	console.log(num);
})

回调函数并不影响函数本身的返回值情况

// 回调函数不影响函数本身的返回值情况
function test(count, callback) {
	var newCount = count + 1;
	var callbackCount = count + 100;
	callback && callback(callbackCount);
	return newCount;
}
var newCount = test(123, function (callbackCount) {
	console.log(callbackCount);
})
console.log(newCount);

回调函数的第一种能力

// 回调函数做功能性扩展
// 主函数计算 +_*/
// 回调函数,接收外界参数a,参数b,参数type,result,dateTime进行打印日志。

function Calculate(callback) {
	return function (a, b, type) { // 为什么需要的返回一个函数呢?利用闭包的作用,因为回调函数一般是静态的,所以只执行一次就好,保存好就行。
		var _a = Number(a),
				_b = Number(b),
				_type = type,
				_res = null,
				dateTime = new Date();
		switch (type) {
			case '+':
				res = a + b;
				break;
			case '-':
				res = a - b;
				break;
			case '*':
				res = a * b;
				break;
			case '/':
				res = a / b;
				break;
			default:
				break;
		}
		// 执行回调函数
		callback && callback(a, b,  type, res, dateTime);
	}
}
// Calculate其实相当于工具函数。
var cal = Calculate(function (a, b, type, res, dateTime) {
	console.log(`${dateTime} ${a} ${type} ${b} = ${res}`)
});
cal(1, 2, '+');
cal(2, 3, '-');
cal(3, 4, '/');
cal(4, 5, '*');

回调函数的第二种能力

function fn(validate) {
	return function (a, b, type) {
		var _a = Number(a),
				_b = Number(b),
				type = type,
				res = null;
		var { isError, errMsg } = validate(_a, _b);
		if (isError) {
			throw new Error(`${errMsg}`);
		}
		console.log(_a);
		console.log(_b);
		switch (type) {
			case '+':
				return res = _a + _b;
			case '-':
				return res = _a - _b;
			case '*':
				return res = _a * _b;
			case '/':
				return res = _a / _b;
			default:
				break;
		}
	}
}
console.log(fn(validate)(101, 100, '*'));

// callback验证函数
function validate(a, b) {
	if (a < 100) {
		return {
			isError: true,
			errMsg: '参数a必须大于100'
		}
	}
	return {
		isError: false,
		errMsg: ''
	}
}

你可能感兴趣的:(前端集合,javascript)