`
charlesEye
  • 浏览: 6879 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

谈谈javascript语法里一些难点问题(二)《转载》

 
阅读更多

3)    作用域链相关的问题

  作用域链是javascript语言里非常红的概念,很多学习和使用javascript语言的程序员都知道作用域链是理解javascript里很重要的一些概念的关键,这些概念包括this指针,闭包等等,它非常红的另一个重要原因就是作用域链理解起来太难,就算有人真的感觉理解了它,但是碰到很多实际问题时候任然会是丈二和尚摸不到头脑,例如上篇引子里讲到的例子,本篇要讲的主题就是作用域链,再无别的内容,希望看完本文的朋友能有所收获。

  讲作用域链首先要从作用域讲起,下面是百度百科里对作用域的定义:

作用域在许多程序设计语言中非常重要。

通常来说,一段程序代码中所用到的名字并不总是有效/可用的,而限定这个名字的可用性的代码范围就是这个名字的作用域。

作用域的使用提高了程序逻辑的局部性,增强程序的可靠性,减少名字冲突。

 

  在我最擅长的服务端语言java里也有作用域的概念,java里作用域是以{}作为边界,不过在纯种的面向对象语言里我们没必要把作用域研究的那么深,也没必要思考复杂的作用域嵌套问题,因为这些语言关于作用域的深度运用并不会给我们编写的代码带来多大好处。但是在javascript里却大不相同,如果我们不能很好的理解javascript的作用域我们就没办法使用javascript编写出复杂的或者规模宏大的程序。

  由百度百科里的定义,我们知道作用域的作用是保证变量的名字不发生冲突,用现实的场景来理解有个人叫做张三,张三虽然只是一个名字,但是认识张三的人根据名字就能唯一确认这个人到底是谁,但是这个世界上叫做张三的人可不止一个,特别是两个叫张三的人有交集的时候我们就要有个办法明确指定这个张三绝不是另外一个张三,这时我们可能会根据两大张三年龄的差异来区分:例如一个张三叫大张三,相对的另外一个张三叫小张三了。编程语言里的作用域其实就是为了做类似的标记,作用域会设定一个范围,在这个范围里我们是不会弄错变量的真实含义。

  前面我讲到在java里通过{}来设置作用域,在{}里面的变量会得到保护,这种保护就是不让{}里的变量被外部变量混淆和污染。那么{}的方式适合于javascript吗?我们看看下面的例子:

复制代码
    var s1 = "sharpxiajun";

    function ftn(){

        var s2 = "xtq";

        console.log(this);// 运行结果: window

        console.log("s1:" + this.s1 + ";s2:" + this.s2);//运行结果:s1:sharpxiajun;s2:undefined

        console.log("s1:" + this.s1 + ";s2:" + s2);// 运行结果:s1:sharpxiajun;s2:xtq

    }

    ftn();
复制代码

 

  在javascript世界里有一个大的作用域环境,这个环境就是window,window环境不需要我们自己使用什么方式构建,页面加载时候页面会自动构造的,上面代码里有一个大括号,这个大括号是对函数的定义,运行之,我们发现函数作用域内部定义的s2变量是不能被window对象访问的,因此s2变量是被{}保护起来了,它的生命周期和这个函数的生命周期有关。

  由这个例子是不是说明在javascript里,变量也是被{}保护起来了,在javascript语言里还有非函数的{},我们再看看下面的例子:

复制代码
    if (true){

        var a = "aaaa";

    }

    console.log(a);// 运行结果:aaaa
复制代码

 

  我们发现javascript里{}有时是起不到定义作用域的功能。这也说明javascript里的作用域定义是和其他语言例如java不同的。

  在javascript里作用域有一个专门的定义execution context,有的书里把这个名字翻译成执行上下文,有的书籍里把它翻译成执行环境,我更倾向于后者执行环境,下文我提到的执行环境就是execution context。这个命名非常形象,这个形象体现在execution这个单词,execution含义就是执行,我们来想想javascript里那些情况是执行:

  情况一:当页面加载时候在script标签下的javascript代码会按顺序执行,而这些能被执行的代码都是属于window的变量或函数;

  情况二:当函数的名字后面加上小括号(),例如ftn(),这也是在执行,不过它执行的是函数。

  如此说来,javascript里的执行环境有两类一类是全局执行环境,即window代表的全局环境,一类是函数代表的函数执行环境,这也就是我们常说的局部作用域

执行环境在javascript语言里并非是一个抽象的概念,而是有具体的实现,这个实现其实是个对象,这个对象也有个名字叫做variable object,这个变量有的书里翻译为变量对象,这是直译,有的书里把它称为上下文变量,这里我还是倾向于后者上下文变量,下文里提到的上下文变量就是指代variable object。上下文变量存储的是上下文变量所处执行环境里定义的所有的变量和函数。

  全局执行环境的上下文变量是可以访问到的,它就是window对象,所以我们说window能代表全局作用域是有道理的,但是局部作用域即函数的执行环境里的上下文变量是代码不能访问到的,不过javascript引擎在处理数据时候会使用到它。

  在javascript语言里还有一个概念,它的名字叫做execution context stack,翻译成中文就是执行环境栈,每个要被执行的函数都会先把函数的执行环境压入到执行环境栈里,函数执行完毕后,这个函数的执行环境就会被执行环境栈弹出,例如上面的例子:函数执行时候函数的执行环境会被压入到执行环境栈里,函数执行完毕,执行环境栈会把这个环境弹出,执行环境栈的控制权就会交由全局环境,如果函数后面还有代码,那么代码就是接着执行。如果函数里嵌套了函数,那么嵌套函数执行完毕后,执行环境栈的控制权就交由了外部函数,然后依次类推,最后就是全局执行环境了。

  讲到这里我们大名鼎鼎的作用域链要登场了,函数的执行环境被压入到执行环境栈里后,函数就要执行了,函数执行的第一步不是执行函数里的第一行代码而是在上下文变量里构造一个作用域链,作用域链的英文名字叫做scope chain,作用域链的作用是保证执行环境里有权访问的变量和函数是有序的,这个概念里有两个关键意思:有权访问和有序,我们看看下面的代码:

  

复制代码
 var b1 = "b1";

    function ftn1(){

        var b2 = "b2";

        var b1 = "bbb";

        function ftn2(){

            var b3 = "b3";

            b2 = b1;

            b1 = b3;

            console.log("b1:" + b1 + ";b2:" + b2 + ";b3:" + b3);// 运行结果:b1:b3;b2:bbb;b3:b3

        }

        ftn2();

    }

    ftn1();

console.log(b1);// 运行结果:b1
复制代码

 

 

  有这个例子我们发现,ftn2函数可以访问变量b1,b2,这个体现了有权访问的概念,当ftn1作用域里改变了b1的值并且把b1变量重新定义为ftn1的局部变量,那么ftn2访问到的b1就是ftn1的,ftn2访问到b1后就不会在全局作用域里查找b1了,这个体现了有序性。

  下面我要总结下上面讲述的知识:

  本篇的小标题是:作用域链的相关问题,这个标题定义的含义是指作用域链是大名鼎鼎了,但是作用域链在广大程序员的理解里其实包含的意义已经超越了作用域链在javascript语言本身的定义。广大程序员对作用域链的理解有两块一块是作用域,而作用域在javascript语言里指的是执行环境execution context,执行环境在javascript引擎里是通过上下文变量体现的variable object,javascript引擎里还有一个概念就是执行环境栈execution context stack,当某一个函数的执行环境压入到了执行环境栈里,这个时候就会在上下文变量里构造一个对象,这个对象就是作用域链scope chain,而这个作用域链就是广大程序员理解的第二块知识,作用域链的作用是保证执行环境里有权访问的变量和函数是有序的,作用域链的变量只能向上访问,变量访问到window对象即被终止,作用域链向下访问变量是不被允许的。

  很多人常常认为作用域链是理解this指针的关键,这个理解是不正确的的,this指针构造是和作用域链同时发生的,也就是说在上文变量构建作用域链的同时还会构造一个this对象,this对象也是属于上下文变量,this变量的值就是当前执行环境外部的上下文变量的一份拷贝,这个拷贝里是没有作用域链变量的,例如代码:

复制代码
    var b1 = "b1";

    function ftn1(){

        console.log(this);// 运行结果: window

        var b2 = "b2";

        var b1 = "bbb";

        function ftn2(){

            console.log(this);// 运行结果: window

            var b3 = "b3";

            b2 = b1;

            b1 = b3;

            console.log("b1:" + b1 + ";b2:" + b2 + ";b3:" + b3);// 运行结果:b1:b3;b2:bbb;b3:b3

        }

        ftn2();

    }

    ftn1();
复制代码

 

  我们看到函数ftn1和ftn2里的this指针都是指向window,这是为什么了?因为在javascript我们定义函数方式是通过function xxx(){}形式,那么这个函数不管定义在哪里,它都属于全局对象window,所以他们的执行环境的外部的执行上下文都是指向window。

  但是我们都知道现实代码很多this指针都不是指向window,例如下面的代码:

复制代码
var obj = {

    name:"sharpxiajun",

    ftn:function(){

        console.log(this);// 运行结果: Object { name="sharpxiajun", ftn=function()}

        console.log(this.name);//运行结果: sharpxiajun

    }

}

obj.ftn();//
复制代码

 

  运行之,我们发现这里this指针指向了Object,这就怪了我前文不是说javascript里作用域只有两种类型:一个是全局的一个是函数,为什么这里Object也是可以制造出作用域了,那么我的理论是不是有问题啊?那我们看看下面的代码:

复制代码
var obj1 = new Object();

obj1.name = "xtq";

obj1.ftn = function(){

    console.log(this);// 运行结果: Object { name="xtq", ftn=function()}

    console.log(this.name);//运行结果: xtq

}

obj1.ftn();
复制代码

   这两种写法是等价的,第一种对象的定义方法叫做字面量定义,而第二种写法则是标准写法,Object对象的本质也是个function,所以当我们调用对象里的函数时候,函数的外部执行环境就是obj1本身,即外部执行环境上下文变量代表的就是obj1,那么this指针也是指向了obj1。

  哦,11点了,明天要上班,今天就写到这里,关于作用域链还有执行环境以及this的关系还有点没讲完,它们的关系会牵涉new的使用,(下面文字我要加粗,因为本文未讲this与new的关系,因此this的结论还不完整)写起来内容很多,所以这些内容就放在本系列的第三篇吧。

  最后祝大家晚安。

分享到:
评论

相关推荐

    JavaScript语法一览表

    这份“JavaScript语法一览表”涵盖了语言的基础到进阶知识,是学习和参考JavaScript语法的重要资源。 一、变量与数据类型 在JavaScript中,变量通过`var`, `let`, 或 `const` 关键字声明。变量可以存储不同类型的...

    javascript基本语法讲解

    由于JavaScript采用弱类型系统,变量可以在运行时改变其数据类型,这为编程提供了灵活性,但也可能导致一些难以预见的问题。此外,JavaScript还支持面向对象编程,允许开发者创建和操作对象,通过对象的方法和属性...

    JavaScript语法基础.rar

    JavaScript是一种广泛应用于网页和网络应用的脚本语言,它的语法基础是学习JavaScript编程的重要部分。在"JavaScript语法基础.rar"这个压缩包中,包含了多个章节,涵盖了JavaScript的基础知识,包括数据类型和变量、...

    使用JSLint完成JavaScript语法检查

    JavaScript是一种广泛应用于Web开发的动态编程语言,但其灵活性可能导致代码质量参差不齐,语法错误和潜在问题难以察觉。为了确保代码的质量和可维护性,开发者常常借助于静态代码分析工具,比如JSLint。本文将深入...

    JavaScript基本语法.pdf

    本篇文章将深入探讨JavaScript的基本语法,包括程序执行顺序、字母大小写敏感性、语句结束标记、以及注释的使用。 1. **程序执行顺序** JavaScript的执行遵循在HTML文件中出现的顺序,即从上到下逐行解析。如果...

    01 JavaScript基本语法

    JavaScript基本语法JavaScript基本语法JavaScript基本语法JavaScript基本语法

    JavaScript及基本语法

    JavaScript是Netscape公司和Sun公司合作推出的一种解释型的、基于对象(Object)和事件驱动的、跨平台的、结构化并具有安全性能的脚本语言。JavaScript通过嵌入标准的HTML语言或其中调入.js文件来增强HTML语言的交互...

    JavaScript基本语法.ppt

    JavaScript基本语法.ppt,非常适合初学者,用后会帮助你很多

    Google语法高亮JavaScript功能

    Google语法高亮JavaScript功能就是为了解决这一问题而设计的,它能够让代码在网页上以颜色鲜明、结构清晰的方式呈现,使得开发者和读者能够更轻松地理解和分析代码。 **什么是语法高亮** 语法高亮(Syntax ...

    javascript语法高亮文件

    与EditPlus软件配合,对javascript代码进行语法高亮显示

    Javascript2-基础语法

    Javascript2 PPT-基础语法

    htmljavascript的概念javascript语法.pptx

    htmljavascript的概念javascript语法.pptx

    最好的脚本编辑器javascript asp html,自动提示语法

    同时,自动纠错功能可以在编码过程中及时发现语法错误,避免了运行时才出现的潜在问题,提高了代码质量。 ASP(Active Server Pages)是微软开发的一种服务器端脚本环境,用于创建动态网页或Web应用程序。在ASP中,...

    JavaScript基础语法.xmind

    自己总结的JavaScript基础语法的笔记,绘制了详细的思维导图,每个思维导图中均有详细的博文解释,方便大家学习和理解,免费分享给大家。适合网页前端的爱好者和学习者

    03-Javascript重难点实例精讲之函数

    03-Javascript重难点实例精讲之函数

    JavaScript基础语法笔记

    此“JavaScript基础语法笔记”涵盖了学习JS所必需的关键概念,包括循环结构、对象以及基本语法。 一、for循环 在JavaScript中,`for`循环是一种常用的迭代结构,用于重复执行一段代码直到特定条件满足。基本语法...

    JavaScript语法集锦与配色表

    7. 异步编程:JavaScript通过回调函数、Promise、async/await等方式处理异步操作,解决“回调地狱”问题,提高代码可读性。 配色表: 配色在编程中扮演着重要角色,良好的配色方案可以帮助开发者更好地识别代码...

    JavaScript:JavaScript基础语法.docx

    JavaScript:JavaScript基础语法.docx

Global site tag (gtag.js) - Google Analytics