谈谈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
, 函数
, 变量
, 函数作用域
, 域环境
, 环境
, 作用域链
, 作用域
, java连接 b1的代码
, 代码作用域
, B1
, 作用
, 上下文
函数环境
javascript难点、javascript重难点、javascript的难点、英语语法难点、高中英语语法难点,以便于您获取更多的相关知识。

时间: 2025-01-02 09:55:34

谈谈javascript语法里一些难点问题(二)的相关文章

谈谈javascript语法里一些难点问题(一)

1)    引子 前不久我建立的技术群里一位MM问了一个这样的问题,她贴出的代码如下所示: var a = 1; function hehe() {          window.alert(a);          var a = 2;          window.alert(a); } hehe(); 执行结果如下所示: 第一个alert:   第二个alert:   这是一个令人诧异的结果,为什么第一个弹出框显示的是undefined,而不是1呢?这种疑惑的原理我描述如下: 一个页面

Javascript 6里的4个新语法_javascript技巧

JS 的 ES6版本已经被各大浏览器广泛支持,很多前端框架也已经使用 ES6,并且还有 Babel 可以做兼容处理,所以ES6已经进入了应用阶段 如果您对 ES6 还不太熟悉,下面4个简单的基础用法可以帮助您快速了解ES6 1.使用 let 和 const 声明变量在传统的 ES5 代码中,变量的声明有两个主要问题 (1)缺少块儿作用域的支持 (2)不能声明常量 ES6中,这两个问题被解决了,增加了两个新的关键字:let 和 const 块儿作用域使用 let var a = 1; if (tr

浅谈javascript语法和定时函数

  初学者可能对Javascript的定时器有误解,认为它们是线程,其实Javascript是运行于单线程中的,而定时器仅仅是计划在未来的某个时间执行,而具体的执行时间是不能保证的,因为在页面的生命周期中,不同的时间可能有其它代码在控制Javascript的里进程. 一.JavaScript基本语法. (一)数据类型与变量类型. 整数,小数,布局,字符串,日期时间,数组 强制转换: parseInt() parseFloat() isNaN() (二)数组 var 数组名 = new Array

12种不宜使用的Javascript语法

这几天,我在读<Javascript语言精粹>. 这本书很薄,100多页,正好假日里翻翻. 该书的作者是Douglas Crockford,他是目前世界上最精通Javascript的人之一,也是Json格式的创造者. 他认为Javascript有很多糟粕.因为1995年Brendan Eich设计这种语言的时候,只用了三个月,很多语言特性没有经过深思熟虑,就推向了市场.结果等到人们意识到这些问题的时候,已经有100万程序员在使用它了,不可能再大幅修改语言本身了.所以,Douglas Crock

谈谈PHP语法(2)

作者:华红狼   上一文<谈谈PHP语法>已谈了PHP的数据类型和表达式.现在,让我们来看看PHP的变量与常数.   让我们先看一例吧. 文件:test.php <?php //这是一种单行注释方法 #这是另一种单行注释方法 /*这是一种多行注释的方法 以下让我们看看例了吧*/   funtion display($file,$line) {   global $message;   echo "FILE:$file<br>";   echo "

在JavaScript SDK里使用SoundCloud API

SoundCloud开发出了一款可被开发者使用的API,这款API能使开发者获得他们想要的几乎任何数据.但是该API的用法有些混乱,特别是对初学者来说,因为此时的SoundCloud API开发文档和文档示例使用的都是SDK(软件开发工具箱)的不同版本. SoundCloud介绍链接地址: http://baike.sogou.com/v128528573.htm SoundCloud API和SoundCloud SDK之间有什么区别呢?从根本上说,SoundCloud API是一个URL的集

JavaScript语法树与代码转化实践

JavaScript 语法树与代码转化实践 归纳于笔者的现代 JavaScript 开发:语法基础与实践技巧系列文章中.本文引用的参考资料声明于 JavaScript 学习与实践资料索引中,特别需要声明是部分代码片引用自 Babel Handbook 开源手册;也欢迎关注前端每周清单系列获得一手资讯. JavaScript 语法树与代码转化 浏览器的兼容性问题一直是前端项目开发中的难点之一,往往客户端浏览器的升级无法与语法特性的迭代保持一致;因此我们需要使用大量的垫片(Polyfill),以保证

jquery-怎么访问javascript脚本$里的函数?

问题描述 怎么访问javascript脚本$里的函数? $ = window.$ || {}; $pt = window.$pt || {}; $.Encryption = $pt.Encryption = function() { function getEncryption() { ... } } (); 怎么调用函数getEncryption? 只有5个C币,不要嫌少啊 解决方案 你这样是访问不了的吧. $ = window.$ || {}; $pt = window.$pt || {};

谈谈PHP语法(2)_php基础

作者:华红狼   上一文<谈谈PHP语法>已谈了PHP的数据类型和表达式.现在,让我们来看看PHP的变量与常数.   让我们先看一例吧. 文件:test.php <?php //这是一种单行注释方法 #这是另一种单行注释方法 /*这是一种多行注释的方法 以下让我们看看例了吧*/   funtion display($file,$line) {   global $message;   echo "FILE:$file<br>";   echo "