变量提升
当栈内存(作用域)形成,JS代码自上而下执行之前,浏览器首先会把所有带 “VAR”/“FUNCTION” 关键词的进行提前 “声明” 或者 “定义” ,这种预先处理机制称之为 “变量提升”
声明(declare):var a (默认值undefined)
定义(defined):a=12 (定义其实就是赋值操作)
变量提升阶段
- 带“VAR”的只声明未定义
- 带“FUNCTION”的声明和赋值都完成了
- 变量提升只发生在当前作用域(例如:开始加载页面的时候只对全局作用域下的进行提升,因为此时函数中存储的都是字符串而已)
- 在全局作用域下声明的函数或者变量是“全局变量”,同理,在私有作用域下声明的变量是“私有变量” [带VAR/FUNCTION的才是声明]
1 | console.log(a);//=>undefined |
变量带var声明和直接声明
在全局作用域下声明一个变量,也相当于给WINDOW全局对象设置了一个属性,变量的值就是属性值(私有作用域中声明的私有变量和WINDOW没啥关系)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 console.log(a);//=>undefined
console.log(window.a);//=>undefined
console.log('a' in window); //=>TRUE 在变量提升阶段,在全局作用域中声明了一个变量A,此时就已经把A当做属性赋值给WINDOW了,只不过此时还没有给A赋值,默认值UNDEFINED in:检测某个属性是否隶属于这个对象
var a = 12;//=>全局变量值修改,WIN的属性值也跟着修改
console.log(a);//=>全局变量A 12
console.log(window.a);//=>WINDOW的一个属性名A 12
a = 13;
console.log(window.a);//=>13
window.a = 14;
console.log(a);//=>14
//=>全局变量和WIN中的属性存在 “映射机制”
//=>不加VAR的本质是WIN的属性
*/
1 | console.log(a, b);//=>undefined undefined |
在当前作用域下,不管条件是否成立都要进行变量提升
- =>带VAR的还是只声明
- =>带FUNCTION的在老版本浏览器渲染机制下,声明和定义都处理,但是为了迎合ES6中的块级作用域,新版浏览器对于函数(在条件判断中的函数),不管条件是否成立,都只是先声明,没有定义,类似于VAR
重名问题
- 带VAR和FUNCTION关键字声明相同的名字,这种也算是重名了(其实是一个FN,只是存储值的类型不一样
2.关于重名的处理:如果名字重复了,不会重新的声明,但是会重新的定义(重新赋值)[不管是变量提升还是代码执行阶段皆是如此]
1
2
3
4
5
6
7
8
9
10
11fn();//=>4
function fn() {console.log(1);}
fn();//=>4
function fn() {console.log(2);}
fn();//=>4
var fn=100;//=>带VAR的在提升阶段只把声明处理了,赋值操作没有处理,所以在代码执行的时候需要完成赋值 FN=100
fn();//=>100() Uncaught TypeError: fn is not a function
function fn() {console.log(3);}
fn();
function fn() {console.log(4);}
fn();let/const在ES6中基于LET/CONST等方式创建变量或者函数,不存在变量提升机制,这种方式声明的全局变量也不再与windo存在映射关系。
在相同的作用域中,基于LET不能声明相同名字的变量(不管用什么方式在当前作用域下声明了变量,再次使用LET创建都会报错)
虽然没有变量提升机制,但是在当前作用域代码自上而下执行之前,浏览器会做一个重复性检测(语法检测):自上而下查找当前作用域下所有变量,一旦发现有重复的,直接抛出异常,代码也不会在执行了(虽然没有把变量提前声明定义,但是浏览器已经记住了,当前作用域下有哪些变量)1
2
3
4
5
6
7
8
9
10
11
12
13
14let a = 10,
b = 10;
let fn = function () {
console.log(a, b);//=>Uncaught ReferenceError: a is not defined
// =>基于LET创建变量,会把大部分{}当做一个私有的块级作用域(类似于函数的私有作用域),在这里也是重新检测语法规范,看一下是否是基于新语法创建的变量,如果是按照新语法规范来解析
let a = b = 20;
/*
* let a=20;
* b=20; //=>把全局中的 b=20
*/
console.log(a, b);
};
fn();
console.log(a, b);全局和私有变量1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35/*
* 变量提升:
* var a; var b; var c;
* fn = xxx...
*/
var a = 12,
b = 13,
c = 14;
function fn(a) {
/*
* 形参赋值
* a = 12
*
* 变量提升
* var b;
*
* =>在私有作用域中,只有以下两种情况是私有变量
* A:声明过的变量(带VAR/FUNCTION)
* B:形参也是私有变量
*
* 剩下的都不是自己私有的变量,都需要基于作用域链的机制向上查找
*/
console.log(a, b, c);//=>12 undefined 14(C是全局的)
var b = c = a = 20;
/*
var b=20;
c=20; =>把全局的C修改为20
a=20;
*/
console.log(a, b, c);//=>20*3
}
fn(a);//=>把FN执行(小括号中是实参:值) =>执行FN把全局变量A的值12当做实参传递给函数的形参 =>fn(12)
console.log(a, b, c);//=>12 13 201
2
3
4
5
6
7
8
9
10
11
12var ary = [12, 23];
function fn(ary) {
console.log(ary); // [12,23]
ary[0] = 100;
ary = [100];
ary[0] = 0;
console.log(ary); // [0]
}
fn(ary);
console.log(ary); //[100,23]上级作用域当前函数执行,形成一个私有作用域A,A的上级作用域是谁,和他在哪执行的没有关系,和他在哪创建(定义)的有关系,在哪创建的,它的上级作用域就是谁
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20// function fn() {
// //=>arguments:实参集合
// //=>arguments.callee:函数本身FN
// //=>arguments.callee.caller:当前函数在哪执行的,CALLER就是谁(记录的是它执行的宿主环境),在全局下执行CALLER的结果是NULL
// console.log(arguments.callee.caller);
// }
var n = 10;
function fn() {
var n = 20;
function f() {
n++;
console.log(n);
}
f();
return f;
}
var x = fn(); //21
x(); // 22
x(); // 23
console.log(n); // 10JS中的内存分为堆内存和栈内存- 堆内存:存储引用数据类型值(对象:键值对 函数:代码字符串)
- 栈内存:提供JS代码执行的环境和存储基本类型值
- 堆内存释放: 让所有引用堆内存空间地址的变量赋值为null即可(没有变量占用这个堆内存了,浏览器会在空闲的时候把它释放掉)
- 一般情况下,当函数执行完成,所形成的私有作用域(栈内存)都会自动释放掉(在栈内存中存储的值也都会释放掉),但是也有特殊不销毁的情况:
1、函数执行完成,当前形成的栈内存中,某些内容被栈内存以外的变量占用了,此时栈内存不能释放,即闭包
2、全局栈内存只有在页面关闭的时候才会被释放掉1
2
3
4
5
6
7
8
9
10
11
12
13var i = 2;
function fn() {
i += 2; // i 为全局变量会有影响,值得注意
return function (n) {
console.log(n + (--i));
}
}
var f=fn();// i=4
f(2);//5 i=3
f(3);//5 i= 2
fn()(2);//5 i=3
fn()(3);// 7 i=4
f(4);//7 i=3
闭包
- 函数执形成一个私有的作用域,保护里面的私有变量不受外界的干扰,这种保护机制称之为“闭包”
一般大家认为,形成一个不销毁的私有作用域(私有栈内存)才是闭包
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 //=>闭包:柯理化函数
function fn() {
return function () {
}
}
var f = fn();
*/
/*
//=>闭包:惰性函数
var utils = (function () {
return {
}
})();
闭包项目实战应用
真实项目中为了保证JS的性能(堆栈内存的性能优化),应该尽可能的减少闭包的使用(不销毁的堆栈内存是耗性能的)
- 闭包具有“保护”作用:保护私有变量不受外界的干扰。
- 闭包具有“保存”作用:形成不销毁的栈内存,把一些值保存下来,方便后面的调取使用
基于构造函数创建自定义类- 在普通函数执行的基础上“new xxx()”,这样就不是普通函数执行了,而是构造函数执行,当前的函数名称之为“类名”,接收的返回结果是当前类的一个实例
- 自己创建的类名,最好第一个单词首字母大写
- 这种构造函数设计模式执行,主要用于组件、类库、插件、框架等的封装,平时编写业务逻辑一般不这样处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25function Fn() {
}
// Fn();//=>普通函数执行
var f = new Fn();//=>Fn是类 f是类的一个实例
var f2 = new Fn();//=>f2也是Fn的一个实例,f2和f是独立分开的,互不影响
/*
* JS中创建值有两种方式
* 1.字面量表达式
* 2.构造函数模式
*/
// var obj = {};//=>字面量方式
// var obj = new Object();//=>构造函数模式
// //=>不管是哪一种方式创造出来的都是Object类的实例,而实例之间是独立分开的,所以 var xxx={} 这种模式就是JS中的单例模式
//=>基本数据类型基于两种不同的模式创建出来的值是不一样的
//> 基于字面量方式创建出来的值是基本类型值
//> 基于构造函数创建出来的值是引用类型
//->NUM2是数字类的实例,NUM1也是数字类的实例,它只是JS表达数字的方式之一,都可以使用数字类提供的属性和方法
// var num1 = 12;
// var num2 = new Number(12);
// console.log(typeof num1);//=>"number"
// console.log(typeof num2);//=>"object"