您的位置:首页 > Web前端 > JavaScript

深入学习js之浅谈作用域(隐藏作用域和块作用域)

2017-08-10 00:55 381 查看
1.通过函数作用域隐藏内部实现

有很多原因促成了这种基于作用域的隐藏方法。它们大多符合最小暴露原则。比如某个API或者模块的设计

function foo(obj) {
a =  obj + fooElse(obj * 2);
console.log(a * 3);
}

function fooElse(obj) {
return obj - 1;
}
var a;
fooElse( 2 );//15


在这段代码中给予外部作用域对a和fooElse的访问权限不仅没有必要而且有可能还是危险的。

更合理的设计

function foo(obj) {
a =  obj + fooElse(obj * 2);
console.log(a * 3);
function fooElse(obj) {
return obj - 1;
}
var a;
}
fooElse( 2 );//15
a和fooElse均无法从外部被访问,而只能被foo(..)所控制,功能性和最终效果没有影响,但从设计上将其私有化了,是一种良好的实现。

2.隐藏作用域的好处

规避冲突,减少同名标识符之间的冲突

2.1变量冲突的一个典型栗子是当程序中加载了多个第三方库时,如果它们没有妥善地将内部私有的函数或变量隐藏起来,就会容易引起冲突。

这些库通常会在全局作用域中声明一个名字足够独特的变量,通常是一个对象。这个对象用作库的命名空间,比如jquery对象,所有需要暴露给外界的功能都会成为这个

对象的属性,而不是将自己暴露在顶级的词法作用域中。

var MyTool = {
doSomething:function () {

},
deleteSomething:function () {

}
};
2.2模块管理

就是从众多模块管理器中挑选一个来使用。(以后介绍)

3.函数作用域

var a = 2;

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


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

虽然这种技术可以解决一些问题,但还是造成了额外的问题,比如必须声明一个具名函数foo(),意味着foo这个名称本身污染了所在的作用域。其次必须显示通过函数名

调用这个函数才能运行其中的代码。

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

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

console.log(a);//2


以(function而不是function开始,函数会被当作函数表达式而不是一个标准的函数声明来处理

区分函数声明和表达式最
4000
简单的方法是看function关键字穿现在声明中的位置。如果function是声明中的第一个词,那么就是一个函数声明,否则就是一个函数表达式。

函数声明和函数表达式之间最重要的区别是它们的名称标识符将会绑定在何处

比较一下前面两个代码片段。第一个片段中foo被绑定在所在作用域中,可以直接通过foo()来调用它。第二个片段中foo被绑定在函数表达式自身的函数中而不是所在的作用域中。

(funtion foo(){..})作为函数表达式意味着foo只能在..所代表的位置中被访问,外部作用域则不行。foo变量名被隐藏在自身中意味着不会污染外部作用域。

3.1立即执行函数表达式

var a = 2;

(function foo() {

var a = 3;

console.log(a);//33

})();

console.log(a);//2

由于函数被包含在一对()括号内部,因此成为了一个表达式,通过在末尾加上另外一个()可以立即执行这个函数,比如(function foo(){})()。第一个()将函数变成表达式,第二个()执行了这个函数。

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

函数名对于IIFE不是必须的,IIFE最常见的用法是使用一个匿名函数表达式。

IIFE的另一个非常普遍的进阶用法是把它们当作函数调用并传递参数进去

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

console.log(a);//2


IIFE还有一种变化的用途是倒置代码的运行顺序,将需要运行的函数放在第二位,在IIFE执行之后当作参数传递进去。这种模式在UMD(Universal Module Definition)项目中

被广泛使用,

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


4.块作用域

从ES3开始,try/catch在catch分局中具有块作用域

在ES6中引入了let关键字,用来在任意代码块中声明变量。

先说说try/catch

try{
underfined();
}catch(error){
console.log(error);//ReferenceError: underfined is not definedat <anonymous>:2:4
}

console.log(error);//Uncaught ReferenceError: error is not definedat <anonymous>:7:15


error仅存在catch分句内部,当试图从别处引用它时会抛出错误

那么用catch分句创建块作用域这件事有什么用呢

当ES6的块级作用代码转换成ES6之前的环境会将其转换为try catch格式(Google的Traceur项目)

再谈谈let

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

var foo = true;

if (foo) {
let bar = foo * 2;
bar = something(bar);
console.log(bar);
}

console.log(bar);//VM84:5 Uncaught ReferenceError


用let变量附加在一个已经存在的块作用域上的行为是隐式的。

推荐下面这种

var foo = true;

if (foo) {
{
let bar = foo * 2;
bar = something(bar);
console.log(bar);
}
}

console.log(bar);//VM84:5 Uncaught ReferenceError


只要声明是有效的,在声明中的任意位置都可以使用{..}括号来为let创建一个用于绑定的块。在这个例子中,我们在if声明内部显式地创建了一个块,如果需要对其进行重构,整个块都可以被方便地移动而不会对外部if声明的位置和语义产生任何影响。

let循环

for (let i = 0; i < 10; i++) {
console.log(i);
}
for循环头部的let不仅将i绑定到了for循环的块中,事实上它将其重新绑定到了循环的每一个迭代中,确保使用上一个循环结束时的值重新进行赋值。

下面通过另一种方式来说明每次迭代时进行重新绑定的行为。

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


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

var foo = true;

if (foo) {
var a = 2;
const b = 3;

a = 3;
b = 4//Uncaught TypeError: Assignment to constant variable.
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: