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 相关文章推荐
[原创]图片分页查看
Aug 28 Javascript
仿迅雷焦点广告效果(JQuery版)
Nov 19 Javascript
再谈javascript图片预加载技术(详细演示)
Mar 12 Javascript
JavaScript中将一个值转换为字符串的方法分析[译]
Sep 21 Javascript
js报$ is not a function 的问题的解决方法
Jan 20 Javascript
深入理解JavaScript系列(48):对象创建模式(下篇)
Mar 04 Javascript
Jquery判断form表单数据是否变化
Mar 30 Javascript
JavaScript 冒泡排序和选择排序的实现代码
Sep 03 Javascript
微信小程序支付之c#后台实现方法
Oct 19 Javascript
vuex + keep-alive实现tab标签页面缓存功能
Oct 17 Javascript
javascript严格模式详解(含严格模式与非严格模式的区别)
Nov 12 Javascript
Openlayers实现点闪烁扩散效果
Sep 24 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基于CURL进行POST数据上传实例
2014/11/10 PHP
Centos7安装swoole扩展操作示例
2020/03/26 PHP
由prototype_1.3.1进入javascript殿堂-类的初探
2006/11/06 Javascript
JQuery 1.4 中的Ajax问题
2010/01/23 Javascript
node.js 一个简单的页面输出实现代码
2012/03/07 Javascript
js返回前一页刷新本页重载页面
2014/07/29 Javascript
JS上传图片前实现图片预览效果的方法
2015/03/02 Javascript
JavaScript实现表格点击排序的方法
2015/05/11 Javascript
jQuery图片切换动画特效
2016/11/02 Javascript
在js里怎么实现Xcode里的callFuncN方法(详解)
2016/11/05 Javascript
折叠菜单及选择器的运用
2017/02/03 Javascript
jQuery插件HighCharts实现的2D条状图效果示例【附demo源码下载】
2017/03/15 Javascript
easyui datagrid 表格中操作栏 按钮图标不显示的解决方法
2017/07/27 Javascript
vue.extend实现alert模态框弹窗组件
2018/04/28 Javascript
Vue.js 图标选择组件实践详解
2018/12/03 Javascript
vue分页器组件编写方法详解
2019/06/28 Javascript
微信小程序实现树莓派(raspberry pi)小车控制
2020/02/12 Javascript
vue tab滚动到一定高度,固定在顶部,点击tab切换不同的内容操作
2020/07/22 Javascript
[01:04]DOTA2上海特锦赛现场采访 FreeAgain遭众解说围攻
2016/03/25 DOTA
python+opencv实现的简单人脸识别代码示例
2017/11/14 Python
pandas string转dataframe的方法
2018/04/11 Python
python cs架构实现简单文件传输
2020/03/20 Python
python 实现数字字符串左侧补零的方法
2018/12/04 Python
解决pycharm导入本地py文件时,模块下方出现红色波浪线的问题
2020/06/01 Python
python高级特性简介
2020/08/13 Python
python更新数据库中某个字段的数据(方法详解)
2020/11/18 Python
巴西最大的家电和百货零售商:Casas Bahia
2016/11/22 全球购物
来自世界各地的优质葡萄酒:VineShop24
2018/07/09 全球购物
教育专业个人求职信
2013/12/02 职场文书
微观物理专业自荐信
2014/01/26 职场文书
国庆节演讲稿
2014/05/27 职场文书
2014财务年度工作总结
2014/11/11 职场文书
2014财务年终工作总结
2014/12/08 职场文书
nginx常用命令放入shell脚本详解
2021/03/31 Servers
聊聊Lombok中的@Builder注解使用教程
2021/11/17 Java/Android
win10电脑关机快捷键是哪个 win10快速关机的几种方法
2022/08/14 数码科技