作用域是用来规定变量或函数访问其他数据的权限。
JavaScript 语言采用的是词法作用域(静态作用域)规则的。这是否与我们常说JavaScript 中的全局作用域,函数作用域,块级作用域有冲突呢?没有冲突!JavaScript 语言采用的是词法作用域,是语言层面的,而全局作用域等是被包含的。
|
大多数现在程序设计语言都是采用静态作用域规则,而只有为数不多的几种语言采用动态作用域规则。 动态作用域和静态作用域,决定的是作用域链的顺序。
词法作用域 |
函数的作用域在函数定义的时候就决定了,跟函数调用的位置无关。比如:JavaScript 中词法分析是在编译阶段决定的,也就是说,在执行之前就已经确定了作用域的范围。
function foo() {
console.log(name);
}
function func() {
var name = 'dog'
foo()
}
var name = 'pig'
func()//pig
因为JavaScript采用的词法作用域,自foo()
定义开始,它就是属于全局作用域。所以无论将它放置何处调用,它都是在全局作用域中执行。执行 foo()
,从 foo()
内部查找是否有局部变量 name
。如果没有,就到沿着其作用域链查找,此处直接就到全局作用域了。全局作用域存在变量name
,就打印出pig。
动态作用域 |
动态作用域与词法作用域相对,函数的作用域是在函数调用的时候才决定的。
//scope.bash
value=1
function foo () {
echo $value;
}
function func() {
local value=2;
foo;
}
func
创建一个.bash
文件,输入以上代码,执行可得:2
执行 foo 函数,依然是从 foo 函数内部查找是否有局部变量 value。如果没有,就从调用函数的作用域,也就是 func函数内部查找 value 变量,所以结果会打印 2
|
在JavaScript 中,作用域又划分为:全局作用域、函数作用域以及块级作用域。
我们把定义在全局作用域中显示声明的变量,称为全局变量,定义在函数里面或块级作用域显示声明的变量称之为局部变量。
全局作用域、函数作用域自然不必多说,在此简述一下ES6提出的块级作用域。
块级作用域 |
任何一对花括号 {}
中的语句集都属于一个块,ES6规定包含有let,const
的{}
会被提升为块级作用域,块级作用域内被let,const
声明的变量对外都是不可见的。
这里的花括号一般指以下几种情形(不包括函数情形):
// 条件语句
if () {}
// switch语句
switch () {}
// for / while循环语句
for () {}
while () {}
// try...catch语句
try () catch (err) {}
// 单大括号
{}
最简单的块级作用域
{
let age = 24;
var name = 'Joe';
}
该age变量对外是不可见的,name对外可见。
|
当访问一个变量时,解释器会首先在当前作用域查找标示符(变量名),如果没有找到,就去父作用域找,直到找到该变量的标示符或者不在父作用域中,这样由多个作用域形成的链表就叫做作用域链。
作用域是分层的,内层作用域可以访问作用域链上的外层作用域声明的变量,外层的作用域却不可访问内层作用域显示声明的变量。
隐式声明的变量都是全局变量,你可以在内层作用域声明,然后外层作用域也能够访问到该变量的。
值得注意的是:隐式声明的变量不存在变量提升,如果在函数内部,还需要那个函数执行后方可生效。
下图为一个简单的作用域链:
案例分析 |
let name = 'name'
function f() {
console.log(this.name);
}
const obj = {
name: 'obj',
foo() {
console.log(this.name);
},
faz: function() {
console.log(this.name);
},
bar: () => {
console.log(this.name);
},
info: {
foo: () => {
console.log(this.name);
},
fun: f
}
}
obj.foo()
obj.faz()
obj.bar()
obj.info.foo()
obj.info.fun()
this
可以改变调用关系,但是没有改变作用域。
箭头函数没有this
,原因是他没有原型对象。箭头函数依赖于它外边第一层作用域的this
。
浏览器全局对象是window
,window
自身具备name
属性,默认为空字符串。
let
声明的全局变量不会作为window
的属性。
this
指向最终调用对象
最后是undefined
的原因是,this指向了obj.info
对象,该对象没有nane
属性
不加this
,直接打印5个name。函数作用域在定义的时候就决定好的了,对象自身不具备作用域,所以对象里面的5个方法都是属于全局作用域,全局作用域定义了let name = 'name'
。
目前,只有对象方法的简写法(getter属性描述符的方法)可以让 JavaScript 引擎确认,定义的是对象的方法。对象内其他形式的方法只能称为对属性的赋值。
const obj={
foo(){},//对象的方法
faz:function(){}//属性的赋值
}
foo(){}
是伴着obj生成的,占据对象内存空间的,而faz:function(){}
,编译阶段就将function(){}
开辟新的内存空间装载好,之后faz指向该函数(函数也是对象)地址,是引用关系。
对象自身不具备作用域的,所以对象里面的“方法”也不会形成作用域。