变量、作用域和内存
1. 原始值与引用值
原始值,简单数据(Null, Undefined, Boolean, Number, String, Symbol)。
引用值:多个值构成的对象
1.1 动态属性
只有引用值才能有属性,原始值给属性赋值虽然不会报错但是实际上不会成功赋值
let name = 'zzzs';
name.first = 'f';
console.log(name.first);
如果使用new 构造函数 实例化,那么他是一个对象(引用值)而不是原始值
let name = new String('name');
console.lg(typeof name);
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);
这里可以理解成把一串地址传进去,通过这个地址可以找到属性(增删改查),但是把这串地址改了(赋值为另外一个对象)对于传入的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函数中存在第三种上下文通过参数指定)但是有其他方式增强上下文。在作用域前端临时添加一个上下文,这种上下文在代码执行完之后会被删除
catch中为错误对象临时开辟上下文
function buildUrl() {
let qs = "?debug=true";
with(location) {
let url = href + qs;
}
}
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";
}
setInterval(() => {
console.log(outerVar);
}, 100)
let outer = function() {
let name = 'Jake';
return function() {
console.log(name);
}
}
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)
这样保证对象不会频繁创建,垃圾回收程序不会频繁启动。
|