一、var作用域
1. 函数可以访问外部变量,但函数里面的变量,不能在全局中使用,除非函数将该变量返回。
var a = 10;
console.log(a); // 10
function exam() {
var b = 20;
c = 30;
console.log(a);
console.log(b);
}
exam(); // 10 20 调用函数,函数里的值
console.log(c); //30 函数内部只赋值,c是全局变量
console.log(b); // ReferenceError: b is not defined 函数里面的变量,不能在全局中使用,要用的话写return返回某个变量,在函数调用时写一个变量接收函数返回的变量即可
2. 允许重复的变量声明,导致数据被覆盖。
var a = 1;
function print() {
console.log(a);
}
var a = 2;
// console.log(print()); // undefined
print(); // 2
3. 变量提升:怪异的数据访问。
if (Math.random() < 0.5) {
var a = 5;
console.log(a);
} else {
console.log(a);
}
相当于:
var a;
if (Math.random() < 0.5) {
a = 5;
console.log(a);
} else {
console.log(a);
}
4. 变量提升:闭包问题。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div id="divButtons"></div>
</body>
// 获取div,往div里面追加按钮,并给按钮添加点击事件。
<script>
var div = document.getElementById("divButtons");
for (var i = 1; i <= 10; i++) {
var btn = document.createElement("button");
btn.innerHTML = "按钮" + i;
div.appendChild(btn);
btn.onclick = function () {
console.log(i); // 不管点哪个都是 11
};
}
</script>
</html>
原因:存在变量提升,如下代码:
使用的是同一个作用域中的i,循环很快,点击事件慢,是在循环结束后的
<script>
var i; // 使用的同一个i
var div = document.getElementById("divButtons");
for (i = 1; i <= 10; i++) {
var btn = document.createElement("button");
btn.innerHTML = "按钮" + i;
div.appendChild(btn);
// 只有点击了btn,才会打印输出,此时,循换早已结束
btn.onclick = function () {
console.log(i); // 不管点哪个都是 11
};
}
</script>
(1)立即执行函数解决:
<script>
var i; // 使用的同一个i
var div = document.getElementById("divButtons");
for (i = 1; i <= 10; i++) {
var btn = document.createElement("button");
btn.innerHTML = "按钮" + i;
div.appendChild(btn);
// 立即执行函数解决闭包问题
(function(i){
btn.onclick = function () {
console.log(i); // i
};
})(i)
}
</script>
(2)let关键字解决:
<script>
var div = document.getElementById("divButtons");
for (let i = 1; i <= 10; i++) {
var btn = document.createElement("button");
btn.innerHTML = "按钮" + i;
div.appendChild(btn);
btn.onclick = function () {
console.log(i); // i
};
}
</script>
原理: 在循环中,用let
声明的循环变量,会特殊处理,每次进入循环体,都会开启一个新的作用域,并将循环变量绑定到该作用域上(每次循环,使用的是一个全新的循环变量),且循环结束后,变量会被销毁。
5. 全局变量挂载到全局对象:导致全局对象成员污染问题。
导致consol.log
不能使用,而consol.log
本是window全局对象
已有的成员。
var console = "abc";
console.log(console); // TypeError: console.log is not a function
二、变量提升
如:
console.log(a); // undefined
var a = '小琦';
console.log(a); // 小琦
相当于:
var a;
console.log(a); //只声明未赋值,返回结果undefined
a = '小琦';
console.log(a); // 小琦
三、函数提升
创建函数有两种形式,1、函数声明;2、函数字面量(即函数表达式)。【而只有函数声明形式才有函数提升】,还有一种是方式:函数构造法:var a = new Fun()
,技术角度来讲也是一个字面量形式。
比如:
console.log(a); // f a(){ console.log(a) }
console.log(b); // undefined
// 函数声明
function a() {
console.log(a);
}
// 函数字面量
var b = function () {
console.log(b);
};
相当于:
var a = "function";
var b;
console.log(a); // function
console.log(b); // undefined
参考:https://blog.csdn.net/hualvm/article/details/84395850
再比如:
console.log(a); // [Function: a]
console.log(a()); // undefined
var a = 456;
function a() {
console.log(123); // 123
}
console.log(a); //456
a = 789;
console.log(a); // 789
console.log(a()); // TypeError: a is not a function
相当于:
// 函数提升,函数提升优先级高于变量提升
function a() {
console.log(123); // 123
}
// 变量提升,变量提升不会覆盖(同名)函数提升,只有变量再次赋值时,才会被覆盖
var a;
console.log(a); // [Function: a]
console.log(a()); // undefined
// 变量赋值,覆盖同名函数字面量
a = 456;
console.log(a); //456
// 再次赋值
a = 789;
console.log(a); // 789
console.log(a()); // TypeError: a is not a function
函数提升要比变量提升的优先级要高一些,且不会被变量声明覆盖,但是会被变量赋值之后覆盖。
注意区别foo()
与foo
:
function foo() {
// console.log("函数声明");
}
var foo;
console.log(foo()); // undefined 调用函数,返回函数执行的结果,函数没return,默认为:undefined
foo = "变量";
console.log(foo); // 变量
function foo() {
// console.log("函数声明");
}
var foo;
console.log(foo); // [Function: foo]
foo = "变量";
console.log(foo); // 变量
四、let关键字
1. let
声明的变量不会
挂载到全局对象,则无污染全局对象的问题,如下代码:
let a = 123;
console.log(window.a); // undefined
2. let声明的变量,不允许当前作用域
范围内重复声明
let a = 123; // 全局作用域定义a
// let a = 456; // 报错,检查到当前作用域已有该变量声明
{
let a = 789; //块级作用域定义a:代码执行时遇到花括号,花括号结束,块级作用域结束,块级作用域外面不可访问块级作用域里的变量
console.log(a); // 789
}
console.log(a); // 123
if (Math.random() < 0.5) {
let a = 5; // 定义在当前作用域中,当前作用域中的a
console.log(a);
} else {
// 这是另一个块级作用域,该作用域找不到a
console.log(a); // ReferenceError: a is not defined
}
console.log(a); // 这是全局作用域a,该作用域找不到a ReferenceError: a is not defined
3. let声明变量之前,不可使用该变量
console.log(a);
let a = 123; // 报错
底层实现上,let
声明的变量实际上也会有提升,但是,提升后会将其放入到“暂时性死区”,如果访问的变量位于暂时性死区,则会报错:“Cannot access 'a' before initialization”
。当代码运行到该变量的声明语句时,会将其从暂时性死区中移除。
五、const关键字
const
和let
完全相同,仅在于const
声明的变量,必须在声明时赋值,而且不可以重新赋值。实际上,在开发中,应尽量使用const
来声明变量,以保证变量的值不会随意篡改,原因如下:
- 根据经验,开发中的很多变量,都是不会更改,也不应该更改的;
- 后续的很多框架或者第三方
JS
库,都要求数据不可变,使用常量一定程度上保证这一点; - 注意的细节:
(1)常量不可变,是指声明的常量的内存空间(内存地址)不可变,并不保证内存空间中的地址指向的其它空间不可变。
(2)常量的命名:
特殊的常量:该常量从字面意义上,一定是不可变得,如圆周率等。通常,该常量名要大写,多个单词之间下划线隔开。
普通的常量:之前命名。
(3)在for
循环中,循环变量不可以使用常量;但for in
可以,因为for in
每次都是新的常量。