JS-函数作用域和块作用域

199 阅读3分钟

函数中的作用域

函数的作用域的含义是指,属于这个函数的全部变量都可以在整个函数的范围内使用及复用(事实上在嵌套的作用域中也可以使用)

隐藏内部实现

可以从所写的代码中挑选出一个任意的片段,然后用函数声明对它进行包装,实际上就是把这些代码隐藏起来

它们大都是从最小特权原则中引审出来的,也叫最小授权或最小暴露原则。这个原则是指软件设计中,应该最小限度地暴露必要内容,而将其他内容隐藏起来,比如某个模块或对象的api设计

规避冲突

隐藏作用域中的变量和函数所带来的另一个好处,是可以避免同名标识符之间的冲突

  1. 全局命名空间

    第三方库通常会在全局作用域中声明一个名字足够独特的变量,通常是一个对象。这个对象被用作库的命名空间,所有需要暴露给外界的功能都会成为这个对象的属性,而不是将自己的标识符暴露在顶级的词法作用域中。

  2. 模块管理

    另外一种避免冲突的方法和现代的模块机制接近,就是从众多模块中挑选一个来使用,通过依赖管理器的机制将库的标识符显式地导入到另一个特定的作用域中。

函数作用域

可以通过在任意代码片段外部添加包装函数,可以将内部的变量和函数定义隐藏起来。

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

这种技术可以解决一些问题,但是会导致一些额外的问题

  1. foo这个名称会污染所在的作用域
  2. 必须显式的调用foo这个函数才能运行其中的代码

js提供了同时解决这两个问题的方案

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

console.log(a); // 2
  1. 包装函数的声明以(function...而不仅仅是以function...开始。
  2. 函数会被当作函数表达式而不是一个标准的函数声明来处理。

区别声明函数和表达式最简单的方法是看function关键字出现在声明中的位置(不仅仅是一行代码,而是整个声明中的位置)。如果function是声明中的第一个词,那么就是一个函数声明,否则就是一个函数表达式

这种模式很常见,几年前社区给它规定了一个术语:IIFE,立即执行函数表达式

IIFE的另一种进阶用法:传递参数进去

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

console.log(a); // 2

IIFE的另一种用途:倒置代码的运行顺序

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

这里def被当作传递进去的函数被调用,并将window传入当作global参数的值

块作用域

下面的代码你一定很常见

for(var i = 0;i<10;i++){
	console.log(i);
}
console.log(i) // 10

我们在for循环的头部直接定义了变量i,通常是想在for循环内部的上下文中使用i,而忽略了i会被绑定在外部作用域(函数或全局)中的事实

幸好,在ES6中引入了一个新的let关键字,提供了除var以外的另一种变量声明方式

let关键字可以将变量绑定到所在的任意作用域中(通常是{ .. }的内部)

for (let i = 0;i<10;i++){
  console.log(i);
}
console.log(i); // ReferenceError

let声明附属于一个新的作用域而不是当前的函数作用域(也不属于全局作用域)

出了let外,还有const

用来创建块作用域域变量,其值是固定的常量