关山难越,谁悲失路之人;萍水相逢,尽是他乡之客。
百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程教程 > 技术文章 > 正文

你所不知道的JavaScript中作用域的那些事

guanshanw 2023-08-22 03:06 34 浏览 0 评论

任何程序设计语言都有作用域的概念,简单的说,作用域就是变量与函数的可访问范围,即作用域控制着变量与函数的可见性和生命周期。在JavaScript中,变量的作用域有全局作用域和局部作用域两种。本文的主要内容有:

  • 变量的作用域名

  • 函数作用域和声明提前

  • 作为属性的变量的作用域

  • 作用域链

变量的作用域

一个变量的作用域(scope) 是程序源代码中定义这个变量的区域。 全局变量拥有全局作用域, 在JavaScript代码中的任何地方都是有定义的。 然而在函数内声明的变量只在函数体内有定义。 它们是局部变量, 作用域是局部性的。 函数参数也是局部变量, 它们只在函数体内有定义。

在函数体内, 局部变量的优先级高于同名的全局变量。 如果在函数内声明的一个局部变量或者函数参数中带有的变量和全局变量重名, 那么全局变量就被局部变量所遮盖。

var scope = "global"; // 声明一个全局变量

function checkscope() {

var scope = "local"; // 声明一个同名的局部变量

return scope; // 返回局部变量的值, 而不是全局变量的值

}

checkscope() // => "local

尽管在全局作用域编写代码时可以不写v a r语句, 但声明局部变量时则必须使用var语句。 思考一下如果不这样做会怎样:

scope = "global"; // 声明一个全局变量, 甚至不用var来声明

function checkscope2() {

scope = "local"; // 糟糕! 我们刚修改了全局变量

myscope = "local"; // 这里显式地声明了一个新的全局变量

return [scope, myscope]; // 返回两个值

}

checkscope2() // => ["local", "local"]: 产生了副作用

scope // => "local": 全局变量修改了

myscope // => "local":全局命名空间搞乱了

函数定义是可以嵌套的。 由于每个函数都有它自己的作用域, 因此会出现几个局部作用域嵌套的情况, 例如:

var scope = "global scope"; // 全局变量

function checkscope() {

var scope = "local scope"; //局部变量

function nested() {

var scope = "nested scope"; // 嵌套作用域内的局部变量

return scope; // 返回当前作用域内的值

}

return nested();

}

checkscope() // => "嵌套作用域"

函数作用域和声明提前

在一些类似C语言的编程语言中, 花括号内的每一段代码都具有各自的作用域, 而且变量在声明它们的代码段之外是不可见的, 我们称为块级作用域(blockscope) ,而JavaScript中没有块级作用域。 JavaScript取而代之地使用了函数作用域(functionscope) : 变量在声明它们的函数体以及这个函数体嵌套的任意函数体内都是有定义的。

在如下所示的代码中, 在不同位置定义了变量i、 j和k, 它们都在同一个作用域内——这三个变量在函数体内均是有定义的。

function test(o) {

var i = 0; // i 在整个函数体内均是有定义的

if (typeof o == "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的这个特性被非正式地称为声明提前(hoisting) , 即JavaScript函数里声明的所有变量(但不涉及赋值) 都被“提前” 至函数体的顶部译注10, 看一下如下代码:

var scope = "global";

function f() {

console.log(scope); // 输出"undefined", 而不是"global"

var scope = "local"; // 变量在这里赋初始值,但变量本身在函数体内任何地方均是有定义的

console.log(scope); // 输出"local"

}

你可能会误以为函数中的第一行会输出“global” , 因为代码还没有执行到var语句声明局部变量的地方。 其实不然, 由于函数作用域的特性, 局部变量在整个函数体始终是有定义的, 也就是说, 在函数体内局部变量遮盖了同名全局变量。 尽管如此, 只有在程序执行到v a r语句的时候, 局部变量才会被真正赋值。 因此, 上述过程等价于: 将函数内的变量声明“提前” 至函数体顶部, 同时变量初始化留在原来的位置:

function f() {

var scope; // 在函数顶部声明了局部变量

console.log(scope); // 变量存在, 但其值是"undefined"

scope = "local"; // 这里将其初始化并赋值

console.log(scope); // 这里它具有了我们所期望的值

}

在具有块级作用域的编程语言中, 在狭小的作用域里让变量声明和使用变量的代码尽可能靠近彼此, 通常来讲, 这是一个非常不错的编程习惯。 由于JavaScript没有块级作用域, 因此一些程序员特意将变量声明放在函数体顶部, 而不是将声明靠近放在使用变量之处。 这种做法使得他们的源代码非常清晰地反映了真实的变量作用域。

作为属性的变量

当声明一个JavaScript全局变量时, 实际上是定义了全局对象的一个属性 。当使用var声明一个变量时, 创建的这个属性是不可配置的 , 也就是说这个变量无法通过delete运算符删除。 可能你已经注意到了, 如果你没有使用严格模式并给一个未声明的变量赋值的话, JavaScript会自动创建一个全局变量。 以这种方式创建的变量是全局对象的正常的可配值属性, 并可以删除它们:

var truevar = 1; // 声明一个不可删除的全局变量

fakevar = 2; // 创建全局对象的一个可删除的属性

this.fakevar2 = 3; // 同上

delete truevar // => false: 变量并没有被删除

delete fakevar // => true: 变量被删除

delete this.fakevar2 // => true: 变量被删除

JavaScript全局变量是全局对象的属性, 这是在ECMAScript规范中强制规定的。 对于局部变量则没有如此规定, 但我们可以想象得到, 局部变量当做跟函数调用相关的某个对象的属性。 ECMAScript 3规范称该对象为“调用对象” (call object), ECMAScript 5规范称为“声明上下文对象” (declarative environment record) 。 JavaScript可以允许使用this关键字来引用全局对象, 却没有方法可以引用局部变量中存放的对象。 这种存放局部变量的对象的特有性质, 是一种对我们不可见的内部实现。 然而, 这些局部变量对象存在的观念是非常重要的。

作用域链

JavaScript是基于词法作用域的语言: 通过阅读包含变量定义在内的数行源码就能知道变量的作用域。 全局变量在程序中始终都是有定义的。 局部变量在声明它的函数体内以及其所嵌套的函数内始终是有定义的。

如果将一个局部变量看做是自定义实现的对象的属性的话, 那么可以换个角度来解读变量作用域。 每一段JavaScript代码(全局代码或函数) 都有一个与之关联的作用域链(scope chain) 。 这个作用域链是一个对象列表或者链表, 这组对象定义了这段代码“作用域中” 的变量。 当JavaScript需要查找变量x的值的时候(这个过程称做“变量解析” (variable resolution) ) , 它会从链中的第一个对象开始查找, 如果这个对象有一个名为x的属性, 则会直接使用这个属性的值, 如果第一个对象中不存在名为x的属性,JavaScript会继续查找链上的下一个对象。 如果第二个对象依然没有名为x的属性, 则会继续查找下一个对象, 以此类推。 如果作用域链上没有任何一个对象含有属性x, 那么就认为这段代码的作用域链上不存在x,并最终抛出一个引用错误(ReferenceError)异常。

在JavaScript的最顶层代码中(也就是不包含在任何函数定义内的代码) , 作用域链由一个全局对象组成。 在不包含嵌套的函数体内, 作用域链上有两个对象, 第一个是定义函数参数和局部变量的对象, 第二个是全局对象。 在一个嵌套的函数体内, 作用域链上至少有三个对象。 理解对象链的创建规则是非常重要的。 当定义一个函数时, 它实际上保存一个作用域链。 当调用这个函数时, 它创建一个新的对象来存储它的局部变量, 并将这个对象添加至保存的那个作用域链上, 同时创建一个新的更长的表示函数调用作用域的“链” 。 对于嵌套函数来讲, 事情变得更加有趣, 每次调用外部函数时, 内部函数又会重新定义一遍。 因为每次调用外部函数的时候, 作用域链都是不同的。 内部函数在每次定义的时候都有微妙的差别——在每次调用外部函数时, 内部函数的代码都是相同的, 而且关联这段代码的作用域链也不相同。

作用域链的概念对于理解w i t h语句是非常有帮助的, 同样对理解闭包 的概念也至关重要。

相关推荐

七条简单命令让您玩转Git
七条简单命令让您玩转Git

凭借着出色的协作能力、快速部署效果与代码构建辅助作用,Git已经得到越来越多企业用户的青睐。除了用于开发商业及消费级应用之外,众多科学及政府机构也开始尝试使用这...

2023-10-07 12:14 guanshanw

基本完整的关于Git分支branch的操作
基本完整的关于Git分支branch的操作

Git使用背景项目中要用到dev或者其他分支开发完代码,需要将该分支合并到master的需求操作步骤下面以dev名称为lex为分支名为例来操作一遍客户端操作:...

2023-10-07 12:14 guanshanw

Git 进阶(合并与变基)
Git 进阶(合并与变基)

在Git中整合来自不同分支的修改主要有两种方法:合并(merge)以及变基(rebase)合并(merge)merge流程图merge的原理是找到这两个分...

2023-10-07 12:13 guanshanw

Git学习笔记 003 Git进阶功能 part5 合并(第一部分)

合并(merge)是很常用的操作。尤其是一个庞大的很多人参与开发的企业级应用。一般会设定一个主分支,和多个副分支。在副分支开发完成后,合并到主分支中。始终保持主分支是一个完整的,稳定的最新状态的分支。...

非标题党,三张图帮你理解git merge和git rebase的区别
非标题党,三张图帮你理解git merge和git rebase的区别

初始场景:基于正常的开发分支修改几个小bug,然后在合并到开发分支上。gitmergegitcheckoutfeaturegitmergeho...

2023-10-07 12:13 guanshanw

git 初次使用(01)
git 初次使用(01)

先从github上克隆代码下来:使用vscode克隆代码如下图,填写上github仓库地址:vscode有时候克隆代码速度比较慢,可以用命令行方式克隆gitc...

2023-10-07 12:12 guanshanw

Git 远程操作

4.Git远程操作命令说明gitremote远程版本库操作gitfetch从远程获取版本库gitpull下载远程代码并合并gitpush上传远程代码并合并4.1远程版本库操作gitre...

Git常用命令-总结
Git常用命令-总结

创建git用户$gitconfig--globaluser.name"YourName"$gitconfig--globaluser.em...

2023-10-07 12:12 guanshanw

git中删除从别人clone下来项目的git信息,并修改为自己的分支

如果你从别人的Git存储库中克隆了一个项目,并想要删除与该存储库相关的Git信息,并将其修改为你自己的分支,则可以执行以下步骤:使用gitclone命令克隆存储库:gitclone<u...

git系列-回滚和放弃本地修改

回滚历史提交就是reset的功能。这种情况是已经提交远程仓库,需要回滚到之前的提交。gitreset--hardcommitId//注:强制提交后,当前版本后面的提交版本将会删掉!gi...

GIT使用小技巧大全
GIT使用小技巧大全

在大型软件工程的开发过程中,版本控制是无法绕过去的;目前来说,最火的版本控制软件就是GIT了。早两年SVN比较火,不过被大神linus喷了几次后,就日落西山了,...

2023-10-07 12:11 guanshanw

git相关命令-上
git相关命令-上

这些命令都是看了文档后,个人觉得比较有用的一些,展示给大家。回到远程仓库的状态抛弃本地所有的修改,回到远程仓库的状态。gitfetch--all&...

2023-10-07 12:10 guanshanw

Git命令行接口:掌握Git的必备技能
Git命令行接口:掌握Git的必备技能

Git是一款强大的分布式版本控制工具,它支持命令行界面操作。熟练掌握Git命令行接口,是开发者使用Git的必备技能之一。在这篇文章中,我们将介绍Git命令行接口...

2023-10-07 12:10 guanshanw

Git命令详解
Git命令详解

相信各位小伙伴们应该都对git有一些了解,毕竟作为代码管理的神器,就算不是IT行业的小伙伴肯定也或多或少的听说过一些。今天就来和小伙伴们分享一下自己总结的常用命...

2023-10-07 12:10 guanshanw

工作7年收集到的git命令
工作7年收集到的git命令

概念git中的术语解释:仓库也叫版本库(repository)stage:暂存区,add后会存到暂存区,commit后提交到版本库git安装linux...

2023-10-07 12:10 guanshanw

取消回复欢迎 发表评论: