目录
一、作用域链详解
作用域
作用域是变量或函数生效的区域。[[scope]]是函数的作用域,其中存储着作用域链。[[scope]]是函数的一个隐式属性,不能被用户主动访问,仅供Javascript引擎存取。
作用域链
函数的[[scope]]属性中存储着不同执行期上下文构成的集合,这个集合呈链式链接,被称为作用域链。函数定义后[[scope]]中存储其所在环境的作用域,预编译会将新生成的AO置于作用域链顶端。每一个函数都有一条作用域链,函数中变量的查询是从作用域链的顶端开始依次向下查找,直至最后也没找到则返回undefined。
函数的链式作用域导致内部函数可以调用外部变量,外部不能调用内部变量。
执行期上下文
预编译产生的AO或GO就是执行期上下文。AO是函数的执行期上下文,GO是全局的执行期上下文。每次预编译产生的AO都是独一无二的,它定义了函数执行时的内部环境,在函数执行完毕后被立刻销毁,之后函数回归到刚定义时的状态,等待下次执行。
代码示例
//GO{
// this: window,
// window: (object),
// document: (object),
// a: function a(){ ... },
// glob: 100
//}
function a(){
var a = 123;
function b(){}
b(); //因为b在a中定义,所以b会随a的执行期上下文一起被销毁
}
var glob = 100;
a();
//函数a,b不同时期(定义时、执行时)的作用域链:
//a defined:a.[[scope]] ---> 0: GO{}
//a doing :a.[[scope]] ---> 0: aAO{}
// 1: GO{}
//b defined:b.[[scope]] ---> 0: aAO{} 由于a执行产生了b定义 ---> b继承a的作用域
// 1: GO{}
//b doing :b.[[scope]] ---> 0: bAO{} 函数预编译时把自己的AO放在作用域链的顶端
// 1: aAO{}
// 2: GO{}
//a,b作用域链中的所有的aAO指的是同一个,所有的bAO指的是同一个,所有的GO指的是同一个
//由作用域链可以看出 ---> 内部的函数可以调用外部的变量,外部的不能调用内部的变量
//a执行时,查找变量顺序:aAO ---> GO
//b执行时,查找变量顺序:bAO ---> aAO ---> GO
示例图解
a函数定义时作用域链:
a函数执行时作用域链:
二、闭包
闭包的概念
把内部函数保存到外部就会形成闭包,闭包可以理解成一个函数,即保存到外部的内部函数。
闭包的作用及好处
闭包是函数内部和外部连接的桥梁,它继承了外部函数的作用域。通过闭包可以读取函数内部的变量,可以让函数中的变量值始终存储在内存里。使用闭包可以避免全局变量的污染。
闭包的缺点
闭包使外层函数作用域链不释放,使用不当易造成内存泄漏。内存泄漏就是内存占用,可以把内存理解成手中的沙子,泄漏的多剩下的就少了。
function a(){
function b(){
console.log(aaa);
}
var aaa = 234;
return b;
}
var glob = 100;
var demo = a(); //即使函数a执行完后被销毁了,但因其作用域链被闭包函数继承,所以它的作用域链不会被释放
//GO{
// glob: 100,
// demo: undefined,
// a: function a(){ ... }
//}
//aAO{
// aaa: 234,
// b: function b(){ ... }
//}
//bAO{
//
//}
demo(); //demo doing: demo.[[scope]] ---> 0: bAO{}
// 1: aAO{}
// 2: GO{}
< 234
闭包的应用
1.实现公有变量,如函数累加器
function add(){
var count = 0; //共有变量,属于函数add的AO
function demo(){
count++;
console.log(count);
}
return demo;
}
var counter = add();
counter();
counter();
< 1
< 2
function test(){
var num = 100;
function a(){
num ++;
console.log(a);
}
function b(){
num --;
console.log(b);
}
return [a,b];
}
var myArr = test();
myArr[0]();
myArr[1]();
2.可以做缓存(存储结构)
function eater(){
var food = "";
var obj = {
eat:function(){
console.log("i am eating" + food);
food = "";
},
push : function (myFood){
food = myFood;
}
}
return obj;
}
var eater1 = eater(); //闭包会导致eater的作用域链无法释放
eater1.push("banana");
eater1.eat();
3.可以实现封装,属性私有化
4.模块化开发,防止污染全局变量
代码示例
function test(){
var arr = [];
for(var i = 0;i < 10;i ++){
arr[i] = function () { //仅仅定义函数,没有执行函数,所以函数引用中的i不会被系统翻译
console.log(i + " ");
}
}
return arr;
}
var myArr = test(); //test函数返回数组arr,里面装的是十个函数,形成十个闭包
for(var j = 0; j < 10;j ++){
myArr[j](); //闭包函数的作用域链中testAO是同一个
//testAO中i = 10,所以十个函数中的i都会被系统翻译成10
}
< 10 10 10 10 10 10 10 10 10 10 10 10
//使用匿名自执行函数+闭包 ---> 输出0-9
function test(){
var arr = [];
for(var i = 0;i < 10;i ++){
(function(j){ //使用立即函数中转一下,当作中间层,在下面执行函数时,不使用变量i
arr[j] = function () {
console.log(j + " ");
}
}(i));
}
return arr;
}
var myArr = test();
for(var j = 0; j < 10;j ++){
myArr[j]();
}
//执行结果 0 1 2 3 4 5 6 7 8 9
//立即执行函数执行完后虽然被销毁了,但因其作用域链被闭包函数继承,所以它的作用域链不会被释放
//每一个闭包函数中的j是不一样的
三、立即执行函数
定义
定义完后立即被执行的函数叫立即执行函数。
特点
立即执行函数没有声明,执行完立即被销毁,适合做初始化工作。销毁的是立即执行函数的引用而不是代码,函数在视觉上客观存在,只是被永久性放弃了 。立即执行函数是Javascript提供的唯一一个能手动销毁函数的方法。立即执行函数除了其独有的特点外其余和普通函数没有任何区别。立即执行函数可以有返回值。
var num = (function(){
return 1;
}());
> num
< 1
//函数执行完后立即释放,并将返回值赋给num
形式
执行符号
立即执行函数不是明文规定的语法,它利用执行符号来起作用。执行符号就是一对小括号,只有表达式才能被执行符号执行。函数声明不是表达式所以不能被执行符号执行,但它可以通过特殊符号(如括号、等号、一元正负、与、或、非)转变为表达式从而被执行。数学运算的小括号优先级高于执行符号。
> function test(){
}();
//只有表达式才能被执行符号执行
//语法解析错误,系统认为这是一个非常底端的错误,扫一遍还没执行就报错了
> function test(){
}
> test()
//成功执行,因为test就和123、"123"(数字表达式,字符串表达式)一样都是表达式
function test(){
}
(1);
//不报错,系统会把函数和(1)分开解释
//(1)就是个1,加数学运算符括号没影响
function test(){
}
(1);
(function test(){
}())
> test
//报错,函数被执行符号执行后,函数名被放弃
//所以可以省略函数名
(function(){
}())
//应为数学运算的小括号优先级高于执行符号
//所以可以把执行符号拿到外面
(function(){
})()
立即执行函数标准形式解析
// ()是数学计算符,它可以使函数变为表达式 ---> (function test(){})
// 省略函数名后 --> (function (){})
// 表达式可以被执行符号执行 --> (function (){})()
// 后面的括号可以被放到里面-->(function(){}()), 这就是立即执行函数最标准的形式
// 这些括号中先执行最外层的括号,只有最外层的括号是数学运算的小括号,其余括号都有语法意义
// 先执行外面的括号会把里面的函数变成表达式,这样就可以被执行符号执行了,执行符号放在最外层括号
// 里面和外面都行
利用特殊符号实现立即执行函数
//第一种:利用括号(官网w3c推荐使用)
(function(){ }())
(function(a,b,c){ }(1,2,3)) //第一个小括号里面可以放形参,第二个放实参
//第二种:利用括号
(function(){ })()
//第三种:利用一元正负
+function(){ }()
-function(){ }()
//+、-指的是一元正负而不是加减
//在函数声明的前面加一个一元正号,意思是把函数转换成一个数字,在趋势上它就是一个表达式了
//第四种:利用与、或、非
!function(){ }() //非是对函数的返回值进行取反操作
true && function(){ }()
false || function(){ }()
//与和或前面必须加表达式,要保证能执行到函数部分才行
//第五种:利用等号
var num = function(){ return 1 }();
//以上形式,执行符号中都可以传实参
//函数被执行符号执行后函数名被放弃,所以以上代码都省略了函数名