闭包 + 回调 -> JS 高阶函数 High-order function
高阶函数要比普通函数处理更多的问题,更高的集成性和封装性
集成性:把所有的功能装到一个程序里面,进行相互的作用
封装性:将一个程序所需要的代码封装到函数内部
1.闭包 closure
理论上,test 就是一个闭包,
原因是 闭包在ECMA262 上的定义:当一个函数被系统创建时,闭包就会被一同创建出来
一个函数统一与外界环境(作用域)捆绑在一起的这种现象叫做闭包
闭包函数
// global scope -> 闭包
function test() { // 闭包函数
// fn test1 + test scope -> 形成闭包
// 函数test1 + test的局部作用域 -> 形成闭包
// 这个test1函数就叫闭包函数
function test1(){ // 闭包函数
}
}
以下函数是否形成闭包
function b(a) {
console.log(a);
}
function test(){
var a = 1;
b(a);
}
test();
不能,判断是否形成闭包只有一个要素,就是看function b是否能直接访问到 test 的环境,b 并不能与 test
的环境捆绑,所以,它不是闭包
在一个函数内部声明函数是否有意义?
function compute(){
function plus(a, b){
return a + b;
}
function minus(a, b){
return a - b;
}
function mul(a, b){
return a * b;
}
function div(a, b){
return a / b;
}
return function(type, a, b){
switch (type){
case 'plus':
return plus(a,b);
case 'minus':
return minus(a,b);
case 'mul':
return mul(a,b);
case 'div':
return div(a,b);
deflaut:
break;
}
}
}
doCompute = compute();
const res = doCompute('mul', 1, 2);
console.log(res);
function compute(type, a, b){
compute.plus = function (a, b){
return a + b;
}
compute.minus = function (a, b){
return a - b;
}
compute.mul = function mul(a, b){
return a * b;
}
compute.div = function div(a, b){
return a / b;
}
return compute[type](a,b);
}
const res = compute('mul', 100, 2);
console.log(res);
有意义,可以在函数内部集成子函数
闭包模拟变量私有化
class Compute{
constructor(){
this.a = 100;
}
add(b){
return this.a + b;
}
minus(b){
return this.a - b;
}
}
var compute = new Compute();
// a -> Compute class private member
// a -> public member
console.log(compute.a);//100
console.log(compute.add(200));//300
如何将 a 变为私有成员,通过闭包模拟变量私有化
问:如何将 a 变为私有成员
答:通过闭包模拟变量私有化
// es6 实现
var Compute = (function (){
var a = 100;
class Compute{
add(b){
return a + b;
}
minus(b){
return a - b;
}
}
return Compute;
})();
var compute = new Compute();
console.log(compute.a);//报错
console.log(compute.add(200));//300
// es5实现
var Compute = (function (){
var a = 100;
function Compute(){};
Compute.prototype.add = function(b){
return a + b;
}
Compute.prototype.minus = function(b){
return a - b;
}
return Compute; // 或者 window.Compute = Compute;
})();
var compute = new Compute();
console.log(compute.a);//报错
console.log(compute.add(200));//300
转移闭包,向外抛出闭包
将函数内部的闭包函数拉到外部使用,并且可以将外部作用域一起拉到外部来
function test(){
var a = 1;
// test scope
function b(){ // 闭包函数
return ++a;
}
// test scope(a) + fn b
return b;
}
var bb = test();
bb(); // 2
bb(); // 3
JS function API
oApp = getElementById('app');
function getElementById(idStr){
// idStr todo ---> node
return node;
}
// -------------------------------------------
const app = express();
app.server();
function express() {
return {
server(){
// todo...
}
}
}
function compute(){
var baseNum = 1003;
return {
plus: function(a, b) { // JS function API
return baseNum + a + b;
},
minus: function(a, b) {
return baseNum -a - b;
},
mul: function(a, b) {
return baseNum * a * b;
},
div: function(a, b) {
return baseNum / a / b;
}
}
}
var comp = compute();
comp.plus(1, 2);
纯函数: 特点:
- 标准的输入输出,每次输入相同的值,返回的结果一定是相同的
- 不能够依赖外界,函数内部所有的东西都不能依赖外界
- 不可以影响外界,不能在函数内部去修改外界的环境
2. 回调 callback
call -> 调用 行为
trigger -> 触发,-> 事件的发生
call 针对的是 function
trigger 针对的是 event
event 和 function 的关系
- event 通过一个程序或者是交互被触发,并且执行相应的处理程序。一个元素box,user 通过 click 这个事件去点击了 box 这个元素,这个行为叫点击事件trigger
- 当一个 event 和一个 function被绑定了之后,这个事件一旦被触发,会引起 function 被调用
box.addEventListener(‘click’, handleClick, false);
- 在浏览器方面,事件 event 是被定义好的,默认存在的。box 存在 click, mouse 相关的事件
- -> 选择一个事件click -> 绑定一个操作程序handler ->当 click 被 trigger 的时候,就会执行 handler(function)
绑定的是一个事件处理函数,而不是一个事件,绑定的是一个函数,而不是函数执行。当针对于 box 的 click 被 trigger
的时候,去执行这个 handler 函数,这叫做 function call, event trigger
- callback
情景
function a(){
// task 1
// task1 要完成,必须要通过 a 这个程序
// 另外的函数 b来完成
}
function a (){
// todo task 1;
function b(){
// continue task
// todo...
// task 1 is finished
}
b();
}
a();
// task1 finished
//需求:分别完成
// 通过回调完成
function a(callback){
// todo task 1;
// var res = task 1 1/2
callback && callback(res);
}
a(function(res){
// get res
// go on doing task 1
})
// 这个回调的作用:相当于 a 函数是一个封装,通过回调拿到封装的结果,然后继续完成我的业务
// task1 finished
两个任务:1. 返回值2. 执行回调
function test(count, callback){
const newCount = count + 1;
const callbackCount = count + 100;
callback && callback(callbackCount);
return newCount;
}
var newCount = test(123, function(callbackCount) {
console.log(callbackCount);
});
console.log(newCount);
通过闭包和回调完成计算,同时打印日志
function Compute(callback){
return function(a, b, type){
let result = 0;
switch(type){
case '+':
result = a + b;
break;
case '-':
result = a - b;
break;
case '*':
result = a * b;
break;
case '/':
result = a / b;
break;
default:
break;
}
callback && callback(a, b, type, result);
}
}
var compute = Compute(function(a, b, type, result){
console.log(`${a} ${type} ${b} = ${result}`);
});
compute(1, 2, '-');
compute(100, 200, '+');
回调做验证
function compute(validator){
return function(a, b, type) {
var { isError, errorMsg } = validator(a, b);
if(isError){
throw new Error(errorMsg);
}
switch(type){
case '+':
return a + b;
case '-':
return a - b;
case '*':
return a * b;
case '/':
return a / b;
default:
break;
}
};
}
var comp = compute(validator);
console.log(comp(1, 2, '+'));
function validator(a, b){
if(a > 50 || b > 30){
return {
isError: true,
errorMsg: 'a 必须小于等于50,b必须小于等于30'
}
}
return {
isError: false,
errorMsg: 'ok'
}
}
例4
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
<script>
var oLi = document.getElementsByTagName('li');
var i = 0; // 3
for(; i < oLi.length; i++){
oLi.onclick = function() {
// 绑定的是事件处理函数,而不是函数执行
console.log(i);
// 捆绑了外界的作用域
}
}
</script>
解决方法
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
<script>
var oLi = document.getElementsByTagName('li');
var i = 0; // 3
// 解决方法
for(; i < oLi.length; i++){
//创建了函数->执行了->i->function
(function(i){
//var li = i;
// 函数的参数 -> 充当函数的临时局部变量
oLi[i].onclick = function() {
// 捆绑了立即执行函数的作用域
console.log(i); // 0, 1, 2 拿的是立即执行函数中的参数
}
})(i)
}
</script>
闭包缺点:内存溢出