JS 变量提升

1. 变量提升的概念

变量提升的概念:
当栈内存(作用域)形成,JS 代码自上而下执行之前,浏览器首先会把所有带 var / function 关键字开头的进行提前声明或者定义,这种预先处理机制称为“变量提升”。

声明(declare):var a (默认 undefined
定义(defined):a = 12(定义其实就是赋值操作)

变量提升阶段:

  • var 是只提升声明未定义(给个默认值 undefined
  • function 是提升声明和定义都完成了
console.log(a); //=> undefined,如果后面都没有声明,那么就会报错
var a = 12;

b(); //=> 1,可以直接使用,如果后面没有定义,那么会报错
function b() {
  console.log(1);
}

因为用 function 关键字声明的函数 ,在变量提升阶段已经赋好值了,所以我们可以在 JS 文件中的任意位置调用这个函数

深入理解变量提升

变量提升只发生在当前作用域
开始加载页面的时候只对全局作用域下进行变量提升,因为此时函数中存储的都是字符串而已

在全局作用域下声明的函数或者变量是全局变量,同理,在私有作用域下声明的变量是私有变量[带 var/function 的才是声明]

代码执行时,跳过已提升的声明和定义
浏览器很懒,做过的事情不会重复执行第二遍,也就是当代码执行遇到创建函数这部分代码后,直接跳过即可。遇到变量声明时,也会直接跳过声明,进行赋值。因为在提升阶段就已经完成变量的声明和函数的赋值操作。

函数执行,形成私有作用域后,也会进行变量提升
私有作用域形成后,会先进行形参赋值,然后进行变量提升

ES5 中的作用域
在 ES5 语法规范中,只有全局作用域和函数执行的私有作用域,其他大括号不会形成栈内存

2. 带 var 和 不带 var 的区别

在全局作用域下声明一个变量,也相当于个 window 全局对象设置了一个属性,变量值就是属性值。而私有作用域中声明的私有变量和 window 没有关系。

a; //=> undefined
window.a; //=> undefined
'a' in window; //=> true 在变量提升阶段,在全局作用域中声明了一个变量 a,此时就已经把 a 当作属性添加到 window 上了,只不过此时还没有给 a 赋值,默认值为 undefined

var a = 12; 
a; //=> 全局变量 a 12
window.a; //=> window 的属性 a 12

//=> 全局变量和 window 中的属性存在映射机制
a = 13;
window.a; //=> 13
window.a = 14;
a; //=> 14

全局作用域中:
不带 var 时,本质是给 window 添加属性
在读取属性时,首先检测其是否是变量,是变量则读取其值,不是变量,则读取 window 中的属性,window 也没有这个属性,则报错。

a; //=> 报错
window.a; //=> undefined,这里是对象机制,没有这个属性则返回 undefined
'a' in window; //=> false,说明 window 中没有 a
a = 12; //=> 相当于 window.a = 12,此时的 a 不是变量,而是 window 的属性
a;//=> 12
window.a; //=> 12
var a = 12, b = 13; 
//=> 此时 b 是带 var 的,相当于 var a = 12; var b = 13;
var a = b = 12;
//=> 此时 b 不带 var 的,相当于 var a = 12; b = 12;

console.log(a, b); //=> undefined undefined
var a = 12, b = 12;
function fn() {
  console.log(a,b); //=> undefined 12
  var a = b = 13;
  console.log(a, b); //=> 13, 13
}
fn();
console.log(a,b); //=> 12, 13

私有作用域中:

  • 带 var 的在私有作用域变量提升阶段,都声明为私有变量,和外界没有任何关系
  • 不带 var 的不是私有变量,会向它的上级作用域查找,一直找到 window 为止(我们把这种查找机制叫做作用域链)
function fn() {
  b = 12;
  'b' in window;
  //=> true,在作用域链查找过程中,如果知道找到全局作用域 window 中也没有这个变量,则是相当于给 window 设置了一个属性 b
  console.log(b); //=> 12
}
fn();
console.log(b); //=> 12,此时读取的也是 window 的属性

具体分析:

a(); //=> f 函数体
var a = 10;
var a = 20;
console.log(a) //=> 20
a(); //=> 报错,此时 a=20,不是函数
function a() {
  console.log(a);
}
/*
 * 变量提升过程:
 * var a; function a = xxxfff000
 * 代码执行
 * a() --> a 执行,里面输出 a,也就是地址对应函数
 * a = 10; --> 这是的变量 a 从地址变成了数字 10
 * a = 20; --> 这时的变量 a 从数字 10 变成数字 20
 * console.log(a); --> 输出变量 20
 * a() --> a 执行,相当于 20(),不是函数,所以直接报错:a is not a function
 */
fn();// 15
var n = 13;
console.log(n); // 13
fn(); // 15
console.log(n);//15
function fn(){
  n = 15;
  console.log(n);
}
fn(); // 15
alert(n);//15
// 在变量提升阶段   n :undefined
// fn()           n : 15
// n = 13;        n : 13
// fn()           n : 15

3. 变量提升的特殊情况

(1)等号右边的不会进行变量提升

fn(); //=> 变量提升,可以执行
a(); //=> 变量提升,只提升了声明未定义,报错 a is not a function
f(); //=> 不提升,报错,f is not defined
var a = function f(){}
function fn() {}

(2)条件语句中的变量提升

  • 对于 var 没有影响的,不管条件是否成立,都会进行变量提升
  • 对于 function 比较特殊:
    • 在老版本浏览器渲染机制下,声明加定义都提升
    • 为了迎合 ES6 中的块级作用域,新版本浏览器对于函数,不管条件是否成立,都只提升声明,没有定义,类似 var
console.log(sum2,sum3); //=> undefined undefined
console.log(f,g); //=> undefined undefined
if(1 > 2){
  var f = 12;
  function sum2() {
    console.log(12)
  }
} else {
  var g = 14;
  function sum3() {
    console.log(13);
  }
}
console.log(sum2,sum3); //=> undefined f:function sum3(){}
console.log(f,g); //=> undefined 14

当条件成立,进入到判断体中(在 ES6 中它是一个块级作用域,不完整的块级作用域),第一件事并不是代码执行,而是类似于变量提升一样先把函数进行定义,也就是判断体中代码执行之前,函数就已经赋值了。

console.log(fn); //=> undefined
if (1 === 1) {
  console.log(fn); //=> 函数本身
  function fn() {
    console.log(1);
  }
}

(3)匿名函数、自执行函数、等号右边函数和 return 返回的函数都不会有变量提升,只有开头直接使用 function 声明的才会变量提升

//=> 即使加上函数名,也不会变量提升
(function(){
  
})()

var a = function() {}

obj.onclick = function(){}

function a() {
  //=> e 不会提升
  return function e() {
  
  }
}

(4)函数的形参与变量提升

var a=12;
function fn1(){
  console.log(a);
  var a=13;
}
fn1(); //=> undefined

var n=13;
function fn2(n){
  // 在变量提升之前就有了一个形参赋值的过程,也就是相当于
  // n = 13
  // var n 这里 n 的值不会改变,仍是 13
  // console.log(n); //13
  console.log(n);
  var n=14;
  console.log(n);
}
fn2(n);//=> 13 14

4. 重名问题的处理

  1. varfunction 关键字声明的名字,都是变量,只是存储的值不同。取相同的名字,也是重名
  2. 关于重名的处理:如果名字重复了,不会重新声明,但是会重新的定义(赋值),不管是变量提升阶段还是代码执行阶段都是如此。

需要注意的是:在变量提升时,如果变量没有值,才会给它默认的值 undefined,如果已经有值,那么这一步不会执行

/*
 * 变量提升阶段
 * function a 定义,后面的 var a 重名,不会重新声明,所以被忽略
 * /
function a() {
  console.log(a);
}
var a = 12;
/*
 * 变量提升阶段
 * 会把函数声明和定义全部提升,重名的时候,也是在这个阶段进行重新定义
 *
 * 后面代码执行会忽略全部变量声明、函数声明和定义
 * 
 */
fn(); //=> 4
function fn() {console.log(1);}
fn(); //=> 4
function fn() {console.log(2);}
fn(); //=> 4
var fn = 100;
fn(); //=> 报错,fn is not a function
function fn() {console.log(3);}
fn();
function fn() {console.log(4);}
fn();
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,444评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,421评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,363评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,460评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,502评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,511评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,280评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,736评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,014评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,190评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,848评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,531评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,159评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,411评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,067评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,078评论 2 352

推荐阅读更多精彩内容

  • 第2章 基本语法 2.1 概述 基本句法和变量 语句 JavaScript程序的执行单位为行(line),也就是一...
    悟名先生阅读 4,145评论 0 13
  • **一.案发现场 **我们先看一段很简单的代码: var v='Hello World'; alert(v); 这...
    抓住时间的尾巴吧阅读 471评论 0 0
  • 概念 首先,看这篇文章之前确保你已经理解了js作用域。其次要补充,js在运行的时候,会优先加载当前作用域下的变量。...
    jacklin1992阅读 966评论 1 7
  • 经由这一段时间的习画,发现负责语言能力和图像能力的,的确不是同一个脑部区域。 为什么习画令许多的前尘往事翻滚不已呢...
    四月流芳阅读 849评论 0 1
  • 坐在图书馆,莘梓呆呆的望着周围埋头看书的人儿,很喜欢,仿佛去年的自己,自信满满,义无反顾!现在的自己是一个不折不扣...
    伊梦65阅读 454评论 1 2