asp.net网站很快吗网站建设公司有哪些
asp.net网站很快吗,网站建设公司有哪些,php企业网站 源码,哈尔滨城乡建设厅网站函数中的作用域
对这些问题的最常见的回答是#xff0c;JavaScript 拥有基于函数的作用域。也就是#xff0c;你声明的每一个函数都为自己创建了一个气泡#xff0c;而且没有其他的结构可以创建它们自己的作用域气泡。但是就像我们一会儿将会看到的#xff0c;这不完全正确…函数中的作用域
对这些问题的最常见的回答是JavaScript 拥有基于函数的作用域。也就是你声明的每一个函数都为自己创建了一个气泡而且没有其他的结构可以创建它们自己的作用域气泡。但是就像我们一会儿将会看到的这不完全正确。
但首先让我们探索一下函数作用域和它的含义。
考虑这段代码
function foo(a) {var b 2;// 一些代码function bar() {// ...}// 更多代码var c 3;
}在这个代码段中foo(..) 的作用域气泡包含标识符 abc 和 bar。一个声明出现在作用域 何处 是 无关紧要的不管怎样变量和函数属于包含它们的作用域气泡。在下一章中我们将会探索这到底是如何工作的。
bar(..) 拥有它自己的作用域气泡。全局作用域也一样它仅含有一个标识符foo。
因为 abc和 bar 都属于 foo(..) 的作用域气泡所以它们在 foo(..) 外部是不可访问的。也就是接下来的代码都会得到 ReferenceError 错误因为这些标识符在全局作用域中都不可用
bar(); // 失败console.log( a, b, c ); // 3个都失败然而所有这些标识符abc和 bar在 foo(..) 内部 都是可以访问的而且在 bar(..) 内部也都是可用的假定在 bar(..) 内部没有遮蔽标识符的声明。
函数作用域支持着这样的想法所有变量都属于函数而且贯穿整个函数始终都可以使用和重用而且甚至可以在嵌套的作用域中访问。这种设计方式可以十分有用而且肯定可以完全利用 JavaScript 的“动态”性质 —— 变量可以根据需要接受不同种类型的值。
另一方面如果你不小心提防跨越整个作用域存在的变量可能会导致一些意料之外的陷阱。
隐藏于普通作用域
考虑一个函数的传统方式是你声明一个函数并在它内部添加代码。但是相反的想法也同样强大和有用拿你所编写的代码的任意一部分在它周围包装一个函数声明这实质上“隐藏”了这段代码。
其实际结果是在这段代码周围创建了一个作用域气泡这意味着现在在这段代码中的任何声明都将绑在这个新的包装函数的作用域上而不是前一个包含它们的作用域。换句话说你可以通过将变量和函数围在一个函数的作用域中来“隐藏”它们。
为什么“隐藏”变量和函数是一种有用的技术
有多种原因驱使着这种基于作用域的隐藏。它们主要是由一种称为“最低权限原则”的软件设计原则引起的note-leastprivilege有时也被称为“最低授权”或“最少曝光”。这个原则规定在软件设计中比如一个模块/对象的API你应当只暴露所需要的最低限度的东西而“隐藏”其他的一切。
这个原则可以扩展到用哪个作用域来包含变量和函数的选择。如果所有的变量和函数都在全局作用域中它们将理所当然地对任何嵌套的作用域来说都是可访问的。但这回违背“最少……”原则因为你很可能暴露了许多你本应当保持为私有的变量和函数而这些代码的恰当用法是不鼓励访问这些变量/函数的。
例如
function doSomething(a) {b a doSomethingElse( a * 2 );console.log( b * 3 );
}function doSomethingElse(a) {return a - 1;
}var b;doSomething( 2 ); // 15在这个代码段中变量 b 和函数 doSomethingElse(..) 很可能是 doSomething(..) 如何工作的“私有”细节。允许外围的作用域“访问” b 和 doSomethingElse(..) 不仅没必要而且可能是“危险的”因为它们可能会以种种意外的方式有意或无意地被使用而这也许违背了 doSomething(..) 假设的前提条件。
一个更“恰当”的设计是讲这些私有细节隐藏在doSomething(..)的作用域内部比如
function doSomething(a) {function doSomethingElse(a) {return a - 1;}var b;b a doSomethingElse( a * 2 );console.log( b * 3 );
}doSomething( 2 ); // 15现在b 和 doSomethingElse(..) 对任何外界影响都是不可访问的而是仅仅由 doSomething(..) 控制。它的功能和最终结果不受影响但是这种设计将私有细节保持为私有的这通常被认为是好的软件。
避免冲突
将变量和函数“隐藏”在一个作用域内部的另一个好处是避免两个同名但用处不同的标识符之间发生无意的冲突。冲突经常导致值被意外地覆盖。
例如
function foo() {function bar(a) {i 3; // 在外围的for循环的作用域中改变iconsole.log( a i );}for (var i0; i10; i) {bar( i * 2 ); // 噢无限循环}
}foo();bar(..) 内部的赋值 i 3 意外地覆盖了在 foo(..) 的for循环中声明的 i。在这个例子中这将导致一个无限循环因为 i 被设定为固定的值 3而它将永远 10。
bar(..) 内部的赋值需要声明一个本地变量来使用不论选用什么样的标识符名称。var i 3; 将修复这个问题并将为 i 创建一个前面提到的“遮蔽变量”声明。一个 另外的 选项不是代替的选项是完全选择另外一个标识符名称比如 var j 3;。但是你的软件设计也许会自然而然地使用相同的标识符名称所以在这种情况下利用作用域来“隐藏”你的内部声明是你最好/唯一的选择。
全局“名称空间”
变量冲突很可能发生的一个特别强有力的例子是在全局作用域中。当多个库被加载到你的程序中时如果它们没有适当地隐藏它们的内部/私有函数和变量那么它们可以十分容易地互相冲突。
这样的库通常会在全局作用域中使用一个足够独特的名称来创建一个单独的变量声明它经常是一个对象。然后这个对象被用作这个库的一个“名称空间”所有要明确暴露出来的功能都被作为属性挂在这个对象名称空间上而不是将它们自身作为顶层词法作用域的标识符。
例如
var MyReallyCoolLibrary {awesome: stuff,doSomething: function() {// ...},doAnotherThing: function() {// ...}
};模块管理
另一种回避冲突的选择是通过任意一种依赖管理器使用更加现代的“模块”方式。使用这些工具没有库可以向全局作用域添加任何标识符取而代之的是使用依赖管理器的各种机制要求库的标识符被明确地导入到另一个指定的作用域中。
应该可以看到这些工具并不拥有可以豁免于词法作用域规则的“魔法”功能。它们简单地使用这里讲解的作用域规则来强制标识符不会被注入任何共享的作用域而是保持在私有的不易冲突的作用域中这防止了任何意外的作用域冲突。
因此如果你选择这样做的话你可以防御性地编码并在实际上不使用依赖管理器的情况下取得与使用它们相同的结果。关于模块模式的更多信息参见第五章。
函数作为作用域
我们已经看到我们可以拿来一段代码并在它周围包装一个函数而这实质上对外部作用域“隐藏”了这个函数内部作用域包含的任何变量或函数声明。
例如
var a 2;function foo() { // -- 插入这个var a 3;console.log( a ); // 3} // -- 和这个
foo(); // -- 还有这个console.log( a ); // 2虽然这种技术“可以工作”但它不一定非常理想。它引入了几个问题。首先是我们不得不声明一个命名函数 foo()这意味着这个标识符名称 foo 本身就“污染”了外围作用域在这个例子中是全局。我们要不得不通过名称foo()明确地调用这个函数来使被包装的代码真正运行。
如果这个函数不需要名称或者这个名称不污染外围作用域而且如果这个函数能自动地被执行就更理想了。
幸运的是JavaScript 给这两个问题提供了一个解决方法。
var a 2;(function foo(){ // -- 插入这个var a 3;console.log( a ); // 3})(); // -- 和这个console.log( a ); // 2让我们分析一下这里发生了什么。
首先注意与仅仅是 function... 相对这个包装函数语句以 (function... 开头。虽然这看起来像是一个微小的细节但实际上这是一个重大改变。与将这个函数视为一个标准的声明不同的是这个函数被视为一个函数表达式。
注意 区分声明与表达式的最简单的方法是这个语句中不仅仅是一行而是一个独立的语句“function”一词的位置。如果“function”是这个语句中的第一个东西那么它就是一个函数声明。否则它就是一个函数表达式。
这里我们可以观察到一个函数声明和一个函数表达式之间的关键不同是它的名称作为一个标识符被绑定在何处。
比较这前两个代码段。在第一个代码段中名称 foo 被绑定在外围作用域中我们用 foo() 直接调用它。在第二个代码段中名称 foo 没有被绑定在外围作用域中而是被绑定在它自己的函数内部。
换句话说(function foo(){ .. }) 作为一个表达式意味着标识符 foo 仅能在 .. 代表的作用域中被找到而不是在外部作用域中。将名称 foo 隐藏在它自己内部意味着它不会没必要地污染外围作用域。
匿名与命名
你可能对函数表达式作为回调参数再熟悉不过了比如
setTimeout( function(){console.log(I waited 1 second!);
}, 1000 );这称为一个“匿名函数表达式”因为 function()... 上没有名称标识符。函数表达式可以是匿名的但是函数声明不能省略名称 —— 那将是不合法的JS程序。
匿名函数表达式可以快速和很容易地键入而且许多库和工具往往鼓励使用这种代码惯用风格。然而它们有几个缺点需要考虑 在栈轨迹上匿名函数没有有用的名称可以表示这可能会使得调试更加困难。 没有名称的情况下如果这个函数需要为了递归等目的引用它自己那么就需要很不幸地使用 被废弃的 arguments.callee 引用。另一个需要自引用的例子是当一个事件处理器函数在被触发后想要把自己解除绑定。 匿名函数省略的名称经常对提供更易读/易懂的代码很有帮助。一个描述性的名称可以帮助代码自解释。
内联函数表达式 很强大且很有用 —— 匿名和命名的问题并不会贬损这一点。给你的函数表达式提供一个名称就可以十分有效地解决这些缺陷而且没有实际的坏处。最佳的方法是总是命名你的函数表达式
setTimeout( function timeoutHandler(){ // -- 看我有一个名字console.log( I waited 1 second! );
}, 1000 );立即调用函数表达式
var a 2;(function foo(){var a 3;console.log( a ); // 3})();console.log( a ); // 2得益于包装在一个 () 中我们有了一个作为表达式的函数我们可以通过在末尾加入另一个 () 来执行这个函数就像 (function foo(){ .. })()。第一个外围的 ( ) 使这个函数变成表达式而第二个 () 执行这个函数。
这个模式是如此常见以至于几年前开发者社区一致同意给它一个术语IIFE它表示“立即被调用的函数表达式”Immediately Invoked Function Expression。
当然IIFE不一定需要一个名称 —— IIFE的最常见形式是使用一个匿名函数表达式。虽然少见一些但与匿名函数表达式相比命名的IIFE拥有前述所有的好处所以它是一个可以采用的好方式。
var a 2;(function IIFE(){var a 3;console.log( a ); // 3})();console.log( a ); // 2传统的IIFE有一种稍稍变化的形式一些人偏好这样(function(){ .. }())。仔细观察不同之处。在第一种形式中函数表达式被包在 ( ) 中然后用于调用的 () 出现在它的外侧。在第二种形式中用于调用的 () 被移动到用于包装的 ( ) 内侧。
这两种形式在功能上完全相同。这纯粹是一个你偏好的风格的选择。
IIFE的另一种十分常见的变种是利用它们实际上只是函数调用的事实来传入参数值。
例如
var a 2;(function IIFE( global ){var a 3;console.log( a ); // 3console.log( global.a ); // 2})( window );console.log( a ); // 2我们传入 window 对象引用但是我们将参数命名为 global这样我们对于全局和非全局引用就有了一个清晰的文体上的划分。当然你可以从外围作用域传入任何你想要的东西而且你可以将参数命名为任何适合你的名称。这几乎仅仅是文体上的选择。
这种模式的另一种应用解决了一个小问题默认的 undefined 标识符的值也许会被不正确地覆盖掉而导致意外的结果。通过将参数命名为undefined同时不为它传递任何参数值我们就可以保证在一个代码块中 undefined 标识符确实是是一个未定义的值。
undefined true; // 给其他的代码埋地雷别这么干(function IIFE( undefined ){var a;if (a undefined) {console.log( Undefined is safe here! );}})();IIFE 还有另一种变种它将事情的顺序倒了过来要被执行的函数在调用和传递给它的参数 之后 给出。这种模式被用于 UMDUniversal Module Definition —— 统一模块定义项目。一些人发现它更干净和易懂一些虽然有点儿繁冗。
var a 2;(function IIFE( def ){def( window );
})(function def( global ){var a 3;console.log( a ); // 3console.log( global.a ); // 2});def 函数表达式在这个代码段的后半部分被定义然后作为一个参数也叫 def被传递给在代码段前半部分定义的 IIFE 函数。最后参数 def函数被调用并将 window 作为 global 参数传入。
块儿作为作用域
虽然函数是最常见的作用域单位而且当然也是在世面上流通的绝大多数 JS 中最为广泛传播的设计方式但是其他的作用域单位也是可能的而且使用这些作用域单位可以导致更好、对于维护来说更干净的代码。
JavaScript 之外的许多其他语言都支持块儿作用域所以有这些语言背景的开发者习惯于这种思维模式然而那些主要在 JavaScript 中工作的开发者可能会发现这个概念有些陌生。
但即使你从没用块儿作用域的方式写过一行代码你可能依然对 JavaScript 中这种极其常见的惯用法很熟悉
for (var i0; i10; i) {console.log( i );
}我们在 for 循环头的内部直接声明了变量 i因为我们意图很可能是仅在这个 for 循环内部的上下文环境中使用 i而实质上忽略了这个变量实际上将自己划入了外围作用域中函数或全局的事实。
这就是有关块儿作用域的一切。尽可能靠近地尽可能局部地在变量将被使用的位置声明它。另一个例子是
var foo true;if (foo) {var bar foo * 2;bar something( bar );console.log( bar );
}我们仅在 if 语句的上下文环境中使用变量 bar所以我们将它声明在 if 块儿的内部是有些道理的。然而当使用 var 时我们在何处声明变量是无关紧要的因为它们将总是属于外围作用域。这个代码段实质上为了代码风格的原因“假冒”了块儿作用域并依赖于我们要管好自己不要在这个作用域的其他地方意外地使用 bar。
从将信息隐藏在函数中到将信息隐藏在我们代码的块儿中块儿作用域是一种扩展了早先的“最低 权限 暴露原则”note-leastprivilege的工具。
再次考虑这个for循环的例子
for (var i0; i10; i) {console.log( i );
}为什么要用仅将或者至少是仅 应当在这个 for 循环中使用的变量 i 去污染一个函数的整个作用域呢
但更重要的是开发者们也许偏好于 检查 他们自己来防止在变量预期的目的之外意外地重使用它们例如如果你试着在错误的地方使用变量会导致一个未知变量的错误。对于变量 i 的块儿作用域如果它是可能的话将使 i 仅在 for 循环内部可用使得如果在函数的其他地方访问 i 将导致一个错误。这有助于保证变量不会被糊涂地重用或者难于维护。
但是悲惨的现实是表面上看来JavaScript 没有块儿作用域的能力。
更确切地说直到你再深入一些才有。
with
我们在第二章中学习了 with。虽然它是一个使人皱眉头的结构但它确实是一个一种形式的块儿作用域的例子它从对象中创建的作用域仅存在于这个 with 语句的生命周期中而不在外围作用域中。
try/catch
一个鲜为人知的事实是JavaScript 在 ES3 中明确指出在 try/catch 的 catch 子句中声明的变量是属于 catch 块儿的块儿作用域的。
例如
try {undefined(); //用非法的操作强制产生一个异常
}
catch (err) {console.log( err ); // 好用
}console.log( err ); // ReferenceError: err not found如你所见err 仅存在于 catch 子句中并且在你试着从其他地方引用它时抛出一个错误。
注意 虽然这种行为已经被明确规定而且对于几乎所有的标准JS环境也许除了老IE来说都是成立的但是如果你在同一个作用域中有两个或多个 catch 子句而它们又各自用相同的标识符名称声明了它们表示错误的变量时许多 linter 依然会报警。实际上这不是重定义因为这些变量都安全地位于块儿作用域中但是 linter 看起来依然会恼人地抱怨这个事实。
为了避免这些不必要的警告一些开发者将他们的 catch 变量命名为 err1err2等等。另一些开发者干脆关闭 linter 对重复变量名的检查。
catch 的块儿作用域性质看起来像是一个没用的只有学院派意义的事实但是参看附录B来了解更多它如何有用的信息。
let
至此我们看到 JavaScript 仅仅有一些奇怪的小众行为暴露了块儿作用域功能。如果这就是我们拥有的一切而且许多许多年以来这 确实就是 我们拥有的一切那么块作用域对 JavaScript 开发者来说就不是非常有用。
幸运的是ES6 改变了这种状态并引入了一个新的关键字 let作为另一种声明变量的方式伴随着 var。
let 关键字将变量声明附着在它所在的任何块儿通常是一个 { .. }的作用域中。换句话说let 为它的变量声明隐含地劫持了任意块儿的作用域。
var foo true;if (foo) {let bar foo * 2;bar something( bar );console.log( bar );
}console.log( bar ); // ReferenceError使用 let 将一个变量附着在一个现存的块儿上有些隐晦。它可能会使人困惑 —— 在你开发和设计代码时如果你不仔细注意哪些块儿的作用域包含了变量并且习惯于将块儿四处移动将它们包进其他的块儿中等等。
为块儿作用域创建明确的块儿可以解决这些问题中的一些使变量附着在何处更加明显。通常来说明确的代码要比隐晦或微妙的代码好。这种明确的块儿作用域风格很容易达成而且它与块儿作用域在其他语言中的工作方式匹配得更自然
var foo true;if (foo) {{ // -- 明确的块儿let bar foo * 2;bar something( bar );console.log( bar );}
}console.log( bar ); // ReferenceError我们可以在一个语句是合法文法的任何地方通过简单地引入一个 { .. } 来为 let 创建一个任意的可以绑定的块儿。在这个例子中我们在 if 语句内部制造了一个明确的块儿在以后的重构中将整个块儿四处移动可能会更容易而且不会影响外围的 if 语句的位置和语义。
注意 另一个明确表达块儿作用域的方法参见附录B。
在第四章中我们将讲解提升hoisting它讲述关于声明在它们所出现的整个作用域中都被认为是存在的。
然而使用 let 做出的声明将 不会 在它们所出现的整个块儿的作用域中提升。如此直到声明语句为止声明将不会“存在”于块儿中。
{console.log( bar ); // ReferenceError!let bar 2;
}垃圾回收
块儿作用域的另一个有用之处是关于闭包和释放内存的垃圾回收。我们将简单地在这里展示一下但是闭包机制将在第五章中详细讲解。
考虑这段代码
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(..) 运行之后这个消耗巨大内存的数据结构可以被作为垃圾回收。然而JS引擎很可能虽然这要看具体实现仍会将这个结构保持一段时间因为click函数在整个作用域上拥有一个闭包。
块儿作用域可以解决这个问题使引擎清楚地知道它不必再保持 someReallyBigData 了
function process(data) {// 做些有趣的事
}// 运行过后任何定义在这个块中的东西都可以消失了
{let someReallyBigData { .. };process( someReallyBigData );
}var btn document.getElementById( my_button );btn.addEventListener( click, function click(evt){console.log(button clicked);
}, /*capturingPhase*/false );声明可以将变量绑定在本地的明确的块儿是一种强大的工具你可以把它加入你的工具箱。
let 循环
一个使 let 闪光的特殊例子是我们先前讨论的 for 循环。
for (let i0; i10; i) {console.log( i );
}console.log( i ); // ReferenceError在 for 循环头部的 let 不仅将 i 绑定在 for 循环体中而且实际上它会对每一次循环的 迭代 重新绑定 i确保它被赋予来自上一次循环迭代末尾的值。
这是描绘这种为每次迭代进行绑定的行为的另一种方式
{let j;for (j0; j10; j) {let i j; // 每次迭代都重新绑定console.log( i );}
}这种为每次迭代进行的绑定有趣的原因将在第五章中我们讨论闭包时变得明朗。
因为 let 声明附着于任意的块儿而不是外围的函数作用域或全局所以在重构代码时可能会有一些坑需要额外小心现存的代码拥有对函数作用域的 var 声明有隐藏的依赖但你想要用 let 来取代 var。
考虑如下代码
var foo true, baz 10;if (foo) {var bar 3;if (baz bar) {console.log( baz );}// ...
}这段代码可以相当容易地重构为
var foo true, baz 10;if (foo) {var bar 3;// ...
}if (baz bar) {console.log( baz );
}但是当使用块儿作用域变量时要小心这样的变化
var foo true, baz 10;if (foo) {let bar 3;if (baz bar) { // -- 移动时不要忘了barconsole.log( baz );}
}附录B介绍了一种块作用域的更加明确的替代形式它可能会在这些场景下提供更易于维护/重构的更健壮的代码。
const
除了 let 之外ES6 还引入了 const它也创建一个块儿作用域变量但是它的值是固定的常量。任何稍后改变它的企图都将导致错误。
var foo true;if (foo) {var a 2;const b 3; // 存在于包含它的if作用域中a 3; // 没问题b 4; // 错误
}console.log( a ); // 3
console.log( b ); // ReferenceError!
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/90499.shtml
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!