1.1.值类型
var a=1
var b=a
b=2
console.log(a);//=>1
打开浏览器是形成一个全局作用域:栈
变量声明:var a , var b
代码自上而下执行:a=1,b=a(将a的值复制一份放到新位置b上,a与b没有关联),b=2给b重新赋值为2,输出a,这时a依然是原来的1
1.2.引用类型
var arr1=[1,2]
var arr2=arr1
arr2.push(3)
console.log(arr1);//=>[1,2,3]
依然是先形成全局作用域:栈
变量声明:var arr1,var arr2
代码自上而下执行:
[1,2]会在堆中开辟一个内存空间存放0:1,1:2,length:2,假设这个内存空间的地址为aaafff111
arr1=aaafff111指向这个内存地址
arr2=arr1=aaafff111指向也为这个内存地址
arr2.push(3),其实质就是向这个内存地址里添加一个3,这时内存里面为0:1,1:2,2:3,length:3
console.log(arr1)输入的是这个内存aaafff111里面的数据,即为:[1,2,3]
1.3.函数
function sum(){
var total=0
for (var i=0;i6
形成全局作用域:栈
函数声明+赋值一次完成,在内存中开辟一个空间,假如为aaafff222存储函数体
字符串
,然后将这个空间地址赋值给sum,sum=aaafff222sum(1,2,'3','a'),执行函数,会形成一个私有的作用域,在私有作用域里,先执行形参赋值,接着变量声明,代码自上而下执行,最后执行结果为返回6
1.4.补充
栈内存:
提供一个供JS代码自上而下的执行环境
由于基本数据类型值比较简单,他们都是直接在栈内存中开辟一个位置,把值直接存储进去的
当栈内存被销毁,存储的基本数据类型的值也跟着被销毁
堆内存:
存储引用类型的值(对象:键值对,函数:代码字符串)
堆内存在没有被任何变量或者其他东西所占用时,浏览器会在空闲是,自主的进行内存回收,把所有不被占用的堆内存销毁掉(谷歌浏览器)
xxx=null,通过这种形式可以释放一个堆内存
2.1.什么是变量提升
当栈内存(作用域)形成,并在JS代码自上而下执行前,浏览器首先会把所有带
var
和function
关键词的进行提前声明
或者定义
,这种预先处理的机制称之为变量提升
变量提升阶段:
带var
的之声明未定义
带function
的声明和定义(赋值)都完成
变量提升只发生在当前作用域
函数中存储的都是字符串
console.log(a)//=>undefined
var a=1
2.2.带var和不带的区别
不带var
console.log(a);//=>js:1 Uncaught ReferenceError: a is not defined
console.log(window.a);//=>undefined
console.log('a' in window);//false
不添加var的声明,相当于在window上添加了一个属性
a=1
console.log(window.a);//=>1
console.log('a' in window);//=>true
这样声明的变量相当于两个都带var
var a=1,b=2
这样声明的变量,b不带var
var a=b=2
带var
在全局作用域下声明一个带var的变量,相当于同时给window全局对象设置了一个属性,变量的值就是属性值(私有作用域中声明的私有变量和window没关系)
var声明的全局变量和window中的属性存在
映射机制
console.log(a);//=>undefined
console.log(window.a);//=>undefined
console.log('a' in window);//=>true
var a=1
console.log(a);//=>1
console.log(window.a);//=>1
案例1
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和不带的区别: 1,带var的在私有作用域变量提升阶段,都声明为私有变量,和外界没有任何关系 2,不带var不是私有变量,会向他的上级作用域查找,看是否为上级变量,不是,继续向上查找,一直找到window为止(我们把这种查找机制叫着:“作用域链”)
2.3.补充
在ES3/ES5语法规范中,只有全局作用域和函数执行的私有作用域,其他大括号不会形成作用域
2.4.等号左边变量提升
sum()
fn()//=>Uncaught TypeError: fn is not a function
var fn=function(){
console.log(1);
}
function sum(){
console.log(2);
}
形成全局作用域:栈
变量提升:var fn,sum=aaafff111
代码执行:sum()正常执行并输出,fn()由于之声明未赋值,所有报错
2.5.条件判断下的变量提升
当前作用域下,不管条件是否成立都要进行变量提升
带var的之声明
带function的在老版本浏览器声明+定义,在新版本浏览器之声明不定义,相当于var
console.log(a);//=>undefined
if('a' in window){
var a=1
}
console.log(a);//=>1
不管if语句的条件是否成立,var a 会在变量提升阶段被声明,同时一旦声明了var a,由于与window映射的关系会在window的属性上添加一个a 属性
案例2
/*
* 变量提升:无
*/
f = function () {return true;};//=>window.f=...(TRUE)
g = function () {return false;};//=>window.g=...(FALSE)
~function () {
/*
* 变量提升:
* function g; //=>g是私有变量
*/
if (g() && [] == ![]) {//=>Uncaught TypeError: g is not a function (此时的g是undefined)
//=>[]==![]:TRUE
f = function () {return false;};//=>把全局中的f进行修改 window.f=...(FALSE)
function g() {return true;}
}
}();
console.log(f());
console.log(g());
形成全局作用域
变量提升:无
代码自上而下执行:
window.f
window.g
自执行函数,形参赋值:无,变量提升:不管判断条件是否成立,g会被提升,但是只声明未定义,所以当代码自上而下执行是,g()会报错
案例3:
/*
* 变量提升:
* function fn;
*/
console.log(fn);//=>undefined
if (1 === 1) {
console.log(fn);//=>函数本身:当条件成立,进入到判断体中(在ES6中它是一个块级作用域)第一件事并不是代码执行,而是类似于变量提升一样,先把FN声明和定义了,也就是判断体中代码执行之前,FN就已经赋值了
function fn() {
console.log('ok');
}
}
console.log(fn);//=>函数本身
2.6.重名问题的处理
带var和function关键字声明相同的名字,算重名
var fn
function fn() {
}
关于重名的处理:如果名字重复了,不会重新声明,但是会重新定义(赋值)【不管是变量提升还是代码执行阶段皆如此】
/*
* 变量提升:
* fn = ...(1)
* = ...(2)
* = ...(3)
* = ...(4)
*/
/*
fn();//=>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();
形成全局作用域:栈
变量提升:fn=....(1) , fn=...(2) ,var fn , fn=...(3) , fn=...(4)
代码执行:4,4,4,fn=100,fn()报错
在ES6中基于let/const等方法创建的变量或者函数,不存在变量提升机制
切断了全局变量和window映射机制
同一作用域中,let不能声明相同名字的变量(不管用什么方式在当前作用域下声明了变量,再次使用let创建都会报错)
let声明的变量,在代码自上而下执行前,浏览器会做一个重复性检测),自上而下查找当前作用域下所有变量,一旦发现有重复的,直接抛出异常,代码也不会在执行了(虽然没有把变量提前声明定义,但是浏览器已经记住了,当前作用域下有哪些变量)
console.log(a);//=>Cannot access 'a' before initialization
let a=1
let a = 10,
b = 10;
let fn = function () {
console.log(a, b);//=>Uncaught ReferenceError: Cannot access 'a' before initialization
let a = b = 20;
/*
* let a=20;
* b=20; //=>把全局中的 b=20
*/
console.log(a, b);
};
fn();
console.log(a, b);
3.1.临时性死区
基于let创建的变量,会把大部分
{}
当做一个私有的块级作用域(类似于函数的私有作用域),在这里也是重新检测语法规范,看是否有基于新语法创建的变量,如果有按照新语法规范来解析
var a=1
if(true){
console.log(a);//=> Cannot access 'a' before initialization
let a=2
}
console.log(a);
大括号会形成一个块级作用域,并且里面声明了let a,在这个块级作用域执行代码之前,浏览器已经记住了let a,在代码执行时,会按照新语法规范检测
在原有浏览器渲染机制下,基于typeof等逻辑运算符检测一个未被声明过的变量,不会报错,返回undefined
console.log(typeof a);//=>undefined
如果当前变量是基于ES6语法处理,在没有声明这个变量的时候,使用typeof检测会报错,解决了原来的JS死区
console.log(typeof a);//=>Cannot access 'a' before initialization
let a
4.1.全局变量和私有变量
var a=1
b=2
c=3
function fn(a){
console.log(a,b,c);//=>1,undefined,3
var b=c=a=10
console.log(a,b,c);//=>10,10,10
}
fn(a)
console.log(a,b,c);//=>1,2,10
形成全局作用域:栈
变量提升:var a,var b, var c , fn=aaafff111,函数相当于在堆内存里面开辟了一个地址为aaafff111的内存,并把函数体以字符串的形式存在这个内存中
代码自上而下执行,a=1,b=2,c=3,fn(1),在执行到fn(1)是,会开辟一个私有作用域,在私有作用域里,先是形参赋值,相当于var a=1,然后变量提升,var b,代码自上而下执行
案例:
var arr=[1,2]
function fn(arr){
console.log(arr);//=>[1,2]
arr[0]=10
arr=[100]
arr[0]=0
console.log(arr);//=>[0]
}
fn(arr)
console.log(arr);//=>[10,2]
4.2.作用域链机制
当前函数执行,形成一个私有作用域A,A的上级作用域是谁,和它在哪里执行没关系,和它在哪里创建有关系,在哪里创建,他的上级作用域就在那里
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);//10
JS中的内存分为堆内存和栈内存
堆内存:存储引用数据类型值(对象:键值对,函数:代码字符串)
栈内存:提供JS代码执行的环境和存储基本类型值
堆内存释放
让所用引用堆内存空间地址的变量赋值为null即可(没有变量占用这个堆内存了,浏览器会在空闲的时候把他释放掉)
栈内存释放
一般情况下,当函数执行完成,所形成的私有作用域(栈内存)都会自动释放掉(在栈内存中存储的值也都会释放掉),但也有特使不销毁情况:
1.函数执行完成,当前形成的栈内存中,某些内容被栈内存以外的变量占用了,此时栈内存不能释放
2.全局栈内存只有在页面关闭时才会被释放
var i = 1;
function fn(i) {
return function (n) {
console.log(n + (++i));
}
}
var f = fn(2);
f(3);//=>6
fn(5)(6);//=>12
fn(7)(8);//=>16
f(4);//=>8
var i = 2;
function fn() {
i += 2;
return function (n) {
console.log(n + (--i));
}
}
var f=fn();//i=4
f(2);//=>i=3,3+2=5
f(3);//=>i=2,2+3=5
fn()(2);//=>i=4,i=3,3+2=5
fn()(3);//=>i=2,2+3=5
f(4);//=>i=4,i=3,3+4=7
函数形成一个私有作用域,保护里面私有变量不收外界的干扰,这种保护机制称为
闭包
市面上的开发者认为的闭包:形成一个不销毁的私有作用域(私有栈内存)才是闭包
6.1.闭包:柯理化函数
function fn(){
return function(){
}
}
var f=fn()
6.2.闭包:惰性函数
var utils=(function(){
return {
}
})()
项目中为了保证JS性能(堆栈内存的性能优化),应该尽可能的减少闭包的使用(不销毁的堆栈内存是耗性能)
特性:
闭包具有保护
作用,保护私有变量不收外界干扰
闭包具有保护
作用:形成不销毁的栈内存,把一些值保存下来,以便后面的调取使用
案例:
Jquery:把需要暴露的方法抛到全局
(function(){
function jQuery(){
//...
}
window.jQuery=$=jQuery
})()
Zepto:这种方式:基于RETURN把需要共外面使用的方法暴露出去
var Zepto=(function () {
//...
return {
xxx:function () {
}
};
})();