前言
这一块的概念其实被反复讲了很多次,每个人都有自己不一样的理解。两者的重要性不言而喻,基本我们每天的代码都会多多少少接触到,而两者的又有千丝万缕的联系。以此做梳理前端知识体系的入口,我觉的有一定的门槛,但是对后续学习而言,这个基础打扎实了,以后万千大楼平地起便有了可能。
作用域
概念:这是一套规则,一套可以用来存储变量的,且方便后续查找变量的规则。
我们通过一个例子来说明这个概念,“ var=1,里面发生了什么 ”,
- 首先编译器会询问作用域内是否存在 a 这个名称的变量,存在的话,编译器会直接忽略声明,然后继续编译。但不存在的话,它会要求作用域声明出一个变量,并命名为a。
- 接着,引擎开始执行,在作用域里面查找叫做 a 的变量,找到的话 执行 =2 的赋值操作,没有的话,会继续从局部找到全局,再找不到的话,会直接抛出异常。<br /
( 引擎:负责程序的编译和执行过程,编译器:负责语法分析和代码生成 )
可见作用域是一个存储变量的地方,无论是引擎还是编译器都要向它拿变量或者添加变量。
接着说明几点规则,
首先是查找规则,我画了一个简陋的图,如图有两层作用域,当在fn 里面需要a时,本身fn是没有的,但是它会向上查找,一层层网上寻找,直到全局环境,找不到的话就报异常。而且作用域会在找到第一个匹配的标识符停止,那以上不过有多少个相同的变量都会被屏蔽掉,这叫“遮蔽效应”。
词法作用域
作用域有两种工作模型: 词法作用域和动态作用域,大部分编程语言使用的都是词法作用域,JS也是。动态作用域可自行查阅了解。
概念:顾名思义就是定义在词法阶段的作用域,何谓词法阶段,就是语言编译器的第一个阶段词法化的过程,将字符串分解组成有含义的代码块。简单点说就是由你在写代码时将变量和块作用域写在哪里决定的。
所以变量也好,作用域也好,早在书写的时候就可以预见到了,这也是区别动态作用域的一点,因为动态作用域是在执行代码的是才确定的。
作用域的具体类型分两种:函数作用域和块作用域
function fn () {
var a = 1
console.log(a)
}
这样形成一个函数作用域,函数内的变量可以再整个函数范围内使用及复用,其中有嵌套他的作用域的话,也能使用。而且外部是无法主动访问内部的变量的,除非使用闭包。这种作用域有几个特点,隐藏内容实现,因为外部无法访问到,而且可以避免同名标识符的冲突。但是也个有问题,一旦定义了一个函数,它将势必“污染”全局作用域,其次无法自动执行,需要手动调动 fn()。
因此便衍生出了,一种概念 IIFE,又名立即执行函数
(function () {
var a = 2;
console.log(a)
})()
!function(){
var a = 2;
console.log(a)
}()
当然类似的写法有好多种如 !,~,+,-等开头的,原理是第一个()将里面包裹函数,成了一个表达式,第二个()执行这个表达式,于是函数就执行了。
块作用域,代码块{},也能有属于自己的变量和函数
在let,const 出来之前,js并没有具体的块作域的功能,但倒是有几个不显而易见的语法,
with(),这个语法很少用,但是它声明出来的内容仅在里面有用,外面并不能用到。
var obj = {a: 1,b:2,c:3}
with(obj) {
a = 3;
b = 4;
c = 5;
}
其次就是 try/catch,这里的catch分句也会形成一个块作用域。但是这两种算是很少用,也很难用,对于要是使用块作用来讲的话,直到es6推出了let,const。
let,const 其所在的区域,大部分是{},会自行形成块级作用域,并且不会出现变量提升,不可重复定义或赋值(const),形成暂时性死区(不声明,不可直接使用)。大大提升了代码的质量。
提升
变量提升是js中非常有趣的一点,在不使用let或const的时候,你要好好分析,变量的执行顺序并适当用到变量提升的知识。但这往往也是令人头大。
a = 2;
var a;
console.log( a );
来看看这段代码会打印什么吧。答案是2,因为var a 在编译时被提升了,于是后续的赋值操作也能继续.当然就算不定义a 在浏览器里面也会正常执行,因为这时候的 a 会自动挂载在windwo下面,也就是window.a
首先明确一点,包括变量和函数在内的所有声明都会在代码被执行前首先被处理。 而它们的声明从代码出现的位置被移动到最上面的过程,叫做“提升”
注意一点,函数声明会被提升,函数表达式却不会。
function foo () {} //会提升
let foo = function bar () {} //这里只会吧 foo 提升上去,后面的bar 不会赋值
当出现多个重复的声明时,函数会被先提升,然后才是变量
foo(); // 1
var foo;
function foo() { console.log( 1 );
}
foo = function() { console.log( 2 );
};
这里的 function foo 会被提升首先执行,然后执行 foo(), 最后再重新赋值。尽管中间插了 var foo,但是它是重复声明,会被忽略,以函数为主。这也符合函数的JS一等公民的说法。
闭包
这个概念其实很难讲,很多文章会把闭包讲的很复杂,虽然他也确实可以变得很复杂,但我觉得还是按简单的,通俗易懂的说法来讲比较好,深度化的东西,只能看自己慢慢探索。
概念:函数可以记住并访问所在的词法作用域,就算函数在外部执行,这个函数就是一个闭包
function foo() {
var a = 2;
function bar() {
console.log( a );
}
return bar;
}
var baz = foo();
baz();
闭包的使用场景很多,定时器,事件监听,ajax请求,Web Workers等实际上都是闭包的使用。它能使外部访问函数内部的变量,做到即是函数执行结束了,内部的变量也不会被垃圾回收机制回收掉。其内部的作用域依赖存在,可供随时使用。但是缺点也是显而易见的,因为变量无法被回收,当你的代码里面存在大量的闭包时,有可能导致内存泄露。
ES6的模块化本身其实就是闭包思想的体现。
结语
以上概念均来自于《你不知道的JavaScript》,这算是对书上知识的总结。有很多细节的地方,没有提到,感兴趣的同学可以去翻翻这本书。
以前总是看文章啊,看书之类的。但是这样的吸收效率其实很低,不久后就忘了。于是想着把它输出出去,事实证明输出才是真正去了解和掌握的过程。写文章真的累,要思考逻辑是否连贯,行文是否清晰,就算全部照搬文字,也要思考如何搬运。期间也加深了理解。
能力有限,不足也有,轻喷。
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!