一、var 变量
- 加var的变量在预编译期间会提升,不加var的变量在预编译期间不会提升。
- 不管是否加var只要是全局变量,在非严格模式下,都会挂在GO上。
- 加var的变量可以做全局变量,也可以做局部变量,没有加var只能做全局变量。
<script>
console.log(a); //und
var a = 110;
console.log(a); //110
console.log(window.a); //110
// console.log(b); //引用错误 以下不执行
b = 666;
console.log(window.b); //666
</script>
二、let 声明变量
<script>
console.log(a); //Uncaught ReferenceError: Cannot access 'a' before initialization
// a没有初始化(赋值),是不能访问的
// 理解成:使用let 声明的变量没有提升
// 理解成:使用let 声明的变量提升了,没有赋值,没有赋值的变量是不能访问的
//使用let 声明的变量不会挂在GO上
let a = 110;
<script>
// {let } 可以形成块级作用域
// 块级作用域中定义的变量,只能在块级作用域中使用,出了块级作用域就是用不了
if (true) {
let c = 10;
}
console.log(c); //引用报错
</script>
<script>
// 使用let 声明的变量不会挂在GO上
let a = 110;
console.log(a); //110
console.log(window.a); //undefined
</script>
<script>
// let 在一个作用域上重复声明一个a,汇报错
let a = 110;
let a = 110;// 报错 Uncaught SyntaxError: Identifier 'a' has already been declared
console.log(a);
</script>
<script>
function fn(a) {
// 形参相当于函数内部定义的局部变量
// VO:AO 已经有a了
let a = 110; //Uncaught SyntaxError: Identifier 'a' has already been declared
}
fn();
// let ES6中提出的,弥补了var声明变量的缺点
</script>
三、const声明常量
使用const 声明常量的特点:
1) 声明的常量不能修改
2) 使用const 声明常量时,必须赋值,不然会报语法错误
3) const 声明的常量也不会提升
4) const和{}也形成块级作用域
5) const 声明的常量也不会挂到GO上
↓练习题 感受let const 与var的区别↓
<script>
const PI;
//const 声明的常量不能被修改
PI = 3.14;// Uncaught SyntaxError: Missing initializer in const declaration
console.log(PI);
</script>
<script>
console.log(fn); //undefined
// window.fn(); // und() (注释掉 window.fn())
console.log(window.fn); //Uncaught TypeError: window.fn is not a function
if ('fn' in window) {
// 如果条件成立,进来第一件事句式给fn赋值 (注释掉 window.fn())
fn();
// 函数位于if 条件中
// 在最新版浏览器中,不会提升整体,仅仅提升fn函数名,提升至代码段最前面
function fn() {
console.log('fn....'); //fn...(注释掉 window.fn())
}
}
fn(); //fn...(注释掉 window.fn())
</script>
<script>
fn();
function fn() {
console.log(1);
}
fn();
function fn() {
console.log(2);
}
fn();
var fn = function() { //执行到此处,fn指向改变log(3)
console.log(3);
}
fn();
function fn() {
console.log(4);
}
fn();
function fn() {
console.log(5);
}
fn();
// 控制台
// 5
// 5
// 5
// 3
// 3
// 3
</script>
<script>
var a = 12;
b = 13; //(3)b=200 (5)b=und
c = 14;
function fn(a) { //(2) a=100
console.log(a, b, c); //(1) 10 13 14
a = 100;
b = 200;
console.log(a, b, c); //(4) 100 200 14
}
b = fn(10); //(5) 没有return ==》 (5)
console.log(a, b, c); //(6) 12 und 14
</script>
<script>
function sum(a) { //预解析 (1)局部有个a ==>und
console.log(a);
let a = 100; //预解析 已经存在a let a 报错Uncaught SyntaxError: Identifier 'a' has already been declared ,
console.log(a);
}
sum(200);
</script>
<script>
var ary = [12, 13]; //ECG中的 ary的地址 指向堆中的 数组[12, 13]
function fn(ary) { //(1)fn执行上下文 ary被赋值为全局执行上下文 ary的地址,所以指向堆中的同一个数组[12, 13]
console.log(ary); //(2) [12,13]
ary[0] = 100; //(3) 通过地址修改堆中的数组[12,13]==>[100,13]
ary = [100]; //(4) fn执行上下文的ary 地址发生改变,指向堆中新的数组[100]
ary[0] = 0; //(5)fn 执行上下文中ary通过地址,修改堆中数组[100]==>[0]
console.log(ary); //(6) [0]
}
fn(ary);
console.log(ary); //(7) [100,13]
</script>
<script>
function fn() {
function gn() {
console.log('gn...');
}
// return 了gn的地址
return gn;
}
let res = fn(); //(1) 这里分两步:1)调用fn() 获得gn 函数地址。2) 将函数地址赋值给res ,使得指向堆中的数据 function gn() {console.log('gn...');}
res(); //(2) 调用res 通过地址指向堆中数据 打印得出gn...
// ---------------上部代码等价于下部------------
function fn() {
return function() { //直接返回地址
console.log('gn...');
}
}
let res = fn(); //调用函数 fn(),返回地址 给res
res();
</script>
↓此段代码从执行上下文理解闭包↓
<script>
//栈中有个ECG 堆中为空
var i = 0; //(1.1) ECG中 产生 i 预解析时值为und
//(2) 代码从此步开始执行 : ECG中 i赋值为0;
function A() { //(1.2) ECG中 产生 函数A 的地址
var i = 10;
function x() {
console.log(i);
}
return x; //返回函数的地址
}
var y = A(); //(1.3) ECG中产生 y 预解析时值为und
// (3.1) 调用A(), ECA 入栈 ,ECA里面包含 i ==>und==>1 ,包含 函数x 地址 , 该地址指向堆中的 x函数体;函数执行完毕返回 x的地址。
// (3.2) 将函数x地址赋值给ECG中的y y==>und==>x的地址,指向x的函数体。
// (3.3)注意此时 ECG中的y的地址 指向了 函数x预解析时存放在堆中的数据。(函数A执行完毕本该进行ECA出栈,堆内存释放空间,但是通过调用A函数,修改了y的值,等同ECA中同于x地址,都指向了x的堆,使得此堆中的值一直被引用,此时的ECA不能出栈回收,把这个不能被回收的栈空间叫做闭包)。
y(); //(4) 调用函数y ECy入栈,函数y的值此时为x函数地址,通过地址找到堆中函数体,执行打印出log(i),Ey中找不到i ,通过作用域链找到函数x 的父级 函数A所产生的ECA中的i=10,控制台打印log(i) 结果为10, 函数y()调用完毕,ECy出栈,释放堆空间。
function B() { //(1.4) ECG中产生 B的地址
var i = 20;
y(); //(5.2)调用 函数y ECy2入栈,ECy2 通过地址(函数x)指向堆中函数x,要求打印i,但是ECy2提供不了i的数据,就找指向的函数的父级A函数,打印出i=10
}
B(); //(5.1)调用函数B ,ECB入栈,ECB中 包含i==>und==>20
// 个人总结:
// 1)函数指向是在代码执行时开始指向的
// 控制台显示:
// 10
// 10
// 闭包的作用:
// 1)保护 保护EC中的变量,不被外界直接访问
// 2)保存 可以让我们像使用全局变量那样使用局部变量,延长了变量的使用周期
<script>
console.log(fn); //und
if (1 == 1) {
// 进来第一件事是给fn赋值(地址)
console.log(fn); //fn(){}
function fn() { //在代码块里面,声明函数名提升到最前面
console.log('ok');
}
}
console.log(fn); //fn(){console.log('ok');}
</script>
<script>
console.log(num); //und
console.log(fn); //und
if ([]) {
// 进来第一件事是给fn赋值(地址)
fn() //'a'
var num = 100;
function fn() {
console.log('a');
}
}
console.log(fn); //fn(){console.log('ok');};
</script>
<script>
function fn(i) {
return function(n) {
console.log(n + (++i));
}
}
var f = fn(2);
f(3); //6
f(5)(6); //9
f(7)(8);
f(4);
// 控制台:
// 6
// 9
// Uncaught TypeError: f(...) is not a function
</script>
<script>
var foo = 'hello';
// 立即执行函数 没有函数名
// 即使有函数名 在函数在外面也不能使用
(function(foo) { //foo形参 相当于函数内部的局部变量
console.log(foo) || 'world'; //hello
console.log(foo); //hello
})(foo); //实参oo="hello"
console.log(foo); //hello
</script>
<!-- <script>
var a = 9;
function fn() {
a = 0;
return function(b) {
return b + a++;
}
}
var f = fn();
console.log(f()(5)); // )(5 不能作为一个参数 Uncaught TypeError: f(...) is not a function
console.log(f(5)); //
console.log(a);
</script> -->
<script>
// let a = {
// num: 0,
// valueOf: function() {
// console.log('valueOf...');
// return ++a.num;
// }
// };
// --------
let a = {
num: 0,
toString: function() {
console.log('valueOf...');
return ++a.num;
}
};
// a和别人作比较时,会自动调用valueof和toString
// 如果a是一个对象,就会调用自己的valueof 或toString,就会调用自己的valueof 或toString,
if (a == 1 && a == 2 && a == 3) {
console.log('ok'); //
}
console.log(++a.num);
// 控制台:
// valueOf...
// valueOf...
//valueOf...
//4
</script>