细说 Javascript 函数篇(六) : 作用域与定名空间

在之前的引见中,我们已晓得 Javascript 没有块级作用,只要函数级作用域。

function test() { // a scope
    for(var i = 0; i < 10; i++) { // not a scope
        // count
    }
    console.log(i); // 10
}

Javascript 中也没有显现的定名空间,这就意味着一切都定义在全局作用域中。每一次援用一个变量时,Javascript 会往上遍历全部全局作用域直到找到该变量。假如遍历完全部全局作用域依然没有找到该变量,则抛出一个 ReferenceError 毛病。

《细说 Javascript 函数篇(六) : 作用域与定名空间》

隐式全局变量

// script A
foo = '42';

// script B
var foo = '42'

上面的两个例子发生不一样的结果。第一个将在全局作用域中定义变量 foo,而第二个则在当前作用域定义变量 foo
我们肯定要注意,假如不运用关键字 var 将会带来意想不到的影响。

// global scope
var foo = 42;
function test() {
    // local scope
    foo = 21;
}
test();
foo; // 21

由于在函数 test 内没用 var 来定义变量 foo,所以将掩盖函数外部的全局变量 foo。只管看上去不是什么大题目,然则假如有不计其数行代码时,这将是个难以追踪的 bug

// global scope
var items = [/* some list */];
for(var i = 0; i < 10; i++) {
    subLoop();
}

function subLoop() {
    // scope of subLoop
    for(i = 0; i < 10; i++) { // missing var statement
        // do amazing stuff!
    }
}

上例中,外部的轮回将会在实行第一次的时刻就住手,这是由于 subloop 函数内部的变量 i 将会掩盖外部的全局变量 i。我们只需要在函数内部加上一个 var 就能够防止这个毛病,所以我们在定义变量时肯定不要遗忘加上关键字 var。除非我们确切愿望对外部的全局变量形成影响。

局部变量

Javascript 中局部变量只能够经由过程两个体式格局发生,一是经由过程关键字 var 来声明,一是作为函数的形参。

// global scope
var foo = 1;
var bar = 2;
var i = 2;

function test(i) {
    // local scope of the function test
    i = 5;

    var foo = 3;
    bar = 4;
}
test(10);

此时,函数 test 内部的变量 ifoo 是局部变量,而 bar 则会掩盖外部的全局变量 bar

提拔(Hoisting)

Javascript 将会提拔变量声明,这就意味着 var 表达式和函数声明都将被提拔到作用域的顶部。

bar();
var bar = function() {};
var someValue = 42;

test();
function test(data) {
    if (false) {
        goo = 1;

    } else {
        var goo = 2;
    }
    for(var i = 0; i < 100; i++) {
        var e = data[i];
    }
}

上面的代码在运转之前, var 表达式和函数 test 的声明都将提拔至顶部,因而递次将一般运转并不会报错。

// var statements got moved here
var bar, someValue; // default to 'undefined'

// the function declaration got moved up too
function test(data) {
    var goo, i, e; // missing block scope moves these here
    if (false) {
        goo = 1;

    } else {
        goo = 2;
    }
    for(i = 0; i < 100; i++) {
        e = data[i];
    }
}

bar(); // fails with a TypeError since bar is still 'undefined'
someValue = 42; // assignments are not affected by hoisting
bar = function() {};

test();

由于 Javascript 没有块级作用域,这不仅将提拔 var 表达式,同时也会使得 if 构造变得不够直观。
在上例中,只管看上去 if 在对全局变量 goo 举行操纵,实际上,由于变量 goo 被提拔,所以修正的是局部变量。
假如没有对提拔划定规矩有所相识,你可能会以为下面的代码将会抛出 ReferenceError 毛病。

// check whether SomeImportantThing has been initialized
if (!SomeImportantThing) {
    var SomeImportantThing = {};
}

固然上面的代码是没有毛病的,由于在代码在运转前,var 表达式已被提拔到顶部。

var SomeImportantThing;

// other code might initialize SomeImportantThing here, or not

// make sure it's there
if (!SomeImportantThing) {
    SomeImportantThing = {};
}

这里要引荐下 @nightire 凡哥的博文 《明白 JavaScript(二)》,里面临提拔的解说异常透辟。

称号剖析递次

当尝试在一个函数作用域内接见一个 foo 变量时,Javascript 将会根据下面的递次查找:

  1. 当前作用域内是不是有 var foo 的定义。
  2. 函数形参中是不是有 foo 变量。
  3. 函数本身的称号是不是为 foo
  4. 跳到外层定义域,再从第一部开始查找起。

定名空间

一个最常见的题目就是定名争执,这是由于 Javascript 只要一个全局作用域所带来的。但这个题目能够经由过程匿名的外部函数处理。

(function() {
    // a self contained "namespace"

    window.foo = function() {
        // an exposed closure
    };

})(); // execute the function immediately

上例中的匿名函数被以为是表达式,所以它们会被实行。

( // evaluate the function inside the parentheses
function() {}
) // and return the function object
() // call the result of the evaluation

固然我们也能够用其他体式格局来挪用函数表达式,差别的构造,然则一样的结果。

// A few other styles for directly invoking the 
!function(){}()
+function(){}()
(function(){}());
// and so on...

总结

发起人人运用匿名的外部函数来将代码封装到空间内,如许不仅能够处理定名空间的争执,同时也有利于递次的模块化。
另外,运用全局变量不是一个好习惯,这将带来高本钱的保护价值而且轻易发生毛病。

参考

http://bonsaiden.github.io/JavaScript-Garden/#function.scopes

    原文作者:StephenLee
    原文地址: https://segmentfault.com/a/1190000000490905
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞