简介
闭包在web前端开发中特别重要,好多前端开发者工作了几年都不知道闭包原理。现在我就讲一下【闭包】希望各位开发者都能够理解其原理。网上的教程也很多,希望这篇您真的能够学会使用。
概念
Closures (闭包)是使用被作用域封闭的变量,函数,闭包等执行的一个函数的作用域。通常我们用和其相应的函数来指代这些作用域。(可以访问独立数据的函数)
变量作用域
变量声明,无论发生在何处,都在执行任何代码之前进行处理。用var声明的变量的作用域是它当前的执行上下文,它可以是嵌套的函数,也可以是声明在任何函数外的变量。如果你重新声明一个 JavaScript 变量,它将不会丢失其值。 将赋值给未声明变量的值在执行赋值时将其隐式地创建为全局变量(它将成为全局对象的属性)。声明和未声明变量之间的差异是:
- 声明变量的作用域限制在其声明位置的上下文中,而非声明变量总是全局的。
- 声明变量在任何代码执行前创建,而非声明变量只有在执行赋值操作的时候才会被创建。
- 声明变量是它所在上下文环境的不可配置属性,非声明变量是可配置的(如非声明变量可以被删除)。
/**例子 1 **/
var a=100;//全局变量a
function demo(){
console.log(a);//由于当前上下文中存在a值,因此这里是undefined;
//也许有些开发者会问为什么呢?因为【变量提升】特性。
var a=50;//局部变量a
d=110;//全局变量 d
}
demo();
复制代码
函数or方法返回值
只要是函数或者方法都有返回值,默认返回值为undefined;
/**例子 2 **/
/**
* 函数返回值
* 方法也同理
* 默认返回:undefined
* */
function demo(){
//如果没有使用return 改变返回值,默认值为undefined
}
var b=demo();//b的值为undefined;
复制代码
闭包
下边这些例子几乎涵盖了JavaScript中所有闭包,只要学会了下边的例子。你就能彻底掌握闭包。
/**例子 3 **/
/**
* 简单的闭包
* return 返回一个匿名函数
* */
function demo(){
var a=100;//局部变量a
return function(){
a=a+1;//在这个作用域可以访问 a值
console.log(a);
}
}
var b=demo();//b接收匿名函数;
b();//执行b,相当于执行匿名函数 结果:101
b();//执行b,相当于执行匿名函数 结果:102
复制代码
/**例子 4 **/
//同例子3
/**
* 简单的闭包
* return 返回一个sum函数
* */
function demo(){
var a=100;//局部变量a
function sum(){
a=a+1;//在这个作用域可以访问 a值
console.log(a);
}
return sum;
}
var b=demo();//b接收匿名函数;
b();//执行b,相当于执行sum函数 结果:101
b();//执行b,相当于执行sum函数 结果:102
复制代码
/**例子 5 **/
/**
* 简单的闭包
* return undefined
* */
var b=null;
function demo(){
var a=100;//局部变量a
b=function (){//把匿名函数的引用赋值给全局变量 b
a=a+1;//在这个作用域可以访问 a值
console.log(a);
}
}
demo();//执行demo函数--改变b的值
b();//执行b,相当于执行匿名函数 结果:101
b();//执行b,相当于执行匿名函数 结果:102
复制代码
/**例子 6 **/
/**
* 闭包
* return {}//返回一个对象,对象包含sum方法
* */
function demo(){
var a=100;//局部变量a
function sum(){
a=a+1;//在这个作用域可以访问 a值
console.log(a);
}
return {//返回一个对象,对象内包含 sum方法
sum:sum
}
}
var b=demo();//执行demo函数返回一个对象赋值给b
b.sum();//执行b.sum,相当于执行demo中的sum函数 结果:101
b.sum();//执行b.sum,相当于执行demo中的sum函数 结果:102
复制代码
/**例子 7 **/
/**
* 闭包
* return sum函数
* */
function demo(){
var a=100;//局部变量a
/**
* 统计值
* @n {number} 数值
* */
function count(n){
console.log('a=',a+n);
}
function sum(){
a=a+1;//在这个作用域可以访问 a值
console.log(a);
count(9);
}
return sum;
}
var b=demo();//执行demo函数返回赋值给b
b();//执行b函数,相当于执行demo中的sum函数 结果:101,
在执行demo函数中的 count函数进行统计 结果:110
复制代码
/**例子 8 **/
/**
* 回调函数,产生的-->闭包
*
* */
function demo(){
var a=100;//局部变量a
function sum(){
a=a+1;//在这个作用域可以访问 a值
console.log(a);
}
demo1(sum);//执行demo1函数 把sum的引用传递进去
}
/**
* 延迟执行函数
* @fn {function} //回调执行的函数
* */
function demo1(fn){
//延迟1秒执行
setTimeout(function(){
var a=1000;//局部变量a为1000
fn();//执行 demo 内的 sum函数,fn 只是 demo 内的 sum函数引用。
},1000);
}
demo();//执行demo函数 结果:101
复制代码
闭包的实际应用
1.使用闭包手写bind函数
/**
* bind 函数
* */
if(!Function.prototype.bind){
Function.prototype.bind = function(){
var _arg = [].slice.call(arguments),//绑定时候的参数
_that= _arg.shift(),//绑定到谁身上
_older=this;//当前函数
return function(){
_older.apply(_that,_arg.concat([].slice.call(arguments)));
}
}
}
var s={
names:'s',
fn:function(){
console.log(this);
}
},
names='window-s';
function demo(){
console.log(this.names);
console.log([].slice.call(arguments));
}
var dome1=demo.bind(s,1,2,3);
dome1(4,5);//结果:s
复制代码
2.我专门写了一个这样的插件用于函数整合队列并顺序执行。参考链接:https://juejin.im/post/5a1b863bf265da432d27cfee
/**
* 作者:小小坤
* 联系:java-script@qq.com
* 日期:2017-11-11
* 版本:1.0.0.4
* -----------使用说明----------
* 1、把所有函数【包含异步执行的函数】按照顺序依次 使用lk.push存入
* 2、带有参数的函数,一定要注意{最一个参数如果是callback}会被认为是 异步执行函数
* 3、异步执行的函数,需要把最一个参数设置为callback。并在函数执行完毕执行callback();函数保证按照顺序执行
*
* */
;! function() {
var list = [], //存储函数的列表
isFun = Object.prototype.toString; //用于验证是否是函数
/**
* 添加到列表中
* @fn {Function} 函数体
* */
function push(fn) {
isFun.call(fn) === "[object Function]" && list.push(fn);
};
/**
* 开始执行列表中的所有函数,
* 按照先进先出原则
*
* */
function star() {
if(list.length) {
var fn = list.shift(),//截取第一个函数
arry=getfunarg(fn),//获取这个函数的参数列表
_length=arry.length;//参数列表的长度
if(_length && arry[_length-1] === 'callback') {
if(_length===1){
fn(star);
}else{
arry.pop();//删除最后一个参数
arry.push(star);//把回调函数存入数组
fn.apply(this,arry);
}
} else {
fn.apply(this,arry);
star();
}
}
}
/**
* 查找函数参数名
* @fn {Function } 要查找的函数
* @return []返回参数数组
* */
function getfunarg(fn) {
var f = /^[\s\(]*function[^(]*\(\s*([^)]*?)\s*\)/.exec(fn.toString());
return f && f[1] ? f[1].split(/,\s*/) : [];
}
//挂在到Windows上。
window.lk = {
push: push,
start: star
}
}();
//使用测试
/**--------一条华丽的分割线--------**/
var a=100,
b=200,
d=300,
f=400;
//定义函数 a2 ,此函数带有一个参数,被认为是异步函数
function a2(a,b,callback) {
console.time('2');
setTimeout(function() {
console.timeEnd('2');
callback();
console.log(a,'a');
}, 1000);
}
//把函数函数 a2 放入数组
lk.push(a2);
//定义函数 a3
function a3(d, f) {
console.log(f,'f');
console.log(3);
}
//把函数函数 a3 放入数组
lk.push(a3);
//定义函数 a4 此函数带有一个参数,被认为是异步函数
function a4(callback) {
console.time('4');
setTimeout(function() {
console.timeEnd('4');
callback();
}, 2000);
}
//把函数函数 a4 放入数组
lk.push(a4);
//最后开始执行
lk.start();
复制代码
3.vue、jQuery、react等框架使用闭包
总结
学习是一个漫长而痛苦的过程,当你真真正正的沉寂到知识的海洋,你会发现好多乐趣。不要让我们单纯为了工作而工作,要为了自己的兴趣而工作。代码是一种神奇的东西,我们可以驱动它干好多事情。为什么我们不好好做一个控制者呢!!