作用域
作用域就是变量和函数的可访问范围,控制着变量和函数的可见性与生命周期。
作用域分为 静态作用域 和 动态作用域
静态作用域
静态作用域还称为词法作用域:词法作用域是指在词法分析阶段就确定了,不会改变。
在词法作用域中:变量的作用域是在定义时决定而不是执行时决定,也就是说词法作用域取决于源码,通过静态分析就能确定
动态作用域
动态作用域是在运行时根据程序的流程信息来动态确定的,而不是在写代码时进行静态确定的。
动态作用域并不关心作用域和函数是如何声明以及在何处声明的,只关心它们在何处调用。
js采用的静态作用域
js所采用的的作用域模型是词法作用域。
下文中所谈的作用域也都是默认指得是js所采用的的“静态作用域”。 静态作用域根据代码的执行环境又分为:全局作用域、局部作用域(函数作用域)、块作用域
在ECMAScript 5(包括ECMAScript 5)之前的版本中,作用域只有全局作用域和局部作用域,不存在块级作用域;ECMAScript 6引入了let和const关键字,利用let和const可以形成块级作用域。
全局作用域
在代码中任何地方都能访问到的对象拥有全局作用域。全局作用域的变量是全局对象的属性,不论在什么函数中都可以直接访问,而不需要通过全局对象,但加上全局对象,可以提供搜索效率。
- 没有用var声明的变量(除去函数的参数)都具有全局作用域,成为全局变量,所以声明局部变量必须要用var。
- 最外层函数体外声明的变量也具有全局作用域
- window的所有属性都具有全局作用域
局部作用域
局部作用域里面的变量的优先级高于全局变量。
- 函数体内用var声明的变量具有局部作用域,成为局部变量
- 函数的参数也具有局部作用域
var a=3; // 全局变量
function fn(b){ // 局部变量
c=2; // 全局变量
var d=5; // 局部变量
function subFn(){
var e=d; // 父函数的局部变量对子函数可见
for(var i=0;i<3;i++){
console.write(i);
}
alert(i);// 3, 在for循环内声明,循环外function内仍然可见,没有块作用域
}
}
alert(c); //2, 在function内声明但不带var修饰,仍然是全局变量
块作用域
使用let和const关键字声明的变量,会在形成块级作用域。
if (true) {
// 'if' 条件语句块不会创建一个新的作用域
// name 在全局作用域中,因为通过 'var' 关键字定义
var name = 'Hammad';
// likes 在局部(本地)作用域中,因为通过 'let' 关键字定义
let likes = 'Coding';
// skills 在局部(本地)作用域中,因为通过 'const' 关键字定义
const skills = 'JavaScript and PHP';
}
console.log(name); // logs 'Hammad'
console.log(likes); // Uncaught ReferenceError: likes is not defined
console.log(skills); // Uncaught ReferenceError: skills is not defined
作用域链
当多个作用域之间发生嵌套时,如果在当前作用域里面无法找到某个变量,引擎就会在外层作用域中继续查找,直到找到该变量或抵达最外层作用域(即全局作用域)为止。这种查找变量的方式就是根根据作用域链进行查找的。
JavaScript 中每个函数都都表示为一个函数对象(函数实例),而函数对象都有一个仅供 JavaScript 引擎使用的[[scope]]
属性。函数在进行语法分析和预解析,将它的[[scope]]
属性指向函数定义时作用域中的所有对象集合。这个集合被称为函数的作用域链(scope chain),包含函数定义时作用域中所有可访问的数据。
function add(num1, num2) {
var sum = num1 + num2;
return sum;
}
当定义 add 函数后,其作用域链就创建了。函数所在的全局作用域的全局对象被放置到 add 函数作用域链([[scope]] 属性)中。我们可以从下图中看到作用域链的第一个对象保存的是全局对象,全局对象中保存了诸如 this , window , document 以及全局对象中的 add 函数,也就是他自己。这也就是我们可以在全局作用域下的函数中访问 window(this),访问全局变量,访问函数自身的原因。
上下文和作用域
作用域(scope)和上下文(context)是两个不同的概念:
- 作用域:指变量的可访问性
- 上下文:是指this在同一作用域内的值
我们也可以使用call()、apply()、bind()、箭头函数等改变上下文。在浏览器中在全局作用域(scope)中上下文中始终是Window对象。在Node.js中在全局作用域(scope)中上下文中始终是Global 对象。
var name = "windowsName";
function a() {
var name = "Cherry";
console.log(this.name); // windowsName
console.log("inner:" + this);// inner: Window
}
a();
console.log("outer:" + this) // outer: Window
执行上下文和作用域
执行上下文:js引擎在执行代码是所做的一个准备工作,即为这段代码生成对应的执行上下文。
js代码在执行时,就是根据代码的执行来对生成的执行上下文进行赋值操作。