你不知道的javascript读书笔记
作用域
作用域就是作用的范围,指它在函数哪些范围内可以使用,要是在其他不可使用范围想使用的话,就要重新定义
词法作用域
词法作用域就是定义在词法阶段的作用域。换句话说,词法作用域是由你自己写代码时将变量和块作用域写在哪里决定的,因此词法分析器处理代码时会保持作用域不变
函数中的作用域
看下面一段代码
function foo(a) {
var b = 2;
// 一些代码
function bar() {
// ...
}
// 更多的代码
var c = 3;
}
在这段代码中,foo(..) 的作用域中包含了标识符 a、b、c 和 bar。
bar(…)拥有自己的作用域,全局作用域也有自己的作用域气泡,它只包含了一个标识符:foo。
由于标识符 a、b、c 和 bar 都附属于 foo(..) 的作用域,因此无法从 foo(..) 的外部 对它们进行访问。也就是说,这些标识符全都无法从全局作用域中进行访问,因此下面的代码会导致 ReferenceError 错误:
bar();//报错
console.log(a,b,c);//报错
函数作用域的含义是指,属于这个函数的全部变量都可以在整个函数的范围内使用及复用。这种设计方案是非常有用的,能充分利用 JavaScript 变量可以根据需要改变值类型的“动态”特性。
隐藏内部实现
对函数的传统认知就是先声明一个函数,然后再向里面添加代码。但反过来想也可以带来一些启示:从所写的代码中挑选出一个任意的片段,然后用函数声明对它进行包装,实际上就是把这些代码“隐藏”起来了。
实际的结果就是在这个代码片段的周围创建了一个作用域气泡,也就是说这段代码中的任何声明(变量或函数)都将绑定在这个新创建的包装函数的作用域中,而不是先前所在的作用域中。换句话说,可以把变量和函数包裹在一个函数的作用域中,然后用这个作用域来“隐藏”它们。
为什么“隐藏”变量和函数是一个有用的技术?
有很多原因促成了这种基于作用域的隐藏方法。它们大都是从最小特权原则中引申出来的,也叫最小授权或最小暴露原则。
这个原则是指在软件设计中,应该最小限度地暴露必要内容,而将其他内容都“隐藏”起来,比如某个模块或对象的 API 设计。
这个原则可以延伸到如何选择作用域来包含变量和函数。如果所有变量和函数都在全局作用域中,当然可以在所有的内部嵌套作用域中访问到它们。但这样会破坏前面提到的最小特权原则,因为可能会暴漏过多的变量或函数,而这些变量或函数本应该是私有的,正确的代码应该是可以阻止对这些变量或函数进行访问的。。
function doSomething(a) {
b = a + doSomethingElse( a * 2 );
console.log( b * 3 );
}
function doSomethingElse(a) {
return a - 1;
}
var b;
doSomething( 2 ); // 15
在这个代码片段中,变量 b 和函数 doSomethingElse(..) 应该是 doSomething(..) 内部具体实现的“私有”内容。给予外部作用域对 b 和 doSomethingElse(..) 的“访问权限”不仅没有必要,而且可能是“危险”的,因为它们可能被有意或无意地以非预期的方式使用, 从而导致超出了 doSomething(..) 的适用条件。更“合理”的设计会将这些私有的具体内容隐藏在 doSomething(..) 内部:
function doSomething(a) {
function doSomethingElse(a) {
return a - 1;
}
var b;
b = a + doSomethingElse( a * 2 );
console.log( b * 3 );
}
doSomething( 2 ); // 15
现在,b 和 doSomethingElse(..) 都无法从外部被访问,而只能被 doSomething(..) 所控制。功能性和最终效果都没有受影响,但是设计上将具体内容私有化了,设计良好的软件都会 依此进行实现。
规避冲突
“隐藏”作用域中的变量和函数所带来的另一个好处,是可以避免同名标识符之间的冲突, 两个标识符可能具有相同的名字但用途却不一样,无意间可能造成命名冲突。冲突会导致变量的值被意外覆盖。
1、全局命名空间
变量冲突的一个典型例子存在于全局作用域中。当程序中加载了多个第三方库时,如果它们没有妥善地将内部私有的函数或变量隐藏起来,就会很容易引发冲突。
这些库通常会在全局作用域中声明一个名字足够独特的变量,通常是一个对象。这个对象被用作库的命名空间,所有需要暴露给外界的功能都会成为这个对象(命名空间)的属性,而不是将自己的标识符暴漏在顶级的词法作用域中。
2、模块管理
另外一种避免冲突的办法和现代的模块机制很接近,就是从众多模块管理器中挑选一个来使用。使用这些工具,任何库都无需将标识符加入到全局作用域中,而是通过依赖管理器 的机制将库的标识符显式地导入到另外一个特定的作用域中。
显而易见,这些工具并没有能够违反词法作用域规则的“神奇”功能。它们只是利用作用域的规则强制所有标识符都不能注入到共享作用域中,而是保持在私有、无冲突的作用域中,这样可以有效规避掉所有的意外冲突。
函数作用域
立即执行函数和普通声明函数的区别与作用?
两者最重要的区别就是它们的名称标识符将会绑定在何处
//代码片段1:
var a = 2;
function foo(){
var a = 3;
console.log(a);//3
}
foo();
console.log(a);//2
//代码片段2:
var a = 2;
(function foo(){
var a = 3;
console.log(a);//3
})();
console.log(a);//2
比较前面两段代码,第一个片段的foo被绑定在所在作用域中,可以直接通过foo()来调用,第二个片段中foo被绑定在函数表达式自身的函数中而不是所在作用域中。换句话说 (function foo(){…})作为函数表达式意味着foo只能在…所代表的位置中被访问,外部作用域则不行。foo变量名被隐藏在自身中意味着不会非必要的污染外部作用域
匿名函数的缺点
1、在栈追踪没有名字,很难调试
2、没有函数名,在需要引用自身时只能使用已经过期的arrguments.callee引用,比如:在递归中,另一个函数需要引用自身的例子,是在事件触发后事件监听器需要解绑自身(不太明白)。
行内函数表达式非常强大且有用——匿名和具名之间的区别并不会对这点有任何影响,给函数表达式指定一个函数名就可以解决上述问题了。
setTimeout(function timer(){ //函数表达式有名字了
console.log("hahahahaha")
},1000)