你不知道的JavaScript(上)作用域与闭包
第一部分 作用域与闭包
第一章 作用域是什么
1、作用域
变量赋值操作会执行两个动作:首先编译器会在当前作用域中声明一个变量(如果之前没有声明过),
然后会在运行时引擎会在作用域中查找该变量,找到就会对他赋值。
2、理解LHS和RHS操作赋值
简单来说 LHS:查找的目的是进行变量赋值,使用LHS查询【存】
RHS:目的是获取变量的值,就会用RHS【取】
3、JavaScript编译原理 例如var a=2会被拆分为 var a在其作用域中声名新变量;a = 2会查询变量a并且对它赋值
4、不成功的RHS会抛出ReferenceError异常。不成功的LHS引用会导致自动隐式地创建一个全局变量(非严格模式下),该变量使用LHS引用的目标未标识符,或者抛出ReferenceError异常(在严格模式下)
第二章 词法作用域
词法作用域意味着作用域由书写代码时函数声明的位置来决定的,JavaScript有两个机制可以“欺骗词法作用域”:eval(...)和with.
1、eval()可以对一段包含一个或多个声明的“代码”字符串进行演算,借此修改已经存在的词法作用域(在运行时)
2、with本质上是通过将一个对象的引用当作作用域来处理,将对象的属性当作左通谕德标识符来处理,
从而创建一个新的词法作用域(同样在运行时)
不推荐使用,因为由副作用:
都会使引擎无法在编译时对作用于查找进行优化,,都会导致代码运行变慢【不要使用它们】
第三章 函数作用域与块作用域
函数作用域的含义时指,属于这个函数的全部变量都可以在整个函数的范围内使用及复用(事实上在嵌套的作用域中也可以使用)
隐藏内部实现
“隐藏”变量和函数是一个有用的技术。原因:
1、最小授权或最小暴露原则,这个原则是指在软件设计中,应该最小限度地暴露必要内容,而将其他内容都“隐藏”起来
2、可以规避同名标识符之间的冲突
如何解决?
方法一:全局命名空间
在全局作用域中声明一个足够独特的变量,通常是一个对象。这个对象被用作库的命名空间,所有需要暴露在外界的功能都会
成为这个对象的属性,而不是将自己的标志符暴露在顶级的词法作用域中
例如
var MyAnimals = { style:"comfortable", eatingSomething:function(){ }, doSomething:function(){ } };
方法二 模块化的管理
3、函数作用域
var a = 2; (function foo(){ var a = 3; console.log(a);//3 })(); console.log(a);//2
区分函数声明和函数表达式最简单的方法是看function关键字出现在声明中的位置(不仅仅是一行代码,而是整个声明中的位置),如果function是声明中的第一个词,那么就是函数声明,否则就是函数表达式。
4、匿名与具名 :函数表达式可以使匿名的,但是函数声明不可以省略函数名
5、立即执行函数表达式
1 var a = 2; 2 (function foo() { 3 var a = 3; 4 console.log( a ); // 3 5 })(); 6 console.log( a ); // 2
6、块作用域
(1)with 用 with 从对象中创建出的作用域仅在 with 声明中而非外部作用域中有效
(2) try/catch try / catch 的 catch 分句会创建一个块作用域,其中声明的变量仅在 catch 内部有效
例子
1 try { 2 undefined(); // 执行一个非法操作来强制制造一个异常 3 } 4 catch (err) { 5 console.log( err ); // 能够正常执行! 6 } 7 console.log( err ); // ReferenceError: err not found
(3)ES6引入的新关键字let。let关键字可以将变量绑定到所在的任意作用域中(通常是 { .. } 内部)。也就是说,let为其声明的变量隐式地在所在的块作用域。
隐式的块
1 var foo = true; 2 if (foo) { 3 let bar = foo * 2; 4 bar = something( bar ); 5 console.log( bar ); 6 } 7 console.log( bar ); // ReferenceError
显式的快 只要声明是有效的,在声明中的任意位置都可以使用 { .. } 括号来为 let 创建一个用于绑定的块
var foo = true; if (foo) { { // <-- 显式的快 let bar = foo * 2; bar = something( bar ); console.log( bar ); } } console.log( bar ); // ReferenceError
注意:使用 let 进行的声明不会在块作用域中进行提升。声明的代码被运行之前,声明并不“存在”的
第四章 提升
(1)正确的思考思路是,包括变量和函数在内的所有声明都会在任何代码被执行前首先被处理。
(2)只有声明本身会被提升,而赋值或其他运行逻辑会留在原地
(3)函数优先 函数声明和变量声明都会被提升。但是一个值得注意的细节(这个细节可以出现在有多个“重复”声明的代码中)是函数会首先被提升,然后才是变量。
第五章 作用域闭包
闭包也就是函数嵌套函数
作用:1、作为返回值【定时器与闭包】
2、作为参数传递
模块模式两个必备条件:
1、必须有外部的封闭函数,该函数必须至少被调用一次(每次调用都会创建一个新的模块实例)
2、封闭函数必须返回至少一个内部函数,这样内部函数才能在私有作用域中形成闭包,并且可以访问或者修改私有的状态。
ES6中引入模块
1、import可以将一个模块中的一个或多个API导入到当前作用域中,并分别绑定在一个变量上
2、module会将整个模块的API导入并绑定到一个变量上
3、export会将当前模块的一个标识符(变量、函数)导出为公共 API
例子:
1 bar.js 2 function hello(who) { 3 return "Let me introduce: " + who; 4 } 5 export hello; 6 7 foo.js 8 // 仅从 "bar" 模块导入 hello() 9 import hello from "bar"; 10 var hungry = "hippo"; 11 function awesome() { 12 console.log( 13 hello( hungry ).toUpperCase() 14 ); 15 } 16 export awesome; 17 18 baz.js 19 // 导入完整的 "foo" 和 "bar" 模块 20 module foo from "foo"; 21 module bar from "bar"; 22 console.log( 23 bar.hello( "rhino" ) 24 ); // Let me introduce: rhino 25 foo.awesome(); // LET ME INTRODUCE: HIPPO