一、作用域与作用域链
1.作用域
(1)什么是作用域
a.就是一块“地盘”,一个代码所在的区域
b.作用域是静态的(相对于执行上下文对象),在编写代码时就确定了
c.作用域是一套非常严格的规则,这套规则跟变量的查询有关
(2)作用域分类
1.全局作用域:是针对于全局变量来说,声明在函数外部的变量(所有没有var直接赋值的变量都属于全局变量)
2.函数作用域:使针对于局部变量来说的,声明在函数内部的变量(所有没有var直接赋值的变量都属于全局变量)在函数中定义的变量在函数外不能被获取
3.没有块作用域(ES6有了)
(3)作用域的作用
隔离变量,不同作用域下同名变量不会有冲突
(4)作用域的定义
1.广义上来说:
作用域是负责收集并维护由所有声明的标识符(变量)组成的一系列查询,并实施一套非常严格的规则,确定当前执行的代码对这些标识符的访问权限。
狭义上来说:
2.作用域是程序代码中定义的一个区域,其控制着变量与参数的可见性及生命周期
总结:
作用域与变量的读取跟存储有着密不可分的联系。
2.作用域链
(1)什么是作用域链
1. 多个上下级关系的作用域形成的链, 它的方向是从下向上的(从内到外) 2. 查找变量时就是沿着作用域链来查找的
3.作用域链的个数=n+1(n表示函数声明的个数)
(2)查找一个变量的规则
1. 在当前作用域下的执行上下文中查找对应的属性, 如果有直接返回, 否则进入上一级 2. 在上一级作用域的执行上下文中查找对应的属性, 如果有直接返回, 否则进入上一级 3. 直到全局作用域, 如果还找不到就抛出找不到的异常
3.左右查询
(1)左查询
等号的左边
在变量所在的作用域链中找不到对应变量的声明,浏览器的JS引擎会主动在全局作用域内声明一个
(2)右查询
等号的非左边
在变量所在的作用域链中找不到对应变量的声明,浏览器引擎会抛出ReferenceError: a is not defined(报错)。
(3)练习
console.log(b) ;//报错 找不到变量的声明
function(){ function test(a){ var b=a; console.log(b);//2 } test(2); })() console.log(b);//b is not defined
作用域面试题
<script>
/*
问题: 结果输出多少?
*/
var x = 10;
function fn() {
console.log(x);
}
function show(f) {
var x = 20;
f();
}
show(fn);
</script>
先var一个x=10,然后继续往下执行,看到函数的声明跳过,(函数的声明,当函数被调用时才会执行),
var fn = function () {
console.log(fn)
}
fn()
var obj = {
fn2: function () {
console.log(fn2)
}
}
obj.fn2()
(4)注意点
function test() { // var a = b = c =1; var a=1; b = 1; c =1; } test(); console.log(b,c) // 用var a= b = c =1 的方式声明变量会使b和c的值污染全局 function test() { // var a , b , c =1; var a; var b; var c=1; console.log(a,b,c) } test(); console.log(b,c) // 用var a , b , c =1;的方式声明变量,a , b的值为undefined
4.内存回收机制
JavaScript 具有自动垃圾收集机制,也就是说,执行环境会负责管理代码执行过程中使用的内存。
这种垃圾收集机制的原理其实很简单:找出那些不再继续使用的变量,然后释放其占用的内存。为此,垃圾收集器会按照固定的时间间隔周期性地执行这一操作。
变量的释放 -->瞬间的
内存的回收 -->周期性的
二、执行上下文和执行上下文栈
1.执行上下文(EC:execute context)
(1)什么是执行上下文
1.广义上来说: 执行上下文是一套非常严格的规则 2.狭义上来说: 执行上下文是浏览器的一个c++对象
执行上下文是动态的
执行上下文从属于其对应的作用域
多少个执行上下文=n+1 (n表示函数执行的次数)
一般情况下:一个时间点,一个作用域对应一个执行上下文
特殊情况下:一个时间点,一个作用域对应多个执行上下文 递归时(一个作用域很有可能对应多个执行上下文)
一个作用域中只会存在一个处于活动状态的执行上下文
(2)执行上下文分类
1. 全局执行上下文 2. 函数执行上下文
2.执行上下文栈
(1)压栈
1. 当全局代码开始执行前,先创建全局执行上下文环境
2. 当全局执行上下文环境创建好了以后将上下文中的所有内容放入栈内存
3. 最先放入的在最下边(global)
4. 其他执行的函数的执行上下文依次放入(放入的顺序是代码的执行顺序)
5. 栈中最后放入的执行完最先出栈。
(2)图解
(3)练习
1. 依次输出什么?
2. 整个过程中产生了几个执行上下文?
-->
<script">
//找变量 依据作用域链 找作用域所对应的活动状态的执行上下文中找对应的值
var i;
console.log(i)
i = 1
foo(1);
function foo(i) {
if (i == 4) {
return;
}
console.log(i);
foo(i + 1);
console.log(i);
}
console.log(i)
</script>
3.作用域和执行上下文栈
(1)区别1
1. 除全局作用域之外,每个函数都会创建自己的作用域,作用域在函数定义时就已经确定了。而不是在函数调用时 2. 全局执行上下文环境是在全局作用域确定之后, js代码马上执行之前创建 3. 函数执行上下文环境是在调用函数时, 函数体代码执行之前创建
(2)区别2
1. 作用域是静态的, 在编码时就被定义,只要函数定义好了就一直存在, 且不会再变化 2. 上下文环境是动态的, 调用函数时创建, 函数调用结束时上下文环境就会被释放
(3)联系
1. 上下文环境(对象)是从属于所在的作用域 2. 全局上下文环境==>全局作用域 3. 函数上下文环境==>对应的函数使用域
4.规则
1.全局执行上下文
在执行全局代码前将window确定为全局执行上下文 对全局数据进行预处理 1.var定义的全局变量==>undefined, 添加为window的属性 2.function声明的全局函数==>赋值(fun), 添加为window的方法 3. 提升 4.this==>赋值(window) 开始执行全局代码
console.log(window.parseInt("123PX"));
var a = "a-val";
function test(){
console.log("test");
}
console.log(window.a);
window.test();
console.log(this);
2.函数执行上下文
在调用函数, 准备执行函数体之前, 创建对应的函数执行上下文对象 对局部数据进行预处理 1.形参变量==>赋值(实参) 2.arguments==>赋值(实参列表) 3.处理 var定义的局部变量 4.处理 function声明的函数 5.提升 6.this==>赋值(调用函数的对象)
开始执行函数体代码
function test() {
var a="a";
function inner() {
}
}
test();
window.test();
console.log(window.a);//原型链
console.log(a);//作用域链
3.变量的查找
如果要查找一个作用域下某个变量的值,就需要找到这个作用域对应的处于活动状态的执行上下文环境,再在其中寻找变量的值
5.提升
1.变量的提升
变量的提升 : 将变量的声明提到当前作用域的最顶层
var a;
console.log(a);
a =1;
2.函数的提升
函数的提升 : 将整个函数提到当前作用域的最顶层
function test() {
console.log("test");
}
test();
3.提升的注意点
函数的提升是整体的提升
变量的提升是声明的提升
函数的提升优于变量的提升
提升是指提升到本层作用域的最顶层
var a = 2;
var a = 3;
//
function test() {
console.log("test1")
}
function test() {
console.log("test2")
}
test();
4.练习
<script type="text/javascript"> /*测试题1: 先预处理变量, 后预处理函数*/ function a() {} var a; console.log(typeof a) // /*测试题2: 变量预处理, in操作符*/ var b; if (!(b in window)) { b = 1; } console.log(b) /*if (!(b in window)) { var b = 1; } console.log(b)*/ /*测试题3: 预处理, 顺序执行*/ function c(c) { console.log(c) var c = 3 } var c ; c = 1 c(2) /* var c = 1 function c(c) { console.log(c) var c = 3 } c(2)*/
/*测试题4*/
/* function test(){
function foo(){
console.log(1);
}
foo=5;
}
var foo;
foo=2;
test();
console.log(foo);*/
var foo=2;
test();
function test(){
foo=5;
function foo(){
console.log(1);
}
}
console.log(foo);
/*测试题5*/
/*function test(){
console.log(a);
}
var a;
test();
a=2;*/
test();
var a=2;
function test(){
console.log(a);
}
5.编码规范
1.使用变量前,一定要先声明(避免污染全局 迎合Js引擎的提升规则)
2.在分支结构中最好不要定义函数