JavaScript - 作用域
1、作用域简介
作用域是可访问变量的集合
作用:控制着变量与参数的可见性和生命周期
优点:(1)减少了名称冲突;(2)提供了自动内存管理; (3)可以访问它们的外部函数的参数和变量,除了this和arguments
分类:(1)全局作用域(Global Scope); (2)块级作用域(Block level scope)
2、JavaScript作用域
注意:JavaScript无块级作用域!!!
分类:(1)全局作用域;(2)局部作用域(函数作用域)
在 JavaScript 中, 对象和函数同样也是变量,作用域为可访问变量,对象,函数的集合。
1)JavaScript全局作用域
定义:全局变量用拥有全局作用域。
使用范围:(1)最外层函数和在最外层函数外面定义的变量
(2)所有未定义直接赋值的变量
(3)所有Windows对象的属性
2)JavaScript局部作用域(函数作用域)
定义:在函数内部访问的变量为局部变量,局部变量拥有局部作用域。
使用范围:(1)特定的代码片段,或者函数中
特点:(1)不同函数可以使用相同的变量名;
(2)局部变量在函数开始执行时创建,函数执行完后局部变量会自动销毁。
3)作用域链和标识符解析
(1)在JavaScript里,万物皆对象,而对象拥有可以编程访问的属性,和一系列不能通过代码访问而仅供JavaScript引擎存取的内部属性,[[Scope]]是其中之一。
[[Scope]]包含了一个函数被创建的作用域中对象的集合,这个集合被称作函数的作用域链。
(2)作用:决定哪些数据能被函数访问
(3)函数作用域中的每个对象被称为一个可变对象,每个可变对象都以“键值对”的形式存在。
例(全局函数):
function add(num1, num2){ var sum = num1 + num2; return sum }
(4)执行环境(执行上下文): 执行函数是创建的一个内部对象。
调用一次函数就会创建一个执行环境,多次调用同一个函数就会创建多个执行环境。当函数执行完毕,执行环境就会被销毁。
每个执行环境都有自己的作用域链,用于解析标识符。
活动对象,作为函数运行时的变量对象,包含所有局部变量,命名参数,参数集合以及this。此对象被推入作用域链的最前端。随执行环境的创建而创建,销毁而销毁。
全局对象,随函数创建而创建,销毁而销毁。
(5)在函数执行过程中,每遇到一个变量,都会经历一次标识符解析过程,以决定从哪里获取或存储数据。每个标识符都要经历这个过程。(所有会影响性能)
变量遮蔽性,名字相同的连个变量存在于作用域链的不同部分,那么标识符就是遍历作用域链时最先找到的哪个。
(6)标识符解析的性能
标识符解析是需要代价的。
全局变量总是存在于执行环境作用域链的最末端。
在执行环境的作用域链中和所有游览器而言,一个标识符所在的位置越深,它的读写速度越慢。
4)改变作用域链
可临时改变作用域链的语句:(1)with语句;(2)try-catch中的catch子句
(1) with语句
作用:给对象的所有属性创建一个变量。
优点:减少代码量
缺点:易产生性能问题(一个新的变量对象被创建,它包含了参数指定的对象的所有属性。这个对象被推入作用域链的首位,而函数的局部变量处于作用域链的第二位,因此访问代价更高了)
(2) try-catch中的catch子句
理由:try代码块中发生错误,执行过程自动跳转到catch子句,然后把异常对象推入一个变量对象饼置于作用域链的首位。在catch代码块内部,函数所有局部变量将会放在第二作用域链对象中
解决办法:尽量简化代码;将错误委托给一个函数来处理
5)动态作用域
(1) 只存在于代码执行过程中,因此无法通过静态分析(查看代码结构)检测出来
(2) 设计动态作用域时,优化的JavaScript引擎就失效了。脚本引擎就必须切回较慢的哈希表的标识符识别方式,这更像是传统的作用域链查找。因此,只有在确切必要时才推荐使用动态作用域
(3) 种类:with语句;try-catch语句的catch子句;包含eval()的函数
eval() => 把一个字符串当作js表达式一样去执行
(4) 例:
function execute(code){ eval(code); function subroutine(){ return window; } var w = subroutine(); console.log(w) }
变量w的值会随code的变化而变化。一般情况下,w=全局的window对象,当code=var window = {}时; w的值就为 {}。如下图所示:
因为eval() 创建了一个局部变量window,此时w的值为局部变量window,而不是全局window对象。