00:01
大家好,我是上硅谷H5学科讲师,我叫闫志勇。在陈序员节日到来之际,由我来给大家分享几道面试题,今天我们主要分享的内容为必包好,在我的这个web里边,我创建一个index.html,那么在这里边呢,外接了一个index.gs的文件,那我们主要是在这个里面来讲述什么是必包,也就我们本次主要分享的内容呢,是必包好,那这个时候呢,我们先来谈一谈。到底B包是个什么东西,那也是我们先来理解一下什么是B包好,那么说到B包这个东西呢,众说纷纭,也就是说你你去网上看的话,也有很多的说法,但是呢,我发现并不是每个人能把B包这个东西给讲清楚的。首先我们得说B包,B包它呢是一个密闭的容器,那么该容器呢,它呢,类似于比如说我们的set。
01:12
还有map容器,这个set和map是在我们G里边,是ES6提出来的一个新的概念,那其实在Java或者Python里边早就有这个容器了,早就有这个数据结构,那么他们这些容器主要是用来去存储数据的。存储数据的,那既然说到数据容器,其实有很多种,比如说我们熟知的对象数组,或者说是set map容器。那为什么又要区分对象数组呢?是因为他们存储数据的结构是不一样的,那在这里呢,我们可以说B包是一个对象,那么当我说到这儿,哎,有的同学就能想到它存放数据的格式是什么样的呢?是键值对的形式,也是key value的形式。那么以上是我们对B包的一个理解。
02:13
刚刚在我提到什么是B包的时候,可能,哎有的同学呢,就想函数嵌套啊,内部函数引用外部函数的局部变量啊,那这些呢,其实是什么,是必包形成的一个条件,哎,其实就两个,第一个。就是我们说的函数嵌套,第二个呢,就是说内部函数引用外部函数的局部变量。来,我们写一个简单的包,首先呢,我先定义一个函数fun,然后呢,在它的函数内部我再写一个函数f fun2,此时呢,我们已经形成了第一个条件,就是函数嵌套,接下来我们再去实现第二个条件,内部函数引用外部函数的局部变量,那么这个比较简单,我在外部函数呢,先去定一个变量,比如说我就叫他Co。
03:12
唯一好,现在有一个变量,Co为一内部函数,想要引用外部函数的变呃变量的话也比较简单,我直接做一个输出。当然了,我外部的函数是不是肯定要调用啊,如果说不调用的话是没有任何意义的,好,那么我们就写一个简单的代码块来,我们来看一下它的输出。那么这个时候呢,我。从这个web里面去打开当前的浏览器,好,那这时候呢,我们来到控制台来到,哎,这个时候大家看一下在这是不是没有任何的输出啊。没有输出是对的,是因为大家看一下在这里我内部的函数是不还没有执行呢。但是我要说一点,此时已经形成了B包,怎么证明它已经形成了B包呢?那这个时候我们来到这个source里边,来找到我们刚才写的代码。
04:09
那么在这一块呢,我可以去打一个断点,大家可以看一下,我在这个第二行打一个断点,然后让我们的代码重新执行一下,好,现在来到了断点,这里大家可以看一下右侧Chrome为我们展示的一些信息,那其实当当前的情况下,Chrome这个浏览器。算是我们前端人员的一个福音,因为在这里边调试已经很方便了,哎这个时候大家可以看一下,在这一块有个有个global,那这两个是什么呢?就是我之前给大家分享的那个执行上下文里边,还记不记得有一个概念叫变量对象。诶,变量对象,那这个global对应的就是全局的变量对象,那么这个后来呢,也变成什么呢?就是我们熟知的window这个对象,而这个local指的就是我们函数里边局部的变量对象,哎,局部的变量对象,那这个时候大家可以看一下在local里边是不是有一个count对应的值是under find,为什么是under呢?注意这个local是函数内部的这一块,它为什么是安find,是因为我的断点是不是停在20行了,那现在它还没有进行这个赋值操作,所以是安范。
05:31
哎,它里边是不是还有个f fun2,哎,还有个race,那通过这的。分析呢,我们正好印证了昨天的一段话,这个时候大家想一下啊,我断点在这儿,下边的内容是不是还没有执行啊,但是我们变量对象里边已经有了一些属性,比如说count f u2以及。那么这些工作就是GS引擎在。进入到执行环境里边做的预处理工作。
06:01
好,这个呢,不是我们本节课的重点,我们主要来看一个东西叫f fun2,我点开f fun2,大家往下看,下边有一个比较特殊的属性叫cos,点开这里,哎,我们能发现这个属性,它其实可以认为是一个什么,也可以认为是一个对象,这不key value的形式什么。但是呢,你,哎,细心的同学会发现这有个零一,那其实通过这来看,我们可以认为它是一个数组。还只不过第一个东西,我们点开它。这有个东西。这是一个介。这是一个介质,对count对应的是and find,往外看,它这有个名称,这个名称呢叫closer,那这个单词翻译过来叫分闭,密闭的意思,其实在我们这里它表示的就是B包,哎,就是B包。哎,通过以上的讲解,我们能确认一个事情,就是我们现在的B包已经产生了,并且呢,大家看在这个B包里边,它保存数据的格式是不是以建值对的形式啊,所以呢,正好响应了我刚才说的一句话,B包呢,我们可以认为它是一个对象,哎,可以认为它是一个对象。
07:16
好,那这是一个最简单的比方,但是呢,这样写呢,又没有什么意义,因为啥我内部的函数是不是还没有调用呢?来可能有的同学说那就调用一次呗,好大家看一下,我现在在fu也是在外部函数的里边去调用这个FU,二来那这个时候呢,我们把断点放过去。把这个先取消了,我重新刷新一下页面。这个呢,就是我们最新的大马道。好,那这个时候我在这打一个断点,也就第二十五行来,我们让他重跑一下,重跑一下,好,那现在断点来到这儿,是不是意味着FU2马上要执行啊。
08:00
我可以让他干嘛,先进入这个函数内部,哎,进入函数内部,那这个时候大家看一下我FU2一旦执行了以后,哎,这个B包他写的在哪里。这个B包塞。它是不是和这个local global它们是平行的,而且这里边的count是不唯一啊,哎,唯一也就是说现在形成了一个壁包,并且这个B包它我们能的的确确的看到它,看到它,好,那这个时候我再往下一放这个函数FU2是不是马上要执行了。咱们让他执行完走。注意看我现在。断点相当于在这一行,那这个时候FU2就行了,来,我给大家把这个logo收起来,你会发现那个B包是不是不在了呀,不在了,诶为什么会不在呢?
09:00
那是因为FU2这个函数一执行完,它的作用是不是要销毁?销毁了以后释放内存,当然了,它里边的那个壁包也被销毁了。来,我再往下放一步的话,是不是来到FUN,这当然我再往下一放,刚刚那个logo也没有了,是因为现在F是吧,也执行完毕了。好,没有问题,FU执行完了它,它的作用是不是也要销毁啊,哎,也要销毁,那么。这个FU2,我们刚刚是不是在这个函数内部执行的,其实这也不是我们B包应用的一个场景啊,通常使用B包的话,是不会这样去用的,是不会这样去用的,好那这个时候呢,我复制一份,先把这个注释掉,来我们把这个放到这儿。那么接下来我们说,哎,这个呢,是必包的一个应用场景,它怎么去用呢?我先把那个FU2调用,哎,给它删了,其实B包真正的用法是我会把内部的函数反到外部去,那这个时候加上return以后,这个函数名是不是就没用了,因为我们return的本质就是函数,其实反应个匿名函数就可以,哎,就可以,那现在fun这个函数调用是不是有一个返回值啊,所以呢,我可以定义一个变量去接收一下它,FU2等于一个它。
10:31
好,那FU2现在接收的函数我是不是可以调用,当然了,我是不是也可以调用多次,好,那来到我们的浏览器这一块,现在呢,我先刷新一下浏览器,好,那这个时候呢,是我们最新的一个代码道来下一个断点刷新走,你好,现在f fun马上要执行,我们给他放过去。现在来到下一行FU2,这执行,我呢可以让它进入到哪儿,进入到函数内部,同样的道理,大家看一下这个B包是吧,又产生了呀,啊又产生了,那注意看啊,现在这个B包你说它在哪。
11:13
在哪儿呢?哎,可能有的同学在这就犯迷糊了,我问的这个在哪呢?是什么意思呢?你可以想一下,B包是跟着谁一块儿出来,他跟着谁一块儿没的啊,一块没有的。哎,可能有同学想到了B包的出现和销毁是不是根据我这个内部函数决定的,内部函数决定的,那当我这个内部函数里边的东西全部执行完,一旦释放内存的话,B包是不是就没有了?诶,没有了,好,那这个B包我们再看一下,它这是不引用的count正好是我外部函数的局部变量啊。外部函数的局部变量,哎,没有问题,那这就是B包的一个形成条件。
12:04
那说到这儿了,我们正好再给大家去说一下这个B包的优点是什么好。B包的优点,B包的优点呢是延长。外部函数局部变量的生命周期。大家可以想一个事情。如果说没有这一块代码,如果说没有它,那我FU2一旦执行完了,这个作用是不是要销毁啊,销毁的话,它内部的这个变量就应该释放的,就应该释放的,也就是说它的生命周期应该结束,但是呢,我加上这一段代码,你想在这儿的时候,F fun是不是已经执行完了,按理来说这个变量是不是应该销毁啊,但是我在下边调用f fun2的时候,是不是很明显的拿到这个变量。
13:04
所以呢,该变量的生命周期被延长了,被延长了,这就是B包的优点,那么同理它有优点呢,也是有缺点的。B包的缺点那么在这儿我要说一下B包它是一把双刃剑,它的优点其实也是它的缺点。你想本应该本应该及时销毁的一个变量,因为被包的原因导致它没有被销毁,如果说长期这样存在的话,那么势必会导致一个问题,容易造成什么呢?哎,它呢,容易造成内存泄露。内存泄露。哎,这是B包的缺点,所以呢,在使用B包的时候,我们要注意,在这儿给大家写一下注意点,第一个那首先是合理的使用B包啊,合理的使用B包,然后呢,就是。
14:07
用完必包要及时清除,或者叫干嘛,或者叫销毁,哎,这样做的目的呢,是避免内存泄露,避免内存泄露好,那这是对B包的一个简单的讲解,那么在这呢,可能就有一道简单的面试题,我给大家写一下,在这儿比如说我写个com加加。那么这个时候我们来看一下这两次打印分别是什么,分别是什么?那这是一道关于B包的基本的面试题,好,那这个时候,哎,可能懂的同学呢,已经能做出来了,这个的结果应该是二,而下边这个结果应该是三。为什么会这样呢?我们先来看结果,是不是二和3OK。
15:00
来刷新一下。把这个断点放过去lo,诶,结果就是二和三。咱们来分析一下为什么是二和三,首先外部这个count对应的值是一。对应的值是一,那么一旦我的FU21直行进来,Cot加加加,加完cot是不是立马为二,所以。第一次执行为二,没有任何问题,没有任何问题,关键是大多数同学不理解的是,这一次为什么是三。它不应该还是二吗?哎,如果说还是二的话,那B包其实就没有起任何的作用,大家想一下,在这一行代码执行完的时候,Count是是不是相当于被我内部的函数间接的去修改成二了,并且呢,我让它存活下来。哎,所以呢,下一次再进来的时候,再加加cot对应的是不是V3啊。
16:02
OK,那这是一道简单的关于B包的面试题,接下来呢,我们来看一道比较经典的B有关于B包的面试题,好,那这个时候呢,我把这个代码拿过来。来放到这一块。哎,可能有的同学见过这道题,我们来看一下,你要是能把这道题做出来,那么在B包这一块是没有任何问题的。首先这边有一个函数叫f fun,哎,它因为它有f fun了,所以呢,为了避免干扰呢,我会把上面这个给它除掉,那我们接下来看当前有函数f fun。这是一个打印,然后这有个return,哎,注意看,现在return出去的是一个对象,而对象里边有一个减值对,而它的Y流值是一个函数。在这呢,有的同学犯嘀咕,这是不是B包呀?哎,刚才说了B包的条件有一个是函数嵌套,但是这个函数嵌套的是一个对象啊。
17:10
不要叫这个这啊,你只要说函数的内部还拥有函数的话,那么它就已经形成了第一个条件,就已经形成了第一个条件。好,那我们接着往下看,那先来看这Y的一个变量等于一个f fun也是外部函数调用。这是肯定的,然后呢,他传了一个零是吧,进去啊。好,那我们来看一下FU调用的返回值是谁啊?赋值给A呢?很明显是不是这一个对象。是这个对象,好,那是这个对象以后这传了个零零,应该是把负值给这个N呀,而我们只传了一个食参,他声明的时候是不是还有个O,也是第二个行参,所以这个conslo o第一次肯定是安顿饭的,那这个比较简单,我们答案直接就写出来了。
18:07
那我们接着往下来看,然后是a.fun调用,哎,也就是说对象点方法调用,那这个时候你一定要分清楚这个FUN是外部的还是内部的fun。很明显A是不是返回值这个对象,然后点FUN是不是点它呀,所以呢,在这一行代码一执行是不是执行的这个函数啊,那我们重点来看一下这个函数执行的结果是什么。好对象点FUN1定要一定要用FUN1调用这传了个几进去啊。一我们来看一下这个FUN1调用,首先它也return一个值,只不过是return之前我是不是应该先执行这个f fun,哎,那这个f fun是谁呢?很明显。
19:03
这个F应该是,它相当于是什么window.fun啊,那window.fun找的是不是全局,这个fun没有问题,好,重点是这f fun1调用传了一个。传了一个M进去,传了一个N进去。那相当于外部函数是不是又执行了M对应的是谁啊这一把注意,那这个N还是这个吗?不是的啊,这个M我们只看位置对吧,M对应的是不是第一个N,而N对应的是不是它。对吧?好,没有问题,那我们来分析一下,第一次传的时候,这是不是一个零啊,首先大家要明确一个道理,我那个行参N和O是不是相当于在函数内部声明了这个变量,然后第一次传的时候,这传了个零,相当于是拿零给谁赋值啊,是不是给这个N呀?
20:02
OK,然后你打印O是吧,N find没有问题,那接下来往下看这个FUN1定要用,注意看这的fun。一定要用M传的是几M是一,也就是说相当于我这Y的一个M是不是等于一啊?M等于一,那N呢?N在当前这个函数。找。有吗?注意在当前函数的作用域找能不能找到,哎,我这已经给大家写出来了,这个N对应的值是不是为零啊,所以呢,这是不是相当于传了个零进去啊,那也就是说这个零赋值给的是谁啊?是O,是O,也就是说在这输出的结果是不是应该是为多少为零?好,那分析完这个以后,我们再来分析这个。
21:03
a.fu u调用,只不过这一次传的值为二。哎,考点其实在这儿,那这个时候大家要。明白一个事情,其实刚刚这个fun它拿到的零是吧,外部函数的是吧,外部函数的,因为你里边在这一段代码执行的时候是吧,问外部去要。啊,那这很明显就是一个B包嘛,很明显就是一个B包,而这个B包你要明确它保存在哪。哎,刚刚我们在说的时候说了,它保存在内部函数里边,内部函数。它是不是存在这个对象里边,它作为一个返回值是不是返回给A了,那也就是说我这个B包是不是在A的里边。B包呢?又是用来存储介质,对的那个K是谁啊?K我们可以认为它是N,而对应的值是什么?
22:03
是不是就是零。也就是说,A里边有一个B包。N为零,所以再往下看,我每次不管我传几进去,哎,不管我传递进去,传的这个值是不是相当于赋值给谁了,赋值给M了,但是咱是不是通过B包养Y变N的值啊,所以在这儿呢,它对应的结果应该是恒为零才对,哎,恒为零才对。那么接下来再看,哎,他又换了一种方式,问我们这个结果是什么,可能有的同学一看,那这个和上边不是差不多吗?无非不是上面的每一次对象点,那这一次呢,它是连起来了,所以呢,还是000。哎,这个时候呢,要提醒大家要注意一点,那这个时候我们先来看这个FUN0。
23:00
一定要用什么,第一次这个,那这个不用说啥,你得看着重点是这儿。点fun调用一,那这个呢,其实我们可以理就这两步和这是一模一样的,对吧,F fun0返回的结果再调用它是不是相当于a.f fun啊好一调用的话。虽然你这传了个一,但是我关注吗?不关注,我们重点是不是看他第一次传进来是几。啊,也就是说这个零是不是赋值给B包里面的N,好,那这个时候在这儿是不是应该为零啊。重点是这儿他继续点fun。只不过只不过这一次是不是传了二,哎,这个时候大家要注意啊。直接给。DU,说明前边这个整体是不是一个对象,那这个整体是一个对象,你一定要能区分清楚,FU,零返回值是一个对象,那再点f fun1。
24:04
再一调用是不是返回的又是一个新的对象,那这两个的结果和第一次这个FU,零单独调用的对象是一个吗?一定要注意它不是一个对象,那既然不是一个对象。它里边存的那个B包也不是一个,那FUN0点f fun1。调用他们呢,返回了一个新的B包,返回了一个新的B包,那我下一次f fun再执行这个M等于什么N啊那。也就是我这一行代码一直行为这个N为几啊。N是不应该为一才对,因为你这传了个一进去嘛。那既然N为一,那我再点一问它这个B包里边要N的值,N为几?N是不是唯一同样的道理。
25:01
再点fun,我不看这传几嘛,其实咱们不关心,我们是看他上一次调用的时候,给B包里边那个K负的值是什么,是不是为二,所以呢,这应该为二。哎,可如果说大家能理解了,那其实我再随便加的话就好理解了,比如说我在这写一个50,我再点一个fun。22,那继续它输出的结果应该是什么呢?零一。这是012,那么接下来再点fu,这是不是负了个三,所以我们应该是个三,那再往下是多少呢?哎,其实是个50,最后这一个就跟我们没关系,哎,没关系,那如果说大家能把这两个理解了的话,下面这个就不难了,那第一个安范呢,点一个fun。哎,这一次呢,这个一。我们是不关心的,因为我们要看上一次它是不是传了个零啊,所以呢,这为零那。
26:03
这为零了以后,它是通过这个接收,通过这个变量指向的那个对象是不是点点fun。哎,这个时候呢,我们一直使用的是同一个对象里边的B包,所以呢,这个对象里里面存在B包,它那个K对应的值才是关键,哎,它的值应该是多少。是不是应该是这个一呀,哎,所以呢,这是一,那后边这个呢,是不是应该也是一呀。好,我们来一块看一下结果,那这个时候呢,为了避,为了避免影响我们的正常的答案呢,我应该是把这两行演示的代码给大家注释掉,那这个时候来到浏览器端,我去刷新一下,然后呢,来到控制台这一块,我们来看,哎,上面这三个是不是为零啊,而中间这一块零一二三五十,下面这个是011,回头再来看一下我们刚刚推测的结果。
27:00
上面三个都是零零一二三五十零幺幺没问题,好,那以上呢,就是对B包以及B包相关的面试题的一个讲解。大家想关注更多更详细的资料呢?可以访问上硅谷的官网。
我来说两句