举个例子:数组
function foo() { console.log('foo函数执行了'); function bar() { console.log('bar函数执行了'); } bar(); } foo();
代码执行时,执行栈如何变化缓存
建立全局上下文G_EC
,并入栈。闭包
执行栈:app
G_EC
执行foo函数,建立foo
的执行上下文foo_EC
, 并入栈。模块化
执行栈:函数
foo_ECG_ECthis
执行console.log
函数,建立console.log
函数的执行上下文clg_EC
, 并入栈。spa
执行栈:code
clg_ECfoo_EC模块化开发
G_EC
console.log
执行完毕,打印出 'foo函数执行了',销毁clg_EC
,出栈。
执行栈:
foo_ECG_EC
执行bar
函数,建立bar
的执行上下文bar_EC
, 并入栈。
执行栈:
bar_ECfoo_EC
G_EC
执行console.log
函数,建立console.log
函数的执行上下文clg_EC
, 并入栈。
执行栈:
clg_ECbar_EC
foo_EC
G_EC
注意:这里的clg_EC
是一个全新的上下文,和上一个不同,函数每次调用都生成一个新的独一无二的执行上下文。
console.log
执行完毕,打印出 'bar函数执行了',销毁clg_EC
,出栈。
执行栈:
bar_ECfoo_EC
G_EC
bar
函数执行完毕,销毁bar_EC
, 出栈。
执行栈:
foo_ECG_EC
foo
函数执行完毕,销毁foo_EC
, 出栈。
执行栈:
G_EC
所有代码执行完毕,销毁G_EC
, 出栈。
执行栈:
空
//用代码表示一个执行上下文 EC = { VO = {...}, SC = [...], this = {...} }
[[scope]]
属性,指向一个数组,数组中保存的是除本身的执行上下文之外的其余执行上下文;不影响执行做上下文的理解,可跳过。
this
的指向问题:
全局环境中,this
指向window
对象
console.log(this); //window
函数中的this
,指向window
(严格模式中指向undefined
)
function foo(){ console.log(this); //window } foo();
使用call
、apply
调用,this
指向call
/apply
的第一个参数
var obj = {a: 1}; function foo(){ console.log(this); //obj } foo.call(obj); foo.apply(obj);
调用对象中的函数,使用obj.fun
方式调用,this
指向obj
var obj = { a: 1, foo: function(){ console.log(this); } }; obj.foo(); //this指向obj var bar = obj.foo; bar(); //至关于将函数放在全局中执行,this指向window
在一个函数的VO建立时,js引擎作的事情
遇到同名属性则覆盖
举个例子:
function foo(a, b){ console.log(bar); //ƒ bar(){} console.log(a); //2 function bar(){} var a = 1; } foo(2, 3);
按照建立步骤生成foo函数的VO——foo_VO
//1. 肯定形参的值,一开始有两个形参的值 foo_VO = { arguments: {...}, //arguments一开始就会在 a: undefined, b: undefined, } //2. 变量声明提高, 内部有一个变量声明a,当前VO对象已经有a属性,因此不变 foo_VO = { arguments: {...}, a: undefined, b: undefined, } //3. 将实参的值赋给形参,执行foo(2, 3)时传入了实参2, 3,分别赋值给a, b foo_VO = { arguments: {...}, a: 2, b: 3, } //4. 函数声明提高, 有一个函数foo,将foo函数做为VO的属性 foo_VO = { arguments: {...}, a: 2, b: 3, foo: function(){} }
因此最后foo函数产生的VO对象就是
foo_VO = { arguments: {...}, a: 2, b: 3, foo: function(){} }
由于VO是在函数运行前建立的,函数在运行的时候就能够在当前VO中查找变量,因此这就解释了为何console.log
放在函数的最前面也能够打印a
和foo
的值。
SC = 上一层执行上下文栈的AO + 上一层执行上下文栈的SC
举个例子:
function foo() { function bar() { function baz(){ } } }
全局执行上下文,一开始的SC为空
g_EC = { SC: [], AO: {...}, this: {...}, }
foo函数执行上下文,其中SC = 全局上下文的AO + 全局上下文的SC
foo_EC = { SC: [g_EC.AO], //[g_EC.AO, ...g_EC.SC] AO: {...}, this: {...}, }
bar函数执行上下文,其中SC = foo函数执行上下文的AO + foo函数执行上下文的SC
bar_EC = { SC: [foo_SC.AO, g_EC.AO], //[foo_SC.AO, ...foo_SC.SC], AO: {...}, this: {...}, }
baz函数执行上下文,其中SC = bar函数执行上下文的AO + bar函数执行上下文的SC
baz_EC = { SC: [bar_SC.AO, foo_SC.AO, g_EC.AO], //[bar_SC.AO, ...bar_SC.SC], AO: {...}, this: {...}, }
做用域图解:
函数运行时,查找变量,会先查找本身的AO。若是没有,再依次沿着SC的第0项、第1项... 日后找。找到SC的最后一项都没有找到就会报错:
Uncaught ReferenceError: xxx is not defined
当一个函数中的函数被保存到该函数的外部就会造成闭包。
function foo(){ var a = 1; return function bar(){ return a; } } var baz = foo(); var qux = baz(); console.log(qux); //1
foo
在运行的时候
foo_EC = { SC: [GO], AO: { a: 1, ... }, this: {...}, }
当foo
运行完毕后会销毁本身的执行上下文,其中的AO也被销毁
可是因为bar
被保存到了外部, 也就是baz
中,而bar
的做用域SC中有foo
的AO,因此这就解释了为何造成闭包时,外部的函数能够使用函数内部的变量。
baz_EC = { SC: [foo_EC.AO, GO], AO: {...}, this: {...}, }
由于bar
函数被保存到全局做用域中,其中的foo
的AO一直存在,没法被销毁,会形成内存泄露;在使用完毕后应该去掉原来的闭包
baz = null
闭包的做用: