深入理解 JavaScript 变量的作用域和作用域链
一个变量的作用域(scope)是程序源代码中定义这个变量的区域。简单的说,作用域就是变量与函数的可访问范围。全局变量拥有全局作用域,在JavaScript代码中的任何地方都有定义。局部变量是在函数体内声明而且只作用在函数体内部以及该函数体的子函数的变量。下面我们对全局作用域和局部作用域来做一个深入的理解。
1. 全局作用域(Global Scope)
全部变量拥有全局作用域,在代码的任何地方都有定义,一般来说以下几种情形拥有全局作用域:
(1)最外层函数和在最外层函数外面定义的变量拥有全局作用域,例如:
var scope="global"; //声明一个全局变量
function checksope(){
function showglobal(){
alert(scope); //弹窗全局变量
}
showglobal();
}
checksope() // global 内部函数可以访问全局变量
(2)所有末定义直接赋值的变量自动声明为拥有全局作用域,例如:
function checksope(){
var scope="local";
scopeglobal="global";
alert(scope);
}
checksope(); // local
alert(scopeglobal); // global 不带var关键词声明的变量,
直接升级为全局变量,同时也是全局变量
的一个属性
alert(scope); //脚本错误
变量scopeglobal拥有全局作用域,而scope在函数外部无法访问到。
(3)所有window对象的属性拥有全局作用域
一般情况下,window对象的内置属性都拥有全局作用域,例如window.name、window.location、window.top等等。
2. 局部作用域(Local Scope)
局部作用域一般只在固定的代码片段内可访问到,最常见的例如函数内部,所有在一些地方也会看到有人把这种作用域称为函数作用域,例如下列代码中的blogName和函数innerSay都只拥有局部作用域。
function checksocpe(){
var socpe="local";
function inner(){
alert(socpe);
}
inner();
}
alert(socpe); //脚本错误
inner(); //脚本错误 函数外部无法访问内部定义的函数
函数作用域和提前声明
一、函数作用域
在一些类似c语言的编程语言中,花括号内的每一段代码都具有各自的作用域,而且变量在声明它们的代码段之外是不可见的,我们称之为块级作用域,而JavaScript没有块级作用域。JavaScript取而代之地使用了函数作用域:变量在声明他们的函数体以及这个函数体嵌套的任意函数体内都有定义的。
在如下所示的代码中,在不同位置定义了变量i、j、k,它们都在同一个作用域内——这三个变量在函数体内均是有定义的。
function test(0){
var i = 0; // i在行函数体内时有定义的,
if(typof 0 == "object"){
var j = 0; //j在函数体内是有定义的,不仅仅是在循环内
for(var k=0; k<10;k++){ //k在行函数体内是有定义的,不仅仅是在循环内
console.log(k);//输出数字0-9 }
console.log(k); //k 已经定义了,输出10
}
console.log(j); //j 已经定义了,但是可能没有初始化
}
二、函数的提前声明
javascript的函数作用域是指在函数内声明的所有变量在函数体内始终是可见的,有意思的是,这意味着变量在声明之前甚至已经可用。JavaScript的这个特性被非正式地称为声明提前,即JavaScript函数里声明的所有变量(但不涉及赋值)都被"提前"至函数体的顶部。
tip:"声明提前"这步操作是在JavaScript引擎的"预编译"时进行的,是在代码开始运行之前。
var scope = "glocal";
function f(){
console.log(scope);//输出"undefined",而不是"global"
var scope = "local";//变量在这里赋初始值,但变量本身在函数体内任何地方均是有定义的
console.log(scope);//输出"local"
}
也许您会误认为第一行会输出“global”,因为代码还没有执行到var语句声明局部变量的地方。其实不然,由于函数作用域的特性,局部变量在整个函数体始终是有定义的,也就是说,在函数体内局部变量覆盖了同名全局变量。尽管如此,只有在程序执行到var语句的时候,局部变量才会被真正赋值。因此,上述过程等价于:将函数内的变量声明“提前”至函数顶部,同时变量初始化留在原来的位置:
function f(){
var scope; //在函数定部声明了局部变量
console.log(scope); //undefined
scope = "local";//
console.log(scope); // 输出local
}
作为属性的变量
当声明一个全局变量时,实际上是定义了全局对象的一个属性。使用var声明的变量不可配置,未声明的可配置。如下:
var truvar = 1; //声明一个不可删除的全局变量
fakevar = 2; //创建全局对象的一个可删除的属性
this.fakecar2 = 3;//同上
delete truevar //=> false:变量并没有被删除
delete fakevar //=> true:变量并没有被删除
delete this.fakevar2 //=> true:变量并没有被删除
此规则只对全局变量有效。
作用域链
1、作用域链变量的寻址
如果讲一个局部变量看做是自定义实现的对象的属性的话,那么可以换一个角度来理解作用域。
每一段javascript代码(全局代码或函数)都有一个与之关联的作用域链。这个作用域连是一个对象列表或者链表,这组对象定义了这段代码“作用域中”的变量。当javascript需要查找变量x的值的时候(这个过程称作“变量解析”),它会从链中的第一个对象开始查找,如果这个对象有一个名为x属性,则会直接使用这个属性的值,如果第一个对象中不存在,则会继续寻找下一个对象,依次类推。如果作用域链上没有任何一个对象含有属性x,则抛出错误(ReferenceError)异常。
2、不同的层级作用域上对象的分布
- 在javascript的最顶层(也就是不包含任何函数定义内的代码),作用域链由一个全局对象组成。
- 在不包含嵌套的函数体内,作用域链上有两个对象,第一个是定义函数参数和局部变量的对象,第二个是全局对象。
- 在一个嵌套的函数体内,作用域链上至少有三个对象。当调用这个函数时,它创建一个新的对象来存储它的局部变量,它实际上保存在同一个作用域链。
对于嵌套函数来讲,事情更有趣,每次调用外部函数时,内部函数又会重新定义一遍。因为每次调用外部函数的时候,作用域链都是不同的。内部函数每次定义的时候都有微妙的差别——在每次调用外部函数时,内部函数的代码都是不相同的,而且关联这段代码的作用域链也不相同。
深入理解 JavaScript 变量的作用域和作用域链的更多相关文章
- 深入理解javascript中执行环境(作用域)与作用域链
深入理解javascript中执行环境(作用域)与作用域链 相信很多初学者对与javascript中的执行环境与作用域链不能很好的理解,这里,我会按照自己的理解同大家一起分享. 一般情况下,我们把执行 ...
- 深入理解Javascript变量作用域
在学习JavaScript的变量作用域之前,我们应当明确几点: a.JavaScript的变量作用域是基于其特有的作用域链的. b.JavaScript没有块级作用域. c.函数中声明的变量在整个函数 ...
- 深入理解javascript函数定义与函数作用域
最近在学习javascript的函数,函数是javascript的一等对象,想要学好javascript,就必须深刻理解函数.本人把思路整理成文章,一是为了加深自己函数的理解,二是给读者提供学习的途径 ...
- JavaScript 变量、函数与原型链
定义 || 赋值 1-函数的定义 函数定义的两种方式: “定义式”函数:function fn(){ alert("哟,哟!"); } “赋值式”函数:var fn = funct ...
- 如何理解JavaScript中的原型和原型链
首先是一张关系图,避免抽象化理解时产生的困难 Function对象 函数对象是JavaScript学习中不可避免的一部分,而且这一部分相对重要且抽象 函数的创建方式有2种: 字面量创建 var foo ...
- 深入理解JavaScript中的继承:原型链篇
一.何为原型链 原型是一个对象,当我调用一个对象的方法时,如果该方法没有在对象里面,就会从对象的原型去寻找.JavaScript就是通过层层的原型,形成原型链. 二.谁拥有原型 任何对象都可以有原型, ...
- 三张图较为好理解JavaScript的原型对象与原型链
最近从网上看到别人详细得讲解了js的原型对象和原型链,看完感觉是看得最清晰的一个,于是,摘录到自己博客里 对于新人来说,JavaScript的原型是一个很让人头疼的事情,一来prototype容易与_ ...
- Javascript变量名混淆细节
前言 UglifyJS会对JS文件的变量名进行混淆处理.要理解Javascript变量混淆的细节.我们须要回答下面几个问题: 1.遇到一个变量myName,我们怎么知道这个myName变量要不要混淆 ...
- JavaScript中作用域和作用域链的简单理解(变量提升)
通过阅读<JS高级程序设计>这本书,对js中的作用域和作用域链知识有了初步的了解和认识,准备成笔记供大家参考,笔记中字数比较多,但个人认为叙述的挺详细的,所以希望读者耐心看.再者,本人了解 ...
随机推荐
- 第一个C语言的小项目
这里先写下主要的业务代码,一些库代码稍后补充上 /** * Feed新闻个性化推送 */ #include "push_service_news.h" /** * 保证单进程运行 ...
- 配置VSCode右键菜单
修改注册表,添加鼠标右键 选择文件 Windows Registry Editor Version 5.00 [HKEY_CLASSES_ROOT\*\shell\VSCode]@="Ope ...
- docker学习(5) 在mac中创建mysql docker容器
github上有一个专门的docker-libary项目,里面有各种各样常用的docker镜像,可以做为学习的示例,今天研究下其中mysql镜像的用法,国内镜像daocloud.io也能找到mysql ...
- [LeetCode] Decode String 解码字符串
Given an encoded string, return it's decoded string. The encoding rule is: k[encoded_string], where ...
- [LeetCode] Perfect Squares 完全平方数
Given a positive integer n, find the least number of perfect square numbers (for example, 1, 4, 9, 1 ...
- sql server如何分组编号
我们在生产实践中经常会有这样的需求:分组编号. 如下有一个城市区域表region: 我们需要对上表region按city分组,对region进行排序,得到如下结果: 具体sql如下: select c ...
- EntityFramework之监听者判断SQL性能指标
前言 当我们利用EF这个ORM框架时,我们可能会利用LINQ或者原生的SQL语句来进行数据操作,此时我们无法确定我们的代码是否会给数据库带来一定的负载,当给数据库带来一定的压力时,由于项目中对数据进行 ...
- 如何从Exchange邮箱数据库批量删除特定邮件
- 前端神器avalonJS入门(一)
转自:http://www.cnblogs.com/vajoy/p/4063824.html avalonJS是司徒正美开发和维护的前端mvvm框架,可以轻松实现数据的隔离和双向绑定,相比angula ...
- python中__getattr__和__setattr__
代码: #!/usr/bin/env python #! -*- coding:utf-8 -*- class A(object): def __setattr__(self, key, value) ...