作用域以及预解析
在javascript中作用域是非常重要的,本文章将会说明作用域以及我们在工作,以及面试中的一些面试题,如果有不足的地方希望大家可以评论指出来,自己一定会及时的改正错误,避免大家走入一些误区。
谈及作用域先就必须要说明预解析和词法作用域。
下面我们先说明一下:
预解析
-
代码在正常执行操作之前会对文档进行一次解析,这个操作就是将声明提升,
-
声明包括全局范围内 1.带有var的变量, 2.函数
-
文档预解析后会把文档中在全局函数中的内容储存起来,将全局中带有var的变量(var和变量名,注意:变量体不会随着提升,加载var只是告诉我们在文档中有一个全局变量是fn,并不会有其他的作用)提升到最前面
-
预解析后会正常的读取代码(由上至下由左到右)
下面举例说明一下预解析:
var fn=456; function fn(){ }
在上述的代码中我们预解析后会变成:
var fn;
function fn(){
}
fn=456;
所以不管function是在前面还是后面我们打印fn出来的结果都是456。
词法作用域
-
词法作用域在书写代码的时候就已经决定了,与运行无关
-
可以分割词法作用域的只是函数,别的不可以分割
下面举例说明一下词法作用域
if(true){
function fn(){
alert("true");
}
}else{
function fn(){
alert("false");
}
}
fn();
语句在预解析后,会得到以下结果
function fn(){
alert("true");
}
function fn(){
alert("false");
}
if(true){
}
else{
}
fn();
再逐行进行解析;所以上面的函数会被下面的函数覆盖(函数内部的内容会被保存);
在函数执行时会进入判断语句为true的语句中,但是这个时候fn这个函数在预解析后已经变成了false,所以这个时候打印出来的应该是false。
注意:
-
全局中的函数在预解析之后内容会被保存,在执行时不会被二次解析,会被直接拿来用
-
上述例子我们写代码中不可能出现,因为函数不能被语句进行包裹,上面只是为了给大家进行演示而做的例子
正式进入主题介绍作用域
- 不被函数包裹的带有var的变量他们的存在于作用域链的零级链中
- 被函数包裹的带有var变量他们是存在于作用域中的一级链中
- 构造函数原型属性(prototype)等是存在于作用域中的二级链中 ,
- 以此类推,函数一直包裹那么,作用域链也会一直递增下去
- 在此中要特别注意的是隐式全局变量,隐式全局变量如果在函数内部只有在函数在执行时才会被调用
下面通过一个复杂的面试题来为大家讲解作用域链:
function Foo(){ getName = function(){ console.log(1); }; return this; } Foo.getName = function(){ console.log(2); }; Foo.prototype.getName = function(){ console.log(3); }; var getName = function(){ console.log(4); }; function getName(){ console.log(5); } Foo.getName(); getName(); Foo().getName(); getName(); new Foo.getName(); new Foo().getName(); new new Foo().getName();第一步当文档加载的时候会进行预解析,将声明和带有var的全局变量名提升,
解析完之后零级链上回出现两个函数foo和getname
第二步会由上往下加载给foo函数添加一个名为getname的函数给foo的原型中添加一个getname;在刚才的预解析中,零级链上有一个getname,加载之后出现另一个getname会把之前加载的getname覆盖掉
第三步进入函数的执行,
函数在执行时如果在原来的作用域链中有这个函数时,不用从新二次加载,如果出现同名会直接拿来用,
- foo.getname在刚才已经定义了,所以第一个直接得到的是2;
- getname在刚才的作用域零级链中已经被从新定义覆盖,所以第二个直接得到的是4;
- 会先执行foo函数,但是在foo的函数中存在了一个隐式的全局变量(getmame函数);所以当foo执行时会将这个全局变量函数释放出来,getname覆盖原来零级链上的getname;最后执行return this,这个时候this指向的是window,所以会这句话翻译过来也就是window.getname;全局变量中的getname得到的是刚才foo释放出来的隐式全局变量。所以第三个得到的是1;
- getname和刚才第三个翻译过来的结果是一样的,只不过是将window省略; 所以第四个的结果同样是1;
- new foo.getname, foo不是一个函数,所以不能被new,会先执行后面的Foo.getname;foo.getname和第一个一样;第五个所以得到的是2;最后执行new随后foo.getname变成了一个空对象
- new foo().getname() foo是一个函数;new之后就是指被构造函数实例化的对象,对象.getname,但是现在foo这个函数中没有this.getname;所以直接沿着Foo的原型链忘上找;得到了存在于原型链中foo.prototype.getname所以第五个得到的数值是3;
- 第七个和第六个一样,会执行后面的new Foo().getname;得到一个结果是3;随后把这个函数new了一下,变成了一个空的对象
所以得到的结果是2,4,1,1,2,3,3
注意:
- 本文章文字比较多,看的过程可能比较有的不太好理解,
- 如果有疑问可以留下评论,只要在自己会得范畴之内一定会帮忙解答
- 学东西不要觉得烦,这个案例弄通之后作用域至少会百分之80了
- 希望大家在看完之后如果真的学到了对象请不要吝啬你的点赞,收藏,谢谢