从今天开始研究讨论javascript作用域,感兴趣的同学请保持关注
什么是javascript作用域?
作用域是指程序源代码中定义变量的区域。
作用域规定了如何查找变量,也就是确定当前执行代码对变量的访问权限。
谈起javascript作用域你会想到哪些知识点?
- 函数作用域
- 块级作用域
- 闭包
- 变量提升
- let 暂时性死区
- var let const
- this
- 一个变量的声明周期(从有到无)
- 全局作用域
- 局部作用域
- 作用域链
等等,这些都是关于作用域的知识点,
我下载了几个源码,我们观察一些图片
jquery.js
vue.js
–
axios.js
上面的三个图,你会发现他们在结构上有一个共同点
(function(){
//。。。代码
}('参数'))
为什么要这么设计?
解释此问题前,我们需要介绍一个概念,方便后边阅读
执行环境
《Javascript高级程序设计(第三版)》对这个概念有介绍
执行环境(Execution Context,简称Context)
Context定义了变量或函数有权访问的其他数据,决定了它们的各自行为,Context只是一个抽象概念,它对应很多内容,变量对象(Variable Object,简写VO)是其一,还有作用域链,this等,这些共同组成了执行环境这个概念。
执行环境: 所有 JavaScript 代码都是在一个执行环境中被执行的。执行环境是一个概念,一种机制,用来完成JavaScript运行时在作用域、生存期等方面的处理,它定义了变量或函数有权访问的其他数据(包含了外部数据),决定他们各自的行为。
包括以下分类:
全局执行环境: 全局环境是最外围的一个执行环境,根据ECMAScript实现所在的宿主环境不同,表示执行环境的对象也不一样,在web中,全局执行环境被认为是window对象。
函数执行环境: 每个函数都有自己的执行环境。
我们看一个栗子
let hisName = '郭靖'
function hero() {
let herName = "黄蓉"
let hisName = "洪七公"
console.log(hisName);//=>洪七公
}
hero()
console.log(hisName);//=>郭靖
console.log(herName);//Uncaught ReferenceError: herName is not defined
输出
//=>洪七公
//=>郭靖
//Uncaught ReferenceError: herName is not defined
hero内部的hisName变量覆盖了外部的全局的hisName变量
hero外部无法访问hero内部的变量(hero内部的变量被保护起来,形成了私有变量)
上面的栗子做一下改动
let hisName = '郭靖'
function hero() {
let herName = "黄蓉"
function likeFun() {
let like = '喜欢'
console.log(`${herName}喜欢${hisName}`);//=>黄蓉喜欢郭靖
}
likeFun()
console.log(like);//ReferenceError: like is not defined
}
hero()
输出
//=>黄蓉喜欢郭靖
//ReferenceError: like is not defined
通过上面的两个小栗子,我们可以得出以下结论:
内部可以访问外部变量,外部不可以访问内部变量
函数进行了嵌套,其实也是作用域进行了嵌套,js 会从内向外一层一层查找,找到后就返回,找不到就报错,形成了作用域链
一个栗子
const element='123321'
const arr1 = [1, 2, 3, 4, 5]
const arr2 = ['a', 'b', 'c', 'd', 'e']
arr1.forEach(element => {
console.log('element1:', element);
arr2.forEach(element => {
console.log('element2:', element);
})
});
虽然可读性很差,但是当内部没有对外部进行引用时,这么写也是允许的,
继续看一个栗子
let a = 1;
let b = 2;
function add(x,y) {
let a = 10;
let b = 20;
return a+b+x+y
}
function sum(x,y) {
let a = 'a';
let b = 'b'
console.log(a+b+x+y);
}
console.log(add(a,b));//=>33
sum(a,b)//=>ab12
add 和sum两个函数,它们将内部逻辑和全局进行了隔离,尽管外部(全局)存在变量a和变量b,
但是对函数内部没有影响,拿add来说,函数add如何与window进行交流,
(当然,交流不是必须的)如果要交流,以参数作为输入,以return作为输出(联想一下import和export)
打印一下全局变量
console.log(window)
现在全局作用域下多了几个变量:add,sum 。a和b呢?
??:let和const声明的变量不在window下,用var声明的变量会在window下,我们今天不讨论var
所以上面的栗子中的全局变量只有函数add和函数sum
当我们的项目越来越庞大的时候,过多的全局变量必然存在危险,首当其冲的就是变量的命名冲突,
或许你千方百计的对你的变量名称进行唯一化,命名了一些很长的,自认为不会引起冲突的名字,但是当你引入了第三方js库时,并且那个第三方库并没有妥善的处理自有变量,这时候依然存在命名冲突的风险,这并不能从根本上解决问题。
我们要最大限度的减少全局变量
现在我们对上面的栗子进行优化
function aFun(params) {
let a = 1;
let b = 2;
function add(x, y) {
let a = 10;
let b = 20;
return a + b + x + y
}
function sum(x, y) {
let a = 'a';
let b = 'b'
console.log(a + b + x + y);
}
console.log(add(a, b)); //=>33
sum(a, b) //=>ab12
}
aFun()
这样,add和sum就不在全局变量下了,有一个全局变量=>aFun,每次使用变量add和sum的时候需要调用aFun
再优化一下
(function aFun() {
//省略
})()
这样,aFun就不在全局作用域下了,不会引发全局变量污染,也不会引起命名冲突
如果你对上面的代码不理解,我们可以拆解一下
const aFun=function(){
//省略
}
aFun()
aFun就是
function(){
//省略
}
函数名后加一对()进行执行操作
我们比较一下两者区别
function aFun() {
//省略
}
aFun()
//----------------
(function aFun(){
//省略
}())
第一个,aFun在所在的作用域中(全局作用域),可以通过aFun进行调用
第二个,aFun在自己的函数表达式中,可以在自身内部调用,不能再外部调用
(function aFun(){
//省略
aFun()//可以在这里调用,小心递归死循环
}())
aFun()//这里调用会报错Uncaught ReferenceError: aFun is not defined
如果不会出现自己调用自己的情况可以直接使用匿名函数
(function (){
//省略
}())
现在回头看下开篇的那三个js源码,他们都采用了这种方式进行封装,对自有变量和自有方法进行隔离,防止污染全局
今天的内容不难,你get到了吗
END
最新评论
这小生活不错呀
不错,必须顶一下!
看着你还在坚持,很好
看来忙了也没时间更新博客了
NIce。学习了。。。。
网站不错!!!!
简洁实用,好文章!
不错,过来支持一下