关于javascript中变量作用域理解

现在就结合网上的一篇文章在重新回顾下作用域这个东西吧。 作用域和上下文并不是同一个东西,很多人可能会把它搞混。每一个函数调用都联系着一个作用域和一个上下文。根本上来说,作用域是基于函数的而上下文是基于对象的。换句话说,作用域与函数调用是能够获取的变量有联系,它对与每一次调用来说都是独一无二的。上下文常常代表this变量的值,它指向“拥有”当前执行的这段代码的对象。

变量作用域

一个变量的作用域是程序源代码中定义这个变量的区域。全局变量拥有全局作用域,在js代码里的任何地方都是有定义的。然而在函数内声明的变量只在函数体内有定义,他们是局部变量,作用域是局部性的,函数参数也是局部变量,他们只在函数体内有定义。在函数体内局部变量的优先级高于同名的全局变量,如果在函数体内声明的一个局部变量或者函数参数中带有的变量和全局变量重名,那么全局变量就被局部变量所遮盖。

Javascript目前并不支持块作用域,在其中你可以在一个if 声明,switch声明,for循环或者while循环中定义一个变量作用域。这意味着变量在开闭花括号之外不能被获取。目前来说,任何定义在块内的变量都能在块之外被获取。然而,这样的情况马上就要改变了,为了支持定义块作用域变量,let关键字已经被官方增加到了ES6的标准中.

“this”上下文

上下文经常决定一个函数是怎么被调用的。当一个函数作为一个对象的方法被掉调用时,this指向调用这个方法的对象:

 代码如下 复制代码

var object = { foo: function(){ alert(this === object); } };

object.foo(); // true `

 同样的原则也适用于当使用new操作符定义一个对象实例的情况。在这种情况下,在函数作用域内的this指向新创建的实例:

 代码如下 复制代码

function foo(){ alert(this); }

foo() // window new foo() // foo`

当作为未绑定对象被调用时,this默认指向全局上下文或者浏览器中的window对象。然而,如果函数在严格模式下被执行,上下文将被默认为undefined

执行上下文和作用域链

Javascript是一门单线程语言,这意味着在浏览器环境下一个时间点只能做一件事。当Javascript解释器初始化执行代码时,它首先默认进入一个全局执行上下文。在此基础上每一次函数的调用都将创建一个新的执行上下文。

这里通常就是产生疑惑的地方,这里所说的“执行上下文”实际上对应着所有指向作用域的意图和目的,它于前面所讨论的上下文有所不同。这是一个很不好的命名管理,但是它很不幸的是已经被定义到了ECMAScript的标准中,这实在是让人有点无法接受。

每次一个新的执行上下文被创建时,它都被添加到了作用域链(有时它也被称为执行栈或者调用栈)的顶部。浏览器总是执行当前位于作用域链顶部的执行上下文。一旦执行完成,它就会被从栈的顶部移除,并将控制权返回到它下面的执行上下文。例如:

function first(){ second(); function second(){ third(); function third(){ fourth(); function fourth(){ // do something } } }
} first(); 运行上边的代码会导致嵌套函数一路执行一直到fourth函数。在这个点上的作用于连从上到下的顺序是:fourth,third,second,first,global。fourth函数可以获取到全局变量以及任何定义在first,second,third 中的变量以及函数。一旦fourth函数执行完成,它将会被从作用域中被移除,执行权将会返回到third函数。这个过程一直继续直到所有的代码完成执行。

不同执行上下文中的命名冲突将由作用域链的攀登(climbing up the scope chain)来解决,它从本地一直移动到全局。这意味着拥有相同名字并位于作用域链更上方的的本地变量会被优先获取。

一个执行上下文分为创建和执行两个阶段。在创建阶段,解释器首先创建一个变量对象(也被成为激活对象),它由执行上下文中定义的所有变量,函数声明以及参数组成。从这里开始接下来作用域连被初始化,this的值随后被决定。接着在执行阶段,代码被解释执行。

简单来说,每次当你试图获取一个函数执行上下文中的值是,查询过程将总是从自己的变量对象开始。如果这个变量在变量对象中没有被找到,搜索将会转向作用域链。它将会攀登作用域链来检查每个执行上下文,查找是个否有名字匹配的变量。

闭包

当一个嵌套函数试图获取外部函数之外的值时,闭包便产生了,它将在外部函数返回之后被执行。它将保持对外部函数本地变量,以及在内部定义的函数的获取能力。封装允许我们在暴露一个公共接口的情况下隐藏和保持来自外部作用域的执行上下文,并用于未来的操控。下面是一个简单的例子:

 代码如下 复制代码

function foo(){ var local = 'private variable'; return function bar(){ return local; } }

var getLocalVariable = foo(); getLocalVariable() // private variable 一个最流行的闭包类型是广为流传的模块模式。它允许你模拟公共,私有以及特权成员:

 代码如下 复制代码

var Module = (function(){ var privateProperty = 'foo';

function privateMethod(args){
    //do something
}
return {
    publicProperty: "",
    publicMethod: function(args){
        //do something
    },
    privilegedMethod: function(args){
        privateMethod(args);
    }
}
})();

这个模块执行的过程似乎是在编译器解释它之后作为一个单体被执行。在这个闭包的执行上下文外部唯一可以获取的成员是你返回对象中的属性和方法(例如Module.publicMethod)。然而,由于执行上下文被保护,所有的私有属性和方法在应用的生命周期内都会保持活跃,这意味着所有变量在未来将可以通过共有方法被获取。

另一个类型的闭包叫做立即执行函数表达式(IIFE),它仅仅是一个在window上下文中自我调用的匿名函数:

 代码如下 复制代码

function(window){

var a = 'foo', b = 'bar';
function private(){
    // do something
}
window.Module = {
    public: function(){
        // do something
    }
};
})(this);

当试图保持全局命名空间时这个表达式非常有用,任何在这个函数体内生命的变量对于闭包来说都是本地变量,但是它们又将始终在运行期间保持活跃。这是一个为应用和框架封装源代码的好方法,尤其适用于暴露一个用于交互的全局接口的情形。

call和apply

这是两个位于所有函数内部的简单的方法,它们使你能在任何想要的上下文中执行任何函数。call函数要求参数显式的一个一个罗列出来而apply要求你以数组的形式提供参数:

 代码如下 复制代码
function user(first, last, age){
// do something
}
user.call(window, 'John', 'Doe', 30);
user.apply(window, ['John', 'Doe', 30]);

上面两个函数调用的结果都相同,user函数都在window上下文中被调用并都给与的三个参数。

ECMAScript 5 引入了Function.prototype.bind方法用来操纵上下文。它返回了一个永久绑定在bind函数第一个参数上下文中的函数,而不管这个函数是怎么使用的。它通过使用一个在合适的上下文中重定向调用的闭包来实现。下面是在不支持bind的浏览器中的实现方法:

 代码如下 复制代码
if(!('bind' in Function.prototype)){ Function.prototype.bind = function(){ var fn = this, context = arguments[0], args = Array.prototype.slice.call(arguments, 1); return function(){ return fn.apply(context, args); } } }`

这在上下文经常性丢失的情形下很常用:面向对象和事件处理。这很有必要因为一个节点的addEventListener方法总是在节点事件处理器被绑定的上下文中执行回调函数,它也应该这样做。然而如果你要使用更高级的面向对象技巧并不要一个对象的方法作为回调函数,你需要去手动调整上下文。下面的是一个用到了bind函数的例子:

 代码如下 复制代码

function MyClass(){ this.element = document.createElement('div'); this.element.addEventListener('click', this.onClick.bind(this), false); }

MyClass.prototype.onClick = function(e){ // do something };

当你回看bind函数的代码是,你可能会注意到其中一行代码涉及了Array对象的一个方法:

`Array.prototype.slice.call(arguments, 1); 有意思的一点是这里的arguments对象并不是真正的数组,然而它经常被描述为一个类数组的对象,辟谷期更像是一个节点列表(由document.getElemntsBytagName()返回的东西)。它们包含一个Length属性和索引值但是它们任然不是数组,因此并不支持任何数组的原生方法例如slice和push。然而,由于它们太相似了,因此数组的方法能被采用或者说劫持。在上面的例子中,数组对象的方法都在一个类数组对象的上下文中被执行。

这种使用另一个对象方法的技巧也被运用于模拟传统继承方法的Javascript中的面向对象编程:

 代码如下 复制代码
MyClass.prototype.init = function(){ //在"MyClass"实例的上下文中调用超类的init方法 MySuperClass.prototype.init.apply(this, arguments); }

通过在一个子类(MyClass)实例的上下文中调用超类(MySuperClass)的方法,我们能够模仿这种强大的设计模式。

结论

在你开始学习更高级的设计模式之前,理解这些概念非常的重要,因为作用域和上下文在现代Javascript中边沿了一个重要又基础的角色。无论我们谈论闭包,面向对象还是继承,或者多种事件的实现,上下文和作用域链都扮演着一个非常重要的角色。如果你的目标是掌握Javascript语言并且更好的理解它,那么作用域链和闭包应该是你学习的起点。

时间: 2024-10-29 15:00:15

关于javascript中变量作用域理解的相关文章

局部变量-javascript中变量作用域的问题

问题描述 javascript中变量作用域的问题 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">

JavaScript中变量作用域详细介绍

  以下变量具有全局作用域:     1.所有定义在最外层的变量(非函数体内部)具有全局作用域.     2.未定义直接赋值的变量,系统会把它声明为全局作用域.     3.所有window对象的属性具有全局作用域. 以下变量具有函数作用域   1. 在函数体内部用var定义的变量,这里要注意一点,只要是在函数里定义的变量,就算是在最       后一句定义,该变量也拥有整个函数的作用域.但是它的赋值是等到运行到那一句代码以         后才赋值的!!!   特别应该说明的一点是,作用域是层

Javascript中的作用域和上下文深入理解_javascript技巧

概述 Javascript中的作用域和上下文的实现是Javascript语言独有的特性,从某种程度上来说,Javascript语言是十分灵活的.Javascript中的函数可以采用各种各样的上下文,作用域也可以被封装和保存.正是由于这些特性,Javascript中也提供了很多很有用的设计模式.然而,作用域和上下文也是Javascript程序员在开发中经常迷惑的地方. 下面会向大家介绍Javascript中作用域和上下文的概念,以及它们的不同. 作用域 VS 上下文 首先要说明的很重要的一点是作用

JavaScript的变量作用域深入理解_javascript技巧

在学习JavaScript的变量作用域之前,我们应当明确几点: a.JavaScript的变量作用域是基于其特有的作用域链的. b.JavaScript没有块级作用域. c.函数中声明的变量在整个函数中都有定义. 1.JavaScript的作用域链 首先看下下面这段代码: 复制代码 代码如下: <script type="text/javascript" language="javascript"> var rain = 1; function rain

javascript中的作用域和上下文使用简要概述

 下面全面揭示了javascript中的上下文和作用域的不同,以及各种设计模式如何使用他们,感兴趣的朋友不要错过 javascript中的作用域(scope)和上下文(context)是这门语言的独到之处,这部分归功于他们带来的灵活性.每个函数有不同的变量上下文和作用域.这些概念是javascript中一些强大的设计模式的后盾.然而这也给开发人员带来很大困惑.下面全面揭示了javascript中的上下文和作用域的不同,以及各种设计模式如何使用他们.    上下文 vs 作用域    首先需要澄清

深入浅析JavaScript中的作用域和上下文_javascript技巧

javascript中的作用域(scope)和上下文(context)是这门语言的独到之处,这部分归功于他们带来的灵活性.每个函数有不同的变量上下文和作用域.这些概念是javascript中一些强大的设计模式的后盾.然而这也给开发人员带来很大困惑.下面全面揭示了javascript中的上下文和作用域的不同,以及各种设计模式如何使用他们. 上下文(Context)和作用域(Scope) 首先需要知道的是,上下文和作用域是两个完全不同的概念.多年来,我发现很多开发者会混淆这两个概念(包括我自己),

javascript中的作用域和上下文使用简要概述_基础知识

javascript中的作用域(scope)和上下文(context)是这门语言的独到之处,这部分归功于他们带来的灵活性.每个函数有不同的变量上下文和作用域.这些概念是javascript中一些强大的设计模式的后盾.然而这也给开发人员带来很大困惑.下面全面揭示了javascript中的上下文和作用域的不同,以及各种设计模式如何使用他们. 上下文 vs 作用域 首先需要澄清的问题是上下文和作用域是不同的概念.多年来我注意到许多开发者经常将这两个术语混淆,错误的将一个描述为另一个.平心而论,这些术语

深入浅析javascript中的作用域(推荐)_javascript技巧

所谓的作用域,可以简单理解为一个可以读.写的范围(区域),有些js经验的同学可能会说:"js没有块级作用域",js除了全局作用域外,只有函数可以创建作用域.作用域的一个好处就是可以隔离变量. 我们通过一些例子来帮助我们理解js中的作用域. alert(a); var a = 1; 如果对作用域一点不了解的同学可能会说 alert的是1或者报错:但实际上是undefined: 说到这里,我们首先说一下js逐行解析代码之前做的一些准备工作, js在逐行读代码之前,会做一些"预解析

JavaScript中变量声明有var和没var的区别

 本文来论述JavaScript中变量声明有var和没var的区别,关于Js中的变量声明的作用域是以函数为单位,所以我们经常见到避免全局变量污染的方法是 1 2 3 (function(){  // ...  })(); 在函数内部,有var和没var声明的变量是不一样的.有var声明的是局部变量,没var的,声明的全局变量,所以可以借此向外暴露接口东东. 在全局作用域内声明变量时,有var 和没var看起来都一样,我们知道,声明的全局变量,就是window的属性,究竟是否一样,我们通过ECMA