1.作用域是什么?
作用域是一套根据名称在当前作用域及嵌套作用域中进行查找变量的规则。(在哪,怎么查)
用来存储变量(标识符)并之后可以方便的找到这些变量,确定当前代码对这些变量的访问权限。
2.如何作用?
在引擎运行时,通过编译器的结果协助引擎查询变量。
代码编译通常有 词法分析,语法分析(AST), 生成可执行代码
引擎查询时分为两种,
一种是LHS查询。主要是查询在赋值操作符左侧的变量,等待赋值,其中函数传参是隐形的LHS查询;
另一种是RHS查询。主要是查询并获取变量的值。
区别:
RHS查询失败,直接抛出ReferenceError;
LHS非严格模式下,自动创建全局变量;严格模式下,抛出ReferenceError异常。
function foo(a) { var b = a; return a + b; } var a = foo(2); // 含有3个LHS查询,4个RHS查询 // 3个LHS: a = , a=2(隐式), b=.. // 4个RHS:foo, =a, a +, +b
3. 作用域嵌套
遍历嵌套作用域的规则是引擎从运行时所在的当前作用域开始查找,如果找不到,向上一级作用域请求,直到全局作用域。
如果找不到,引擎抛出异常ReferenceError。
下图是个嵌套作用域。
1是全局作用域,包含标识符foo;
2是foo创建的作用域,包含标识符a, b,bar;
3是bar创建的作用域, 包含标志符c;
4.词法作用域
词法作用域也就是词法阶段的作用域。由写代码时的位置决定。一般不变。
无论函数在哪里被调用,如何被调用,它的词法作用域都只由函数被声明时所处的位置决定。
词法作用域只能查找一级标识符,如a,b,c
对于访问foo.bar,baz。词法作用域只能查找foo,剩下的通过对象属性访问规则访问。
欺骗词法作用域
对于大多数情况来说,代码写完,词法作用域就不再变化。但也有例外,可以在运行时修改(欺骗)词法作用域。
⚠️最好不使用。会导致性能下降。并且在严格模式下失效。
实现机制有两种:
1)eval()
eval(str)函数接收一个字符串作为参数,并将其中的内容看作书写时就存在的代码。
function foo(str, a) { eval(str); // 欺骗--相当于var b = 3; console.log(a, b); } var b = 2; foo("var b = 3;", 1); // 1, 3 function foo(str, a) { "use strict" eval(str); // 严格模式下,eval有自己的作用域 console.log(a, b); } var b = 2; foo("var b = 3;", 1); // 1, 2
2) with(已被禁止不做考虑)
性能下降原因:
js引擎在编译阶段会对作用域查找进行优化。预先确定变量和函数的定义位置,快速找到标志符。
但是如果使用了eval(), 引擎会默认所有标识符的位置都不确定,则所作的优化无效。
尽量不要使用eval()
5. 函数作用域
函数作用域是属于这个函数的所有标志符(变量、函数)都能在函数范围内使用和复用。但是函数外部不能使用。
function foo(a) { function bar(){ } } bar(); // ReferenceError
隐藏内部实现
根据函数作用域的规定,可以将一些代码包裹在一个声明的函数中,使外部无法访问,达到“隐藏”代码的目的。
必要性:
依据软件设计的最小授权(最小暴露)原则,将一些代码隐藏在函数作用域内部,不影响功能和实现,
但是可以将代码私有化,不过多的暴露变量或者函数。
而且可以避免命名重复的冲突。
⚠️为了避免全局变量污染和冲突,自定义的插件后者第三方库,最好定义自己的命名空间,将所有的变量和方法挂载到一个对象上。
如jquery。(单例模式)
缺点:
1)需要声明一个函数,可能导致污染全局作用域
2)需要调用声明的函数
解决方案:
//改善前: var a= 2; function foo() { // 函数声明,foo处于全局作用域 var a= 3; console.log(a); // 3 } console.log(a); // 2 //改善后 var a= 2; (function foo(){ // 不是以function开始,是函数表达式,作用域在foo内部 var a = 3; console.log(a);//3 })(); console.log(a); // 2 foo() // ReferenceError: foo is not defined
具名和匿名
匿名函数在栈追踪中没有有意义的函数名,会使得调试困难;不能引用自身;没有可读性;
所以建议在所有使用匿名函数的地方都改为具名函数。
setTimeout(function time(){ // 有名字的函数表达式 },100)
立即执行函数表达式IIFE
(function IIFE(){ //... })(params) // 第一个括号表示函数表达式,第二个括号立即执行 等同于 (function IIFE(){ }(params));
立即执行函数表达式可以传递参数,参数类型可以是变量,如window;也可以是函数。
// 传参是变量 var a = 2; (function IIFE(global){ var a = 3; console.log(a); //3 console.log(global.a); //2 })(window); // 注意node环境中没有window;在console中可以测试
// 传参是函数 var a = 2; (function IIFE(def){ def(window); })(function def(global) { var a = 3; console.log(a); //3 console.log(global.a); //2 })