JavaScript 变量、作用域和内存

变量、作用域和内存

1. 原始值与引用值

原始值,简单数据(Null, Undefined, Boolean, Number, String, Symbol)。

引用值:多个值构成的对象

1.1 动态属性

只有引用值才能有属性,原始值给属性赋值虽然不会报错但是实际上不会成功赋值

let name = 'zzzs';
name.first = 'f';
console.log(name.first); // undefined

如果使用new 构造函数 实例化,那么他是一个对象(引用值)而不是原始值

let name = new String('name'); 
console.lg(typeof name); // object

1.2 复制值

  • 原始值:副本
  • 引用值:复制引用,指向同一片堆内存

1.3 传递参数

在ES中函数的传递值传递而不存在引用传递。 所有函数内的改变不可能影响外面。

下面的例子具有一定迷惑性,但是要谨记ES中只有值传递。

function fn(obj) {
    obj.name = 'nico';
    obj = new Object();
    obj.name = 'kasa';
}

let obj = {
    name: 'bin';
}
fn(obj);
console.log(obj.name); // nico;

这里可以理解成把一串地址传进去,通过这个地址可以找到属性(增删改查),但是把这串地址改了(赋值为另外一个对象)对于传入的obj没有任何影响。所以本质上就是值传递。


1.4 确定类型

  • 原始值:多用typeof 确定
  • 引用值:多用instanceof 确定

2 执行上下文和作用域

上下文可以理解为当前代码块的执行环境。每个上下文都有关联的变量对象,里面存储着这个上下文中定义的函数和变量。

var color = 'blue';

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VaWRpavF-1640435830314)(images/1.png)]

每个上下文都可以到上一级上下文中搜索变量和函数,但任何上下文不能到下一级搜索。函数参数也被认为当前上下文中的变量。

2.1 作用域链增强

执行上下文只要有全局上下文和函数上下文两种(eval函数中存在第三种上下文通过参数指定)但是有其他方式增强上下文。在作用域前端临时添加一个上下文,这种上下文在代码执行完之后会被删除

  • try/catch的catch块

catch中为错误对象临时开辟上下文

  • with语句
function buildUrl() {
    let qs = "?debug=true";
    with(location) {
        let url = href + qs; // href这里相当于location.href 
	}
}

2.2 变量声明

2.2.1 var

使用var声明变量时,变量会被自动添加到最近的上下文中。如果未经声明就被初始化,那么会被加到全局对象上(在严格模式下,会报错)。

var声明的变量会被提升

// 以下两种写法等价
var name = "Jake";

name = "Jake";
var name;
2.2.2 let

let的作用域是块级作用域,花括号的区域称为一个块级作用域。

2.2.3 const

const的作用域也是块级的,但是声明的同时就必须初始化。另外,const生命的变量是不可以修改的,但其下的属性是可以的。要想完全不能改,可以用Object.freeze()冻结对象

2.2.4 标识符查找

在查找变量时,优先查找当前上下文,当前上下文没有没有则一直往上找,直到找到该变量。


3. 垃圾回收

当变量的声明周期结束之后,垃圾回收机制会及时清理这些”垃圾“释放内存空间。垃圾回收的机制主要有标记清理引用计数两种方式

3.1 标记清理

垃圾回收机制在运行的时候,会标记内存中存储的变量。然后在上下文的变量都会被去掉标记,在此之后仍然有标记的即为垃圾,因为在上下文找不到他们了。所以这时候便做一次内存清理


3.2 引用计数

一个值如果被另外一个值引用,那么引用次数+1,如果该变量的引用次数为0,那么将其视为垃圾。进行一次清理。但是会出现循环引用的问题(特别是dom以及js对象的引用)。

function problem() {
    // 循环引用无法进行垃圾清理
    let objectA = new Object();
    let objectB = new Object();
    
    objectA.attr = objectB;
    objectB.attr = objectA;
}

3.3 性能

IE做法,给垃圾回收设置阈值(分配变量,字面量,数组槽位)如果本次回收的垃圾不及15%那么阈值翻倍,如果回收的垃圾达到85%则重置阈值


3.4 内存管理

如果数据不用,那么把他设置成null,让回收机制回收

3.4.1 通过const和let声明提升性能

相比于var, const和let的作用域以块为作用域,能够更早的被垃圾回收机制检测并回收。

3.4.2 隐藏类和删除操作

V8引擎会将创建的对象通过隐藏类关联起来。能够共享相同隐藏类的对象性能会更好。

function Article() {
    this.title = 'ff';
}

let a1 = new Article();
let a2 = new Article();
// 上面两者共享同一个隐藏类

但是添加或者删除对象的属性会导致隐藏类不同,也就不能共享

a1.author = 'ff';
delete a1.author 

解决方案在构造函数中预先留有槽位如下

function Article(opt_author) {
    this.title = 'ff';
    this.author = opt_author;
}

3.3 内存泄漏

  • 全局变量
function setName() {
    name = "Jake"; // 意外成为全局变量
    // 解决方法:加上let const var
}
  • 定时器
setInterval(() => {
    console.log(outerVar); // 一直引用外部变量
}, 100)
  • 闭包
let outer = function() {
    let name = 'Jake';
    return function() {
        console.log(name);
	}
}
// 只要outer()执行后返回的变量存在则outer内部变量就不会被回收

3.4 静态分配和对象池

为了提升性能,应该尽可能减少垃圾回收的次数和数量。

function addVecotr(a, b) {
    let resultant = new Vector();
    resultant.x = a.x + b.x;
    resultant.y = a.y + b.y;
    return resultant;
}
// 如果频繁运行上面的程序,那么对象会被频繁创建和失去引用,那么回收机制发现对象更新很快,那么会跟频繁安排垃圾回收

解决方法:不要动态创建对象,而是使用一个外部变量传入

// 池化
const POOL_SIZE = 10;

function getPooledElement() {
    const constructor = this;
    if(pool.length > 0) {
        const instance = constructor.pool.pop();
        constructor.call(instance, ...arguments);
        return instance;
    }
    return new constructor(...arguments);
}

function releasePooledElement(element) {
    const constructor = this;
    if(element instanceof constructor  === false) {
        throw Error()
	}
    element.destory();
    if(constructor.pool.length < POOL_SIZE) {
        constructor.pool.push(element)
    }
}

function addPoolingTo(constructor) {
  constructor.pool = []
  constructor.getPooled = getPooledElement
  constructor.release = releasePooledElement
}

let tPool = addPoolingTo(Teacher);

function Teacher(age) {
    this.age = age;
}
Teacher.prototype.destory = function() {
    this.age = null;
}


let teacher = tPool.getPooled(36);
tPool.release(teacher)

使用对象池创建数据

这样保证对象不会频繁创建,垃圾回收程序不会频繁启动。
t tPool = addPoolingTo(Teacher);

function Teacher(age) {
this.age = age;
}
Teacher.prototype.destory = function() {
this.age = null;
}

let teacher = tPool.getPooled(36);
tPool.release(teacher)


[使用对象池创建数据](https://juejin.cn/post/6844904168356839432)

这样保证对象不会频繁创建,垃圾回收程序不会频繁启动。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值