原始值和引用值

原始值就是最简单的数据类型,而引用值就是对象。

原始值不能有属性,尽管给原始值添加属性不会报错

let name = 'Nicholas';
name.age = 27;
console.log(name.age);//undefined

原始值赋给另一个值时,会在内存中复制一份,因此两份互不干扰

let num1 = 5;
let num2 = num1;
num1 = 3;
console.log(num2);//5

把引用值从一个变量赋给另一个变量时,实际上是复制了指向这个对象的指针(对象本身在堆中),因此一个变量对对象进行修改另一个也会修改。

js函数只存在值传递,也就是说参数是原变量复制一份传进去的,也就是说如果传递的是对象我们在函数中修改了对象的属性在函数外也是成立的。

function setName(obj)
{
obj.name = 'b';
}

let person = new Object();
setName(person);
console.log(person.name);//b
---
function setName(obj)
{
obj.name = 'b';
obj = new Object();//如果是引用传递,那么函数外的obj的值也会随之更改,他将指向新的Object对象
obj.name = 'a';
}
let person = new Object();
setName(person);
console.log(person.name);//b

我们可以使用instanceof来判断具体的引用类型。使用方法为variable instanceof constructor

console.log(person instanceof Object);//person是Object类型吗
console.log(colors instanceof Array);//colors是Array类型吗

作用域

变量或函数的上下文决定了他们可以访问那些数据。每个上下文都有一个关联的变量对象,而这个上下文的所有变量和函数都存在于这个对象上,虽然无法直接访问对象,但是后台处理时会用到它。

全局上下文是最外层的上下文。在浏览器中,全局上下文是window对象,因此所有通过var定义的全局变量和函数都会成为window对象的属性和方法。let和const声明的全局变量不会挂在window对象中,因为他们声明的全局变量是无效的。

每个函数都有自己的上下文,他们在一个上下文栈中。上下文在所有代码被执行完后会销毁

例如:

var color = 'blue';

function changeColor()
{
let anotherColor = 'red';

function swapColors()
{
let tempColor = anotherColor;
anotherColor = color;
color = tempColor;
}
swapColors();
}

changeColor();

上下文代码执行时,有一个变量的作用域链,如图所示。里层可以访问外层,而外层不能访问里层。

with和catch都会变更上下文,catch会创建一个新的上下文。而with会添加到作用域链的尾部

变量声明

var

使用var时,会自动添加到最接近的上下文。在函数中,最接近的上下文是函数局部上下文。

如果变量未经声明直接赋值,那么会被自动添加到全局上下文

function add(num1, num2)
{
sum = num1 + num2;//直接赋值
return sum;
}

let result = add(10, 20); //30
console.log(sum);//30,因为它是全局上下文,所以在外部可以访问到

letconst作用域都是块级的,也就是说是由最近的一队{}决定的。

垃圾回收

js和java一样,都是使用垃圾回收机制。垃圾回收是自动回收的。基本思路是每隔一定时间会自动执行看那些变量不再使用。

标记清理

js最常用的垃圾回收策略是标记清理。只有在上下文中的变量才有可能被清理,不再上下文中的变量因为其他的上下文还可能使用因此永运不应该释放他们的内存。

给变量加标记的方法有多种,例如当变量进入上下文时,反转某一位,或者维护在上下文或不在上下文的列表。

垃圾回收程序运行时,会标记内存中存储的所有变量,之后会将所有在上下文以及被上下文引用的变量的标记去掉,在此之后再被标记就是待删除的了。

现在大多数浏览器都是使用这种方法。这种方式的好处是在上下文结束后会释放相关变量

引用计数

引用计数的思路是每个值都记录被引用的次数。

var object = new Object();//引用一次
var objectb = object;//引用两次
object = null;//引用一次

但是有时候会导致内存泄露,例如

function()
{
let objecta = new Object();
let objectb = new Object();

objecta.otherobject = objectb;
objectb.otherobject = objecta;
}

现在objecta和objectb都有两个引用并且是相互引用,也就是说他们永运都不会释放。

内存泄露及应对

下面是一些内存泄露的情况

function setName()
{
name = 'Jake';//没类型,是全局变量
}

let name = 'Jake';
setInterval(()=>{
console.log(name);//定时器会一直运行
}, 100);


let outer = function()
{
let name = 'Jake';
return function()
{
return name;
}
}
只要outer变量存在,那么name就不会被清除。

静态分配和对象池

浏览器决定何时运行回收程序的一个标准就是对象更替的速度,如果有很多对象初始化,然后又一下子超出作用域,那么就会更频繁的调用垃圾回收程序。

例如:

function addVector(a, b)
{
let resultant = new Vector();
resultant.x = a.x + b.x;
resultant.y = a.y + b.y;
return resultant;
}

这个函数是两个向量相加,其中的临时变量resultant会很快创建又被清除,造成了浪费。

解决这个问题的方法是不要创建临时变量,而始终用一个变量

function addVector(a, b, resultant)
{
resultant.x = a.x + b.x;
resultant.y = a.y + b.y;
return resultant;
}


如果是从外部传入的变量那么在函数结束之后就不会被清除。但是这样的话resultant需要自己控制释放,不然可能多个结果引用同一个对象

我们可以使用对象池来管理一组对象,需要时可以使用它,不需要时释放控制权,但是只是提交给对象池,并不代表这个对象被销毁。

let v1 = vectorPool.allocate();
let v2 = vectorPool.allocate();
let v3 = vectorPool.allocate();

v1.x = 10;
v1.y = 5;
v2.x = -3;
v2.y = -6;

addVector(v1, v2, v3);

console.log(v3.x, v3.y);//7 -1

vectorPool.free(v1);
vectorPool.free(v2);
vectorPool.free(v3);

```