局部变量:能够简单理解成函数内部申明的变量
全局变量:能够简单理解成最外层被申明的变量
复制代码
var a = 'web'
function Foo(){
console.log(a)
}
console.log(a)
Foo()
复制代码
变量a和函数Foo定义在最外层,因此在代码的任何地方均可以访问到他们。
前端
function Foo(){
var a = 'web'
console.log(a)
}
console.log(a) //报错
Foo() // web
复制代码
变量a在函数Foo定义的内层(局部变量),因此只能在函数内部访问,外部没法访问。
因此全局变量在任何位置均可以访问,可是局部变量只能在当前做用域下面访问 *** 未声明的变量,自动定义为全局变量(不管在函数内部仍是外部) ***web
说道函数做用域,就要说起一个概念是函数的做用域链
面试
当一个函数建立的时候,咱们须要使用其中的变量,函数内部没有建立这个变量,咱们会去上一级去寻找这个变量看是否被建立拿到他使用他,若是上一级没有,继续去上一级去寻找,这一个寻找路径(内部,上一级,上一级的上一级...)就被称做函数做用域链
bash
做用域实际上是指一个包含了全部在同一个区域声明的变量和函数的集合,那么如何决定这些变量数据和函数是属于同一区域的呢?这就由他们最初声明时的位置来决定的。咱们在看一段代码来理解一下做用域以及做用域链
复制代码
var a = 1
function fn1(){
function fn2(){
console.log(a)
}
function fn3(){
var a = 4
fn2()
}
var a = 2
return fn3
}
var fn = fn1()
fn() //2
复制代码
他为何会输出2呢,首先咱们看一下这个函数体,fn是其实就是fn1,而fn1()是内部的fn3,fn3内部有申明了一个变量a = 4,而且执行函数fn2(),fn2打印a,可是在fn2当前内部做用域没有变量a,这个时候他会去定义他的环境里面找变量a,也就是fn1()内部,固然fn1内部也申明了变量a = 2,因此他会打印2 刚开始很容易弄错他会打印4,函数的内部须要变量的话,他寻找的点是内部-》词法做用域(也就是定义他的外部做用域)=》外层做用域这个顺序依次去寻找闭包
外部不能使用函数的内部变量,若是使用呢,这个时候咱们就可使用闭包这个手段,闭包首先咱们理解成一个手段,什么手段,能够从外部拿到函数内部变量的手段,首先咱们看一个简单的闭包
复制代码
function add() {
var a = 1
function addNum(){
a++
return a
}
return addNum
}
var addFunc = add()
console.log(addFunc()) //2
console.log(addFunc()) //3
console.log(addFunc()) //4
console.log(addFunc()) //5
console.log(addFunc()) //6
console.log(addFunc()) //7
console.log(addFunc()) //8
复制代码
这个时候确定会有疑问了,怎么我就能拿到内部变量,而且这个变量能够一直保存
首先咱们看一下这个函数,这个函数add内部定义了一个变量和一个函数addNum(),而且这个addNum函数对这个局部变量进行了操做++,函数最后把这个值给return了出去,而且add函数又把addNum这个函数当作一个值return了出去,那么add()这个函数就是他内部的addNum()
这个时候赋值给addFunc这个变量,这个变量就获取到add()(也就是addNum()也就是a的值)
而后console.log(addFunc())就能够获取到这个内部值并进行操做
上面说的只是表现,也就是咱们看到的东西,接下来咱们来讲一说咱们看不见的
1.当函数被申明,执行事后,他内部的变量被释放,也就是说我申明普通函数使用后,再去调用一次的时候,他内部的变量还会变得和原谅同样,这并非函数内部的特性,而是为了让内部的变量不污染全局,在内部申明以后再次使用后他会重新被使用,可是若是函数内部的变量使用的是全局变量就能够不用考虑这个问题
2.由于变量申明在调用的时候会被从新执行,那么咱们就要用个小手法来让这个变量保存住,而且能被外部使用
2-1.被外部使用很简单,return出去就能够了
2-2.保存住咱们能够在函数内部建立一个函数,而且把获取值进行操做之后return 给此函数,在把这个函数return 出去给外部函数,至关于在函数内部建立一个做用域,保存住了内部的变量
咱们扩展一下,加深一下对于闭包的理解,需求是咱们须要一个汽车,咱们能够设置他的速度,可是外部的变量不能随意获取到他内部的变量,而且在外部还能操做这个变量,使用闭包咱们能够写成异步
function catFunc(){
var speed = 0;
function set(s){
speed = s
}
function get(){
return speed
}
function speedUp(){
speed++
}
function speedDown(){
speed--
}
return {
set,
get,
speedUp,
speedDown
}
}
var Car = catFunc()
Car.set(30)
console.log(Car.get()) //30
Car.speedUp()
console.log(Car.get()) //31
Car.speedDown()
console.log(Car.get()) //30
复制代码
这个函数咱们能够理解成 在Car内部建立了四个函数都在修改这个变量,可是最后把函数名组成一个对象return出去,这样内部的函数保存住了变量,return出去的函数也就是方法能够操做内部变量 看了实际操做之后,咱们看一下关于闭包常见的面试题 闭包常见面试题函数
//以下代码,输出的是什么
for(var i=0; i<5; i++){
setTimeout(function(){
console.log('delayer:' + i )
}, 0)
}
复制代码
若是你们对任务队列或者异步有些了解,就不能看出,i是全局变量,在for循环结束之后,才会执行setTimeout里面的内容,这个时候打印i的时候,就会去寻找全局的的变量i,也就是刚刚循环结束之后的那个i,这个时候打印的固然就是五个4
若是咱们想解决这个问题,使它打印0-5怎么作到呢,咱们是不是须要建立一个做用域保存住每次循环次数打印出来,固然咱们可使用闭包这个手法来保存这个变量学习
for(var i = 0 ;i < 5 ;i++){
(function(j){
setTimeout(function(){
console.log('delayer'+j)
},0)
})(i)
}
复制代码
以上这段代码,咱们使用当即执行函数来建立一个做用域,使用i做为形参传入使用当即执行函数来保存住这个变量,这个时候咱们打印出来的内容固然就是0-4了 上述代码还有一种写法,思想也是同样,建立做用域来保存咱们的变量ui
for(var i = 0 ;i < 5 ;i++){
setTimeout((function(j){
return function(){
console.log(j)
}
})(i),0)
}
复制代码
思想是同样的,可是咱们的作法不同,咱们在异步的setTimeout中使用当即执行函数保存住变量i而后return出去,这样setTimeout内部的函数其实就是return出来的console.log(被保存的变量)
spa
其实闭包咱们能够理解成保存变量的一个手法(建立做用域),由于项目中一个文件中的全局变量过多会形成变量的复燃,变量会很不规范已经命名颇有可能形成冲突,全部咱们可使用闭包来保存住某个需求须要的变量,这边全局变量减小后,咱们的代码也会特别清晰,代码对于开发者来讲也是特别友好的,在下一个文章中,我会讲到函数节流和函数防抖,会使用闭包来封装函数达到函数复用的效果,小伙伴们喜欢的就来个素质三连(点赞,收藏,关注)