关于作用域和预解析的不常见重要知识
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了关于作用域和预解析的不常见重要知识相关的知识,希望对你有一定的参考价值。
作用域和预解析
在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了一下,变成了一个空的对象
补充知识:
在前面的知识点中为大家讲述了如果在函数内部存在隐式的全局变量,当我们调用这个隐式的全局变量之后,这个隐式的全局变量会提升到全局作用域中;在昨天发现了一个面试题之后本人发现这个认识不是绝对的,现在就下面的例子给大家做一下补充,以及对于作用域中的return;
var Foo=1;
function fn(){
Foo=10;
return ;
function Foo(){
}
}
fn();
console.log(Foo);
当我们看上诉代码时按照正常执行的代码预解析时看起来这个最后得到Foo的结果应该是10;
但是我在这里需要纠正大家一下,这个代码最后Foo的执行结果是1;
下面我为大家讲解一下当我正常得到代码的时候
第一步会进行预解析,预解析之后代码会变成如下(将带有var的变量和声明式函数整体提升):
var Foo
function fn(){
function Foo(){
}
Foo=10;
return ;
}
Foo=1;
fn();
console.log(Foo);
第二步正常的加载代码;在加载时候我们会得到函数体,但是在全局变量中的Foo等于1是先执行的,当我们在调用函数fn的时候函数体中的内容会被读取,但是在这个时候在函数fn中的内容会被形成一个局部变量,但是在这里有一个Foo的隐式全局变量,正常情况下我们执行函数时会将函数中的隐式全局变量释放出来,使其变成一个全局变量,但是在函数中有一个局部的函数Foo其与Foo隐式全局变量同名,通过打断点发现,隐式全局变量继承了函数Foo的局部变量特性,只是值改变了而已。
注意:
-
我们可以得到一个结论函数内部的return只会截断正常的函数执行和读取,但是不会截断正常的预解析,
- 当在函数内的局部变量中有两个同名的对象时,先执行的对象会将自身的局部特性赋给后面的变量,只是将其值改变了,但是自身中的局部特性并不会改变
后言:本人会将一些平时遇到的问题当做补充,不段的跟新在博客内,如果大家有什么疑问也可以留言,只要自己会的一定不吝赐教
以上是关于关于作用域和预解析的不常见重要知识的主要内容,如果未能解决你的问题,请参考以下文章