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

3) 做用域链相关的问题

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

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

做用域在许多程序设计语言中很是重要。java

一般来讲,一段程序代码中所用到的名字并不老是有效/可用的,而限定这个名字的可用性的代码范围就是这个名字的做用域。程序员

做用域的使用提升了程序逻辑的局部性,加强程序的可靠性,减小名字冲突。编程

在我最擅长的服务端语言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语言里还有非函数的{},咱们再看看下面的例子:this

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。

原文出处:谈谈javascript语法里一些难点问题(二)

相关文章
相关标签/搜索