JS作用域详解

来源:互联网 发布:webgl高级编程pdf下载 编辑:程序博客网 时间:2024/06/15 10:35

参考文章书籍:
JavaScript高级程序设计-第四章
深入了解JavaScript,从作用域链开始(1)
JS作用域面试题总结
索引:

    • 一涉及概念知识点
      • 执行环境
      • 作用域
        • 全局作用域
        • 局部作用域函数作用域
      • 作用域链
        • 作用域链的用途
        • 作用域链搜索方法
        • 作用域链访问不可逆
        • JavaScript没有块级作用域
    • 二作用域习题测试
      • 纯作用域作用域链类
      • 作用域变量函数提升类
    • 三延长扩展作用域
      • 延长作用域链
      • 扩展作用域
        • PS


一、涉及概念、知识点

1.执行环境

  • 执行环境定义了变量或者函数有权访问的其他数据,执行环境有与之相关联的变量对象。
  • 执行环境会在环境内所有代码执行完毕后,销毁该环境。(全局执行环境会等到应用程序退出或者浏览器窗口关闭才会销毁)

    1. 全局执行环境
      全局执行环境也即window对象,因此所有的全局变量、函数都是window对象的属性和方法。
    2. 局部执行环境(函数执行环境)
      当执行流进入一个函数时,执行环境变为这一特定函数的局部执行环境。待函数执行完后,栈将环境弹出,转而进入下一个执行环境。

正由于不同执行环境间的切换,因此产生了变量和函数的作用域

2.作用域

  • 作用域代表变量与函数的可访问范围,可以说作用域控制着变量与函数的可见性和生命周期。
  • 在JavaScript中,变量的作用域有全局作用域和局部作用域两种,局部作用域又称为函数作用域。

全局作用域

拥有全局作用域的对象:
1.程序最外层定义的函数或者变量

var a = "tsrot";function hello(){    alert(a);}function sayHello(){    hello();}alert(a);     //能访问到tsrothello();      //能访问到tsrotsayHello();   //能访问到hello函数,然后也能访问到tsrot

2.所有末定义直接赋值的变量(不推荐)

function hello(){    a = "tsrot";    var b = "hello tsrot";}alert(a);  //能访问到tsrotalert(b);  //error 不能访问

3.所有window对象的属性和方法
如window.name、window.location、window.top等等。

局部作用域(函数作用域)

局部作用域在函数内创建,在函数内可访问,函数外不可访问

function hello(){    var a = "tsrot";    alert(a);}hello(); //函数内可访问到tsrotalert(a); //error not defined

3.作用域链

▷作用域链的用途:

保证该执行环境下有权访问的所有变量、函数的有序访问

▷作用域链搜索方法:

目标标识符的解析是从执行环境的最前端开始,沿着作用域链一级一级向后回溯,直到找到标识符为止。

var color = "blue";function changeColor(){        if(color=="blue"){            color = "red";        }        else{            color = "blue";        }    }changeColor();console.log(color); //red

以上代码解释了标识符color的搜索过程。在调用函数changeColor()后,执行到if判定时需要用到变量color。于是在当前执行环境下先于函数内部作用域搜索变量color;未找到后向外层检索,此时访问到了全局变量color="blue"if判定符合条件,执行颜色修改为red

▷作用域链访问不可逆。

整个搜索访问的过程中,可以通过作用域链从内部环境访问外部环境,但不可逆转,是线性有向的过程。

var color = "blue";function changeColor(){    var anotherColor = "red";    function swapColor(){        var tempColor = anotherColor;        anotherColor = color;        color = tempColor;        //这里可以访问color、anotherColor和tempColor    }    //这里可以访问color和anotherColor}//这里只能访问colorchangeColor();

以上代码中,全局环境changgeColor()swapColor()为三个不同的执行环境,可以由内层向外层搜索访问,但不可向内层访问

★JavaScript没有块级作用域

JavaScript与其它语言不同的一点是,没有块级作用域
块级作用域:由花括号封闭的代码块都有自己的作封闭执行环境(作用域)。

JavaScript中只有函数具有块级作用域。
其它if、for、while等具有花括号的语句没有块级作用域,也即语句执行结束后,内部变量、函数仍可以在外层执行环境中访问到!

for (var i;i<10;i++) {                     ------for示例    dosomething(i);}alert(i);    //10//依旧可以访问iif(true){                                  ------if示例    var color = "blue";}alert(color);  //"blue"//同样可以访问到colorfunction add(a,b){                         ------function示例    var sum = a+b;    return sum;}var result = add(10,20);alert(sum);      //error:sum is not defined

二、作用域习题测试

1.纯作用域、作用域链类

1.

var x = 10;function foo() {    var y = 20;    function bar() {        var z = 30;        console.log(x + y + z);    };    bar()};foo();

代码的输出结果为”60″。**函数bar可以直接访问”z”,然后又通过作用域链访问上层
的”x”和”y”。**

2.

var y = 'global';  function test(x){      if(x){          var y ='local';      }      return y;  }  console.log(test(true)); //local 

在当前作用域(test()函数)内,可以找到目标标识符y,因此不需要向上访问全局变量y=“global”

3.

var y = 'global';  function test(x){      (function(){          if(x){              var y = 'local';          }      })();      return y;  }  console.log(test(true));  //global

在当前作用域(test()函数)内,找不到标识符y,因此按照向上搜索的规则,沿着作用域链访问全局变量y=“global”
不能向内层访问自执行函数中的y

4.函数嵌套类

var a=10; function aaa(){  alert(a);};            function bbb(){var a=20;aaa();}bbb();   //10

因为bbb()访问不了内层作用域的变量a,因此向上访问全局变量a = 10.
再进行稍微修改:

var a = 10;function aaa() {                 //step-4    var a=30;    alert(a);                    //step-5->执行alert,此时只能找到外面的a=10故弹框10}; function bbb() {                 //step-2    var a = 20;    aaa();                      //step-3}//定义了函数没啥用,调用才是真格的所以这里是step-1bbb();        //30              //step-1

★5. a=b=10类特殊情况

function aaa(){      var a=b=10; } aaa(); alert(a);//结果为,无法访问到 alert(b);//结果为10;

var a=b=10; 可以解析成 b=10;var a=b; 也就是b为全局变量,a为局部变量,所以外部访问a访问不到,访问b结果为10;

2.作用域+变量(函数)提升类

1.

var y = 'global';  function test(x){      console.log(y);                //undefined      if(x){          var y = 'local';      }      return y;  }  console.log(test(true));           //local  

这里涉及到JavaScript中的变量提升,JavaScript中会自动把变量生命的语句提升到当前作用域的最前方 。
以上代码可以这样来理解

var y = 'global';  function test(x){      var y;                   //声明提前了    console.log(y);      if(x){          y = 'local';        //赋值仍留着原地    }      return y;  }  console.log(test(true));  

当test函数中打印y时,变量y只是被声明了,并没有赋值,所以先打印出了undefined;
当程序继续向下执行,则输出local
2.

var y = 'global';  function test(x){      console.log(y);         //undefined    var y = 'local';      return y;  }  console.log(test(true));    //local

3.

var a = 1;  function b(){      a = 10;    console.log(a);  //10      return;      var a = 100;  }  b();  console.log(a);     // 1

变量a先是全局声明,在调用b()函数时,在内部改变其值为10.在执行完毕后,退出函数环境,因此a的值又变回1
4.

var a = 100;    function testResult(){      var b = 2 * a;      var a = 200;      var c = a / 2;      alert(b);      alert(c);    }    testResult()        //NaN  100

同样是基于变量声明提前,原理参考第1题过程。局部变量a在函数中声明提前到第一行,值为undefined。因此b值为NaN,在a赋值后,c值为100。

5.

var getName = function(){    console.log(2);}function getName (){    console.log(1);}getName();

这个例子涉及到了变量声明提升函数声明提升
上例等同于:

var getName;    //变量声明提升function getName(){    //函数声明提升到顶部    console.log(1);}getName = function(){    //变量赋值依然保留在原来的位置    console.log(2);}getName();    // 最终输出:2

由于变量和函数声明均提升到顶部,因此后面getName又被函数表达式的赋值操作给覆盖了,所以输出2

三、延长、扩展作用域

1.延长作用域链

实现原理:在作用域链的前端增加一个变量对象,当执行流进入下列语句时,作用域链就得到加长:
- try-catch语句的catch
- with语句

1.对于with语句来说,会将指定的对象添加到作用域链中。
2.对于catch语句来说,会创建一个新的变量对象。

例:

function alterUrl() {        var qs = "?debug=true";        with(location) {            var url = href + qs;        }        return url;}

with语句接收location对象,等同于with语句的作用域链扩展添加了location对象作用域部分。
因此,在with语句中可以调用所有的location对象的属性和方法。此时的href默认将获取浏览器的href,无需赋值。

2.扩展作用域

扩展作用域的方法常用有两种:
- call()方法
- apply()方法

PS:

callapply方法是函数本身具有的非继承的方法,不仅可以传递参数,还可以扩充函数运行的作用域

apply()方法能劫持另外一个对象的方法,继承另外一个对象的属性.

Function.apply(obj,args)`方法接收两个参数:
obj:这个对象将代替Function类里this对象
args:这个是数组,可以是Array,也可以是arguments对象。总之它将作为参数传给 Function(args–>arguments)

call:和apply的意思一样,只不过是将参数数组改为了参数列表.

Function.call(obj,[param1[,param2[,…[,paramN]]]])
obj:这个对象将代替Function类里this对象.
params:这个是一个参数列表.

示例:
1.call():
(1)不传递参数,只改变this指向,以扩充作用域。

 window.color = 'red';        document.color = 'yellow';        var s1 = {color: 'blue' };        function changeColor(){            console.log(this.color);        }        changeColor.call();         //red (默认传递参数为window对象参数)        changeColor.call(window);   //red        changeColor.call(document); //yellow        changeColor.call(this);     //red        changeColor.call(s1);       //blue

★(2)call的参数列表必须一一对应,否则将会出现赋值交叉。

function food(name,price){    this.name = name;    this.price = price;}function fruits(name,price,weight){    food.call(this,name,price,weight);    this.weight = weight;}var result = new fruits("pingguo",5,2)alert(result.name+"--"+result.price+"--"+result.weight);   //pingguo--5--2

以上代码参数列表与food的变量顺序是保持一致。如果将参数列表顺序打乱(或者food函数参数顺序打乱),就会出现赋值交叉的情况,如:

function food(name,price){    this.name = name;    this.price = price;}function fruits(name,price,weight){    food.call(this,price,name,weight);    this.weight = weight;}var result = new fruits("pingguo",5,2)alert(result.name+"--"+result.price+"--"+result.weight);  //5--pingguo--2

2.apply():
(1)不传递参数,只改变this指向,扩展作用域

window.number = 'one';document.number = 'two';var s1 = {number: 'three' };function changeColor(){    console.log(this.number);}changeColor.apply();         //one (默认传参为window对象下参数)changeColor.apply(window);   //onechangeColor.apply(document); //twochangeColor.apply(this);     //onechangeColor.apply(s1);       //three

(2)函数间作用域扩展,实现方法调用。

function Person(name,age){                //定义一个人类    this.name = name;    this.age = age;}function Student(name,age,grade){        //定义一个学生类    Person.apply(this,arguments);        //扩展Student的作用域到Person。    //或者Person.call(this,name,age); 也可实现作用域扩展!    this.grade = grade;}var student1 = new Student("xiaowang",21,90);alert(student1.name+student1.age+student1.grade); //xiaowang2190            
原创粉丝点击