JavaScript中的作用域

发布于 Heng温

很多(JavaScript)开发者都在讨论"作用域",但它是什么?它们在JavaScript中的任何地方!我发现很多年轻的开发者不知道作用域是什么。他们中大多数人可以用jQuery做一些很酷的东西。但只是停留在从网上复制一些代码片段,修改一下他们懂的地方并且粘贴到一个JavaScript文件中的程度。

了解什么是作用域和你能用它做什么是很重要的,我希望能帮你更好地理解它。我讲的大多数内容都来自我为一些年轻开发者们做了很多次的演讲。(JavaScript基础教程)

什么是作用域

让我们从全局作用域开始。从你开始写JavaScript的第一刻起,你的代码就处于全局作用域下。全局作用域可以从任何地方访问。除此之外你可以在全局作用域下通过函数创建更小的作用域。局部作用域无法从全局作用域访问,只能从相同作用域(或者叫函数)访问。全局作用域可以从任何地方访问!

全局作用域

当一些东西没有定义在一个函数中时他就被定义在了全局作用域下。只在一个JavaScript文件中定义一些变量,他们就在全局作用域中。

JavaScript 例子

var myGlobalScope = 'Global scope value';
console.log('myGlobalScope: ' + myGlobalScope);
// "myGlobalScope: Global scope value"

局部作用域

局部作用域通过一个函数创建。其中定义的任何东西都只能在其内部访问,或者它的子作用域(在那个函数内的函数)。在一个局部作用域中,你可以访问全局作用域或者父级作用域(父级函数)!

JavaScript 例子

var myGlobalScope = 'Global scope value';

function myLocalScopeFunction() {
  var myLocalScope = 'myLocalScope value';

  console.log('----- start of local scope in function -----');
  console.log('myGlobalScope: ' + myGlobalScope);
  console.log('myLocalScope: ' + myLocalScope);
  console.log('----- end of local scope -----');
}

myLocalScopeFunction();
console.log('----- start of Global scope in function -----');
console.log('myGlobalScope: ' + myGlobalScope);
console.log('myLocalScope: ' + myLocalScope);
console.log('----- end of Global scope -----');

Console输出的结果:

"----- start of local scope in function -----"
"myGlobalScope: Global scope value"
"myLocalScope: myLocalScope value"
"----- end of local scope -----"
"----- start of Global scope in function -----"
"myGlobalScope: Global scope value"
"ReferenceError: myLocalScope is not defined
    at domigov.js:15:57
    at https://static.jsbin.com/js/prod/runner-3.39.15.min.js:1:13926
    at https://static.jsbin.com/js/prod/runner-3.39.15.min.js:1:10855"

这个例子中,正如你上面看到的当你试图在全局作用域下获取定义在子作用域中的变量时将会报错。

为什么在JavaScript中使用作用域?

在JavaScript中,你可以在不同作用域中使用相同的变量名定义变量,它不会被重写。如果你想改变变量的值只需要直接改变而不用重新定义一遍。

不重写

(function() {
    var var1 = 'variable 1 value in parent scope';

    (function() {
        // Scope 1
        var var1 = 'variable 1 value in scope 1';
        console.log('scope 1: ' + var1); // Is 'variable 1 value in scope 1'
    }());
    
    (function() {
        // Scope 2
        var var1 = 'variable 1 value in scope 2';
        console.log('scope 2: ' + var1); // Is 'variable 1 value in scope 2'
    }());
    
    console.log('Parent scope: ' + var1); // Is 'variable 1 value in parent scope'

}());

Console输出的结果:

"scope 1: variable 1 value in scope 1"
"scope 2: variable 1 value in scope 2"
"Parent scope: variable 1 value in parent scope"

重写

(function() {
    var var1 = 'variable 1 value in parent scope';

    (function() {
        // Scope 1
        var1 = 'variable 1 value in scope 1';
        console.log('scope 1: ' + var1); // Is 'variable 1 value in scope 1'
    }());
    
    (function() {
        // Scope 2
        var1 = 'variable 1 value in scope 2';
        console.log('scope 2: ' + var1); // Is 'variable 1 value in scope 2'
    }());
    
    console.log('Parent scope: ' + var1); // Is 'variable 1 value in parent scope'

}());
console.log('Global scope: ' + var1); // Is undefined

Console输出的结果:

"scope 1: variable 1 value in scope 1"
"scope 2: variable 1 value in scope 2"
"Parent scope: variable 1 value in scope 2"
"ReferenceError: var1 is not defined
    at zodaqo.js:19:57
    at https://static.jsbin.com/js/prod/runner-3.39.15.min.js:1:13926
    at https://static.jsbin.com/js/prod/runner-3.39.15.min.js:1:10855"

如果你想防止自己不小心在不同作用域改变变量的值,只需要在函数体开始部分声明你的变量。

变量提升

这也被称作变量提升。它将防止你在意外情况下重写你的变量。(注:个人理解作者这里写的变量提升并不是我们平时说的变量提升的现象,简单地理解成手动提前声明变量就好。)

变量提升例子

// Global scope
(function() {
  // Parent scope
  var var1, var2, var3;
  
  var1 = 'variable 1 value in parent scope';
  var2 = 'variable 2 value in parent scope';
  var3 = 'variable 3 value in parent scope';

    (function() {
      var var1, var2, var3;
      // Scope 1
      var1 = 'variable 1 value in scope 1';
      var2 = 'variable 2 value in scope 1';
      var3 = 'variable 3 value in scope 1';
      console.log('scope 1: ' + var1);
      console.log('scope 1: ' + var2);
      console.log('scope 1: ' + var3);
    }());
    
  console.log('Parent scope: ' + var1);
  console.log('Parent scope: ' + var2);
  console.log('Parent scope: ' + var3);

}());

Console输出的结果:

"scope 1: variable 1 value in scope 1"
"scope 1: variable 2 value in scope 1"
"scope 1: variable 3 value in scope 1"
"Parent scope: variable 1 value in parent scope"
"Parent scope: variable 2 value in parent scope"
"Parent scope: variable 3 value in parent scope"

当然,如果这真是你的代码,你永远都不需要这么做。但是假如你正在写一个更大规模的JavaScript应用,非常推荐这么做!

然而,一些有经验的开发者出于一些原因不赞成这么做。但根据我的经验,它能防止很多问题!

私有 & 公有

使用过其他库或框架并且看过它们源码的开发者知道有些方法你可以在你自己的代码中使用,但是这些方法内部更小的函数则不能使用或访问。

这是因为存在私有和公有函数(属性)。公有函数可以从其他作用域访问到。私有函数只能从相同作用域访问,因此它对于其他作用域或父作用域是隐藏的。

公有和私有函数被用作JavaScript模块。一个模块很容易创建,只需通过一个"立即执行函数表达式"定义变量。这些模块可以在你的JavaScript任何地方使用。如果你想了解更多模块模式相关内容,转向Todd Motto的“精通模块模式”这篇文章!

JavaScript模块

var JavaScriptModule = (function () {
  var javascriptModuleObject, privateFunction;

  privateFunction = function () {
    return 'privateFunction is called!';
  };

  javascriptModuleObject = {
    methodOne:  function () {
      return 'methodOne is called!';
    },
    methodTwo:  function () {
      return 'methodTwo is called!';
    }
  };
  
  return javascriptModuleObject;

})();

私有函数

当创建一个模块后我们现在可以发现私有和公有的区别。如果我们尝试获取私有函数将会报错。

var JavaScriptModule = (function () {
  var javascriptModuleObject, privateFunction;

  privateFunction = function () {
    return 'privateFunction is called!';
  };

  javascriptModuleObject = {
    methodOne:  function () {
      return 'methodOne is called!';
    },
    methodTwo:  function () {
      console.log(privateFunction());
      return 'methodTwo is called!';
    }
  };
  
  return javascriptModuleObject;

})();

  
console.log(JavaScriptModule.privateFunction());

Console输出的结果:

"TypeError: JavaScriptModule.privateFunction is not a function
    at dowazit.js:23:55
    at https://static.jsbin.com/js/prod/runner-3.39.15.min.js:1:13926
    at https://static.jsbin.com/js/prod/runner-3.39.15.min.js:1:10855"

这个函数只有模块内部可以访问。无法从外部访问!因此私有函数对内部逻辑比如复杂的计算,错误处理等等非常有用。

公有函数

对于我们的模块,公有函数(也叫方法)可以从模块外部获取。

var JavaScriptModule = (function () {
  var javascriptModuleObject, privateFunction;

  privateFunction = function () {
    return 'privateFunction is called!';
  };

  javascriptModuleObject = {
    methodOne:  function () {
      return 'methodOne is called!';
    },
    methodTwo:  function () {
      console.log(privateFunction());
      return 'methodTwo is called!';
    }
  };
  
  return javascriptModuleObject;

})();

  
console.log(JavaScriptModule.methodOne());
console.log(JavaScriptModule.methodTwo());

Console输出的结果:

"methodOne is called!"
"privateFunction is called!"
"methodTwo is called!"

公有函数可以在你的模块外执行。例如你以Google Maps API为例。它是指全部公有和私有方法的集合。

var map;
function initMap() {
  map = new google.maps.Map(document.getElementById('map'), {
    center: {lat: -34.397, lng: 150.644},
    zoom: 8
  });
}

Map()方法是一个公有函数!这个公有函数执行了许多私有函数。所以你只需要调用一个函数然后它将帮你处理好剩下的事情!

如何创建私有和公有函数

如果你看了样例模块会发现它很简单!如果一个函数被返回了就将变为公有的。否则,它会保持私有!在模块中存在javascriptModuleObject对象,这个对象通过return语句被返回。

所以如果我们想让其它函数也变成公有的我们只要将它添加到这个对象上就可以了。我们可以将这个函数添加到对象内,也可以这样

javascriptModuleObject.newPublicFunction = function() {}

大多数情况下我喜欢这样创建我得模块:

var JavaScriptModule = (function () {
  var javascriptModuleObject, privateFunction;

  privateFunction = function () {
    return 'privateFunction is called!';
  };

  javascriptModuleObject = {};
  
  javascriptModuleObject.newPublicFunction = function() {
    return 'new public functions!';
  };
  
  javascriptModuleObject.methodOne = function() {
    return 'methodOne is called!';
  };
  
  javascriptModuleObject.methodTwo = function() {
    console.log(privateFunction());
      return 'methodTwo is called!';
  };
  
  return javascriptModuleObject;

})();

  
console.log(JavaScriptModule.methodOne());
console.log(JavaScriptModule.methodTwo());
console.log(JavaScriptModule.newPublicFunction());

Console输出的结果:

"methodOne is called!"
"privateFunction is called!"
"methodTwo is called!"
"new public functions!"

这样创建它是因为对哪个函数是公有或私有的可读性非常好。看一眼你就能发现所有绑定在对象上(会被返回)的函数都将成为公有的,剩下的将成为私有的!

总结

我希望你对JavaScript中的作用域有了更好的理解并且知道通过它能做什么和如何在你自己的代码中实现它!如果没有,请在评论区提问!我很乐意帮你理解它!

本文根据@Raymon Schouwenaar的《The scope in JavaScript explained》所译,整个译文带有我们自己的理解与思想,如果译得不好或有不对之处还请同行朋友指点。如需转载此译文,需注明英文出处:http://blog.mrfrontend.org/2016/08/the-scope-in-javascript-explained/

Heng温

前端开发,音乐,动漫,技术控,喜欢折腾新鲜事物,以不断学习的态度,在前端的路上走下去。

如需转载,烦请注明出处:https://www.fedev.cn/javascript/the-scope-in-javascript-explained.htmlAir Jordan I Mid