JavaScript初阶--------函数闭包立即执行函数
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JavaScript初阶--------函数闭包立即执行函数相关的知识,希望对你有一定的参考价值。
函数
有时候我们的代码重复了很多次,编程里面称为耦合,但是编程要讲究高内聚,弱耦合。为了将重复多的聚在一起就出现了函数。
定义
函数基本要素:函数声明(function),函数名称,参数(形参,实参),返回值。
1.首先函数命名方式采用小驼峰式写法,即第一个单词小写,后面的单词首字母大写,如 function oneNumber(){}
2.函数表达方式里面有函数表达式,匿名函数表达式
var a = function lala() {}//函数表达式
var b = function () {}//匿名函数表达式
为了便于使用以及方便,我们之后基本都是采用匿名函数表达式,并且称为函数表达式。
return返回值
作用一:返回函数最终执行结果
作用二:终止函数
函数作用域
变量和函数生效的区域叫作用域,作用域分为全局作用域和局部作用域。访问时,里面的作用域可以访问外面的,外面的不能访问里面的。
[[scope]]:每个javascript函数都是一个对象,对象中有些属性我们可以访问,但有些不可以,这些属性仅供javascript引擎存取,[[scope]]就是其中一个。
[[scope]]指的就是我们所说的作用域,其中存储了运行期上下文的集合。
作用域链:[[scope]]中所存储的执行期上下文对象的集合,这个集合呈链式链接,我们把这种链式链接叫做作用域链。
运行期上下文:当函数执行时,会创建一个称为执行期上下文的内部对象。一个执行期上下文定义了一个函数执行时的环境,函数每次执行时产生对应的执行上下
文都是独一无二的,所以多次调用一个函数会导致创建多个执行上下文,当函数执行完毕,执行上下文被销毁。
预编译
js运行三步:语法分析,预编译,开始执行
暗示全局变量
函数里面没有声明直接赋值的变量称为暗示全局变量,能够被全局调用,而且全局变量都是window上的属性,可以通过window.name来调用。
预编译四部曲
1.创建AO对象(执行上下文对象)
2.找函数声明和函数中的变量声明,并赋值undefined
3.赋值(形参和实参统一)
4.函数体赋值给对应的AO属性
举个栗子看看吧
function fn(a){
console.log(a); // ?unction a(){ }
var a = 123;
console.log(a); // 123
function a(){ }
console.log(a); //123
console.log(b); //undefined
var b = function(){};
console.log(b); //function () {}
console.log(d); // function d(){}
function d(){}
}
fn(1);
首先产生执行期上下文,然后找函数声明和变量声明a,b,d,下面是整个预编译过程。
a --> undefined --> 1 --> function a ( ) { } --> 123
b --> undefined --> function ( ) { }
d --> undefined --> function d( ) { }
最后得出各AO属性对应的值
function与var之间覆盖的问题,例如:
function bar(){ return foo; function foo(){ } var foo = 111 } console.log(bar()); // 一开始就被return
function bar(){ foo = 10; function foo(){ {; var foo = 11; return foo; } console.log(bar()); // 11 foo被赋值为11,最后才被return!!
以上就是整个预编译过程
闭包
说了那么多其实就是为了给闭包做铺垫,闭包会导致多个执行函数共用一个公有变量。所以一般如果不是特殊需要,尽量少用。容易污染全局变量。
举个栗子
function a(){ function b(){ var bbb = 234; console.log(aaa); } var aaa = 123; return b; } a(); //*****************************function b() {}
var demo = a();
demo();
最后我们会发现,demo --> function b () { },demo() --> 123。这是因为当b执行完了相当于a也执行完了,这样a会把执行上下文销毁,但是b已经被return出去并且给了demo,当demo执行时也就是相当于function b () { }执行,得出123。这就是内存泄露,原有的作用域链不释放导致的。
来个栗子巩固一下:
function a() { var aaa = 100; function b() { console.log(aaa); } return b; } var demo = a(); demo(); //100 b的劳动成果已经保存到了demo里面
当a函数定义的时候,产生一个执行上下文,里面有一个aaa,和一个函数b,当b定义的时候,已经含有a的劳动成果,意思就是它已经有a的执行上下文,并且在b执行的时候,产生它自己的执行上下文,最后当a函数执行完之后,把函数b返回到了全局作用域,虽然a执行完,并且销毁了它自己的执行上下文,但是因为其内部b函数的存在,仍然有a的全部执行上下文,所以,仍然可以通过demo来访问function a里面的aaa变量。
闭包的应用
1.实现公有变量
function add() {
var num = 0;
function demo(){
num++;
console.log(num);
}
return demo;
}
var test = add();
test();//1
test();//2
对于上述栗子,公有变量就是num。add函数将demo函数返回出去,demo函数依然有add函数的执行上下文,每次执行test(),就相当于执行demo函数,每次访问的num都是同一个
num变量,这样num就是一个公有变量了,通过这种方式就能利用闭包产生一个累加器了。
2.做缓存机构
function test(){
var num = 0;
function a() {
console.log(++num);
}
function b() {
console.log(--num);
}
return [a,b];
}
var arr = test();
arr[0]();//1
arr[1]();//0
a函数和b函数都被return到了外部,这样a函数和b函数都与num产生了一个闭包,并且a和b执行的都是同一个变量,当a改变num的时候,b的num也会发生改变,同理,b操作了
num,a的num也会发生改变,因为它们指向的num是同一个num,这就相当于一个缓存。
再来一个栗子
function eater() {
var food = "";
var obj = {
eat : function() {
if(food == "" ){
console.log(‘empty‘);
}else{
console.log("I am eating " + food);
food = "";
}
},
push : function (myFood) {
food = muFood
}
}
return obj;
}
var eater1 = eater();
eater1.eat();
eater1.push(‘orange‘);
eater1.eat();
和上一个栗子一样,obj对象被return到了外部,并且用eater1来接收。eater1.eat()和eater1.push()所对应的food是同一个,这就是利用闭包产生缓存机构。
3.私有化变量
这个作用的了解需要先了解一下构造函数
function Deng(){
var prepareWife = "xiaozhang";
var obj = {
name : "Laodeng",
age : 40,
sex : "male",
wife : "xiaoliu",
divorce : function () {
this.wife = delete this.wife;
},
getMarried :function () {
this.wife = prepareWife;
},
changePrepare : function (someone) {
preparewife = someone;
},
sayMywife : function (){
console.log(this.wife);
}
}
return obj ;
}
deng = Deng();
运行一下看看
deng.sayMyWife() //"xiaoliu" deng.divorce() //undefined (没有返回值) deng.sayMywife() // true已经删除 deng.changePrepare(‘xiaoxiaozhang‘) //undefined (函数没有返回值)******** deng.getMarried() //undefined deng.sayMywife() //"xiaoxiaozhang" deng.prepareWife //undefined
prepareWife函数里面的变量,不在全局作用域里,所以我们访问不了,但是我们所有的操作都是围绕prepareWife来进行的,它们都可以正常访问这个变量,所以,像这种只能通过与
这个变量产生闭包的方法,属性,才能给对那个变量进行访问,所以,我们就称之为,私有化变量,我们可以通过定制接口(各种方法),来对变量的安全程度进行设置。
立即执行函数
此类函数没有声明,在一次执行过后即释放。适合做初始化工作。比如有些数学公式,或者是其他一些常数的计算,我们没有必要把它一直放在全局的空间里,这样会很好内存,于
是就诞生了立即执行函数。
var x = (function(a, b){
return(a + b) ;
}(1, 2)) // x = 3
立即执行函数特点就是当JavaScript引擎解析到这个语句的时候就会马上执行,执行结束后马上把自己的执行上下文都销毁。这样就可以释放这里的内存,立即执行函数可以有返回值
以及形参等。
我们要知道函数声明不能执行,只有函数才能执行,所以
函数声明----->表达式
function test() { } // 函数声明,不是表达式
var test = function () ; // 函数表达式
我们可以把函数声明转换成表达式
1.+function test(){ } -----> +号运算符,这样就将函数声明转换成表达式,就可以执行了
2. !function test(){ } ------> !
3. (function test(){ })( ) -------> ( )
利用立即执行函数解决闭包问题
想要实现打印0-9,结果却出人意料。。。。。。。(出现了10个10)。这个我想了好久才想通了,唉,脑子不够用。。。
function test() { var arr = [ ]; for(var i = 0 ;i < 10; i++){ arr[i] = function (){ console.log(i + ","); } } return arr; } var demo = test(); for(var j = 0; j < 10 ;j ++ ){ demo[j](); }// 10 * 10
这是为什么呢?输出的10个全是10,说明这里的i都是同一个i,为什么会这样呢,原来是function执行的时候i已经就是10了,由于test与arr之间产生了闭包,先这样说吧,每次return出
去一个arr,但是function现在还没有执行,也就是都是arr[i],当终止循环的时候i=10,这时候function执行的时候10个i都是相加到10的那个i,是同一个i,所以最后打印出10个10。理解了
吗?感觉说的太通俗了,希望大家能够理解哈。
如果觉得我说的有问题,欢迎提出来,大家一起进步哈!
function test() { var arr = []; for(var i = 0; i < 10; i++){ (function (j) { console.log(j); }(i)) } return arr; }
利用立即执行函数,每次访问的i都不一样,所以打印出来的就是0-9了。
最后再来一个栗子
a = 100 ;
function demo(e) {
function e() {}
arguments[0] = 2;
document.write(e); //2 因为形参列表将e改变为2
if(a) {
var b = 123;
function c() { }
}
var c ;
a = 10 ;
var a ;
document.write(b); // undefined
f = 123;
docuemnt.write(c); //function (){}
docuemnt.write(a); // 10
}
var a;
demo(1)
docuemnt.write(a);
document.write(f);
以上是关于JavaScript初阶--------函数闭包立即执行函数的主要内容,如果未能解决你的问题,请参考以下文章