作用域
通常来说,一段程序代码中所用到的名字并不总是有效和可用的,而限定这个名字的可用性的代码范围就是这个名字的作用域。作用域的使用提高了程序逻辑的局部性,增强了程序的可靠性,减少了名字冲突
JS作用域(ES6之前)
全局作用域
整个script标签或者是一个单独的js文件
局部作用域(函数作用域)
作用于函数内部的代码环境
注意:
JS在ES6之前没有块级作用域:块作用域由 { } 包括,比如 if { } ; for { } 。因此 if 和 for 中定义的变量在外侧可以随意使用
变量的作用域
- 全局变量:在函数外部定义的变量(显示定义)或在内部不用var定义,直接赋值的变量(隐式定义) 在浏览器页面关闭后才销毁,比较占内存资源空间
- 局部变量:在函数内部用var定义的变量或函数的形参 函数调用结束后就销毁,节省内存资源
- 块级变量:ES6中使用let关键字在语句块中定义的变量 当语句块结束就销毁,更节省内存空间
作用域链
在一个函数内部声明另一个函数时,内层函数只能在外层函数作用域内执行,在内层函数执行过程中,若引入某个变量,先在当前作用域中寻找,若未找到,则继续向上一层级作用域中寻找,直到全局作用域,即采用链式查找。
采取就近原则的方式来查找变量最终的值。
var num=10;
function fn(){
var num=20
function fun(){
console.log(num) //输出结果为:20
}
fun()
}
fn()
预解析
JavaScript 代码是由浏览器中的 JavaScript 解析器来执行的。
JavaScript 解析器在运行 JavaScript 代码的时候分为两步:预解析和代码执行。
预解析: 在当前作用域下, JS 代码执行之前,浏览器会默认把带有 var 和 function 声明的变量在内存中进行提前声明或者定义。
代码执行: 从上到下依次执行
变量预解析
变量预解析也称变量提升:变量的声明会被提升到当前作用域的最上面,变量的赋值不会提升
console.log(num) //执行结果为:undefined
var num=10
//以上两行相当于执行以下代码
var num
console.log(num)
num=10
fun()
var fun=function(){
console.log(22) //执行报错:fun is not a function
}
//相当于执行以下代码
var fun
fun()
fun=function(){
console.log(22)
}
//函数表达式 调用必须写在函数表达式下面
函数预解析
函数预解析也称函数提升:函数的声明会被提升到当前作用域的最上面,但是不调用函数
fn()
function fn(){
console.log(11) //执行结果为:11
}
预解析综合案例
var num=10
fun()
function fun() {
console.log(num)
var num=20
}
//相当于执行以下代码
var num
function fun(){
var num
console.log(num) //根据作用域链就近原则,输出结果为undefined
num=20
}
num=10
fun();
f1()
console.log(c)
console.log(b)
console.log(a)
function f1() {
var a = b = c = 9 //与var a=9,b=9,c=9 集体声明不同
console.log(a)
console.log(b)
console.log(c)
}
//相当于执行以下代码
function f1() {
var a
a=b=c=9
// b和c直接赋值,没有var声明,相当于 全局变量
console.log(a) //输出结果:9
console.log(b) //9
console.log(c) //9
}
f1()
console.log(c) //9
console.log(b) //9
console.log(a) //报错:a is not defined
闭包函数
有权访问另一函数作用域内变量的函数
简单理解:一个作用域可以访问另一个函数的局部变量
function fn() {
var times=0
/* var c=function(){
return ++times
}
return c*/
return function(){
return ++times
}
}
//js中对闭包的语法规范
//在函数外部访问函数内部的变量
var count=fn() //类似于函数表达式
//js解析器使得times不再初始化,给它一个空间存储
console.log(fn)
console.log(fn())
console.log(count())
console.log(count())
console.log(count())
应用:
1、点击对应的 li 输出当前索引号
2、3秒钟之后打印 li 内容
//点击li输出当前索引号
// 1、利用动态添加属性方式
var lis = document.querySelector('.nav').querySelectorAll('li')
for (var i = 0; i < lis.length; i++) {
lis[i].index = i
lis[i].onclick = function () {
console.log(this.index)
}
}
//2、闭包方式
for (var i = 0; i < lis.length; i++) {
(function (i) {
lis[i].onclick = function () {
console.log(i) //使用了闭包
}
})(i) //立即执行函数均为闭包,函数里任何一个函数都可以使用括号里的变量
}
// 3秒钟之后,打印所有li元素内容
var lis = document.querySelector('.nav').querySelectorAll('li')
//for是同步任务,定时器是异步任务
for (var i = 0; i < lis.length; i++) {
(function (i) {
setTimeout(function () {
console.log(lis[i].innerHTML)
}, 3000)
})(i)
思考案例:以下两种条件有无闭包的产生
var name="The Window"
var object={
name:"My Object",
getNameFunc:function(){
return function(){
return this.name
}
}
}
console.log(object.getNameFunc()()) //输出 The Window
//分解类似于
var f=object.getNameFunc()
f()
//进一步分解类似于
var f=function(){
return this.name
}
f() //相当于立即执行函数 function(){}()
//无闭包的产生
var name="The Window"
var object={
name:"My Object",
getNameFunc:function(){
var that=this //在函数里,指向的是object
return function(){
return that.name
}
}
}
console.log(object.getNameFunc()()) //输出 My Object
//分解类似于
var f=object.getNameFunc()
f()
//进一步分解
var f=function(){
return that.name
}
f()
//有闭包的产生 在函数外部访问了that变量 变量所在的函数getNameFunc()是闭包函数
用途:
- 在函数外部读取函数内部的变量
- 延伸了变量的作用范围
- 让变量的值始终保持在内存中
注意:由于闭包会使得函数中变量一直被保存在内存中,内存消耗很大,所以闭包的滥用可能会降低程序的处理速度,造成内存损耗等问题