通过自己的总结和学习,记录的一点笔记
声明变量
- 先声明一个变量 
a,没有赋值 - 在当前作用域开辟一个位置存储 
12 - 让变量 
a和12关联在一起(定义:赋值) - 基础值类型就是直接操作值,和变量没有关系
 - 引用类型是按照引用的空间地址进行操作,地址都是一个。
 - 函数也是引用类型
 - 栈内存:作用域(代码都是在栈中执行的) 1. 提供一个提供 js 代码自上而下执行的环境 2. 由于基本数据类型值比较简单,他们都是直接在栈内存中开辟一个位置,把值直接存储进去的 3. 栈内存被销毁,存储的基本值也销毁
 - 堆内存:引用值对应的空间 1. 存储引用类型值的(对象:键值对 函数:代码字符串) 2. 当前堆内存销毁,那么引用消失 3. 堆内存没有被任何变量或者其他东西占用,就会被浏览器在空闲的时候,自主的进行内存回收,把所有不被占用的堆内存销毁 4. 销毁堆:XX=null
 
变量提升
当栈内存(作用域)形成,js 代码自上而下执行之前,浏览器首先会把所有带
var和function关键词的进行提前的“声明”或者“定义”,这种预先处理机制称之为“变量提升”- 声明(declare):
var a/function sum(默认值 undefined) - 定义(defined):
a=12(定义其实就是赋值) - 变量提升阶段:带
var的只声明未定义,带funtion的声明和赋值都完成了 
- 声明(declare):
 变量提升只发生在当前作用域(例如:开始加载页面的时候只对全局作用域下的进行提升,因为 此时函数中存储的都是字符串而已)
- 在全局作用域下声明的函数或者变量是“全局变量”,同理,在私有作用域下声明的变量是“私有变 量”(带
var/function的才是声明) - 浏览器很懒,做过的事情不会重复执行第二遍,也就是,当代码执行遇到创建函数这部分代码后,直接的跳过即可(因为在提升阶段就已经完成函数的赋值操作了)
 - 私有作用域形成后,也不是立即执行代码,而是先进行变量提升(变量提升钱,先形参赋值)
 - 在
es3/es5语法规范中,只有全局作用域和函数执行的私有作用域(栈内存),其他大括号不会形成栈内存 
带 var 和不带 var 的区别
- 在全局作用域下声明一个变量,也相当于给 
window全局对象设置了一个属性,变量的值就是属性值(私用作用域中声明的私有变量和window没啥关系) - 在变量提升阶段,在全局作用域中声明了一个变量
a,此时就已经把a当做属性赋值给window了,只不过此时还没有给a赋值,默认undefined in操作符可以检测某个属性是否属于这个对象- 全局变量和 
window中的属性存在“映射机制” a=12=>window.a=12不加var的本质是window的属性。var a=b=12这样写b是不带var的- 私有作用域中带 var 和不带也有区别
- 带 
var的在私有作用域变量提升阶段,都声明为私有变量,和外界没有任何的关系 - 不带 
var不是私有变量,会向它的上级作用域查找,一直找到window为止(我们把这种查找机制叫做:‘作用域链’),也就是我们在私有作用域中操作的这个非私有变量,是一直操作别人的。 
 - 带 
 
### 作用域链的扩展
- 在作用域链查找的过程中,如果找到 
window也没有这个变量,相当于给window设置一个属性 
变量提升的一些细节问题(关于条件判断下的处理)
- 匿名函数之函数表达式
var fn = function () {} var fn只对左边进行变量提升- (条件判断下)在当前作用域下,不管条件是否成立都要进行变量提升,带 
var的还是只声明,带function的在老版本浏览器渲染机制之下,声明加定义都处理了,但是为了迎合es6中的块级作用域, 新版浏览器对于函数(在条件判断中的函数),不管条件是否成立,都只是先声明,没有定义,类似var
4. 
1  | // 没有变量提升  | 
条件判断下的变量提升到底有多坑
1  | // 变量提升:function fn;  | 
变量提升机制下重名的处理
- 带 
var和function关键字声明相同的名字,这种也算是重名了(其实是一个变量,只是存储值的类型不一样) - 关于重名的处理:如果名字重复了,不会重新的声明,但是会重新的定义(重新赋值)<不管是变量的提升还是代码执行阶段皆是如此>
 - 代码有报错,下面的代码不在执行
 
ES6 中的 let 不存在变量提升
- 在 
es6中基于let/const等方式创建变量或者函数,不存在变量提升机制,切断了全局变量和window属性的映射机制 - 在相同的作用域中,基于 
let不能声明相同名字的变量(不管用什么方式在当前作用域下声明了变量,再次使用 let 创建都会报错) - 虽然没有变量提升机制,但是在当前作用域代码自上而下执行之前,浏览器会做一个重复性检测(语法检测):自上而下查找当前作用域下所有变量,一旦发现有重复的,直接抛出异常,代码也不会再执行了(虽然没有把变量提前升声明定义,但是浏览器已经记住了,当前作用域下有哪些变量)
 
暂时性死区
- 基于 
let创建变量,会把大部分{}当做一个私有的块级作用域(类似于函数的私有作用域),在这里也是重新检测语法规范,看一下是否基于新语法规范创建的变量,如果是则按照新语法规范来解析 - 在原有浏览器渲染机制下,基于 typeof 等逻辑运算符检测一个未被声明过的变量,不会报错,返回 
undefined - 如果当前变量是基于 
es6语法处理,在没有声明这个变量的时候,使用typeof检测会直接报错,不会undefined解决了原有的js的暂时性死区 
区分私有变量和全局变量
- 在私有作用域中,只有以下两种情况是私有变量:A.声明过的变量(带 var/function)B.形参也是私有变量; 剩下的都不是自己私有的变量,都需要基于作用域链的机制向上查找
 
上级作用域的查找
- 当前函数执行,形成一个私有作用域 A,A 的上级作用域是谁,和他在哪执行的没有关系,和他在哪创建(定义)的有关系,在哪创建的,他的上级作用域就是谁
 arguments是函数实参集合,arguments.callee是函数本身,arguments.callee.caller是当前函数在哪执行的,caller就是谁(记录的是他执行的宿主环境),在全局下执行caller的结果是null
闭包及堆栈内存释放
js中的内存分为堆内存和栈内存
堆内存:存储引用数据类型值(对象:键值对 函数:代码字符串)
栈内存:提供js代码执行的环境和存储基本类型值堆内存释放:让所有引用堆内存空间地址的变量赋值为
null即可(没有变量占用这个堆内存了,浏览器会在空闲的时候把它释放掉)
栈内存释放: 一般情况下,当函数执行完成,所形成的私有作用域(栈内存)都会自动释放掉(在栈内存中存储的值也会释放掉),但是也有特殊不销毁的情况:- 函数执行完成,当前形成的栈内存中,某些内容被栈内存以外的变量占用了,此时栈内存不能释放(一旦释放外面找不到原有的内容了)
 - 全局栈内只有在页面关闭的时候才会被释放掉,如果当时栈内存没有被释放,那么之前在栈内存中存储的基本值也不会被释放,能够一直保存下来。
 
var f = fn(2)=>先把fn执行(传递实参 2),把fn执行的返回结果(return 后面的值)赋值给f,f()=>把返回的结果执行。fn(2)()=>和上面两步骤类似,都是先把fn执行,在fn执行的返回结果再次执行i++自身累加1: 先运算在累加++i自身累加1: 先累加在运算
闭包的应用
- 闭包:
 
- 函数执行形成一个私有的作用域,保护里面的私有变量不受外界的干扰,这种保护机制称之为闭包
 - 市面上的开发者认为的闭包:形成一个不销毁的私有作用域(私有栈内存)才是闭包
 - 闭包:柯理化函数
 
1  | function fn() {  | 
- 闭包项目实战应用
 
真实项目中为了保证 js 的性能(堆栈内存的性能优化),应该尽可能的减少闭包的使用(不销毁的堆栈内存是耗性能的)
闭包具有保护作用:保护私有变量不受外界的干扰 jq
在真实项目中,尤其是团队协作开发的时候,应当尽可能的减少全局变量的使用,以防止相互之间的冲突(‘全局变量污染’),那么此时我们完全可以把自己这一部分内容封装到一个闭包中,让全局变量转换为私有变量(function(){
var n =12
function fn(){}
})()不仅如此,我们封装类库插件的时候,也会把自己的程序都存放到闭包中保护起来,防止和用户的程序冲突,但是我们又需要暴露一些方法给客户使用,这样我们如何出理?
- jq 这种方式:把需要暴露的方法抛到全局
 
1
2
3
4
5
6
7
8
9
10(function(){
function jQuery() {
...
}
...
把需要提供外面使用的方法,通过给win设置属性的方式暴露出去
window.jQuery = window.$ = jQuery;
})()
jQuery();
$()- Zepto 这种方式:基于 return 把需要供外面使用的方法暴露出去
 
1
2
3
4
5
6
7
8
9var Zepto = (function(){
...
return {
xxx: function(){
}
}
})();
Zepto.xxx()闭包具有保存作用:形成不销毁的栈内存,把一些值保存下来,方便后面的调取使用
- 闭包作用之保存
 
- 循环和判断都不会形成私有作用域(栈内存),
for循环执行,形成一个私有的栈内存,遇到变量i,i不是私有变量,向上一级作用域查找(上级作用域是 win) 
- 所有的事件绑定都是异步编程(同步编程:一件事一件事的做,当前这件事没有完成,下一个任务不能处理/异步编程:当前这件事件没有彻底完成,不再等待,继续执行下面的任务),绑定事件后,不需要等待执行,继续执行下一个循环任务,所以当我们点击执行方法的时候,循环早已结束(让全局的等于循环最后的结果)
 解决方案:
- 自定义属性
 
1
2
3
4
5
6
7for (var i = 0,i<tabList.length;i++){
tabList[i].myIndex = i;
tabList[i].onclick = function(){
changeTab(this.myIndex)
}
// this:给当前元素的某个时间绑定方法,当事件触发,方法执行的时候,方法中的this是当前操作的元素对象
}- 闭包解决
 
1
2
3
4
5
6
7
8
9
10
11for (var i = 0,i<tabList.length;i++){
tabList[i].onclick = (function(n){
// 让自执行函数执行,把执行的返回值(return)赋值给此处on-click(on-click绑定的是返回的小函数,点击的时候执行的是小函数),自执行函数在给事件赋值的时候就已经执行了
var i=n;
return function () {
changeTab(i); // 上级作用域:自执行函数形成的作用域
}
})(i)
}
// 总结:循环几次,形成几个不销毁的私有作用域(自执行函数执行),而每一个不销毁的栈内存中都存储了一个私有变量,而这个值分别是每一次执行传递进来的全局i的值,当点击的时候,执行返回的小函数,遇到变量i,向它自己的上级作用域查找,找到i的值- 基于 ES6 解决
 
1
2
3
4
5
6
7
8
9for (let i = 0,i<tabList.length;i++){
// 循环体也是块级作用域,初始值设置的变量是当前本次块级作用域中的变量(形成了i个块级作用域,每个块级作用域中都有一个私有变量i,变量值就是每一次循环的i)
tabList[i].onclick = function(){
changeTab(i)
}
}
// 基于es6中的let来创建变量,是存在块级作用域的(类似于私有作用域)
// 作用域(栈内存)1. 全局作用域 2.私有作用域 3.块级作用域(一般用大括号包起来的都是块级作用域,前提是es6语法规范)
// 对象的{}不是块级作用域