你不知道的Javascript(上卷)读书笔记之三 ---- 函数做用域与块做用域

1. 函数中的做用域 程序员

函数做用域的含义是指属于这个函数的所有变量均可以在整个函数范围内使用以及复用数据结构

 

2. 隐藏内部实现闭包

函数常用于隐藏”内部实现”,能够把变量和函数包裹在一个函数的做用域中,而后用这个做用域来隐藏它们。函数

这种基于做用域的隐藏方法基于软件设计中的最小特权原则(最小受权/最小暴露原则),好比模块的API设计。工具

引伸一下,若是变量和函数都放在全局做用域中,那么就会暴露过多的变量和函数,从而违背了最小特权原则,而这些变量本该是私有的,应该阻止外部访问的。额外的多余的访问权限可能被有意或者无心的以非预期的方式调用,从而致使超出了原本应该的使用条件,会使程序变得”危险”。这有点相似于Java的OOP中的封装,咱们能够经过函数隐藏内部,从而封装好程序。spa

        隐藏内部实现是为了规避冲突设计

为了保持变量或者函数的可读性,咱们老是但愿尽量想要起更加简单的标识符。可是,在大型多人开发项目中,标识符越简单,越容易重名冲突。标识符越复杂,虽然不容易发生重名冲突,可是可读性会变弱,并且繁琐冗长的代码容易引发程序员的反感,特别是在API十分的繁杂的状况下。调试

同时,在软件设计过程当中,存在某些状况要求使用一样的标识符名称,这种状况不是没有,偏偏相反,十分常见。(就好比Java的集合库,假如经常使用的集合类的添加方法,名称彻底不同,那真的是一件很恐怖的事情,这意味着咱们须要耗费不少脑容量去记忆这些API)code

在这种状况下,隐藏内部声明是惟一的最佳选择。可使用Javascript Object 去存储标识符、封装成对象、并隐藏内部函数的具体实现。对象

        a.全局命名空间

变量冲突的一个典型例子存在于全局做用域中。当程序加载了多个第三方库时,若是他没有妥善的将内部私有的变量或者函数隐藏起来,就会很容易引起冲突

这些库一般会在全局做用域中声明一个名字足够独特的变量,一般是一个对象。这个对象被用做库的命名空间,全部要暴露给外界的功能都会变成这个对象的属性。

        b.模块管理

还有另一种避免冲突的方法,它与现代的模块机制十分接近,这种方式并非将库加载到全局做用域中,而是经过依赖管理器将库的标识符显示的导入到另一个特定的做用域中.

 

3. 函数做用域

函数表达式和函数声明

函数声明进行调用:

function foo() {
    var a = 3;
    console.log(a);
}
foo();

函数表达式:

(function foo(){ 
    var a = 3; 
    console.log(a); 
})();

        3.1 匿名和具名

若是你仍是不理解函数表达式是什么意思,咱们其实常常用到:

setTimeout(function() { 
    console.log(“I waited 1 second”); 
}, 1000);

这叫匿名函数表达式。函数表达式是能够匿名的,函数声明则不能够。看起来函数表达式简单快捷,几近完美,可是它仍是有缺点的:

        A.匿名函数在栈追踪时,不会显示有意义的函数名,调试困难

        B.因为没有函数名,所以函数在引用自身只能使用已通过期的arguments.callee 引用,一种例子是递归,还有一种例子是事件触发后事件监听器要解绑自身

        C.匿名函数省略了对于代码理解比较重要的函数名,一个描述性的名称可让代码不言自明。

 

        3.2 当即执行函数表达式(IIFE)

给一个函数包含在()括号内部,所以成为了一个表达式,经过在末尾加上一个(),会使 得这个表达式当即执行。

IIFE的一个进阶用法就是把他们看成函数调用而且传递参数进去。

例如:

var a = 2; 
(function() IIFE(global){ 
    var a = 3; 
    console.log(a); 
    console.log(global.a); 
})(window); 
console.log(a);

IIFE有一种变化的用户是倒置代码的运行顺序,将要运行的函数放在第二位,在IIFE 执行以后看成参数传递进去。

var a = 2; 
(function IIFE(def){ 
    def(window); 
})(function def(global){ 
    var a = 3; 
    console.log(a); 
    console.log(global.a); 
});

 

4 块做用域

        4.1 with关键字是Js中为数很少的块做用域的一个例子,用with从对象中建立出的做用域仅仅在with声明中有效

        4.2 try、catch

        4.3 let

ES6提供了let关键字,提供了除了var以外的另一种变量声明方式,它会将变量绑定在循环所在的块做用域上。

可使用显式的{}块,显式地将变量使用let绑定在块做用域上

可是使用let声明的变量不会进行变量提高。

使用let 还有额外的好处:

                a. 垃圾收集

function process(data) { 
    // 在这里作点有趣的事情 
} 
var someReallyBigData = { .. }; 
process( someReallyBigData ); 
var btn = document.getElementById( "my_button" ); 
btn.addEventListener( "click", function click(evt) { 
    console.log("button clicked"); 
}, /*capturingPhase=*/false );

click 函数的点击回调并不须要 someReallyBigData 变量。 理论上这意味着当 process(..) 执行后, 在内存中占用大量空间的数据结构就能够被垃圾回收了。 可是, 因为 click 函数造成了一个覆盖整个做用域的闭包, JavaScript 引擎极有可能依然保存着这个结构( 取决于具体 实现)。

块做用域能够打消这种顾虑, 可让引擎清楚地知道没有必要继续保存 someReallyBigData 了:

function process(data) { 
    // 在这里作点有趣的事情 
}
//在这个块中定义的内容能够销毁了! 
{ 
    let someReallyBigData = { .. }; 
    process( someReallyBigData ); 
} 
var btn = document.getElementById( "my_button" ); 
btn.addEventListener( "click", function click(evt){ 
    console.log("button clicked"); 
}

为变量显式声明块做用域,并对变量进行本地绑定是很是有用的工具, 能够把它添加到你的代码工具箱中了。

                b.let循环

for(let i = 0; i < 10; i++) 
    console.log(i);

与正常使用var变量的for循环不一样,var变量声明可能会污染全局空间,将变量绑定在全局做用域上,使用let进行变量声明能够直接把变量绑定在块级做用域上。

        4.4 const

除了 let 之外, ES6 还引入了 const, 一样能够用来建立块做用域变量, 但其值是固定的( 常量)。 以后任何试图修改值的操做都会引发错误。

相关文章
相关标签/搜索