执行环境
执行环境定义了变量或函数有权访问的其他数据,决定了它们各自的行为。每个执行环境都有一个相关联的变量对象,环境中定义的变量和函数都保存在这个对象中。
执行上下文
JavaScript 代码是按顺序从上到下被解析的,当然 JavaScript 引擎并非逐行的分析和执行代码,而是逐段的去分析和执行。当执行一段代码时,先进行预处理,如变量提升、函数提升等。
js可执行代码的类型有哪些:
- 全局代码:例如加载外部的js文件或者本地标签内的代码。全局代码不包括 function 体内的代码
- 函数代码:function体内的代码
- eval代码:eval()函数计算某个字符串,并执行其中的js代码。比如eval("alert('hello world')")。虽然很强大,但实际用得很少,不讨论。
每执行一段可执行代码,都会创建对应的执行上下文。在脚本中可能存在大量的可执行代码段,所以 JavaScript 引擎先创建执行上下文栈,来管理脚本中所有执行上下文。
执行上下文是评估和执行javascript代码的环境的抽象概念。每当javascript代码在运行的时候,它都是在执行上下文中运行。执行上下文可以理解为当前代码的执行环境,它会形成一个作用域
JavaScript代码的整个执行过程,分为两个阶段,代码编译阶段与代码执行阶段。编译阶段由编译器完成,将代码编译成可执行代码,这个阶段作用域规则会确定。执行阶段由引擎完成,主要任务是执行可执行代码,执行上下文在这个阶段创建。
执行上下文栈
在一个javascript程序中,必定会产生多个执行上下文,javascript引擎会以栈的方式来处理它们,也就是执行上下文栈(很多文章可能会称它为执行栈,执行上下文堆栈,函数调用栈)。
执行上下文链接:www.cnblogs.com/hezhi/p/100…
变量对象和活动对象链接:www.cnblogs.com/hezhi/p/100…
一、作用域
作用域是在运行时代码中的某些特定部分中变量,函数和对象的可访问性。换句话说,作用域决定了代码区块中变量和其他资源的可见性。
function foo() { var studentName = "亮仔"; console.log(studentName);//亮仔 } foo(); console.log(studentName);//Uncaught ReferenceError: studentName is not definedju
上面的例子中,studentName在全局作用域中没有声明,所以取值时会报错,变量在函数内声明,在函数中有局部作用域,只能在函数内部访问。
作用域就是一个独立的地盘,让变量不会外泄、暴露出去。也就是说作用域最大的用处就是隔离变量,不同作用域下同名变量不会有冲突。
ES6 之前 JavaScript 没有块级作用域,只有全局作用域和函数作用域。ES6 的到来,为我们提供了‘块级作用域’,可通过新增命令 let 和 const 来体现。
全局作用域:在代码中任何地方都能访问到的变量拥有全局作用域。
一般情况下,window 对象的内置属性都拥有全局作用域。
全局作用域有个弊端:如果我们写了很多行 JS 代码,变量定义都没有用函数包括,那么它们就全部都在全局作用域中。这样就会 污染全局命名空间, 容易引起命名冲突。
函数作用域:声明在函数里面的变量。局部作用域一般只在固定的代码片段内可访问到,最常见的例如函数内部。
注意:在js中,块语句(大括号“{}”中间的语句),如 if 和 switch 条件语句或 for 和 while 循环语句,不像函数,它们不会创建一个新的作用域。在块语句中定义的变量将保留在它们已经存在的作用域中。
块级作用域:ES6中通过let和const来声明指定块的作用域外无法被访问。块级作用域在如下情况下被创建:
(1)在函数内部;(2)在一个代码块(由一对花括号包裹)内部;
let与var的用法一样,不同点是声明的变量只在代码块内有效,类似于C,C++,JAVA局部变量的概念。它有以下特点:
(1)不存在变量提升
var 声明时存在变量提升的现象,即变量可以在声明之前使用,值为undefined
// var 的情况
console.log(foo); // 输出undefined
var foo = 2;// let 的情况
console.log(bar); // 报错ReferenceError
let bar = 2;
也就是let声明的变量,必须先定义才能使用。
(2)不允许重复声明
var a = 123; var a = 123; // 可以实现
let b = 123;let b = 123; // Identifier 'a' has already been declared
(3)暂时性死区(同一变量名存在的内部变量屏蔽外部变量)
在被let修饰的同名变量下,根据就近原则,内部变量屏蔽外部变量。
let a = 456; { let a = 123; console.log(a);//123 } console.log(a);//456
for与块级作用域
for (var i = 0; i < 5; i++) { setTimeout(function () { console.log(i); }, i * 1000); }
上面的代码,我们预期是分别输出数字0-4。但实际代码在运行时会输出五次5。
首选,延迟器函数的回调在循环结束时才执行。而在我们每次循环时五个函数是在迭代时分别定义的,但它们都被封闭在一个共享的全局作用域中,因此实际上只有一个i,所有导致每次输出是5。最简单的方法是使用let声明(使用闭包也可以),每次迭代都会生成一个新的块级作用域,i的值才会正确。
for (let i = 0; i < 5; i++) { setTimeout(function () { console.log(i); }, i * 1000); }
with和try/catch也会创建块作用域,这里不多做介绍。
二、作用域链
当代码在一个环境中执行时,会创建变量对象的一个作用域链。作用域链的用途是当变量取值在当前作用域未取到值时,就会去上级作用域查找,直到全局作用域,这样一个查找过程形成的链条叫做作用域链。
var a=5; function foo(){ var a=10; var b=20; console.log(a);//10 (function bar(){ console.log(b)//20 })() } foo()
console.log(a);//5
注意:内部环境可以通过作用域链访问所有的外部环境,但外部环境不能访问内部环境的任何变量和函数。这些环境之间的联系都是线性的、有次序的。每个进入环境都可以向上搜索作用域链,以查询变量和函数名;但任何环境都不能通过向下搜索作用域链而进入下一个执行环境。
延长作用域链
(1)with语句;
(2)try-catch语句中的catch块;
以上两种情况都会延长作用域链。
function bar(obj){ a=1; with(obj){ b=a } return b; } var o1={ a:3 } console.log(bar(o1))
with接受了obj对象,当发现a是它的属性时,停止查找,所以b等于3。
with 语句的原本用意是为逐级的对象访问提供命名空间式的速写方式. 也就是在指定的代码区域, 直接通过节点名称调用对象。with 通常被当做重复引用同一个对象中的多个属性的快捷方式,可以不需要重复引用对象本身。
with代码块中,javascript引擎对变量的处理方式是:先查找是不是该对象的属性,**如果是,则停止。**如果不是继续查找是不是局部变量。
对于catch语句来,try中的代码捕获到错误以后,会把异常对象推入一个可变对象并置于用域的头部,在catch代码块内部,函数的所有局部变量将会被放在第二个作用域对象中,catch中的代码执行完,会立即销毁当前作用域。
三、闭包
官方解释:闭包是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分。(函数就是一个表达式)
概念:指函数外部访问函数作用域中变量(局部变量)的函数;
是指有权访问另一个函数作用域中变量的函数。
function foo(){ var a=2; function bar(){ console.log(a) } return bar; } var baz=foo(); baz();
上面就是一个闭包的例子。一般情况下,当函数执行完毕后,局部活动对象就会被销毁,内存中仅保存全局作用域(全局执行环境的变量对象);而闭包有所不同,foo()函数内部定义的函数bar()作用域中,会包含foo()的活动对象,所以当执行foo()函数后,返回的bar的内部作用域一直存在,导致foo()的活动对象的一直在内存中,当bar执行完后,foo()的活动对象才会被销毁。
1.函数嵌套函数。
2.函数内部可以引用外部的参数和变量。
3.参数和变量不会被垃圾回收机制回收
闭包的作用:
正常函数执行完毕后,里面声明的变量被垃圾回收处理掉,但是闭包可以让作用域里的 变量,在函数执行完之后依旧保持没有被垃圾回收处理掉 。
(1)可以读取函数内部的变量;
(2)让这些变量的值始终保持在内存中;
(3) 增加块级作用域;
1:变量长期驻扎在内存中;
2:避免全局变量的污染;
3:私有成员的存在 ;
使用闭包的注意事项: