JS进阶Ⅰ
站在JS初步的肩膀上,我们继续JS学习之旅。
作用域
作用域是可访问变量的集合。
在 JavaScript 中, 对象和函数同样也是变量。
那么在 JavaScript 中, 作用域就是可访问变量,对象以及函数的集合。
在JS初步时我们知道变量分为全局变量以及局部变量,那么作用域也分为全局作用域和局部作用域。
- 全局作用域:全局作用域中声明的变量,对象及函数可以被任何地方访问到
- 局部作用域:局部作用域中声明的变量,对象及函数只能在声明的这个局部作用域中进行访问
var a = 123; // 此时的a是在全局作用域中定义的,是全局变量,可以在任何地点被访问到;
function f(){
var b = 456; //此时的b是局部作用域中定义的,是局部变量,只能在这个局部作用域中进行访问;
console.log(a); // 123
console.log(b); // 456
};
f();
console.log(a); //123
console.log(b); // Uncaught ReferenceError: b is not defined
tip:需要注意的是,如果你在函数中只是单纯的给一个变量赋值而没有使用任何关键字进行声
明,那么这个变量其实是一个全局变量,当然,这个函数还是一个局部作用域。
var a = 123; // 此时的a是在全局作用域中定义的,是全局变量,可以在任何地点被访问到;
function f(){
b = 456; //此时的b虽然是在局部作用域中赋值的,但它是全局变量,可以在任何地点被访问到;
console.log(a); // 123
console.log(b); // 456
};
f();
console.log(a); //123
console.log(b); // 456
作用域链
当我们需要某个变量时,如果自身所在的作用域中没有,那么就会去这个作用域的上一级,也就是父级
作用域中去寻找,如果还找不到,就继续去父级找,直到找到GO为止。这条循着父级、父级的父级一级一级
去寻找所需变量的链,就叫做作用域链。
正是因为作用域链的这种只会去父级寻找而不会去子级寻找的特性,才使得作用域划分为全局
作用域和局部作用域
var a = 123; // 全局变量
function f(){
var b = 456; //局部变量
function g(){
var c = 789; //局部变量
console.log(a); // 123
console.log(b); // 456
console.log(c); // 789
};
g();
console.log(a); // 123
console.log(b); // 456
console.log(c); // Uncaught ReferenceError: c is not defined 未捕获引用错误 代码到此结束,不会继续向下执行
};
f();
console.log(a); //
console.log(b); //
console.log(c); //
JS代码执行过程中的一些概念
ECStack EC(G) VO EC(f) AO GO 这些名词在JS初步中我提过一嘴,下面就来深入认识一下
1. EC-Stack:执行上下文栈;
JS代码在执行时是由上至下一行一行执行的,当代码开始执行时内存会分配一些空间作为执行空间,
一行行的代码进入执行空间中执行,执行完后退出空间并销毁以释放内存,接着下一行代码进入空
间执行,直到执行完所有代码,这些空间才会全部释放。这些分配的内存空间就是执行上下文栈。
2. EC(G) :全局执行上下文
提供了全局作用域中的代码的执行空间,它是最先进入执行上下文栈的执行上下文,代码执行时先
预编译,进行变量和函数的提升,然后再一行一行地执行。
3. VO :变量对象
存储着全局数据(包括变量、函数、对象等)
4. EC(f) :函数执行上下文
提供了局部作用域中的代码的执行空间,第一步先进行形参的赋值,第二部进行局部数据的提升,
第三步执行代码。
5. AO :活动对象
存储着局部数据(包括变量、函数、对象等)
6. GO: 全局对象
默认情况下,里面就存储了很多已经定义好的变量、函数、对象等数据;并且我们自己定义的全局
数据也会存一份在GO中,例如加var
的全局数据,加functon
的全局数据以及不加var
的变量都会
存储到GO中。
for example
var a = 123; // 此时的a是在全局作用域中定义的,是全局变量,可以在任何地点被访问到;
function f(){
var b = 456; //此时的b是局部作用域中定义的,是局部变量,只能在这个局部作用域中进行访问;
console.log(a); // 123
console.log(b); // 456
};
f();
console.log(a); //123
console.log(b); // Uncaught ReferenceError: b is not defined
闭包 closure
不会被垃圾回收机制销毁的局部执行上下文(栈)
var name = "The Window";
var getNameFunc = function(){
var name = "My Object";
return function(){
return name;
};
};
alert(getNameFunc()()); // My object
咦?为什么输出的是My object
呢 它不是定义在函数内部,是一个局部变量么?按理说应该输出
我们定义的全局变量The window
才对啊,这与我们前面说的好像不一样。其实之所以输出的是My object
而不是The window
,是因为这里面构成了一个闭包,使得getNameFunc
这个函数在调用完后
没有被销毁,而是暂时保存了下来,那么内部的My object
也会暂时存在,因此能被它的子级访问
到。
那么闭包在哪呢 ?一个函数中嵌套着一个引用着它定义的数据的函数,就形成了闭包,这就使得这
个函数在调用完之后不会被垃圾回收机制回收销毁,因为它定义的变量仍被它的子级函数使用。
闭包的优点
1.闭包可以延长变量的生命周期,不会在被调用后清除;
2.闭包可以读取函数内部的变量。
闭包的缺点
1.由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包
,否则会造成网页的性能
问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量
全部删除。
2.闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象
(object)使用,把闭包当
作它的公用方法(Public Method),把内部变量当作它的私有属性(private value
),这时一定要小心,不
要随便改变父函数内部变量的值。