JS 作用域与作用域链详解


Posted in Javascript onApril 07, 2015

(1)作用域

一个变量的作用域(scope)是程序源代码中定义的这个变量的区域。

1. 在JS中使用的是词法作用域(lexical scope)

不在任何函数内声明的变量(函数内省略var的也算全局)称作全局变量(global scope)

在函数内声明的变量具有函数作用域(function scope),属于局部变量

局部变量优先级高于全局变量

var name="one";

function test(){

  var name="two";

  console.log(name); //two

}

test();

函数内省略var的,会影响全局变量,因为它实际上已经被重写成了全局变量

var name="one";

function test(){

  name="two";

}

test();

console.log(name); //two

函数作用域,就是说函数是一个作用域的基本单位,js不像c/c++那样具有块级作用域 比如 if  for 等

function test(){

  for(var i=0;i<10;i++){

    if(i==5){

      var name = "one";

    }

  }

  console.log(name); //one

}

test();  //因为是函数级作用域,所以可以访问到name="one"

当然了,js里边还使用到了高阶函数,其实可以理解成嵌套函数

function test1(){

  var name = "one";

  return function (){

    console.log(name);

  }

}

test1()();

test1()之后将调用外层函数,返回了一个内层函数,再继续(),就相应调用执行了内层函数,所以就输出 ”one"

嵌套函数涉及到了闭包,后面再谈..这里内层函数可以访问到外层函数中声明的变量name,这就涉及到了作用域链机制

2. JS中的声明提前

js中的函数作用域是指在函数内声明的所有变量在函数体内始终是可见的。并且,变量在声明之前就可以使用了,这种情况就叫做声明提前(hoisting)

tip:声明提前是在js引擎预编译时就进行了,在代码被执行之前已经有声明提前的现象产生了

比如

var name="one";

function test(){

  console.log(name);  //undefined

  var name="two";

  console.log(name); //two

}

test();

上边就达到了下面的效果

var name="one";

function test(){

  var name;

  console.log(name);  //undefined

  name="two";

  console.log(name); //two

}

test();

再试试把var去掉?这是函数内的name已经变成了全局变量,所以不再是undefined

var name="one";

function test(){

  console.log(name);  //one

  name="two";

  console.log(name); //two

}

test();

3. 值得注意的是,上面提到的都没有传参数,如果test有参数,又如何呢?

function test(name){

  console.log(name);  //one

  name="two";

  console.log(name); //two

}

var name = "one";

test(name);

console.log(name); // one

之前说过,基本类型是按值传递的,所以传进test里面的name实际上只是一个副本,函数返回之后这个副本就被清除了。
千万不要以为函数里边的name="two"把全局name修改了,因为它们是两个独立的name

(2)作用域链

上面提到的高级函数就涉及到了作用域链

function test1(){

  var name = "one";

  return function (){

    console.log(name);

  }

}

test1()();

1. 引入一大段话来解释:
每一段js代码(全局代码或函数)都有一个与之关联的作用域链(scope chain)。

这个作用域链是一个对象列表或者链表,这组对象定义了这段代码中“作用域中”的变量。

当js需要查找变量x的值的时候(这个过程称为变量解析(variable resolution)),它会从链的第一个对象开始查找,如果这个对象有一个名为x的属性,则会直接使用这个属性的值,如果第一个对象中没有名为x的属性,js会继续查找链上的下一个对象。如果第二个对象依然没有名为x的属性,则会继续查找下一个,以此类推。如果作用域链上没有任何一个对象含有属性x,那么就认为这段代码的作用域链上不存在x,并最终抛出一个引用错误(ReferenceError)异常。

2. 作用域链举例:

在js最顶层代码中(也就是不包括任何函数定义内的代码),作用域链由一个全局对象组成。

在不包含嵌套的函数体内,作用域链上有两个对象,第一个是定义函数参数和局部变量的对象,第二个是全局对象。

在一个嵌套的函数体内,作用域上至少有三个对象。

3. 作用域链创建规则:

当定义一个函数时(注意,是定义的时候就开始了),它实际上保存一个作用域链。

当调用这个函数时,它创建一个新的对象来储存它的参数或局部变量,并将这个对象添加保存至那个作用域链上,同时创建一个新的更长的表示函数调用作用域的“链”。

对于嵌套函数来说,情况又有所变化:每次调用外部函数的时候,内部函数又会重新定义一遍。因为每次调用外部函数的时候,作用域链都是不同的。内部函数在每次定义的时候都要微妙的差别---在每次调用外部函数时,内部函数的代码都是相同的,而且关联这段代码的作用域链也不相同。

 (tip: 把上面三点理解好,记住了,最好还要能用自己的话说出来,不然就背下来,因为面试官就直接问你:请描述一下作用域链...)

举个作用域链的实用例子:

var name="one";

function test(){

  var name="two";

  function test1(){

    var name="three";

    console.log(name);  //three

  }

  function test2(){

    console.log(name);  // two

  }

  test1();

  test2();

}

test();

上边是个嵌套函数,相应的应该是作用域链上有三个对象
那么在调用的时候,需要查找name的值,就在作用域链上查找

当成功调用test1()的时候,顺序为 test1()->test()->全局对象window 因为在test1()上就找到了name的值three,所以完成搜索返回

当成功调用test1()的时候,顺序为 test2()->test()->全局对象window  因为在test2()上没找到name的值,所以找test()中的,找到了name的值two,就完成搜索返回

还有一个例子有时候我们会犯错的,面试的时候也经常被骗到。

<!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">

<head>

<script type="text/javascript">

function buttonInit(){

    for(var i=1;i<4;i++){

        var b=document.getElementById("button"+i);

        b.addEventListener("click",function(){ 

            alert("Button"+i); //都是 Button4

        },false);

    }

}

window.onload=buttonInit;

</script>

</head>

<body>

<button id="button1">Button1</button>

<button id="button2">Button2</button>

<button id="button3">Button3</button>

</body>

</html>

为什么?
根据作用域链中变量的寻找规则:

b.addEventListener("click",function(){ 

            alert("Button"+i);

        },false);

这里有一个函数,它是匿名函数,既然是函数,那就在作用域链上具有一个对象,这个函数里边使用到了变量i,它自然会在作用域上寻找它。
查找顺序是 这个匿名函数 -->外部的函数buttonInit() -->全局对象window

匿名函数中找不到i,自然跑到了buttonInit(), ok,在for中找到了,

这时注册事件已经结束了,不要以为它会一个一个把i放下来,因为函数作用域之内的变量对作用域内是一直可见的,就是说会保持到最后的状态

当匿名函数要使用i的时候,注册事件完了,i已经变成了4,所以都是Button4

那怎么解决呢?

给它传值进去吧,每次循环时,再使用一个匿名函数,把for里边的i传进去,匿名函数的规则如代码

<!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">

<head>

<script type="text/javascript">

function buttonInit(){

    for(var i=1;i<4;i++){

        (function(data_i){

        var b=document.getElementById("button"+data_i);

        b.addEventListener("click",function(){ 

            alert("Button"+data_i);

        },false);

        })(i);

    }

}

window.onload=buttonInit;

</script>

</head>

<body>

<button id="button1">Button1</button>

<button id="button2">Button2</button>

<button id="button3">Button3</button>

</body>

</html>

这样就可以 Button1..2..3了

4.上述就是作用域链的基本描述,另外,with语句可用于临时拓展作用域链(不推荐使用with)

语法形如:

with(object)

statement

这个with语句,将object添加到作用域链的头部,然后执行statement,最后把作用域链恢复到原始状态

简单用法:

比如给表单中各个项的值value赋值

一般可以我们直接这样

var f = document.forms[0];

f.name.value = "";

f.age.value = "";

f.email.value = "";

引入with后(因为使用with会产生一系列问题,所以还是使用上面那张形式吧)

with(document.forms[0]){

f.name.value = "";

f.age.value = "";

f.email.value = "";

}

另外,假如 一个对象o具有x属性,o.x = 1;
那么使用

with(o){

  x = 2;

}

就可以转换成 o.x = 2;
假如o没有定义属性x,它的功能就只是相当于  x = 2; 一个全局变量罢了。

因为with提供了一种读取o的属性的快捷方式,但他并不能创建o本身没有的属性。

以上所述就是本文的全部内容了,希望能够对大家学习javascript有所帮助。

Javascript 相关文章推荐
google地图的路线实现代码
Aug 20 Javascript
JavaScript 字符串与数组转换函数[不用split与join]
Dec 13 Javascript
Jquery.LazyLoad.js修正版下载,实现图片延迟加载插件
Mar 12 Javascript
JS关键字变色实现思路及代码
Feb 21 Javascript
javascript根据时间生成m位随机数最大13位
Oct 30 Javascript
js实现div层缓慢收缩与展开的方法
May 11 Javascript
JavaScript获取客户端IP的方法(新方法)
Mar 11 Javascript
终于实现了!精彩的jquery弹幕效果
Jul 18 Javascript
jQuery.datatables.js插件用法及api实例详解
Oct 28 Javascript
遍历js中对象的属性和值的实例
Nov 21 Javascript
如何在js代码中消灭for循环实例详解
Jul 29 Javascript
javascript之Object.assign()的痛点分析
Mar 03 Javascript
JavaScript实现在页面间传值的方法
Apr 07 #Javascript
简易的投票系统以及js刷票思路和方法
Apr 07 #Javascript
使用 TypeScript 重新编写的 JavaScript 坦克大战游戏代码
Apr 07 #Javascript
TypeScript具有的几个不同特质
Apr 07 #Javascript
实现前后端数据交互方法汇总
Apr 07 #Javascript
JavaScript使用cookie记录临时访客信息的方法
Apr 07 #Javascript
javascript结合CSS实现苹果开关按钮特效
Apr 07 #Javascript
You might like
浅析PHP关键词替换的类(避免重复替换,保留与还原原始链接)
2015/09/22 PHP
PHP安装GeoIP扩展根据IP获取地理位置及计算距离的方法
2016/07/01 PHP
解决安装WampServer时提示缺少msvcr110.dll文件的问题
2017/07/09 PHP
javascript基于jQuery的表格悬停变色/恢复,表格点击变色/恢复,点击行选Checkbox
2008/08/05 Javascript
Jquery post传递数组方法实现思路及代码
2013/04/28 Javascript
jQuery+css实现百度百科的页面导航效果
2014/12/16 Javascript
JavaScript输出当前时间Unix时间戳的方法
2015/04/06 Javascript
JavaScript构造函数详解
2015/12/27 Javascript
jQuery qrcode生成二维码的方法
2016/04/03 Javascript
JavaScript中ES6 Babel正确安装过程
2016/07/18 Javascript
js 定义对象数组(结合)多维数组方法
2016/07/27 Javascript
通过扫描二维码打开app的实现代码
2016/11/10 Javascript
Bootstrap Modal遮罩弹出层(完整版)
2016/11/21 Javascript
Javascript基础回顾之(二) js作用域
2017/01/31 Javascript
利用原生js实现html5小游戏之打砖块(附源码)
2018/01/03 Javascript
python实现的二叉树算法和kmp算法实例
2014/04/25 Python
Python上传package到Pypi(代码简单)
2016/02/06 Python
Django权限机制实现代码详解
2018/02/05 Python
python 统计列表中不同元素的数量方法
2018/06/29 Python
python 以16进制打印输出的方法
2018/07/09 Python
python获取中文字符串长度的方法
2018/11/14 Python
OpenCV HSV颜色识别及HSV基本颜色分量范围
2019/03/22 Python
解决python tkinter界面卡死的问题
2019/07/17 Python
python3的数据类型及数据类型转换实例详解
2019/08/20 Python
python基于三阶贝塞尔曲线的数据平滑算法
2019/12/27 Python
python2 对excel表格操作完整示例
2020/02/23 Python
Python 求向量的余弦值操作
2021/03/04 Python
IE浏览器单独写CSS样式的几种方法
2014/10/14 HTML / CSS
HTML5新标签兼容——&gt; 的两种方法
2018/09/12 HTML / CSS
 Alo Yoga官网:购买瑜伽服装
2018/06/17 全球购物
Myprotein台湾官方网站:全球领先的运动营养品牌
2018/12/10 全球购物
办理信用卡工作证明
2014/01/11 职场文书
美德好少年事迹材料
2014/01/19 职场文书
2014乡镇干部纪律作风整顿思想汇报
2014/09/13 职场文书
军人离婚协议书样本
2014/10/21 职场文书
2015年物业公司保洁工作总结
2015/10/22 职场文书