本章我们主要是通过变量来使用基础值和引用值,理解执行上下文(作用域)和理解垃圾回收等
原始值和引用值
js有两种不同类型的数据:原始值和引用值。
原始值就是最简单的值(undefined、string、number、boolean、Null、smybol)
引用值就是由多个值构成的对象
我认为这两个值我们最容易弄错的便是他们在传值的时候
//基础值在传值的时候是把值赋值给另一个变量,相当于副本
var a = 2;
varb b = a;
//如果把b重新赋值,a不会受任何影响;
//而引用值就不一样了
var obj1 = new Object();
let obj2 = obj1;
obj2.name="哈哈";
console.log(obj1.name) //输入哈哈
//引用值传值的时候是把复制的对象的指针,相当于我复制了一把我家的门钥匙,都还是指向同一个地址,所以修改一个,另一个对象也会受影响
再提一句:其实我们只要记住原生值和引用值的特性,不管在传值还是传参数,一般都不会弄错
执行上下文与作用域
有些东西可能不知道什么是执行上下文,其实我们就可以把他理解为当前代码的运行环境(也就是作用域)在浏览器中,我们所说的全局上下文,就是window对象。
这里提上一嘴:var定义的全局变量和函数是window对象的属性和方法,但是let和const的顶级声明就不会定义在全局上下文(也就是window对象)中,但在作用域中解析的效果是一样的。
函数参数是当前上下文中的变量。
为什么要说一下这个呢,是因为要让我们明白作用域的问题,子作用域可以访问父级作用域,但父级作用域不能访问子级作用域
var a = 1;
function fn(){
var b = 2;
console.log(a);//输出1,子级访问父级
}
console.log(b)//报错,因为父级不能访问子级作用域
变量声明
注意:在初始化变量之前一定要先声明变量,不然会被浏览器当成全局作用域处理
function fn(){
age=12;
}
console.log(age) //输出12
这里在说一下var 、let 、const
var声明会被拿到函数或者全局作用域的顶部,位于作用域所有代码之前,这个现象叫做提升,提升的好处是在统一作用域下不必考虑变量是否声明就可以直接使用。但是这种在实践中,建议不要使用
let声明和var声明相似,let有块级作用域,for、while、if等,甚至连单独的块({})都是let声明变量的作用域
for(var i = 0 ; i<10;i++){
}
console.log(i)// 输出10
for(let i = 0 ; i<10;i++){
}
console.log(i)// 报错
//这里就可以说明var只有函数作用域,在for这些中,没有作用域,使用var声明的迭代变量会泄漏到循环外部
const声明在使用时,必须初始化为某个值
建议在实践中,如果开发流程并不会因此受很大的影响,就应该尽可能的使用const声明变量,除非将来确实会修改这个变量
垃圾回收和性能提升
在js中,垃圾回收是计算机自己执行的,但在什么时候执行,这个不确定。我们有两种垃圾回收的方法:标记清理和引用清理
标记清理时我们最常用的清理方式,在运行时,会将内存中所有的变量进行标记,然后,它会将所有在本作用域的变量以及被引用的变量的标记去掉,如果在此之后被加上标记的变量就会被清理
引用清理时将一个变量赋予一个引用值,该值为1,然后这个值被赋值给另一个就加一,被覆盖就减一,如果一个变量的引用值为0时,就会被清理掉(不建议使用)
最佳实践:如果一个数据不再有必要,可以把他设为null
function fn(name){
let obj = new Object();
obj.name=name;
return obj
}
let myfn = fn("haha");
//因为后面不再有必要使用这个了 我们就可以把这个变量设置为null
//解除myfn对值的引用
myfn = null
//解除引用的关键是在于确保相关的值已经不再上下文(作用域)里面了
内存泄漏的几种可能
- 意外声明全局变量,是最常见的也是最容易修改的内存泄漏问题
function fn(){
name="哈哈"
}
- 定时器也可能会导致内存泄漏
let name="haha",
setInterval(()=>{
console.log(name)
},1000)
//只要定时器一直运转,回调函数中引用的name就一直占用着内存,垃圾回收程序就不会清理
- 使用js闭包很容易在不知不觉间造成内存泄漏
let out = function(){
let name = "haha";
return function(){
return name;
};
};
//调用out()会导致分配给name的内存被泄露,以上代码创建了一个闭包,只要返回的函数存在就不会清理name,因为闭包一直在引用它,如果name的内容很大,就可能出现问题
总结
js的变量可以保存两种数据类型的值:基础值和引用值,基础值包含Boolean,undefined、null、number、String、Symbol。下面我们所说这两种值的特点:
- 原始值大小固定,因此保存在栈内存中;而引用值是对象,保存在堆内存中
- 从一个变量到另一个变量复制原始值相当于是创建了这个值的副本
- 从一个变量到另一个变量复制引用值实际上只会复制指针,因此结果是两个变量都指向同一个对象
- 包含引用值的变量,实际上是包含的这个对象的指针而不是对象本身
- typeof操作符可以确定值的原始类型,引用类型都返回object;而instanceof操作符用于确保值的引用类型
任何变量都存在于某个执行上下文中(也就是作用域),这个上下文决定了变量的生命周期,以及他们可以访问的内容有哪些
- 执行上下文分全局上下文、块级上下文、函数上下文(局部作用域包括块级和函数作用域)
- 代码执行每一个上下文时,都会创建一个作用域链,用于搜索变量和函数
- 局部作用域不仅可以访问自己作用域内的变量,还可以访问上层作用域乃至全局作用域的内容
- 全局作用域只能访问全局作用域的变量和函数,不能访问局部上下文中的任何数据
- 变量的执行上下文用于确定什么时候释放内存
js是使用垃圾回收的编程语言,我们不需要担心内存分配和回收。
- 离开当前我们访问作用域的值,会被标记为可回收,然后在垃圾回收期间被删除
- 主流的垃圾回收算法是标记清理,即先给当前不使用的值加上标签,再回来回收他的内存
- 引用计数是另一种垃圾回收策略,需要记录值被引用了多少次。js引擎已经不再使用这种算法了,但在一些旧版本的IE仍然会受这种算法的影响,原因是js会访问非原生js对象(如DOM元素)
- 解除变量的引用不仅仅可以消除循环引用而且对垃圾回收也有帮助。为促进内存回收,全局对象、全局对象的属性和循环引用都应该在不需要时解除引用。