你不知道的JavaScript-----函数作用域&&块作用域

目录

函数作用域的特点

函数作用域的作用

变量私有化

规避冲突

1. 全局命名空间

2. 模块管理

函数作用域

函数作用域的创建方式

匿名和具名

立即执行函数表达式

 块作用域

with

try/catch

let

const

总结


函数作用域的特点

  • 属于这个函数的全部变量都可以在整个函数的范围内使用及复用。
  • 外部无法访问到包裹在函数内部的任何内容。

函数作用域的作用

变量私有化

  • 变量私有化(最小权限原则),阻止某些变量或函数的访问

最小特权原则:在软件设计中,应最小限度的暴露必要内容,其他内容“隐藏”起来。比如某个模块或对象的API接口。

规避冲突

 规避冲突,避免标识符之间的冲突

1. 全局命名空间

  • 变量冲突的典型例子出现在全局作用域。
  • 当加载多个第三方库的时候,如果没有隐藏内部私有的变量和函数,就会出现冲突。
  • 通常做法是在全局作用域声明一个独特变量(通常是对象),这个变量叫做命名空间,所有变量都是它的属性。

2. 模块管理

函数作用域

在任意代码片段外部添加包装函数,可以将内部的变量和函数定义“隐藏”起来,外部作用域无法访问包装函数内部的任何内容。

痛点

  • 需要声明一个函数,但这个函数的函数名会污染所在作用域

  • 必须要调用这个函数才能执行内部的代码

如果函数不需要变量名且可以自动运行,那么效果将更加理想(立即执行函数)

函数作用域的创建方式

函数作用域的创建需要声明一个函数,而声明函数这个行为又有函数声明和函数表达式两种操作方式。

函数声明和函数表达式的辨别,可以通过一个小技巧来一眼分辨:看function关键字出现在声明中的位置,注意,不仅仅是一行代码,而是整个声明中的位置,如果function是声明中的第一个词,那就是函数声明,否则就是函数表达式。

function foo() {} // 函数声明
(function foo() {}); // 函数表达式,声明中的第一个词是()

函数声明和函数表达式的区别是它们的名称标识符将会绑定在何处。举个例子:

var a = 10;
function foo(a){
    var b = a * 2;
    return b;
}
console.log(foo);       //正确打印foo函数
foo();

(function fn(a){
    var c = a + 10;
    console.log(fn);    
    return c;
})();
console.log(fn);    //ReferenceError: fn is not defined

假设代码所处作用域为全局作用域,foo函数的访问作用域是全局作用域,fn函数的访问作用域被绑定在函数表达式自身的函数中而非所在的全局作用域。此时,fn变量被隐藏在自身作用域中就意味着不会非必要的污染外部作用域。

匿名和具名

setTimeout(function() { // 其中 function() {} 叫做匿名函数表达式
    console.log(1);
}, 1000);

这种函数表达式的常用之地是回调函数,它是没有名称标识符的。函数表达式可以省略函数名,但函数声明则不可以省略函数名,否则会报错。

匿名函数表达式的应用非常常见,很多工具或库都有用到,但其也存在几个缺点:

  • 匿名函数在栈中不显示有意义函数名,调试困难;
  • 由于没有函数名,所以如需调用自身,比如递归或者事件触发后事件监听器需要解绑自身等,就不太好办了,除非使用arguments.callee,但这个已非官方推荐实践,将被彻底废弃;
  • 同样由于没有函数名,导致代码可读性差,毕竟有个好的描述性名称,胜过额外添加注释。

setTimeout(function timeHandler() { // <-- 指定名字 timeHandler
    console.log(1);
}, 1000);

立即执行函数表达式

由于一个函数被包裹在()内,此时就变成了一个表达式,()外面再加上一个()则表示立即执行该函数。

立即执行函数也可以在调用的时候传参

var a = 2;
(function foo(global) {
    var a = 3;
    console.log(a); // 3
    console.log(global.a); // 2
})(window); // <-- 将立即调用函数当做函数调用,并传递参数进去
console.log(a); // 2

 块作用域

先来看个for循环

for(var i=0; i<10; i++) {
	console.log(i);
}
  • 我们在for循环头部直接定义了变量i,通常只是因为想在for循环内部的上下文使用i
  • 但问题是i会被绑定在外部作用域(函数或全局)中。

with

with 从对象中创建出的作用域仅在 with 声明中而非外 部作用域中有效。

try/catch

try/catch 的 catch 分句会创建一个块作用域,其中声明的变量仅在 catch 内部有效。

try {
  undefined();
} catch (e) {
  var err = e;
  console.log(err); // TypeError: undefined is not a function
}
console.log(err); // TypeError: undefined is not a function
console.log(e); // ReferenceError: e is not defined

let

let 关键字可以将变量绑定到所在的任意作用域中(通常是 { .. } 内部)。换句话说,let 为其声明的变量隐式地了所在的块作用域。

但是使用 let 进行的声明不会在块作用域中进行提升。声明的代码被运行之前,声明并不 “存在”。

var foo = true;
if (foo) {
	let bar = foo * 2;
	bar  = something(bar);
	console.log(bar);
}
console.log(bar); // 报错

 在来看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不仅绑定到了for循环的块中。
  • 事实上,它将其重新绑定到了循环的每一个迭代中,确保使用上一个循环迭代结束时的值重新进行赋值。

const

const,同样可以用来创建块作用域变量,但其值是固定的 (常量)。之后任何试图修改值的操作都会引起错误。

注意:如果是引用类型,那么可以修改引用类型的属性值。因为变量中保存的是引用,引用不变就不会引起报错。

总结

js中的作用域,主要有函数作用域和块级作用域,当然还有全局作用域。

  • 函数作用域的使用,可以隐藏代码实现,减少变量暴露,避免命名冲突,符合软件设计的最小特权原则。关于函数作用,还讲了函数声明与函数表达式的辨别方法和区别。在函数表达式中,还分出了命名函数表达式和匿名函数表达式。
  • 块级作用域的实现,有赖于ES6的版本进步,提供let和const关键字,可以实现同其他语言相同的由{...}包裹起来的块级作用域。比较典型的就是let版的for循环和var版的for循环

1.var

  • 变量提升:只有声明本身被提升,而赋值操作或其他运行逻辑会留在原地。
  • 作用域:有全局作用域和函数作用域,函数作用域外的无法访问到包裹在函数内部的任何内容。
  • 重复声明:同一作用域内可以重复声明,但是第二个声明会被忽略,只是将第二次的赋值进行覆盖。(需要避免)
  • for循环中:迭代遍历只有一个。退出循环时,迭代变量保存的是导致循环退出的值,在之后执行超时逻辑时,迭代变量是同一个。

2. let

  • 块级作用域:只要是两个花括号包含的区域,就形成块(即使没有if、for等语句)。块外部无法访问到内部的内容。
  • 暂性死区:let声明一个变量,就会与该块进行锁定。在块的内部,如果let初始化前的代码如果使用了该变量,不会去搜索上一级块内的变量,而是出现报错。只有let声明的变量初始化之后,才能访问。
  • 不能重复声明:同一作用域内,同一标识符不能重复声明。
  • for循环中:每个迭代循环声明一个新的迭代变量。

3. const

  • 基本同let
  • 与let的区别:
  • 定义的是常量,不能修改,如果是对象可以修改属性。
  • 必须在声明时初始化
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值