变量存储在闭包中的问题
按照常理来说栈中数据在函数执行结束后就会被销毁,那么 JavaScript
中函数闭包该如何实现,先简单来个闭包:
function count () {
let num = -1;
return function () {
num++;
return num;
}
}
let numCount = count();
numCount();
// 0
numCount();
// 1
复制代码
按照结论,num
变量在调用 count
函数时创建,在 return
时从栈中弹出。
既然是这样的逻辑,那么调用 numCount
函数如何得出 0
呢?num
在函数 return
时已经在内存中被销毁了啊!
因此,在本例中 JavaScript
的基础类型并不保存在栈中,而应该保存在堆中,供 numCount
函数使用。
抛开栈,只在堆中存储数据
function test () {
let num = 1;
let string = 'string';
let bool = true;
let obj = {
attr1: 1,
attr2: 'string',
attr3: true,
attr4: 'other'
}
return function log() {
console.log(num, string, bool, obj);
}
}
伴随着 test
的调用,为了保证变量不被销毁,在堆中先生成一个对象就叫 Scope
吧,把变量作为 Scope
的属性给存起来。堆中的数据结构大致如下所示:
由于 Scope
对象是存储在堆中,因此返回的 log
函数完全可以拥有 Scope
对象 的访问。下图是该段代码在 Chrome
中的执行效果:
例子中 JavaScript
的变量并没有存在栈中,而是在堆里,用一个特殊的对象(Scopes
)保存。
变量到底是如何在 JavaScript
中存储的
在 JavaScript
中,变量分为三种类型:
- 局部变量
- 被捕获变量
- 全局变量
局部变量
在函数中声明,且在函数返回后不会被其他作用域所使用的对象。下面代码中的fx*
都是局部变量。
function fxFn () {
let fx1 = 1
var fx2 = 'str'
const fx3 = true
let fx4 = {name: 'fx'}
return
}
被捕获变量
被捕获变量就是局部变量的反面:在函数中声明,但在函数返回后仍有未执行作用域(函数或是类)使用到该变量,那么该变量就是被捕获变量。下面代码中的 fx*
都是被捕获变量。
function fxFn () {
let fx1 = 1
var fx2 = 'str'
const fx3 = true
let fx4 = {name: 'fx'}
return function () {
console.log(fx1, fx2, fx3, fx4)
}
}
let fx = fxFn()
console.dir(fx)
function fxFn () {
let fx1 = 1
var fx2 = 'str'
const fx3 = true
let fx4 = {name: 'fx'}
return class {
constructor() {
console.log(fx1, fx2, fx3, fx4)
}
}
}
let fx = fxFn()
console.dir(fx)
复制代码到 Chrome
即可查看输出对象下的 [[Scopes]]
下有对应的 Scope
。
全局变量
全局变量就是 global
,在 浏览器上为 window
在 node
里为 global
。全局变量会被默认添加到函数作用域链的最低端,也就是上述函数中 [[Scopes]]
中的最后一个。
全局变量需要特别注意一点:var
和 let/const
的区别。
var:全局的 var
变量其实仅仅是为 global
对象添加了一条属性。
var name = 'fx';
// 与下述代码一致
windows.name = 'fx';
let / const:全局的 let/const
变量不会修改 windows
对象,而是将变量的声明放在了一个特殊的对象下(与 Scope
类似)。
var pwd = 123
变量赋值
其实不论变量是存在栈内,还是存在堆里(反正都是在内存里),其结构和存值方式是差不多的,都有如下的结构:
赋值为常量
何为常量?常量就是一声明就可以确定的值,比如 1
、"string"
、true
、{a: 1}
,都是常量
假设现在有如下代码:
let foo = 1
JavaScript
声明了一个变量 foo
,且让它的值为 1
,内存中就会发生如下变化
如果现在又声明了一个 bar
变量:
let bar = 2
那么内存中就会变成这样:
对于对象类型
let obj = {
foo: 1,
bar: 2
}
内存模型如下:
通过该图,我们就可以知道,其实 obj
指向的内存地址保存的也是一个地址值,那好,如果我们让 obj.foo = 'foo'
其实修改的是 0x1021
所在的内存区域,但 obj
指向的内存地址不会发生改变,因此,对象是常量!
赋值为变量
何为变量?在上述过程中的 foo
、bar
、obj
,都是变量,变量代表一种引用关系,其本身的值并不确定。
那么如果我将一个变量的值赋值给另一变量,会发生什么?
let x = foo
如上图所示,仅仅是将 x
引用到与 foo
一样的地址值而已,并不会使用新的内存空间。
OK
赋值到此为止,接下来是修改。
变量修改
与变量赋值一样,变量的修改也需要根据 =
号右边变量的类型分为两种方式:
修改为常量
foo = 'foo'
如上图所示,内存中保存了 'foo'
并将 foo
的引用地址修改为 0x0204
。
修改为变量
foo = bar
如上图所示,仅仅是将 foo
引用的地址修改了而已。
const 的工作机制
const
为 ES6
新出的变量声明的一种方式,被 const
修饰的变量不能改变。
其实对应到 JavaScript
的变量储存图中,就是变量所指向的内存地址不能发生变化。也就是那个箭头不能有改变。
比如说以下代码:
const foo = 'foo';
foo = 'bar'; // Error
如上图的关系图所示,foo
不能引用到别的地址值。那好现在是否能解决你对下面代码的困惑:
const obj = {
foo: 1,
bar: 2
};
obj.foo = 2;
其 obj
所引用的地址并没有发生变化,发生变的部分为另一区域。如下图所示
对象的修改
OK
进入一个面试时极度容易问到的问题:
let obj1 = {
foo: 'foo',
bar: 'bar'
}
let obj2 = obj1;
let obj3 = {
foo: 'foo',
bar: 'bar'
}
console.log(obj1 === obj2);
console.log(obj1 === obj3);
obj2.foo = 'foofoo';
console.log(obj1.foo === 'foofoo');
请依次说出 console
的结果。
我们不讨论结果,先看看内存中的结构。所以结果为 true false true
标签:闭包,存储,bar,变量,fx,JS,let,const,foo From: https://blog.51cto.com/u_13028258/5754017