了解JavaScript的机制
我们学习作用域的方法是将这个过程模拟成几个人之间的对话,那么我们先看一下都有谁参与到了这个对话:
演员表
引擎:从头到尾负责整个JavaScript的编译和执行的过程
编译器:引擎的好朋友之一,主要的工作是语法的分析和代码的生成
作用域:引擎的另外一位好朋友,负责收集并且维护所有声明的标识符进而组成一系列的查询,并执行一非常严格的规则,确定当前执行的代码的这些标识符的访问权限。
对话
当我们看到var a = 2;
这段程序的时候,很可能认为这是一句声明的语句。但是在引擎的眼中,这个是两个过程:一个由编译器在编译的时候处理,另外的一个是引擎在执行的时候处理。
我们仔细的看一下这段代码在底层究竟是如何实现的:
- 遇到
var a;
这个语句的时候,会询问作用域是否已经有一个该名字的变量存在在同一个作用域中,如果有的话,就会忽略这个声明,否则,他会要求作用域在当前作用域的集合中声明一个新的变量。 - 接下来编译器会为引擎生成运行时需要的代码,这些代码用来处理
a = 2
这个赋值操作,引擎运行时先访问作用域,在当前的作用中是否有一个叫做a
的变量,如果有,引擎就会使用这个变量,如果没有,引擎就会继续查找这个变量,如果找到这个变量,就会将2
赋值给他,如果没有找到的话,就会抛出一个异常。
函数作用域
在任意的代码片段外添加包装函数,可以将内部的变量和函数定义隐藏起来,导致外部是没有办法访问内部的任何内容的。
var a = 2;
foo = ()=>{ // <--添加了这一行
var a = 3;
console.log(a); //3
}// <--添加了这一行
foo();// <--添加了这一行
console.log(a) // 2
这个技术解决了一写问题,但是还是有一点不理想,因为会导致一些其他的问题,因为这么做应该先声明一个具名函数foo()
,但是这个foo()
污染了全局作用域,其次,还是需要显示的调用才可以,但是我们期望可以不用使用函数名,并且可以自行运行,这样就更加理想了。
var a = 2;
(()=>{ // <--添加了这一行
var a = 3;
console.log(a); // 3
})() // <--添加了这一行
console.log(a) // 2
匿名和具名
对于函数表达式最熟悉的场景就是毁掉参数,举个例子:
setTimeout(()=>{
console.log("I waited 1 second")
})
上面的就是匿名函数表达式,因为function()..
没有名称标识符,函数表达式是可以匿名的,然而函数表达式是不可以忽略函数名的-- 在JavaScript
中是违法的。
匿名函数的缺点:
- 匿名韩式在栈追踪中不会显示出有具体意义的函数名,让调试变得很有困难。
- 没有函数名,当函数需要引用自身的时候只能使用已经过期的
arguments.callee
引用,比如:在递归中,另一个行数需要引用自身的例子,在时间触发之后时间监听器需要解绑自身。 - 匿名函数省略了对于代码可读性/可理解性很重要的函数名,一个描述性的名称可以让代码不言而喻。
立即执行函数表达式
var a = 2;
(foo =()=>{
var a = 3;
console.log(a); // 3
})();
console.log(a); // 2
上面的函数被包含在一个括号的内部,因此形成了一个表达式,然后在结尾还添加了一个(),可以来立即执行这个函数,比如(foo=()={..})()
立即执行函数表达式的进阶使用方法:就是你把他们当做函数调用并传递参数进去。
var a = 2;
(function foo(global){
var a = 3;
console.log(a); // 3
console.log(global.a); // 2
})(window);
console.log(a); // 2
我们将window
对象的引用传递进去,并将其命名为global
,因此在代码风格上对全局对象的引用变得比引用一个没有全局字样的变量更加清楚啦。这个模式的另外的一个应用场景就是解决undefined
标识符的默认值被错误覆盖导致异常的情况。比如说:我们将一个参数命名成undefined
,但是在对应的位置不传入任何的值,这样就可以保证在代码块中的undefined
标识符的值就是undefined
。
undefined = true; // 这样做对其他的代码是不好的,一定不要这么做
(foo= (undefined)=>{
var a ;
if(a ===undefined){
console.log('undefined is safe here');
}
})();
任何的声明在某个作用域内的变量,都将依附于这个作用域。
a = 2;
var a;
console.log(a); // 2
// ===> 等价于
var a; // 进行编译
a = 2; // 执行的时候进行赋值
console.log(a)
console.log(a); // undefined
var a = 2;
导致上面的结果很简单,声明本身会被提升,提升到最开始,然而赋值或是其他的运行逻辑,会被留在原地。注意一下,我们在进行状态提升的时候会先提升函数的状态,其次才是函数的状态。
foo(); // 1
var foo;
function foo(){
console.log(1);
}
foo = function(){
console.log(2)
}
因为对于上面的代码,JavaScript
的引擎是这样进行理解的:
function foo(){
console.log(1);
}
foo();
foo = function(){
console.log(2)
}
网友评论