04:JavaScript 的闭包
一、定义:
常规定义:
闭包的定义: 有权利访问外部函数作用域的函数。
通俗定义:
1、函数内部包含了函数。然后内部函数可以访问外部函数的作用域。
2、内部函数可以访问 父级函数的作用域。
...等等等
二、思考:
1、我们在日常的开发过程中会应用到 闭包么?
2、如果有遇到的话,会是在什么情况下遇到的?
3、举一些 具体的例子。
1、我们在日常的开发过程中会应用到 闭包么?
以之前的知识对于 闭包的理解来讲是这样的
(function(){
for(var i=0; i<10; i++) {
console.log(i)
}
})()
或者说是这样的
var fnX = function() {
var x = 123
function y() {
alert(x)
}
y()
}
fnX() // 123
总结下之前的理解就是: 内部函数能访问外部函数作用域,能够保存变量不被销毁而一直存在。
三、作用:
在JavaScript中有作用域和执行环境的问题,在函数内部的变量在函数外部是无法访问的,在函数内部却可以得到全局变量。由于种种原因,我们有时候需要得到函数内部的变量,可是用常规方法是得不到的,这时我们就可以创建一个闭包,用来在外部访问这个变量。
通过将一个方法或者属性声明为私用的,可以让对象的实现细节对其他对象保密以降低对象之间的耦合程度,可以保持数据的完整性并对其修改方式加以约束,这样可以是代码更可靠,更易于调试。封装是面向对象的设计的基石。
3.1 什么是作用域
3.1.1 ES5的作用域问题
在 ES5 中 我们常常会说的一个概念是 局部变量 和 全局变量
那么 局部变量 和 全局变量 所这个 局部 和 全局则为 作用域。
这个概念其实介绍起来还是比较多虚无。 但是我记得有一本书 叫 《你不知道的JS》
在这本书的 上册 作者详细的介绍了 作用域 这个概念。
作用域是什么
1.现代JavaScript已经不再是解释执行的,而是编译执行的。但是与传统的编译语言不同,它不是提前编译,编译结果不能进行移植。编译过程中,同样会经过分词/词法分析,解析/语法分析,代码生成三个阶段。
2.以var a = 2;语句为例,对这一程序语句对处理,需要经过引擎,编译器,作用域三者的配合。其中,引擎从头到尾负责整个javascript程序的编译和执行过程;编译器负责语法分析和代码生成;作用域负责收集并维护由所有声明的标识符组成的系列查询,并实施一套规则,确定当前执行的代码对这些标识符的访问权限。
3.对于var a = 2;编译器首先查找作用域中是否已经有该名称的变量,然后引擎中执行编译器生成的代码时,会首先查找作用域。如果找到就执行赋值操作,否则就抛出异常
4.引擎对变量的查找有两种:LHS查询和RHS查询。当变量出现中赋值操作左侧时是LHS查询,出现中右侧是RHS查询
词法作用域
1.词法作用域就是定义在词法阶段的作用域。词法作用域是由你在写代码时将变量和块作用域写在哪里决定的,词法处理器分析代码时会保持作用域不变
2.作用域查找会在找到第一个匹配的标识符时停止
3.eval和with可以欺骗词法作用域,不推荐使用
函数作用域和块作用域
1.JavaScript具有基于函数的作用域,属于这个函数的变量都可以在整个函数的范围内使用及复用
2.(function fun(){})() 函数表达式和函数声明的区别是看function关键字出现在声明中的位置。如果function是声明中的第一个词,那么就是一个函数声明,否则就是一个函数表达式
3.with,try/catch具有块作用域,方便好用的实现块级作用域的是es6带来的let关键字
提升
1. 变量的提升
2. 函数提升 (这里就不过多的赘述了)
动态作用域
1.词法作用域是一套引擎如何寻找变量以及会在何处找到变量的规则。词法作用域最重要的特征是它的定义过程发生中代码的书写阶段
2.动态作用域让作用域作为一个在运行时就被动态确定的形式,而不是在写代码时进行静态确定的形式。eg:
function foo(){
console.log(a); // 2
}
function bar(){
var a = 3;
foo();
}
var a = 2;
bar();
词法作用域让foo()中的a通过RHS引用到了全局作用域中的a,所以输出2;动态作用域不关心函数和作用域如何声明以及在何处声明,只关心从何处调用。换言之,作用域链是基于调用栈的,而不是代码中的作用域嵌套。如果以动态作用域来看,上面代码中执行时会输出3
3.JavaScript不具备动态作用域,但是this机制中某种程度上很像动态作用域,this关注函数如何调用。
3.1.2 ES6的作用域问题
在 ES6 中 出现了块级作用域的概念
let const 在() 内则 ()内的作用域 为 块级作用域。
3.2 什么是执行环境
执行环境 即为 当前作用域内的环境。
3.3 什么是作用域链
这个概念其实 也是比较虚的概念,不太好理解。但是一旦理解就不会忘记了。
所谓 链 其实就是链条, 将需要链接在一起的东西链接在一起(感觉说了一句废话)
作用域链的通俗理解:
在函数内部作用域 通过 作用域链 可以访问 函数外部作用域 的属性或者方法。
一层层的 作用域链 往外走 到最后 则为 window 对象的全局作用域。
然后这一条条的 作用域链 就形成了一整条关联的链条。
四、具体案例的分析:
这里 我们举了一个栗子
eg1:
function Person(name) {
this.name = name
this.getName = function() {
return this.name
}
}
var one = new Person('zhang')
one.getName() // zhang
one.name // zhang
var two = new Person('wang')
two.getName() // wang
one.name // wang
eg2:
function Person(name) {
var _name = name
this.getName = function() {
return _name
}
}
var one = new Person('zhang')
one.getName() // zhang
var two = new Person('wang')
two.getName() // wang
eg1 vs eg2
这二个例子进行对比,虽然 都拿到了自己想要的 name 但是 eg1 的方式会比 eg2 获取 name
的方式要多一个, 即为 作为对象的属性来 获取到 当前的 name (one.name)
那如果 你想让你的 name 属性只能通过 getName 方法来获取,不希望有别的方法来获取 甚至是改变的话,那么 闭包 设置私有属性就是一个很安全的做法,那么这个时候闭包的作用就体现出来了。
Github地址,欢迎 Star