变量声明
- 不使用关键字(作用域链机制)
- 使用
var
,function
关键字声明变量- 使用
let
,const
关键字声明变量变量提升
- 在栈内存(作用域)形成,JS代码自上而下执行之前,JS引擎首先会将所有使用
var
/function
关键字的变量进行提前声明或定义- 浏览器中,
var
/function
声明的全局变量会与window的属性存在映射机制(会在变量提升阶段在window对象中创建相应的属性,在赋值的同时对window对象对应的属性进行赋值)var
只声明未定义function
声明 + 定义(变量赋值,值为代码字符串在堆中地址(指针))条件判断下(或块级作用域内)的变量提升
- 老版本(IE10及之前):
- 全局(或函数)作用域顶部:
function
声明 + 定义 提升- 新版本(IE11及之后):
- 全局(或函数)作用域顶部:
function
只有声明提升,也就是function
和var
一样了- 块级作用域顶部:
function
声明 + 定义 提升ES6变量重复检测机制
- 在栈内存(作用域)形成,JS代码自上而下执行之前,JS引擎会对使用ES6语法(
let
,const
)声明的变量进行变量重复检测(函数声明时也会报错,而不是延迟到函数执行时才报错)ES6的变量声明方法解决了旧版本JS的暂时性死区问题
一、不使用关键字“操作”变量
1. 此时不应该叫变量声明,而应该叫变量的“操作”
2. 不使用关键字来“操作”的变量不是私有变量,会向它的上级作用域查找,看是否为上级作用域的变量,一直找到全局作用域的window对象上的属性。(这种查找机制称为作用域链机制)
3. 全局作用域下本质是操作 window 的属性
- 例如
a = 10
本质是window.a = 10
的简写- 例如
document.getElementById()
本质是window.document.getElementById()
的简写- 其本质是属性,不是变量,无变量提升(区分与
var
,function
声明的变量)- 注意这种情况会导致全局环境的污染(内存泄漏)
若查找到 window 的属性都未找到该变量,直接使用会报错
/* 栗子1 全局作用域中 */
console.log('a' in window); // false 其本质是属性,不是变量,无变量提升(区分与 `var`, `function` 声明的变量)
a = 0;
window.a; // 0
/* 栗子2 全局函数中 */
function func(){
b = 1;
}
func();
console.log(window.b); // 1
/* 栗子3 嵌套函数 */
function func1(){
function func2(){
c = 2;
}
func2();
}
func1();
console.log(window.c); // 2
/* 栗子4 若查找到 window 的属性都未找到该变量,直接使用会报错 */
console.log(d); // Uncaught ReferenceError: d is not defined
function func3(){
console.log(d);
}
func3(); // Uncaught ReferenceError: d is not defined
二、var
- 没有块的概念(可以跨块访问,不能跨函数访问)
- 未初始化时默认为
undefined
- 变量提升
- 可重复定义(使用变量提升解释)
- 全局作用域下声明的变量会被挂载到window上(在变量提升阶段完成键的创建,也就是挂载也会提升)
注: 区分
var a = 10, b = 10
与var a = b = 10
-
变量提升
var
声明的变量在js中会经历了两个阶段- 编译阶段进行变量声明提升
- 执行阶段进行赋值
下面举几个栗子
/* 栗子1 */ console.log(value); var value = 8; console.log(value); // js进行变量提升后实际的执行顺序是 var value; console.log(value); value = 8; console.log(value); // undefined 8
/* 栗子2 */ console.log(v1); var v1 = 100; function foo() { console.log(v1); var v1 = 200; console.log(v1); } foo(); console.log(v1); // js进行变量提升后实际的执行顺序是 var v1; console.log(v1); v1 = 200; function foo(){ var v1; // var 声明的变量不能跨函数,且函数内的变量会覆盖掉函数外层的同名变量 console.log(v1); v1 = 200; console.log(v1); } foo(); console.log(v1); // 打印结果:undefined undefined 200 100
-
全局作用域下声明的变量会被挂载到window上(在变量提升阶段完成键的创建,也就是挂载也会提升)
// window console.log('a' in window); // true 挂载会提升 var a = 10; console.log(window.a); // 10
-
注: 区分
var a = 10, b = 10
与var a = b = 10
/* var a = 10, b = 10 这里的 a, b 都是使用 var 声明的变量 var a = b = 10 这里的 a 是 var 声明的变量,b 没有使用 var 声明,本质是window上的属性 var a = b = 10 的本质其实是 { b = 10; var a = b; } */ function func(){ var a1 = 10, b1 = 10; var a2 = b2 = 10; } func(); console.log(window.a1); // undefined console.log(window.b1); // undefined console.log(window.a2); // undefined console.log(window.b2); // 10
三、function
- 没有块的概念(可以跨块访问,不能跨函数访问)
- 函数提升(
function
会进行 声明+定义 提升)- 可重复定义(重写)
- 全局作用域下声明的函数会被挂载到window上(在变量提升阶段完成键的创建,也就是挂载也会提升)
- 条件判断下(或块级作用域内)的变量提升
- js 函数不可重载
-
函数提升
函数声明有两种形式
- 变量字面量式:
var func = function() {}
- 函数声明式:
function func() {}
-
变量字面量式
与
var
的变量提升结果相同/* 变量字面量式 */ console.log(foo); var foo = function(){ console.log(1) } // undefined
-
函数声明式
会进行 声明、定义 提升,且不会被变量声明覆盖(因为已有变量不会被重复声明),但会被变量赋值覆盖
/* 函数声明式 栗子1 */ console.log(foo) function foo(){ console.log(1) } // ƒ foo(){console.log(1)}
/* 函数声明式 栗子2 */ // 函数声明+函数定义 提升 console.log(foo) // f foo(){ return 1 } var foo function foo(){ return 1 } // 不会被变量声明覆盖(因为已有变量不会被重复声明) function bar(){ return 2 } // 先声明定义 var bar // 后声明同名变量 console.log(bar) // f bar(){ return 2 } ,函数不会被变量声明覆盖 // 会被变量赋值覆盖 bar = 10 console.log(bar) // 10 函数会被变量赋值覆盖
- 变量字面量式:
-
全局作用域下声明的函数会被挂载到window上(在变量提升阶段完成键的创建,也就是挂载也会提升)
// window console.log('func' in window); // true 挂载也会提升 function func(){ return 1 } console.log(window.func); // f func(){ return 1 }
-
条件判断下(或块级作用域内)的变量提升
/* 栗子1 */ func(); if(true){ function func(){ console.log(1) } } else{ function func(){ console.log(2) } } // IE11及以后: error: foo is not a function // IE10及之前: 2 /* 栗子2 进阶 */ foo = function() {return true;} bar = function() {return false;} ~function(){ if(bar() && [] == ![]){ foo = function() {return false;} function bar(){return true}; } }() console.log(foo); console.log(bar); // IE11及以后: Uncaught TypeError: bar is not a function // IE10及以前: f foo() {return false;} // f bar() {return false;} /* 栗子3 块级作用域顶部 */ console.log('块级作用域外:', func2); { console.log('块级作用域内顶部', func2); // function func2() {return true;} } // IE11及以后 // 块级作用域外:undefined // 块级作用域内顶部:f func2() {return true;} // IE10及以前 // 块级作用域外:f func2() {return true;} // 块级作用域内顶部:f func2() {return true;}
-
js 函数不可重载
重载:函数名相同,函数参数不同(Java中函数可以重载)
重写:多用于类中的继承,重写父类的函数JS并没有重载,JS可以这样理解, 万物皆对象。
var func = function() { alert(1) }
这里的func
只是对function() { alert(1) }
的引用。当你重写时,func = function() { alert(2) }
,这时func
的引用指向了function() { alert(2) }
,调用不到alert(1)
了,所以JS只有重写。Java是强类型语言, 根据传入的参数类型,个数不同等可以找到不同的方法, 所以Java有重载;
四、let
- 有块的概念(不可跨块访问)
- 未初始化时默认为
undefined
- 无变量提升(所以不能在声明前使用,否则会报错)
- 不可重复定义(重复定义会报错)
-
无变量提升(所以不能在声明前使用,否则会报错)
/* 栗子1 */ a = 1; let a; // Uncaught ReferenceError: Cannot access 'a' before initialization
/* 栗子2 */ function func(){ a = 1; let a; } func(); // Uncaught ReferenceError: Cannot access 'a' before initialization
-
不可重复定义(重复定义会报错(在所有代码执行前报错))
在栈内存(作用域)形成,JS代码自上而下执行之前,JS引擎会对使用ES6语法(
let
,const
)声明的变量进行变量重复检测(函数声明时也会报错,而不是延迟到函数执行时才报错)/* 栗子1 */ let a = 10; console.log(a); let a; // Uncaught SyntaxError: Identifier 'a' has already been declared /* 栗子2 */ let b = 10; console.log(b); { let b; let b; // Uncaught SyntaxError: Identifier 'b' has already been declared } /* 栗子3 函数声明时也会报错,而不是延迟到函数执行时才报错 */ let c = 10; console.log(c); function f(){ let c = 10; let c; // Uncaught SyntaxError: Identifier 'c' has already been declared } /* 栗子4 */ let can = 1; console.log(can); // 1 console.log(d); // Uncaught SyntaxError: Identifier 'd' has already been declared let d = 10;
五、const
- 有块的概念(不可跨块访问)
- 必须初始化,且初始化后其值不能修改
- 无变量提升(同
let
,不能在声明前使用,否则会报错)- 不可重复定义(重复定义会报错)
-
必须初始化
const a; // Uncaught SyntaxError: Missing initializer in const declaration
-
无变量提升(同
let
,不能在声明前使用,否则会报错)/* 栗子1 */ console.warn(a); const a = 1; // Uncaught ReferenceError: Cannot access 'a' before initialization
/* 栗子2 */ function func(){ console.warn(a); const a = 1; } func(); // Uncaught ReferenceError: Cannot access 'a' before initialization