作用域和作用域链:

一.变量作用域
1)全局变量
      在全局环境下声明的变量被视为全局变量。
      在没有使用var进行声明的时候,变量就被定义为全局变量。在ES5的严格模式下,如果变量没有使用var来声明是会报错的。
2)局部变量
      在函数体内部声明的变量被视为局部变量。其中涉及到js中的函数作用域问题。
二. 函数作用域
      因为js中是没有块级作用域的概念,所谓的块级作用域就是指花括号内的的每一段代码都有其自己的作用域,但js中并不是,例如for循环中定义的变量,外界也可以访问;但是js中有函数作用域的概念,即在每一个函数体内,变量是有其自己的作用域的,外界访问不到;
三. 变量提升
      js在编译阶段有个特性,就是变量提升。当js引擎在解析代码的时候,会将变量声明提升至其函数体的顶部。
四:预解析
1.var num=12;
这行简单的代码其实是两个步骤:声明和定义。
声明:var num ; 告诉浏览器在全局作用域中有一个num变量了,如果一个变量只是声明了,但是没有赋值,默认值是undefined。
定义:num = 12; 定义就是给变量进行赋值。
2. var声明的变量和function声明的函数在预解析的区别
var声明的变量和function声明的函数在预解析的时候有区别,var声明的变量在预解析的时候只是提前的声明,function声明的函数在预解析的时候会提前声明并且会同时定义。也就是说var声明的变量和function声明的函数的区别是在声明的同时有没同时进行定义。
3. 预解析只发生在当前的作用域下
程序最开始的时候,只对window下的变量和函数进行预解析,只有函数执行的时候才会对函数中的变量很函数进行预解析。
console.log(num);
var num = 24;
console.log(num);
 
func(100 , 200);
function func(num1 , num2) {
 var total = num1 + num2;
 console.log(total);
}
第一次输出num的时候,由于预解析的原因,只声明了还没有定义,所以会输出undefined;第二次输出num的时候,已经定义了,所以输出24。
由于函数的声明和定义是同时进行的,所以 func()虽然是在func函数定义声明处之前调用的,但是依然可以正常的调用,会正常输出300。
4. 遇到重名,变量和函数重名,就留下函数,与上下关系没有关系,注:只先找var ,function声明的
console.log(a);
// function a({console.log(4);} var a=1; console.log(a); //1 function a(){console.log(2);} console.log(a); //1 var a=3; console.log(a); //3 function a(){console.log(4);} console.log(a); //3
具体过程:
1.找到第一个var存值为undefined(注意不会为其赋值为1,而是undefined)
2.function a(){console.log(2);}代替前面var
3.var =3不会替代function
4.function a(){console.log(4);}替代function a(){console.log(2);}
5.预解析结果:把a=function a(){console.log(4);}存在预解析的仓库里面

(2)逐行解读代码

表达式= + - / ++ -- !参数等,(一个动作能够去做一些改变
表达式可以预解析该仓库里面的值
而function a(){alert(2);}只是一个声明并不会改变a的值
5.如:利用局部改全局 (闭包:在函数外面获取到函数内部的值)
var str=""; function fn1(){ var a='哈哈哈~'; str=a; } fn1(); //console.log(a);//Uncaught ReferenceError: a is not defined console.log(str); //哈哈哈~
6. 函数内部调用外部函数:
function fn2(){ var a='这是fn2的东西!'; fn3(a); } fn2(); function fn3(b){ console.log(b); //注:与fn2里面的a不一样,输出为这是"fn2的东西!" }
7.if(){}
for(){}  while()
不是作用域 块级作用域
五.函数声明提升
声明包括两种: 变量声明和函数声明。不仅变量声明可以提升,函数声明也有提升操作
foo(); function foo(){ console.log(1); //1}
实际执行: function foo(){ console.log(1);}foo();
**函数声明会提升,但函数表达式却不会提升 
foo(); var foo = function(){ console.log(1); //TypeError: foo is not a function}
**即使是具名的函数表达式也无法被提升
foo(); //TypeError: foo is not a function var foo = function bar(){ console.log(1);};
**函数表达式的名称只能在函数体内部使用,而不能在函数体外部使用
var bar; var foo = function bar(){ console.log(1);};bar(); //TypeError: bar is not a function

六.块级作用域:
ES6引入了新的let关键字,提供了除var以外的另一种变量声明方式。let关键字可以将变量绑定到所在的任意作用域中(通常是{...}内部),实现块作用域
比较:
{ let i = 1; };console.log(i); //ReferenceError: i is not defined
for ( var i= 0; i<10; i++) { console.log(i);}console.log(i); //10
块级作用域的优点:使用let方便,由于let循环有一个重新赋值的过程,相当于保存了每一次循环时的值
var a = []; for(let i = 0; i < 5; i++){ a[i] = function(){ return i; }}console.log(a[0]()); //0
重复声明
  let不允许在相同作用域内,重复声明同一个变量
{ let a = 10; var a = 1; //SyntaxError: Unexpected identifier}
{ let a = 10; let a = 1; //SyntaxError: Unexpected identifier}
作用域链:
谈一谈JavaScript作用域链 
  当执行一段JavaScript代码(全局代码或函数)时,JavaScript引擎会创建为其创建一个作用域又称为执行上下文(Execution Context),在页面加载后会首先创建一个全局的作用域,然后每执行一个函数,会建立一个对应的作用域,从而形成了一条作用域链。每个作用域都有一条对应的作用域链,链头是全局作用域,链尾是当前函数作用域。 

  作用域链的作用是用于解析标识符,当函数被创建时(不是执行),会将this、arguments、命名参数和该函数中的所有局部变量添加到该当前作用域中,当JavaScript需要查找变量X的时候(这个过程称为变量解析),它首先会从作用域链中的链尾也就是当前作用域进行查找是否有X属性,如果没有找到就顺着作用域链继续查找,直到查找到链头,也就是全局作用域链,仍未找到该变量的话,就认为这段代码的作用域链上不存在x变量,并抛出一个引用错误(ReferenceError)的异常。 


根据在内部函数可以访问外部函数变量的这种机制,用链式查找决定哪些数据能被内部函数访问。
1. 执行环境:
js为每一个执行环境关联了一个变量对象。环境中定义的所有变量和函数都保存在这个对象中。
全局执行环境是最外围的执行环境,全局执行环境被认为是window对象,因此所有的全局变量和函数都作为window对象的属性和方法创建的。
js的执行顺序是根据函数的调用来决定的,当一个函数被调用时,该函数环境的变量对象就被压入一个环境栈中。而在函数执行之后,栈将该函数的变量对象弹出,把控制权交给之前的执行环境变量对象。
作用域和作用域链:_第1张图片

深入理解作用域:
一.内部原理分成编译、执行、查询、嵌套和异常五个部分进行介绍
1.以var a = 2;为例
(1)分词:把由字符组成的字符串分解成有意义的代码块,这些代码块被称为词法单元(token)
var a = 2;被分解成为下面这些词法单元:var、a、=、2、;。这些词法单元组成了一个词法单元流数组
(2)解析(parsing)
把词法单元流数组转换成一个由元素逐级嵌套所组成的代表程序语法结构的树,这个树被称为“抽象语法树”
(3)代码生成
  将AST转换为可执行代码的过程被称为代码生成
2.执行
  简而言之,编译过程就是编译器把程序分解成词法单元(token),然后把词法单元解析成语法树(AST),再把语法树变成机器指令等待执行的过程
3.查询:在引擎执行的第一步操作中,对变量a进行了查询,这种查询叫做LHS查询。实际上,引擎查询共分为两种:LHS查询和RHS查询 
  从字面意思去理解,当变量出现在赋值操作的左侧时进行LHS查询,出现在右侧时进行RHS查询
  更准确地讲,RHS查询与简单地查找某个变量的值没什么区别,而LHS查询则是试图找到变量的容器本身,从而可以对其赋值
4.嵌套:在当前作用域中无法找到某个变量时,引擎就会在外层嵌套的作用域中继续查找,直到找到该变量,或抵达最外层的作用域(也就是全局作用域)为止
5.异常: 区分LHS和RHS是一件重要的事情。因为在变量还没有声明(在任何作用域中都无法找到变量)的情况下,这两种查询的行为不一样
RHS
【1】如果RHS查询失败,引擎会抛出ReferenceError(引用错误)异常
【2】如果RHS查询找到了一个变量,但尝试对变量的值进行不合理操作,比如对一个非函数类型值进行函数调用,或者引用null或undefined中的属性,引擎会抛出另外一种类型异常:TypeError(类型错误)异常
LHS
【1】当引擎执行LHS查询时,如果无法找到变量,全局作用域会创建一个具有该名称的变量,并将其返还给引擎

(前端小白,如有错误,欢迎指正~~)

你可能感兴趣的:(作用域和作用域链:)