基础 typeof 知多少 前置知识:
typeof
判断数据类型会输出如下这些:
number string boolean object undefined function
需要注意的:
console .log(typeof (null )) console .log(typeof (undefined ))
开门见山,下面输出会是什么?
console .log(typeof (1 -'1' ))
console .log(typeof ('1' -'1' ))
查看答案
还是number
,诶?怎么肥事?为啥不是 string
呢?
上面两个题,答的怎么样呢?后续讲解显示/隐式类型转换时会给大家解答,接着继续 GO!
下面代码会是怎样的结果?
查看答案
可能你会说输出 undefined
,但你又入坑了…答案是会报错 。
好的,接下来继续,我再来变一下,下面又会输出什么?还是会报错吗?
查看答案
答案是 undefined
,又中招了? 这里 a
没声明,判断类型当然是 undefined
呐!
go on ! 那么下面这个代码我先提示一下,有坑,接上一题,我们得到了 undefined
,下面这题我们又套了一层 typeof
,按照开头提到的注意点,会是 undefined
吗?
console .log(typeof (typeof (a)))
下一题,这个应该是输出?
console .log(typeof (typeof (123 )))
查看答案
答案还是 string
(这里要解释了,不然就要哭了┭┮﹏┭┮),原因是 typeof
判断数据类型时,会返回字符串形式的类型,例如上上题的typeof(typeof(a))
,里面会返回一个 'undefined'
这样的结果,而 typeof(typeof(123))
里面会返回一个'number'
。因此我们再次 typeof
时,就会返回 string
类型。(恍然大悟,给自己鼓个掌~)
显示、隐式类型转换 显示转换 搞简单点,先来看看显示转换吧:
let a = '123' console .log(typeof (Number (a)) + '-' + Number (a))
显然会输出数字 123
我们把 a
改成 字符串 'true'
,又会输出什么呢?
let a = 'true' console .log(typeof (Number (a)) + '-' + Number (a))
此时会输出 NaN
,如果把 a 改成 true
,就会输出数字 1
(即改成下述代码)。
let a = true console .log(typeof (Number (a)) + '-' + Number (a))
继续,来点特殊一点的东西:
let a = null console .log(typeof (Number (a)) + '-' + Number (a))
let a = undefined console .log(typeof (Number (a)) + '-' + Number (a))
查看答案
对于 null
,结果会输出数字 0
对于 undefined
,结果会输出 NaN
下面给出一些其他例子,小伙伴们可以好好消化一下。
var a = 'a' console .log(typeof (Number (a)) + '-' + Number (a)) var a = '1a' console .log(typeof (Number (a)) + '-' + Number (a))var a = 3.14 console .log(typeof (Number (a)) + '-' + Number (a))console .log(typeof (NaN ))
接下来,我们一起来探讨一下 parseInt
:
还是先来个简单的热热身,我们知道 parseInt
会将数字转换成整形,例如:
let a = '123' console .log(typeof (parseInt (a)) + '-' + parseInt (a))
那么,对于下面这个情况呢?
let a = true console .log(typeof (parseInt (a)) + '-' + parseInt (a))
查看答案
答案是 number-NaN
,因为 parseInt
不会对非数字进行取整操作。
对于 true
、false
、undefined
、null
、NaN
都会输出 NaN
。如果是小数,不会四舍五入,而是直接舍弃小数点后的位数。
parseInt()更多用法:
parseInt()
函数可解析一个字符串,并返回一个整数。
参数
描述
string
必需。要被解析的字符串。
radix
可选。表示要解析的数字的基数。该值介于 2 ~ 36 之间。如果省略该参数或其值为 0,则数字将以 10 为基础来解析。如果它以 “0x” 或 “0X” 开头,将以 16 为基数。如果该参数小于 2 或者大于 36,则 parseInt() 将返回 NaN。
直接看下面例子,应该就能明白了
parseInt ("10" ); parseInt ("19" ,10 ); parseInt ("11" ,2 ); parseInt ("17" ,8 ); parseInt ("1f" ,16 ); parseInt ("010" );
最后,再来两个输出,结束我们的 parseInt
的介绍
console .log(parseInt ('abc123' )) console .log(parseInt ('123abc456' ))
parseFloat()用法(保留小数位数):
console .log(parseFloat ('3.1415926' ).toFixed(2 )) console .log(parseFloat ('3.1415926' ).toFixed(4 ))
上述我们进行了字符串到整数的转换,下面我们介绍整数到字符串的转换:
由于还算比较简单,这里我就直接上代码了。
let str = 123 + '' let str1 = 123 str1 = str1.toString() console .log(typeof (str)) console .log(typeof (str1))
比较特殊的是,下面会报错,因为 null
和 undefined
会报错。
let str = null let str1 = undefined str = str.toString() str1 = str1.toString() console .log(typeof (str)) console .log(typeof (str1))
另外, toString()
也能传一个基数,可以进行进制转换。
let num = 10 console .log(num.toString(2 ))
接下来,我们探讨一下 Boolean
,直接看下面几个输出就好了,记住下面几个 false
就行,其它就为 true
了。
console .log(Boolean (1 )) console .log(Boolean (null )) console .log(Boolean (undefined )) console .log(Boolean (NaN )) console .log(Boolean ("" )) console .log(Boolean (0 ))
隐式转换 开门见山,还是先看一道题,注意隐式转换标题!
let a = '123' a++ console .log(a)
查看答案
答案是 124
,因为看到 a++
操作,会有一个 Number(a)
的操作,然后数字自加,得到 124
。
再来看一道吧:
let a = 'a' + 1 console .log(a)
查看答案
答案是 a1
,这应该好理解,此时将 1
进行 String(1)
的操作,然后进行字符串拼接。
接下来,再给几个样例,小伙伴们看一看就好了。(*^▽^*)
let a = '3' > 2 console .log(a) let a = 1 == '1' console .log(a)
下面来一道经典题,会输出什么?
let a = 1 === '1' console .log(a)
查看答案
答案是 false
,因为 ===
是全等于,不会进行隐式转换,而上面 ==
是会进行隐式转换的。
继续,会输出什么?
let a = NaN == NaN console .log(a)
查看答案
答案是 false
,因为和谁都不相等,连自己也不相等。
继续,会输出什么?难倒你为止!
let a = 4 > 3 > 2 let b = 2 > 1 == 1 console .log(a,b)
查看答案
答案是 false
和 true
,因为 4 > 3
会返回 true
,然后再隐式转换为 1
,下面类似。
let a = undefined > 0 let b = undefined == 0 let c = undefined < 0 let d = undefined == undefined let e = undefined == null let f = null > 0 let g = null == 0 let h = null < 0 console .log(a,b,c,d,e,f,g,h)
查看答案
答案是 false false false true true false false false
,因为 null
和 undefined
只和它们比才相等。
补充:
显示转换那里,我们介绍了字符串转整数的几种方式,还例举了很多题目,现在再补充一个:
let a = '123' console .log(typeof (+ a) + '-' + +a)
有了前面转换知识了,那就看看下面会输出什么吧
let a = NaN let b = '123' let c = 'a' let d = null let e = undefined console .log(isNaN (a))console .log(isNaN (b))console .log(isNaN (c))console .log(isNaN (d))console .log(isNaN (e))
最后的最后,我们回到开头让你想不明白的问题,现在再来做一下吧
console .log(typeof (1 -'1' ))console .log(typeof ('1' - '1' ))
函数基础 函数种类、字面量 开门见山,下面代码会输出什么,不知道小伙伴平常有咩有想过这类问题
var test = function test1 ( ) { var a = 1 ; console .log(a); } test() test1()
查看答案
答案是 1
和 报错
,报错信息如下:
这是为什么呢?实际上,我们申明了一个变量 test
,把这个函数 test1
赋给它的时候,就已经把这个变量赋予了函数 的功能,调用 test()
方法当然能够正常运行,输出 1
。而对于表达式赋值,会自动忽略后面的函数名称,也就是说写与不写并不影响 test()
方法的执行。不信,看看下面代码会输出什么?
var test = function ( ) { var a = 1 ; console .log(a); } test()
那这样,是不是说 test1
完全没作用了,那写着干嘛,还多几个字符。当然不是! ,test1
在函数体内部是可见的,而在外部却不可见,通过这样,我们就可以实现递归操作
var test = function ( ) { var a = 1 ; console .log(a); }
而对于上述代码,后面函数体没有名字,我们称之为 匿名函数 ,是不是有点印象了,原来就是这东西,哟西~
而通过这种方式赋值的表达式,我们称之为 匿名函数表达式 ,也称为 函数字面量 ,这些专有名词一出来,瞬间觉得有点逼格了有没有!
字面量这种东西,简单来说就是数据,例如下述,有数字字面量,字符串字面量,数组字面量
等等。简单理解就是,对于赋值的过程,右边的数据就是字面量。
var a = 10 var b = '111' var c = [1 ,2 ,3 ]
形参实参映射 补充:如何获取形参和实参对应的长度?
function test (a,b ) { console .log(test.length) console .log(arguments .length) } test(1 ,2 ,3 )
我想小伙伴们应该清楚实参和形参是什么玩意,但是我们可以更改实参的值吗?例如下述代码,会输出什么呢?
function test (a, b ) { a =3 ; console .log(arguments [0 ]); } test(1 ,2 );
刚刚那题只是简单热个身,继续下一题吧,我们可以改变 b
的值吗?(提示:注意我并没有传对应实参哦~)
function test (a, b ) { b = 3 ; console .log(arguments [1 ]); } test(1 );
查看答案
答案是 undefined
,因此对于上一题表述,要修改一下:对于实参传递过来确定的值,我们是可以进行修改的,而如果实参并没有传递值过来,我们是不能进行修改的。这就是形参和实参的映射关系 。
简单解释一下形参和实参的映射关系 ,其实实参 和形参 不能说是一类的,看上述代码,我们可以通过 arguments
来获取我们的实参,可以看做是一个数组里面的某一项值,而数组是存放堆内存 的,而对应我们形参其实是存放在栈内存 的,它们之间会有一个映射关系,并且是一对一对应的,上述我们实参没有对b
进行赋值,尽管修改了形参,但改变不了我们的 arguments[1]
就是这个道理。(没有建立一对一映射关系)。
让人费解的 GO 和 AO 作用域引入 再来一道引申题,为后续内容做铺垫。下面 a b c
分别会输出什么?
a = 1 ; function test1 ( ) { var b = 2 ; console .log(a) function test2 ( ) { var c = 3 console .log(b); } test2(); console .log(c); } test1();
查看答案
答案是 1 2 报错
,这就牵扯到 scope
问题了,简单理解就是函数内部能访问外面的变量,而函数外面却不能访问内部的变量,也就是闭包 问题。(这个后文会提到)
函数默认参数 如果实参没有赋值,那么形参怎样设置默认参数呢?说到默认参数,我想你应该会想到如下代码:
function test (a = 1 , b = 1 ) { console .log(a) console .log(b) } test()
好的,上述问题算是开胃小菜,我们继续,我如果给 a
设定默认值,而 b
通过实参传递过来呢?可以实现吗?之前没有传参的话,不是默认打印 undefined
吗,那我现在给 a
传递一个 undefined
,是不是就会定为默认值。
function test (a = 1 , b ) { console .log(a) console .log(b) } test(undefined , 2 )
查看答案
答案是可以的,上述代码输出结果为 1 2
。简单解释一下,在之前我们将了形参实参是有一个映射关系,对于堆内存 arguments
里面,如果给了 undefined
,那么就会去栈内存形参里面找,如果不为 undefined
,则会设置形参的默认值。(其实这是 es6
的语法了)
那么,可以用es5
的方式实现一下吗?(当然可以,见代码)
function test (a, b ) { a = arguments [0 ] || 1 b = arguments [1 ] || 1 console .log(a) console .log(b) } test(undefined , 2 )
预编译 预编译总结一下就是如下几点:
下面这两段代码,熟悉的同学一下就明白了,面试常考 的经典题!
test() function test ( ) { console .log(1 ) }
console .log(a);var a = 1 ;
这就扯到了函数声明提升和变量提升相关的问题了。这里总结整理一下:
函数声明会进行整体的提升,而变量只有声明进行了提升,赋值不会提升
关于变量那块,举例下面代码,其实是有两个步骤:第一,进行变量声明 var a;
第二,进行赋值操作,a = 1;
好了,这里我就认为你已经理解了提升相关的知识了,我们来看一道题吧:
console .log(a)function a (a ) { var a = 10 ; var a = function ( ) { } } var a = 1 ;
查看答案
答案是[Function: a]
(即函数 a
),这里可能一下也想不明白,我们先来讲一下知识,再来解决这个问题吧。
讲解暗示全局变量 imply golbal variable
下面代码会输出 1
,比较简单,就直接说答案了。实际上这里就暗示全局变量了,因为全局有一个 window
对象 ,下面代码也可以这样表示 window.a = 1
,所有权归 window
。
这又让我想到了下述代码,b
能打印出来吗?还是会报错?
function test ( ) { var a = b = 1 ; } test(); console .log(b)
答案是 1
,能打印出来 b
,这种写法就是经典的变量泄露 问题。而 a
没办法打印,因为它是test
函数的局部变量,相当于闭包内的变量,外层没办法访问闭包内的变量。
继续,这次加大一点难度。下面代码又分别输出什么呢?
function test (a ) { console .log(a); var a = 1 ; console .log(a); function a ( ) {} console .log(a); var b = function ( ) {} console .log(b) function d ( ) {} } test(2 );
查看答案
放答案之前,先总结一下知识点,函数在执行之前,会生成一个 AO
(activation object
,也称为活动对象 或者函数上下文 )这个AO
会按照如下形式创建:
第一步:寻找函数里的形参和变量声明
第二步:将实参的参数值赋值给形参
第三步:寻找函数声明,然后赋值函数体
第四步:执行
下述代码是预编译(即函数执行之前AO
对象的结果):
AO = { a: undefined -> 2 -> function a ( ) ( ) b : undefined d : function d ( ) {} }
下述代码是执行函数之后AO
对象的结果:
AO = { a: undefined -> 2 -> function a ( ) ( ) -> 1 b : undefined -> function ( ) {} d : function d ( ) {} }
直接看着上述执行完后的AO
,对于第一个输出,由与在赋值 a
为 1
之前,所以我们直接打印 Function: a
,对于第二个输出,对 a
变量进行了赋值为 1
的操作,所以取 AO
对象中的最后一个 1
,打印 1,对于第三个输出,没有其它赋值操作了,直接输出 1 ,对于 b
,输出 Function: b
,这个不需要太多解释。
最后,给出例题的答案:
[Function : a] 1 1 [Function : b]
下面来一道例题,巩固一下,小伙伴们可以自己拿过去做一遍。
function test (a,b ) { console .log(a); c = 0 ; var c; a = 5 ; b = 6 ; console .log(b); function b ( ) {} function d ( ) {} console .log(b) } test(1 )
接下来,换类型了哈,下面会输出什么呢?
var a = 1 ;function a ( ) { console .log(2 ) } console .log(a)
查看答案
答案是 1
,不用过多解释。
举上述例子是为了引出下文,因为这已经不是函数内部的问题了,是全局 的问题了。JS
执行之前,会产生一个叫做 GO
的东西,也称为 global object
(全局上下文),也会按照如下步骤进行创建:
第一步:寻找变量声明
第二步:寻找函数声明
第三部:执行
GO = { a: undefined -> function a ( ) {} -> 1 }
所以,答案为 1
。实际 GO
,就是 window
,window
存储过程就是这样的!
好的,继续下一题,又会输出什么?
console .log(a,b)function a ( ) {}var b = function ( ) {}
查看答案
答案是 [Function: a] undefined
,这个答案也许和你想的完全相反,有没有?按照 GO
来解决这道题吧!
GO = { a: undefined -> function ( ) {} b: undefined }
这是因为在 console.log(a,b)
在 var b = function() {}
之前,此时还没有对 b
变量进行赋值,而 a
通过函数声明整体提升,会输出 funtion a() {}
,如果上述代码改成如下代码 b
变量就会有对应值啦。
var b = function ( ) {}console .log(a,b)function a ( ) {}
回到我们开头的一道题,现在解决它应该没问题了,会输出什么呢?
console .log(a)function a (a ) { var a = 10 ; var a = function ( ) { } } var a = 1 ;
由于并没有执行函数,我们不用 AO
,改用 GO
,这道题其实和上一道题差不多,因为我们 console.log(a)
在赋值操作之前,因此我们不会有 1
的结果,所以我们会打印 function a(){}
GO = { a: undefined -> function a ( ) {} }
加大难度,看这一题,结合上述所说 AO
和 GO
来做,试一试!
var b = 3 ;console .log(a);function a (a ) { console .log(a); var a = 2 ; console .log(a); function a ( ) { var b = 5 ; console .log(b); } } a(1 );
查看答案
答案:
[Function : a] [Function : a] 2 5
我觉得能把这道题完整做出来, AO
和 GO
基本没啥问题了,现在来解释一波:
直接上 GO
、AO
,后续不再过多解释了,如果还有不明白的小伙伴,建议往上再复习一遍,相信可以独立解决这道题的 (*^▽^*)
GO = { b: undefined -> 3 a: undefined -> function a ( ) {} } AO = { a: undefined -> 1 -> function a ( ) {} -> 2 b: undefined -> 5 }
不知道上一道题做的咋样,这道题我们找一点自信,看看会输出什么?
a = 1 ; function test ( ) { console .log(a); a = 2 ; console .log(a); var a =3 ; console .log(a); } test(); var a;
查看答案
答案是 undefined 2 3
,还是老规矩,直接上 GO
和 AO
GO = { a: undefined -> 1 test: function ( ) {} } AO = { a: undefined -> 2 -> 3 }
此时,可能就有小部分长的比较帅的小伙伴就要问了,为啥我第一个 a
不是打印 1
呢,GO
里面不是可以取嘛? 确实,但是你漏掉了前提, AO
里面此时存在变量 a
,就不会去GO
里面找了,这里容易掉坑,必须注意!
function test ( ) { console .log(b); if (a){ var b = 2 ; } c = 3 ; console .log(c); } var a;test(); a = 1 ; console .log(a);
查看答案
答案: undefined 3 1
,直接上 GO
和 AO
,解释一个地方,为啥 b
要放在 AO
里面,因为 js
在预编译时,不会管你什么条件执不执行,只看你是否进行了申明,简单来说,只要在函数内声明了,那么我们就放入 AO
里面,否则放在全局的GO
里面。
GO = { a: undefined -> 1 test: fucntion ( ) {...} c: undefined -> 3 } AO = { b: undefined }
最后,我们来几道题找点自信,结束这让人费解的 GO
和 AO
吧,答案里我就只给 GO
和 AO
啦,还不会方法的,建议多看上文内容,我就不作解释了。
function test ( ) { return a; a = 1 ; function a ( ) {} var a = 2 ; } console .log(test());
查看答案
答案: [Function: a]
GO = { test: function ( ) {...} } AO = { a: undefined -> function a ( ) {} -> 1 -> 2 }
继续,下一题:
function test ( ) { a = 1 ; function a ( ) {} var a = 2 ; return a; } console .log(test())
查看答案
答案: 2
GO = { test: function ( ) {...} } AO = { a: undefined -> function a ( ) {} -> 1 -> 2 }
加大难度,注意头发…
a = 1 ; function test (e ) { function e ( ) {} arguments [0 ] = 2 ; console .log(e); if (a){ var b = 3 ; } var c; a = 4 ; var a; console .log(b); f = 5 ; console .log(c); console .log(a); } var a;test(1 ); console .log(a);console .log(f);
查看答案
答案是 2 undefined undefined 4 1 5
GO = { a: undefined -> 1 test: function ( ) {...} f: undefined -> 5 } AO = { e: undefined -> 1 -> function e ( ) {} -> 2 b: undefined a: undefined -> 4 c: undefined }
下面来一点加餐,我想小伙伴们一定是不满足于上述 “简单” 的输出题的,下面来几道经典笔试题:
第一题,下面会输出什么?
var a = false + 1 ;console .log(a);
var b = false == 1 ;console .log(b)
可能前两道题比较简单,看看下面这份代码吧:
if (typeof (a) && (-true ) + (+undefined ) + '' ){ console .log(1 ); }else { console .log(0 ); }
查看答案
答案是 1
,也是存在隐式类型转换,并且结合了上文的知识,一道非常不错的题。
console .log(-true ) console .log(+undefined ) if (typeof (a) && (-true ) + (+undefined ) + '' ){ console .log(1 ); }else { console .log(0 ); } console .log(typeof (-'123' )) console .log((-true ) + (+undefined ) + '' )
上面那道题解决了的话,下面这道题就是小试牛刀了,看看会输出什么?
if (1 +5 *'3' === 16 ){ console .log(1 ); }else { console .log(0 ); }
查看答案
答案是 1
,虽然 ===
不能进行隐式转换,但是没说左边或右边单独不能进行隐式转换呐。
继续,
console .log(!!' ' + !!'' - !!false || '未通过' );
查看答案
答案:1
,因为左边 1+0-0
得到 0
,所以不会走右边。
window .a || (window .a = '1' )console .log(window .a)
查看答案
答案:1
,因为这里有 ()
括号,优先级最高,首先会对 window.a
赋值 1
,然后判断左边 window.a
为真,直接走下面输出函数。 而如果说先走左边window.a
判断为 false
,然后走右边进行赋值为 1
,最后打印 1
,这样回答的话就错啦,没有考虑括号优先级 。
作用域、作用域链 有了上文 AO
和 GO
的知识,作用域和作用域链就好理解了。
function test (a,b ) {} console .log(test.name) console .log(test.length)
从上述代码我们可以知道,函数也是一种对象类型,它也有一些属性,比如 test.name
,test.length
,test.prototype
。而对象,有些属性是我们无法访问的,这些属性是JS
引擎`内部固有的隐式属性。可以理解为内部的私有属性。引出 [ [scope] ] ,总结整理如下:
函数创建时,生成的一个 JS
内部的隐式属性
函数存储作用域链的容器,作用域链存放的就是 AO
(函数的执行期上下文)、GO
(全局的执行期上下文),函数执行完成以后,AO
是要销毁的,也就是说 AO
是一个即时 的存储容器。
推荐观看视频:小野老师讲解作用域、作用域链
总结一下关系:
函数在被定义的时候,已经存在作用域了,已经形成作用域链了,已经创建了 GO
,而当函数在执行的时候,才会生成自己的 AO
。
闭包基础 function test1 ( ) { function test2 ( ) { var b = 2 ; console .log(a); } var a = 1 ; return test2; } var c = 3 ;var test3 = test1();test3();
总结如下:
当内部函数被返回到外部并保存时,一定会产生闭包。闭包会产生原来的作用域链不释放,过度的闭包可能会导致内存泄露或者加载过慢。
接着,我们来一道简单题,热个身吧,下面会输出什么,注意,这里我通过数组的方式,抛出了两个函数。
function test ( ) { var n = 100 ; function add ( ) { n++; console .log(n); } function reduce ( ) { n--; console .log(n); } return [add,reduce]; } var arr = test();arr[0 ](); arr[1 ]();
查看答案
答案是 101 100
,因为 test
函数在执行的时候, add
和 reduce
函数已经被定义,此时作用域环境就是 test
函数的 AO
,而这个 AO
里面就会创建一个 n:100
,和对应函数,如下:
test函数执行时: AO = { n: undefined ->100 add : function ( ) {...} reduce: function ( ) {...} }
当我们抛出 add
和 reduce
函数时,此时 AO
并不会销毁。只是把线剪断了,因为 add
和 reduce
作用域链还连着的。都会一起返回出去,然后给到外部全局变量使用,当我们操作 add
函数时,就能访问原本 test
函数创建的 AO
,将 n++
得到 101
,然后操作 reduce
函数将此时 n=101
进行 --
操作得到 100
,就是我们的最终答案了。
下面举一个比较好玩的例子,面包售卖进货管理系统。和上述代码思想一致,都是利用了闭包的思想。
function breadMgr (num ) { var breadNum = num || 10 ; function supply ( ) { breadNum += 10 ; console .log(breadNum); } function sale ( ) { breadNum--; console .log(breadNum); } return [supply,sale]; } var breadMgr = breadMgr(50 );breadMgr[0 ](); breadMgr[1 ](); breadMgr[1 ](); breadMgr[1 ]();
查看答案
答案是 60 59 58 57
,就不多解释了,和上一题代码几乎一致。
同样,我们也可以通过对象的形式,一起抛出去,见下面例子:
function sunSched ( ) { var sunSched = '' ; var operation = { setSched: function (thing ) { sunSched = thing; }, showSched: function ( ) { console .log("My Schedule on sunday is " + sunSched); } } return operation; } var sunSched = sunSched();sunSched.setSched('studying' ); sunSched.showSched();
立即执行函数 观察如下代码,我们发现函数都是放在 GO
里的,它不会自动释放,也就是说放在 GO
里的函数我们可以直接调用,不用重新定义新的函数,想调用随时调用。
function test1 ( ) { console .log(1 ); } function test2 ( ) { console .log(2 ); } test2(); function test3 ( ) { test1(); } test3();
在上述我们讨论了闭包,这一块我们来探讨立即执行函数,简单来说就是自动执行,执行完后立即释放。
IIFE - immediately - invoked - function - expression
立即执行函数有一个功能性的说法就是初始化函数 。
下面,我们简单看一个立即执行函数的例子吧,重点学习语法,下面规范也是 W3C
建议:
(function (a,b ) { console .log(a+b); }(1 ,2 ))
下面写法也是可以的,但不是 W3C
建议的写法。
或者 (function (a,b ) { console .log(a+b); })(1 ,2 )
有的时候,我们想要拿到函数返回值,那么立即执行函数每次执行完都会立即释放,直接 return
我们是拿不到的,该如何是好?
原来,立即执行函数也是有返回值的,我们可以将 return
的结果直接返回给一个变量存起来,如下:
var res = (function (a,b ) { return a + b; }(1 ,2 )) console .log(res);
总结一下:()
包括的内容相当于表达式 。
比较下面结果,思考一下:
var test1 = function ( ) { console .log(1 ); }() (function (a,b ) { console .log(a+b); }(1 ,2 )) function test3 ( ) { console .log(3 ); }();
得到的结果:一定是表达式才能被执行符号执行 。
证明立即自执行函数执行完后立即释放:
var test = function ( ) { console .log(1 ); } console .log(test); var test1 = function ( ) { console .log(1 ); }() console .log(test1);
打印 1 undefined
,因为立即自执行函数执行完后,立即销毁,所以 test1
接收不到函数体。
补充:函数声明变成表达式的方法: + - ! && ||
举个例子:
+ function test ( ) { console .log(1 ); }()
下面来一道经典面试题,将数组每一位都存放一个匿名函数,最后遍历执行时会打印什么?
function test ( ) { var arr = []; for (var i=0 ;i<10 ;i++){ arr[i] = function ( ) { console .log(i); } } return arr; } var myArr = test();for (var i=0 ;i<10 ;i++){ myArr[i](); }
查看答案
答案是 10个10
,其实是在 test
函数里面返回了 10
个匿名函数,而这 10
个函数还没有执行,和上文闭包基础讲的例题一样,形成了一个闭包。原本 test
的 AO
中是有变量 i
的,经过for循环,使得变量 i
变为 10了,而test
的 AO
只是剪掉了,而那十个匿名函数的作用域链还连着的。所以后面我们一个个执行的时候,拿到的还是 test
里面的 AO
的 i
值。
那么,对于上述代码,如果想要打印0-9
,要怎么做呢? 根据当前标题,就可知道可以使用立即执行函数。
function test ( ) { for (var i = 0 ; i < 10 ; i++) { (function ( ) { console .log(i); }()) } } test();
function test ( ) { var arr = []; for (var i = 0 ; i < 10 ; i++) { (function (j ) { arr[j] = function ( ) { console .log(j); } }(i)) } return arr; } var myArr = test();for (var i = 0 ; i < 10 ; i++) { myArr[i](); }
接下来,再来一道面试题,看看会返回什么?
var fn = ( function test1 ( ) { return 1 ; }, function test2 ( ) { return '2' ; } )(); console .log(typeof (fn));
查看答案
答案是返回 string
,因为括号里的逗号运算,会返回最后一个,如下,会返回 2
。
因此括号里面放两个函数,以逗号分隔,就会立即执行 test2
,然后 typeof
之后,就会返回 string
了。
继续,我们再来一道面试题:
var a = 10 ;if (function b ( ) {}){ a += typeof (b); } console .log(a);
查看答案
答案是 10undefined
,解释一下,首先 if
判断是要执行的,然后 (function b(){})
这个,是不是就是一个表达式了,然后忽略名称, function(){}
,到下面的时候函数 b
已经不存在了都。