我把闭包这块知识分为两个概念
首先是闭包函数
function bibao () { let num = 0; return function () { num++; return num; } }
这就是一个最简单的闭包函数
可以看到,bibao这个函数返回了一个匿名函数
这个匿名函数内部引用了bibao函数内的局部变量num,让其自增,自增后作为返回值返回
我把这个返回的匿名函数统称为闭包钥匙函数。相当于一个钥匙。
因为这个钥匙函数在向上搜索作用域的时候会搜索到闭包函数的作用域,所以也可以利用到闭包函数内的变量。
而js引擎一旦检测到这种结构,在闭包函数运行结束之后依然会保留其作用域以支撑闭包钥匙函数的功能。
固以下代码的输出就是可以理解的了
function bibao () { let num = 0; return function () { num++; return num; } } let fun = bibao(); console.log(fun()); console.log(fun());
输出
$ node ./src/index.js 1 2 Done in 1.81s.
这里的fun变量用于承接闭包函数返回的闭包钥匙函数,也就相当于其内部的那个匿名函数。
现在我们再来看这样一个代码
function bibao () { let num = 0; return function () { num++; return num; } } console.log(bibao()()); console.log(bibao()());
输出
$ node ./src/index.js 1 1 Done in 2.16s.
看似差不多,但是结果不一样
这是因为bibao这个闭包函数运行了两次,准确意义上说,这个函数创建了两个作用域,且因为都产生了闭包钥匙函数,所以这两个作用域都被保留下来了。既然两个作用域都被保留下来了,那么num自然也就变了成了两份,自然也都是1。
还可以这样写
function bibao () { let num = 0; function fun () { num++; return num; }; // let fun = () => { // num++; // return num; // }; // let fun = function () { // num++; // return num; // }; return fun; }
这都是可以的,可见只要是建立了一个对外的。
这样写也是可以的
function bibao () { let num = 0; return { num: num, fun: () => { num++; return num; } }; } let haha = bibao(); console.log(haha.num); console.log(haha.fun()); console.log(haha.num); console.log(haha.fun());
输出
$ node ./src/index.js 0 1 0 2 Done in 4.50s.
可见num成员变量并没有形成闭包,只是简单的作为一个值复制过去了而已,但是fun函数变成了闭包钥匙。
然后在看一个闭包的例子,这是一个双层闭包
function bibao () { let num1 = 0; return () => { num1++; let num2 = 0; return () => { num2++; return { num1: num1, num2: num2 }; }; }; } let fun = bibao()(); console.log(fun()); console.log(fun());
这段程序会返回什么结果呢?
$ node ./src/index.js { num1: 1, num2: 1 } { num1: 1, num2: 2 } Done in 1.50s.
可以看到num2自增了,但是num1只自增了一次,这是为什么呢?
其实很简单,我们把代码变换成下面这个样子就可以知道了
function bibao () { let num1 = 0; return () => { num1++; let num2 = 0; return () => { num2++; return { num1: num1, num2: num2 }; }; }; } let fun1 = bibao(); let fun2 = fun1();//实际上fun1这个外层闭包钥匙函数只执行了一次,所以num1只自增了一次 console.log(fun2()); console.log(fun2());
接着,用闭包实现一个单例
function bibao () { let str = null; return () => { if (!str) { str = "你好,世界"; console.log("单例赋值"); } return str; }; } let fun = bibao(); console.log(fun()); console.log(fun()); console.log(fun());
输出结果
$ node ./src/index.js 单例赋值 你好,世界 你好,世界 你好,世界 Done in 1.45s.
可以看到,单例的值只在最初调用的时候被赋了一次
上面这个例子也可以被修改成如下形式的
function bibao () { let str = null; this.instance = () => { if (!str) { str = "你好,世界"; console.log("单例赋值"); } return str; }; } let tst = new bibao(); console.log(tst.instance()); console.log(tst.instance()); console.log(tst.instance());
这样看起来是不是舒服很多呢?
function bibao () { let connect = null; this.instance = async () => { if (!connect) { connect = await mongoClient.connect("mongodb://localhost:27017"); console.log("单例赋值"); } return connect; }; } (async () => { let tst = new bibao(); console.log(await tst.instance()); console.log(await tst.instance()); console.log(await tst.instance()); })();
稍加改造,就变成了mongo链接的一个单例,哈哈,到这里单例总算是有点用处了
在实际项目里面,我是这样用的
function myDB () { let db = null; this.instance = async () => { if (!db) { let connect = await mongoClient.connect("mongodb://localhost:27017"); db = connect.db("kuaishou-hub"); console.log("单例赋值"); } return db; }; } (async () => { let db = new myDB(); console.log(await db.instance()); console.log(await db.instance()); console.log(await db.instance()); })();