基础

typeof 知多少

前置知识:

typeof 判断数据类型会输出如下这些:

number string boolean object undefined function

需要注意的:

console.log(typeof(null)) // object
console.log(typeof(undefined)) // undefined

开门见山,下面输出会是什么?

console.log(typeof(1 -'1'))

number,你有没有中招呢?

console.log(typeof('1' -'1'))

还是number,诶?怎么肥事?为啥不是 string 呢?

上面两个题,答的怎么样呢?后续讲解显示/隐式类型转换时会给大家解答,接着继续 GO!

下面代码会是怎样的结果?

console.log(a)

可能你会说输出 undefined,但你又入坑了…答案是会报错

好的,接下来继续,我再来变一下,下面又会输出什么?还是会报错吗?

console.log(typeof(a))

答案是 undefined ,又中招了? 这里 a 没声明,判断类型当然是 undefined呐!

go on ! 那么下面这个代码我先提示一下,有坑,接上一题,我们得到了 undefined,下面这题我们又套了一层 typeof ,按照开头提到的注意点,会是 undefined吗?

console.log(typeof(typeof(a)))

答案是 string,在下一题进行解释。

下一题,这个应该是输出?

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)) // number-NaN
var a = '1a'
console.log(typeof(Number(a)) + '-' + Number(a))// number-NaN
var a = 3.14
console.log(typeof(Number(a)) + '-' + Number(a))// number-3.14
console.log(typeof(NaN)) // number

接下来,我们一起来探讨一下 parseInt

还是先来个简单的热热身,我们知道 parseInt会将数字转换成整形,例如:

let a = '123'
console.log(typeof(parseInt(a)) + '-' + parseInt(a)) //number-123

那么,对于下面这个情况呢?

let a = true
console.log(typeof(parseInt(a)) + '-' + parseInt(a))

答案是 number-NaN,因为 parseInt不会对非数字进行取整操作。

对于 truefalseundefinednullNaN 都会输出 NaN。如果是小数,不会四舍五入,而是直接舍弃小数点后的位数。

parseInt()更多用法:

parseInt() 函数可解析一个字符串,并返回一个整数。

参数 描述
string 必需。要被解析的字符串。
radix 可选。表示要解析的数字的基数。该值介于 2 ~ 36 之间。如果省略该参数或其值为 0,则数字将以 10 为基础来解析。如果它以 “0x” 或 “0X” 开头,将以 16 为基数。如果该参数小于 2 或者大于 36,则 parseInt() 将返回 NaN。

直接看下面例子,应该就能明白了

parseInt("10");			//返回 10
parseInt("19",10); //返回 19 (10+9)
parseInt("11",2); //返回 3 (2+1)
parseInt("17",8); //返回 15 (8+7)
parseInt("1f",16); //返回 31 (16+15)
parseInt("010"); //未定:返回 10 或 8

最后,再来两个输出,结束我们的 parseInt 的介绍

console.log(parseInt('abc123')) // NaN
console.log(parseInt('123abc456')) // 123

parseFloat()用法(保留小数位数):

console.log(parseFloat('3.1415926').toFixed(2)) // 3.14
console.log(parseFloat('3.1415926').toFixed(4)) // 3.1416

上述我们进行了字符串到整数的转换,下面我们介绍整数到字符串的转换:

由于还算比较简单,这里我就直接上代码了。

let str = 123 + ''
let str1 = 123
str1 = str1.toString()
console.log(typeof(str)) //string
console.log(typeof(str1)) // string

比较特殊的是,下面会报错,因为 nullundefined 会报错。

let str = null
let str1 = undefined
str = str.toString()
str1 = str1.toString()
console.log(typeof(str)) // error
console.log(typeof(str1)) // error

另外, toString()也能传一个基数,可以进行进制转换。

let num = 10
console.log(num.toString(2)) // 1010

接下来,我们探讨一下 Boolean,直接看下面几个输出就好了,记住下面几个 false就行,其它就为 true 了。

console.log(Boolean(1)) // true
console.log(Boolean(null)) // false
console.log(Boolean(undefined)) // false
console.log(Boolean(NaN)) // false
console.log(Boolean("")) // false
console.log(Boolean(0)) // false

隐式转换

开门见山,还是先看一道题,注意隐式转换标题!

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) // true
let a = 1 == '1'
console.log(a) // true

下面来一道经典题,会输出什么?

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)

答案是 falsetrue ,因为 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,因为 nullundefined 只和它们比才相等。

补充:

显示转换那里,我们介绍了字符串转整数的几种方式,还例举了很多题目,现在再补充一个:

let a = '123'
console.log(typeof(+ a) + '-' + +a) // number-123

有了前面转换知识了,那就看看下面会输出什么吧

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))

答案

true
false
true
false
true

最后的最后,我们回到开头让你想不明白的问题,现在再来做一下吧

console.log(typeof(1 -'1'))
console.log(typeof('1' - '1'))

答案都是 number,不解释了哈


函数基础

函数种类、字面量

开门见山,下面代码会输出什么,不知道小伙伴平常有咩有想过这类问题

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() // 1

那这样,是不是说 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) // 形参的长度 2
console.log(arguments.length) // 实参的长度 3
}
test(1,2,3)

我想小伙伴们应该清楚实参和形参是什么玩意,但是我们可以更改实参的值吗?例如下述代码,会输出什么呢?

function test(a, b) {
a =3;
console.log(arguments[0]);
}
test(1,2);

答案是 3,我们可以修改实参的值。

刚刚那题只是简单热个身,继续下一题吧,我们可以改变 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() // 1 1

好的,上述问题算是开胃小菜,我们继续,我如果给 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;

var 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

a = 1;
console.log(a);
这又让我想到了下述代码,`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);

放答案之前,先总结一下知识点,函数在执行之前,会生成一个 AOactivation 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); // 1
c = 0;
var c;
a = 5;
b = 6;
console.log(b); // 6
function b(){}
function d(){}
console.log(b) // 6
}
test(1)
/*
AO = {
a: undefined -> 1 -> 5
b: undefined -> function b(){} -> 6
c: undefined -> 0
d: function d(){}
}
*/

接下来,换类型了哈,下面会输出什么呢?

var a = 1;
function a () {
console.log(2)
}
console.log(a)

答案是 1,不用过多解释。

举上述例子是为了引出下文,因为这已经不是函数内部的问题了,是全局的问题了。JS执行之前,会产生一个叫做 GO 的东西,也称为 global object(全局上下文),也会按照如下步骤进行创建:

  • 第一步:寻找变量声明
  • 第二步:寻找函数声明
  • 第三部:执行
GO = {
a: undefined -> function a() {} -> 1
}

所以,答案为 1。实际 GO,就是 windowwindow存储过程就是这样的!

好的,继续下一题,又会输出什么?

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(){}
}

加大难度,看这一题,结合上述所说 AOGO 来做,试一试!

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` 基本没啥问题了,现在来解释一波:

直接上 GOAO,后续不再过多解释了,如果还有不明白的小伙伴,建议往上再复习一遍,相信可以独立解决这道题的 (*^▽^*)

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,还是老规矩,直接上 GOAO

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,直接上 GOAO,解释一个地方,为啥 b 要放在 AO 里面,因为 js在预编译时,不会管你什么条件执不执行,只看你是否进行了申明,简单来说,只要在函数内声明了,那么我们就放入 AO里面,否则放在全局的GO里面。

GO = {
a: undefined -> 1
test: fucntion(){...}
c: undefined -> 3
}
AO = {
b: undefined
}

最后,我们来几道题找点自信,结束这让人费解的 GOAO吧,答案里我就只给 GOAO啦,还不会方法的,建议多看上文内容,我就不作解释了。

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);

答案是 1,存在隐式类型转换。

var b = false == 1;
console.log(b)

答案是 false,存在隐式类型转换。

可能前两道题比较简单,看看下面这份代码吧:

if(typeof(a) && (-true) + (+undefined) + ''){
console.log(1);
}else{
console.log(0);
}

答案是 1,也是存在隐式类型转换,并且结合了上文的知识,一道非常不错的题。

console.log(-true) // -1
console.log(+undefined) // NaN
if(typeof(a) && (-true) + (+undefined) + ''){
console.log(1);
}else{
console.log(0);
}
console.log(typeof(-'123')) // number
console.log((-true) + (+undefined) + '') // NaN

上面那道题解决了的话,下面这道题就是小试牛刀了,看看会输出什么?

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,这样回答的话就错啦,没有考虑括号优先级

作用域、作用域链

有了上文 AOGO 的知识,作用域和作用域链就好理解了。

function test(a,b) {
}
console.log(test.name) // test
console.log(test.length) // 2

从上述代码我们可以知道,函数也是一种对象类型,它也有一些属性,比如 test.nametest.lengthtest.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函数在执行的时候, addreduce 函数已经被定义,此时作用域环境就是 test函数的 AO,而这个 AO里面就会创建一个 n:100,和对应函数,如下:

test函数执行时:
AO = {
n: undefined ->100
add : function(){...}
reduce: function(){...}
}

当我们抛出 addreduce 函数时,此时 AO并不会销毁。只是把线剪断了,因为 addreduce 作用域链还连着的。都会一起返回出去,然后给到外部全局变量使用,当我们操作 add函数时,就能访问原本 test函数创建的 AO,将 n++得到 101,然后操作 reduce 函数将此时 n=101进行 --操作得到 100,就是我们的最终答案了。


下面举一个比较好玩的例子,面包售卖进货管理系统。和上述代码思想一致,都是利用了闭包的思想。

// 面包售卖进货管理
function breadMgr(num){
var breadNum = num || 10;
// 进货面包,每次进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(); // My Schedule on sunday is studying

立即执行函数

观察如下代码,我们发现函数都是放在 GO 里的,它不会自动释放,也就是说放在 GO里的函数我们可以直接调用,不用重新定义新的函数,想调用随时调用。

function test1(){
console.log(1);
}
function test2(){
console.log(2);
}
test2(); // 2
function test3(){
test1(); // 1
}
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); // 3

总结一下:()包括的内容相当于表达式

比较下面结果,思考一下:

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); // [Function: 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个函数还没有执行,和上文闭包基础讲的例题一样,形成了一个闭包。原本 testAO 中是有变量 i的,经过for循环,使得变量 i 变为 10了,而testAO只是剪掉了,而那十个匿名函数的作用域链还连着的。所以后面我们一个个执行的时候,拿到的还是 test里面的 AOi值。

那么,对于上述代码,如果想要打印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

console.log((1,2)); // 2

因此括号里面放两个函数,以逗号分隔,就会立即执行 test2,然后 typeof 之后,就会返回 string了。

继续,我们再来一道面试题:

var a = 10;
if(function b(){}){
a += typeof(b);
}
console.log(a);

答案是 10undefined ,解释一下,首先 if 判断是要执行的,然后 (function b(){})这个,是不是就是一个表达式了,然后忽略名称, function(){},到下面的时候函数 b已经不存在了都。