JS之作用域是什么(二)

函数作用域和块级作用域

一、函数中的作用域

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

   function foo(a) {
     var b = a;
     function bar() {
       var c = 3;
       console.log(b)  // a的值
     }
     console.log(c)  // c is not defined
   } 

隐藏内部实现: 我们将一段代码封装到一个函数内部,那么函数外部的作用域就无法访问函数内部的变量等,这时,就相当于当前函数的作用域将此代码块"隐藏" 起来。

疑问(为什么要隐藏变量或者是函数?有什么优势吗?)
大多数情况下我们都不希望变量或者函数暴露在全局作用域中,这么做虽然方便了我们在局部作用域中访问它们,但这样会破坏最小暴露原则(指在软件设计中,应该最小限度地暴露必要内容,而将其他内容都隐藏起来),因为可能会暴露过多的变量或函数,而这些变量或函数本应该是私有的,正确的代码应该是可以阻止对这些变量或函数进行访问的!

举个栗子:

function doSomething(a) {
  b = a + doSomethingElse(a * 2);
  console.log(b * 3)
}
function doSomethingElse(a) {
  return a - 1
}
var b
doSomething(2)  

上述例子,将函数doSomethingElse和变量b都声明在了全局作用域中,这是没必要的,并且可能会存在"危险"。因为可能会被有意或无意的非预期的方式使用。跟合理的写法如下:

function doSomething (a) {
  var b
  function doSomethingElse(a) {
    return a - 1
 }
 b = a + doSomethingElse(a * 2)
 console.log(b * 3)
}
doSomething (2)

同样的功能,下面的方式就显得相对严谨,此时b和函数doSomethingElse在外部作用域都无法访问,最终只有被doSomething函数控制,具体内容被私有化了。

另一个"隐藏"的好处:规避冲突
“隐藏” 作用域中的变量和函数所带来的另一个好处,是可以避免同名标识符之间的冲突,两个同一标识符可能具有不同的用途,无意间可能造成命名冲突,从而导致变量的值被意外覆盖。

匿名和具名:

  • 匿名:没有名称标识符的函数表达式(比如回调函数)
setTimeOut(function() {
  console.log("我是被一秒后打印出来的")
}, 1000)

这里的第一个参数是一个匿名函数,函数表达式可以是匿名的,而函数声明就不可以省略函数名。具名函数和匿名函数的区别就是一个有名字一个没名字。功能都是一样的,具名函数能帮助我们快速理解函数表达式的功能。

立即执行函数表达式:

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

// 另一种方式
  var a = 2;
 (function foo() {
   var a = 3;
   console.log(a); // 3
 }());
 console.log(a)   // 2

上述例子中,函数被()括起来变成了函数表达式,又通过()调用了函数。这种模式就是IIFE(立即执行函数表达式),IIFE最常见的用法是使用一个匿名函数表达式。两种方式都是同样效果,只是写法不同。

// 多么可爱的例子
var a = 2;
(function IIFE(def) {
  def(window);
}) (function def(global) {
  var a = 3;
  console.log(a); 
  console.log(global.a); 
});
二、块级作用域

良言: 变量的声明应该距离使用的地方越近越好,并最大限度的本地化。

一个例子:

function process(data) {
  // 在这里做点有趣的事情
}
// 在此定义一个显示块级作用域
{
  let someReallyBigData = {...}
  process( someReallyBigData  )
}

var btn = document.getElementById("my_button");
btn.addEventListener("click", function click(evt) {
  console.log("button clicked");
})

使用let的for循环:

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


// ========================================================================


{
  let j;
  for (j=0; j<10; j++) {
    let i = j; // 每个迭代重新绑定?
    console.log(i);
  }
}

疑惑:for循环头部的let不仅将i绑定到了for循环的块中,事实上它将其重新绑定到了循环的每一个迭代中,确保使用上一个循环迭代结束时的值重新进行赋值。(这里重新赋值有点模糊)

const关键字
也可创建块级作用域,但其值时固定不变的,也不能进行修改操作。

var foo = true
if (foo) {
  var a = 2;
  const b = 3;
  a = 3;
  b = 4;
}
console.log(a)
console.log(b) 
提升

引擎会在解释JavaScript代码之前首先对其进行编译。编译阶段中的一部分工作就是找到所有的声明,并用合适的作用域将他们关联起来

console.log(a)
var a = 2
//=================
 var a 
 console.log(2)
 a = 2

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

函数优先:
函数声明和变量声明都会被提升,但是一个值得注意的细节是函数首先会被提升,然后才是变量

foo()
var foo
function foo() {
  console.log(1)
}
foo = function() {
  console.log(2)
]

//================================
function foo() {
  console.log(1)
}
foo ()
foo = function() {
  console.log(2)
}

重复声明,函数声明会被提前,而函数表达式赋值这种形式,会被当做变量提升来处理(若后面还有函数声明,同样也会覆盖前面的)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值