作用域和闭包

引擎:从头到尾负责整个javascript程序的编译和执行的过程。

编译器:负责语法分析以及代码生成的过程。

作用域:负责变量的查询的一套规则,并且确定当前代码对某个变量的访问权限。

LHS查询就是查询变量存储的位置并可以赋值。RHS查询就是对变量值沿作用域链进行逐级查询。LHS、RHS查询都是沿着作用域链进行搜索查询,直到找到第一个匹配的标识符为止。之所以区分LHS和RHS,是因为LHS在搜索到顶级作用域时,如果还没有找到该变量,在非严格模式下,引擎为自动在全局作用域下创建一个该具有该名称的变量,并将其返回给引擎;在严格模式下,不允许自动创建或者隐式创建全局变量,此时引擎会抛出ReferenceError异常的错误。而RHS搜索到顶级作用域时,如果还没有找到该变量,此时引擎就会抛出ReferenceError异常的错误。如果RHS查询到了一个变量,却对变量的值进行不合理的操作,比如对非函数类型的值进行函数调用,那么引擎也会抛出一个叫做TypeError的错误。

作用域:是引擎如何在当前作用域以及嵌套的作用域根据标识符名称进行变量查找的一套规则。

词法作用域:是指定义在词法阶段的作用域,也就是由你在写代码时将变量和代码块写哪里来决定的,所以当词法分析器处理代码时会保持作用域不变(大部分情况是这样的)。

欺骗词法作用域,即词法作用域完全由写代码期间函数所声明的位置来定义,在代码运行时,修改词法作用域就是欺骗词法作用域。而欺骗词法作用域会导致性能下降。有两种机制可以欺骗作用域:eval()和with。

eval()的原理:eval()接受一个字符串的参数,并将参数里的内容当作写代码时就出现在这个地方一样。例如:

function foo(str,a){
eval(str);
console.log(a,b);
}
var b=2;
foo(“var b=3;”,1);

执行完eval(str)时,就相当于在foo函数里声明了变量b,并且初始化为3,从而覆盖了全局变量b的值,修改了b的作用域。

With:with通常被当作重复引用同一个对象中的多个属性的快捷方式,将一个对象的引用当作作用域来处理,将对象的属性当作标识符来处理,with会根据传递进去的对象进行创建一个全新的词法作用域。

function foo(obj){
with(obj){
a=2;
}
}
var o1={a:3};
var 02={b:3};
foo(o1);
console.log(o1.a);//2,LHS查询a=2并找到o1.a赋值2
foo(o2);
console.log(o2.a);//02没有a这个属性
console.log(a);//沿着作用域查找a,因此执行a=2时就会在全局作用域下创建一个变量a=2;(非严格模式下)

函数作用域

函数声明和函数表达式的区分:函数声明的第一个词是function,函数表达式的第一个词不是function。

块级作用域

js没有块级作用域的概念,而关键字with、catch可以创建块级作用域。with从对象中创建出的作用域仅在with声明的作用域中有效。Es6中let、const分别可以创建块级作用域变量、块级作用域常量。但是let声明的变量不会被提升到块级作用域最上头。let为其声明地变量隐式地创建了包括let声明的变量所在的{}的块级作用域。

提升

var a=2;js引擎会把它当作两个声明,var a(编译阶段任务);a=2(执行阶段任务);

变量和函数会提升,而函数表达式则不会提升。如果函数和变量重名,先函数提升,再变量提升。

foo();//TypeError,函数表达式不会提升,提升的是变量foo
bar();//ReferenceError
var foo=function bar(){
}

闭包

当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行。

你可能感兴趣的:(web前端,js,作用域,闭包)