在当前执行上下文中(不管是全局,仍是函数执行私有的),
JS
代码自上而下执行以前,首先会默认把全部带VAR
和FUNCTION
关键字的进行声明或者定义。javascript
- 带
VAR
的只是提早声明- 带
FUNCTION
的会提早声明+定义
拿var n = 100
为例,咱们以前讲过的操做步骤应该是:java
值100
存储到栈内存中(引用数据类型首先建立堆,存完数据后,把堆的地址存放到栈中)var a
;这是咱们以前说过的建立变量并赋值的过程;面试
变量声明(declare)
;变量定义(defined)
上面咱们所说的是咱们常规的建立变量并赋值的操做。浏览器
那当咱们只声明变量时,也就是
var a
;可是没有给变量赋值(也就是未定义),因此默认值是undefined
未定义 (这也是undefined
的由来)bash
为了更好的理解变量提高,咱们仍是得先从浏览器的机制提及:函数
执行环境栈ECStack(Execution Context Stack)
这里咱们不在过多讲解堆栈内存,前面已经专门讲过。ui
有了栈内存代码就能够自上而下的执行了,只不过刚开始,是要把全局下的代码先执行;spa
二、开始执行全局下的代码,就会造成一个全局执行上下文EC(GLOBAL 简写 为G)
(上下文就是上文下文加一块儿就是环境的意思);code
一、2 两步总体原理:咱们的目的是为了执行代码,因此围绕代码执行引起的下列操做:cdn
- ==> 浏览器加载页面,想让代码执行,首先会造成一个
栈内存(执行环境栈 ECStack)
;而后开始让代码准备执行;- ==> 最开始要执行的必定是全局下的代码,
- ==> 此时造成一个
全局代码的执行环境(全局执行上下文 EC(G))
- ==> 造成以后,
把EC(G)压缩到栈内存中去执行(进栈
);每个函数的执行也是这样操做的...- ==> 有些上下文在
代码执行完成后,会从栈内存中移除去(出栈)
,可是有些状况是不能移出去的(例如:全局上下文就不能移除...);- ==> 在下一次有新的执行上下文进栈的时候,会把
以前没有移除的都放到栈内存的底部,让最新要执行的在顶部执行
三、对应代码会在本身所属的执行上下文中执行,而这个环境中有一个存放变量的地方:变量对象(VO/AO)
全局变量对象VO(G) ->全称Variable Object (Global)
在咱们以前的理解,建立完一系列的环境后,就能够执行代码了,然而并无,在代码执行以前还有一系列的操做要作;例如:词法解析 => 形参赋值 => 变量提高...等好多好多事情;词法解析和形参赋值咱们后面会说,如今咱们先来理解:变量提高
变量提高:就是在当前上下文中,JS代码执行以前要作的事情;首先会默认把全部带
VAR
和FUNCTION
关键字的进行声明或者定义。
- 带
VAR
的只是提早声明- 带
FUNCTION
的会提早声明+定义
咱们如下题为例
console.log(a);
console.log(func);
var a = 10;
function func(){
console.log(b);
var b=20;
console.log(b);
}
func();
console.log(a);
console.log(func);
复制代码
VAR
和先声明,找到带FUNCTION
的声明+定义
var a
;function func(){...}
;(按照函数建立的步骤,即声明又定义)
func
与十六进制地址关联;conlose.log(a)
:=> 此时在当前执行上下文中,已经声明过a
可是并无赋值,因此打印结果为undefined
;console.log(func)
:=> 此时当前上下文中已经声明了func
变量,而且已经赋值,因此打印func
的结果为f func(){...}
var a = 10
:=> 以前咱们在变量提高阶段,已经完成了var a
操做(浏览器是又懒惰性的,作完了这件事,不会在作第二遍了),因此此时只建立了一个值10
,没有在建立变量a
;把10
与以前在变量提高阶段声明的变量a
关联;function func(){...}
:=> 上面咱们说了,同一件事情浏览器不会作两便;很明显以前在变量提高阶段已经作过这件事了,因此此步就会跳过;直接进行下一步;func()
:=> 让函数执行;
私有上下文EC(func随便起的名字)
;私有变量对象AO(func1随便起的名字) ->全称Active Object
;VAR
和先声明,找到带FUNCTION的声明+定义
var b
;function
的console.log(b)
:=> 此时b
以声明为定义,因此是undefined
;var b=20
:=> 同全局同样,跳过var b
,直接建立值,并与之声明的b
关联;console.log(b)
:=> 此时,b
已经被赋值,因此结果为20
;console.log(a)
:=> 此时a
的值为10
;console.log(func)
:=> 此时func
依然指向堆内存所对应的十六进制地址,因此打印结果为f func(){...}
;上面咱们简单的理解了变量提高的含义;
func();
function func(){
console.log('OK');
}
+ 答案是不会报错
复制代码
func();
var func = function (){
console.log('OK');
}
+ 答案是会报错:
+ 变量提高阶段:var func; 只定义不赋值 (默认值是undefined)
+ //=> undefined() =>Uncaught TypeError: func is not a function
复制代码
对比上面两题的区别,咱们得出结论:
- 函数表达式方式建立函数,在变量提高阶段,并不会给函数赋值,这样只有代码执行的过程当中,函数赋值后才能够执行(不能在赋值以前执行) =>符合严谨的执行逻辑 =>真实项目中推荐的方式
- 为了保证语法的规范性,JS中本来建立的匿名函数,咱们最好设置一个名字,可是这个名字在函数外面仍是没法使用的,只能在本函数体中使用(通常也不用) 例如:
var func = function anonymous() {
// console.log(anonymous); //=>当前这个函数
};
// console.log(anonymous); //=>Uncaught ReferenceError: anonymous is not defined
func();
//================================================
var func = function func() {
console.log(func); //=>函数名func
};
console.log(func); //=>变量func,存储的值是函数
func();
复制代码
截止目前(注意是目前),咱们的上下文只有两种:
- => 全局上下文
- => 函数执行产生的私有上下文
VAR
的/* * 全局上下文中的变量提高: * [VO(G) 全局变量对象] * var n; 不论IF条件是否成立都进行(只要不在函数里面,带VAR的都要变量提高) * 不管是IF仍是FOR中的VAR都进行提高; */
console.log(n); //=>undefined
if (1 > 1) { //=>条件不成立
var n = 100;
}
console.log(n); //=>undefined
复制代码
FUNCTION
的function func(){ ... }
声明+定义都处理了function func
; 用判断或者循环等包裹起来的函数,在变量提高阶段,不论条件是否成立,只会先声明/* * 全局上下文中的变量提高: * [VO(G) 全局变量对象] * [IE 10及之前以及谷歌等浏览器低版本状态下] * function func(){ ... } 声明+定义都处理了 * * [最新版本的浏览器中,机制变了] * function func; 用判断或者循环等包裹起来的函数,在变量提高阶段,不论条件是否成立,此处只会先声明 */
console.log(func); //=>undefined
if (1 === 1) {
// 此时条件成立,进来的第一件事情仍是先把函数定义了(迎合ES6中的块做用域) => func=function(){ .... }
console.log(func); //=>函数
function func() {
console.log('OK');
}
console.log(func); //=>函数
}
console.log(func); //=>函数
复制代码
一、在当前执行上下文中,无论条件是否成立,变量提高是有效的
二、[IE 10及之前以及谷歌等浏览器低版本状态下]:
function func(){ ... }
声明+定义都处理了三、[最新版本的浏览器中,机制变了]:
function func
; 用判断或者循环等包裹起来的函数,在变量提高阶段,不论条件是否成立,只会先声明 重点在写一遍强调:
// 浏览器有一个特征:作过的事情不会从新再作第二遍(例如:不会重复声明)
/* * 全局上下文中的变量提高 * fn = function(){ 1 } 声明+定义 * = function(){ 2 } * var fn; 声明这一步不处理了(已经声明过了) * = function(){ 4 } * = function(){ 5 } * 结果:声明一个全局变量fn,赋的值是 function(){ 5 } */
fn(); //=>5
function fn(){ console.log(1); } //=>跳过(变量提高的时候搞过了)
fn(); //=>5
function fn(){ console.log(2); } //=>跳过
fn(); //=>5
var fn = function(){ console.log(3); } //=>var fn; 这一步跳过,可是赋值这个操做在变量提高阶段没有搞过,须要执行一次 => fn = function(){ 3 }
fn(); //=>3
function fn(){ console.log(4); } //=>跳过
fn(); //=>3
function fn(){ console.log(5); } //=>跳过
fn(); //=>3
复制代码
/* * 低版本浏览器(包含IE10及之内) * 全局上下文中变量提高 * 没有 */
f=function (){return true;};//给GO中设置一个属性 f = function () {return true;}
g=function (){return false;};//给GO中设置一个属性 g = function () {return false;}
(function () {
/* * 自执行函数执行,造成一个私有的执行上下文 * [变量提高] * function g(){return true;} */
// 条件解析:
// g() => 私有的G执行 TRUE
// []==![] => []==false => 0==0 => TRUE
if (g() && [] == ![]) { //=>条件成立
f = function () {return false;} //f不是本身私有的,则向上查找,属于全局对象中的f,此处是把全局对象中的 f = function () {return false;}
function g() {return true;} //跳过(变量提高处理过了)
}
})();
console.log(f()); //=>FALSE
console.log(g()); //=>FALSE 这个G找全局的(函数里面的G是本身私有的)
复制代码
/* * 高版本浏览器 * 全局上下文中变量提高:没有 */
f=function (){return true;};//给GO中设置一个属性 f = function () {return true;}
g=function (){return false;};//给GO中设置一个属性 g = function () {return false;}
(function () {
/* * 自执行函数执行,造成一个私有的执行上下文 * [变量提高] * function g; 高版本浏览器中,在判断和循环中的函数,变量提高阶段只声明不定义 */
// 条件解析:
// g() => undefined() => Uncaught TypeError: g is not a function 下面操做都不在执行了
if (g() && [] == ![]) {
f = function () {return false;}
function g() {return true;}
}
})();
console.log(f());
console.log(g());
复制代码