赋值就是把某一个值赋给变量。
变量相当于名字。
拿我举例,如果我没有名字,当别人叫我帮忙的时候,就只能说:
“那个个头不高、颜值爆表、头发很硬、坐在角落的小哥哥,过来帮我一下呗!”
而有名字的情况是:
“小强快来!”
可见变量赋值的意义在于便于使唤。
基本类型的赋值,好比在每个盒子里放东西。
例如,
var a = '手机'
console.log(a) // '手机'
相当于,给一个盒子起名为a,并放进去一个字符串‘手机’。
例如,
var a = '苹果'
var b = a
a = ''
console.log(a) // ''
console.log(b) // '苹果'
当var b = a时,相当于:
给一个盒子起名为b,并偷看一下盒子a里面放的什么,然后自己里面放同样的东西。
当a = ''时,相当于:
盒子a里面原来的东西不要了,改放一个''进去。
但是,这并不会影响盒子b中的值。
因为,在盒子a里面发生变化的时候,盒子b并不关心。
只有将变量a赋值给变量b,即b = a时,
b才会出现偷看行为,进而使自身的值和a的值一样。
可见,赋值就是复制,特点是赋值过后互不影响。
好比我把感冒复制给朋友,我吃完药好了,并不能代表他也好了。
引用类型的赋值,看上去是共享,实际还是复制。
首先,基本类型采用栈存储,一个萝卜一个坑。
前面也说到,基本类型的赋值,就像在每个盒子里放东西,比如,
我在a盒子里放个香蕉、
我在b盒子里放个手机、
我在c盒子里放个老鼠、
我在d盒子里放个房子...
问题出现了,
之前,我们在盒子里放一些很傻很天真的东西,基本没问题,
但是,盒子里可以放房子吗?
这时候,我们进一步认识一下我们的盒子(栈存储)有什么特点?
- 盒子按顺序排列
- 每个盒子只能存放一件物品
- 每个盒子都不是很大
显然,我们的盒子不足以装下房子。
而且常识告诉你:
房子里倒是能堆很多的盒子,因此房子就是堆存储。
堆存储就像找了一片空地,然后在上面尽情放盒子(请不要想到《我的世界》)。
特点就是存的多、存的乱。
理解堆和栈的区别,也可以参照公交车,
每个座位,
有序、只能坐一人、大小有限...
好比栈;
其它地方,
能挤下一堆人,很乱...
好比堆。
对象采用的就是堆存储。
我们知道,javascript引用类型包括对象。
先随便来一个对象:
var a = {} // 堆存储,相当于建了个房子,名为a
再随便来一个数字:
var b = 10 // 栈存储,相当于选了个盒子,名为b
再随便来一下:
b = a
console.log(b) // {}
太随便就容易出问题:
原本b是一个正经盒子,a是一个正经房子,
执行b = a之后,b变成了{},
根据上面说的基本类型的赋值,这是不是在说
盒子b虽然之前只是一个小盒子、
但是偷看了一眼房子a之后、
自己发奋图强变成像房子a一样大的盒子、
并复制了房子a里面的东西?
不是的。
盒子b并没有为了复制房子a里面的东西而变大,
实际上,a其实从头到尾都只是一个盒子、而不是房子,仅管我们看到是a被赋值了一个堆存储的对象。
为什么呢?
因为引用类型只暴露一个地址给我们,
操作引用类型的数据时(比如赋值),我们也只是在操作这个地址、引用、指针...爱叫啥叫啥。
这就好比,你送女朋友戒指,可以直接放到她手里,这是基本类型;
你送女朋友法拉利,并非把法拉利直接放到她手里,而是把车钥匙放到她手里,这就是引用类型;
你送女朋友房子,并非把房子直接交给她(除非你们是蜗牛),而是把房钥匙交给她,这也是引用类型。
从赋值上来进一步认识引用类型:
开始说过,
变量相当于名字。
另一个事实是,
我们只能给盒子起名字,不能给房子起名字,但是我们能拿到房子的钥匙。
这就是为什么说,a其实从头到尾都只是一个盒子。
var b = 10相当于取一个盒子叫b,然后里面放啥啥啥,这没有问题;
由于我们不能给房子起名字,
所以var a = {}肯定不是说:取了一个房子叫a,然后里面放啥啥啥。
其实,和var b = 10的解释一模一样,var a = {}也相当于
取了一个盒子叫a,然后里面放啥啥啥。
只不过是,b盒子里面放很傻很天真的东西,
a盒子里面放很傻很天真的钥匙,这把钥匙对应一个大房子。
引用类型的直接赋值就是把钥匙放到对应盒子里。
代码中,会出现很频繁的变量赋值行为,
为了保证运行速度,这些行为被优先安排在一批有序的盒子中,偷看、复制、再偷看...
可以说,我们大部分时间在玩盒子。
可想而知,如果换成玩房子的话,要费多大的力气。
但是呢,房子在我们的程序中也有着不可或缺的作用,
这时候它就暴露出一个可以找到它的钥匙,相当于它的联系方式,
然后放进相应的盒子里,并说:
当你需要我的时候,我会在你身边。
正是因为这样,我们既便于使唤房子,又便于操作房子里的东西。
var obj = {name: '小强'}
var obj2 = obj
console.log(obj2) // {name: '小强'}
首先,var obj = {name: '小强'}是引用类型的直接赋值,
相当于找到一个盒子名obj,把{name: '小强'}这个房子的钥匙放进盒子obj里面。
而obj2 = obj可以说和基本类型的变量赋值给变量一样,
盒子obj2偷看一眼盒子obj中放的东西,复制一下,自己里面放同样的东西。
喜出望外的是,竟然是一把对应某个房间的钥匙!
这时,obj2就和obj一样,都能访问这把钥匙对应的房间了。
所以引用对象的赋值都是操作钥匙。
答案是否定的。
这相当于在问,这两个房子的钥匙相同吗?
这两个房子只是在装修上极其相似,我们不能通过将自己的房子布置得和邻居的房子一样、就能得到邻居家的房钥匙,
程序也是如此。
例一、
var a = {n: 1}
var b = a
a.x = a = {n: 2}
console.log(a.x) // undefined
console.log(b.x) // {n: 2}
逐句翻译吧:
var a = {n: 1}
取一个盒子名a,建一个房子,钥匙放到盒子a里面;
房子里有个盒子n,放着1。
var b = a
取一个盒子名b,盒子b偷看一下盒子a,哇哦,一把钥匙,
盒子b里面也有了这把钥匙,也能去访问这个房间了。
a.x = a = {n: 2}
- 变量赋值是从右向左的
- 对象用.赋值的时候,就是操作对象的某个属性,如果没有该属性就添加一个
我们通过盒子a中的钥匙,来到了这把钥匙对应的房间,
然后,我们在这个房间取一个盒子名x,并企图在里面放东西。
执行到a.x = a的时候,我们还以为:
是把盒子a里面的钥匙,放进我们所处房间的盒子X里面吗?
差点就是了,但是后面又有=赋值。
根据变量赋值从右向左,
我们暂时先不在这个房间里的盒子x放东西,而是优先执行a = {n: 2},
这条语句显然是引用类型的直接赋值,
即建了一个是{n: 2}这种样子的房子,然后把钥匙放到盒子a里面。
在栈和堆里面我们提到过:
每个盒子只能存放一件物品。
因此,盒子a首先会抛掉之前的钥匙,然后存下这把新的钥匙。
刚才我们拿着盒子a之前的钥匙,进到对应的房间,企图在房间的盒子x里放东西;
然后,发现后面还有赋值行为,所以优先执行后面的赋值行为。
但是,当时我们只是暂停,而不是放弃。
换句话说,是不忘初心,有始有终。
当初我们进的哪个房子,想在哪个盒子放东西,
现在我们就回到哪个房子,然后给哪个盒子放东西,
从a.x = a可以看出,我们在盒子x里放的是盒子a的钥匙,
在这个例子中,盒子a中现在的钥匙就是能打开{n: 2}这间房子的钥匙。
虽然说,
变量赋值是从右向左的。
但是,代码执行是从左向右的。
无论后面发生了多大变化,a.x都是最先执行的,它的作用就是:
通过钥匙来到一个房间,取盒子x,然后等着在里面放东西。
后面的代码,只能影响这个盒子里放什么东西。
于是,时过境迁:
盒子a里,抛弃旧房子钥匙,放进了一把新房子钥匙,等价于
a = {n: 2}
盒子b里,还是旧房子的钥匙。
同时,因为在盒子a换钥匙之前,我们通过盒子a拿到旧钥匙来到旧房子,
并将盒子a换钥匙之后的新钥匙,放进了旧房子的盒子x里面,那盒子b等价于
b = {
n: 1,
x: {n: 2}
}
也可以将这个例子稍加处理:
var xiaoMing = {moneyBox: 1}
var xiaoQiang = xiaoMing
xiaoMing.keyBox = xiaoMing = {moneyBox: 200}
console.log(xiaoMing.keyBox) // undefined
console.log(xiaoQiang.keyBox) // {moneyBox: 200}
再逐句翻译:
var xiaoMing = {moneyBox: 1}
小明有一把房钥匙,这个房子里有个钱柜,里面放着1元钱。
var xiaoQiang = xiaoMing
小强偷偷复制了一把小明的房钥匙,从此他也可以进出小明的房子。
xiaoMing.keyBox = xiaoMing = {moneyBox: 200}
小明在此房子里做了一个钥匙柜,这个钥匙柜能自动生成一把小明口袋里的钥匙(xiaoMing.keyBox = xiaoMing的作用,可能有点超现实),
但是小明想,我口袋里的钥匙现在就是这个房子的钥匙,放在我的钥匙柜里也没什么意义,
不如这样吧,我再买一套房子,把口袋里的钥匙替换成新房子的钥匙,那这个钥匙柜里不就存下新房子的钥匙了吗。
于是,小明果断又买了一套房子,这个房子里也有个钱柜,里面放200元钱。
小明正准备回旧房子呢,突然想起来,自己口袋里的钥匙已经替换成新房子的钥匙了,
现在他只能进新房子,而进不去旧房子了,郁闷...
再说小强,
小强当初复制的是小明旧房子的钥匙,所以小强依然能来到这个旧房子,
进来后发现,多了一个钥匙柜,并且里面放着一把钥匙,
没错,这就是小明新房子的钥匙。
所以现在的局势很明朗了:
小明只有新房子的钥匙,只能进新房子(而且他应该觉得旧房子已经没人能进去了)。
而小强有小明旧房子的钥匙,
同时这个房间里还有小明的新房子的钥匙,所以小强也能进小明的新房子。
用代码表示,就相当于
xiaoMing = {moneyBox: 200}
xiaoQiang = {
moneyBox: 1,
keyBox: {moneyBox: 200}
}
如果说引用类型是地点,我们操作的就是它的地址;
如果说引用类型是房子,我们操作的就是它的钥匙;
如果说引用类型是人物,我们操作的就是它的手机号;
如果说我们不用比喻,我们操作的就是它的引用。
至此,除了赞叹我的排比句是多么荡气回肠之外,你一定也发现:
- 引用类型就是一种有联系方式的数据类型
- 这种间接关系,使不同变量共享同一数据成为事实
例如
var a = {n: 1}
用变量a保存了{n: 1}的引用,通过这个引用可以操作{n: 1},这个引用就是联系方式。
var b = a
将a的引用赋值给b,变量b也保存了{n: 1}的引用,也可以操作{n: 1},这就是共享。
好比,你和你的房间的联系方式,就是房钥匙。
因为你有房钥匙,所以你才能够进房间,操作房间里面的东西。
如果路人甲默默复制一把,路人甲就可以和你一样,能够操作房间里的东西,这就是共享。
问题来了————
你俩虽然有你房间的钥匙,但是你俩拥有这个房间吗?
答案是肯定的。
即使是间接拥有,也是拥有。
好了,明白了上面的东西,原型就不是问题了。
在表达我对原型的理解之前,我想先讲一个发生在我身上的不真实的故事。
接下来,我将讲述我的一次创办学校的经历。
回到多年前。
我是小强,此时我正打算创建一所学校,所以大家也可以叫我小强校长。
首先,我要培养一个老师来替我上课,因为作为一个校长,我还有很多其它事情要做。
很快,我将多年的教学经验传授给一个叫孔仔的人,培养出第一个老师。
后来呢,孔仔也培养出一批又一批的学生。
学校算是建成,孔仔也能完成教学任务,不断培养学生。
但是我发现一个问题,一个人的力量毕竟有限,虽然孔仔能完成基本教学,却没有精力在每个学科上做到专精。
这就导致,培养出来的学生没有专长,大多很普通。
终于有一天,我的学校得到许多好心人的赞助,我当时表示力度大点十分感谢。
钱多了,我就可以培养出更多的老师,不同特色的老师又可以培养出不同特色的学生,这不正是我的毕生所追求的东西吗?
于是,我的学校除了孔仔老师,又有了语文老师、数学老师、思想品德老师......
而且我还可以培养更多学科的老师。
不知何时开始,有一些老师会找我申请一些教学方法的书,这我是十分支持的,可以说是有求必应。
随着申请的老师越来越多,我总结了一些很多老师会用到的教学方法的书,于是我就想:
不如我在自己办公室旁边建设一个图书室,把这些常用的书放在里面,老师们想看的时候来这里看不就行了。
很快有老师向我反映,他们其实也有相同的困扰,学生也会向他们申请一些课程相关的书,而且经常是相同的。
于是我决定,只要是老师,都要有一个自己的图书室,存放自己学生通常会用到的书或器材。
我也是老师,我是老师的老师。
所以,各科老师的图书室一般都存放各自文化知识方面的教材,而我的办公室存放的是教学经验方面的教材。
马上就要开工了!
可是我又发现,像《小学生必读》这类书,几乎在每个老师的办公室都有,我想:
不如把这类全校学生都读的书,放在同一个地方吧,这样就可以省下更多钱吃喝玩乐建设学校。
我第一想到的,就是孔仔老师,孔仔老师毕竟是我们学校的元老,同时他教的内容是最宽泛的、最不专门的,图书室应该相对空旷,所以就把《小学生必读》这类几乎所有学生都会看的书籍统一放到他的图书室吧。
于是我决定,在给每个老师建造图书室的同时,都建造一条隧道通往孔仔老师的图书室。
我的图书室也是那时候建的,所以我的图书室也有一条通往孔仔老师的隧道。
这样,我培养出来的老师,想看教学经验方面的书籍,就可以到我的图书室。
而我校老师培养出来的学生,想看自己学习方面的书籍,就可以到自己老师的图书室;
如果没有的话,就可以通过自己老师的图书室里的隧道,来到孔仔老师的图书室,看有没有想看的书籍。
这样我的学校就完美了,小强校长还是挺不错的哈?
先把图放在这里。
在javascript中,Function扮演的就是校长的角色。
首先,他不想亲自去培养学生,所以他培养出了第一个老师孔仔。
Object就相当于孔仔一样可以培养出学生,即通过new Object可以创建对象。
但是,校长为了学生的多样化,又培养了语文老师、数学老师、思想品德老师等老师,而且还可以按需要培养更多老师;
同样,开发者想要创建多样化的数据、符合需要的对象,诸如String、Number、Boolean这些函数对象也必不可少的,而且也可以根据需要写各种构造函数。
可以看到,校长培训出老师,老师再培训出学生;
Function创建函数对象,函数对象再创建对象;
只不过javascript的培训方式是new。
校长为了避免给每位老师买教学经验方面的书,所以创建了自己的图书室,用来让自己培养的老师共享这些资源;
而我们的Function,也并不想把那些常用的方法都复制一份,保存到Object、String、Array...以及后来新创建的N多构造函数当中,于是新增一个属性prototype(即原型对象),把一些公共的方法存在里面;
Function让它创建出来的函数对象共享prototype的方式,就是在通过new Function创建函数对象的同时,会使
被创建的函数对象.__proto__ = Function.prototype
比如,在创建Object的时候,会使
Object.__proto__ = Function.prototype
这个场景似曾相识?
没错,这就是我小强校长在创建自己的图书室后,将图书室钥匙送给孔仔老师一把的情景。
此后,孔仔老师就可以来我的图书室,查阅教学经验相关的书籍。
同理,Object也因此可以访问Function的prototype,使用Function为他们精心准备的函数对象的方法。
验证
Object.__proto__ === Function.prototype // true
这里的true表示的就是,钥匙已拿到,Object已拥有Function的prototype。
为什么呢?
虽然,根据开篇对引用类型的介绍,可知Object.__proto__ = Function.prototype所做的事情:
无非就是,Function把公共的方法都放在某个房间里,再将钥匙存在自己的属性prototype里;
在赋值时,就是复制了一把prototype里的钥匙,然后存在Object的属性__proto__中。
从这个过程可以看出,原型也不过是引用类型的赋值,就是通过一种联系方式间接拥有某个东西。
本篇文章开始说过,
即使是间接拥有,也是拥有。
首先,对引用类型的及其赋值的理解是必要的。
但是,在理解原型和原型链过程中,可以不必太关注引用类型赋值过程这种细节。
因为原型的目的,就是让实例们都拥有有一个公共区域,方便使用一些常用的方法。
所以类似Object.__proto__ === Function.prototype这种吓人的结构,它要表达的不过就是这个意思:
前者已经拥有了后者提供的公共区域。
就像我的校长生涯那样,当我发现建图书室的作用之后,并不止为自己培养的老师建了图书室。
只要是老师,都要有一个自己的图书室,存放自己学生通常会用到的书或器材
这样,把一些学生的教材也变成共享,就又免得给每个学生都重复买教材。
也就是,不仅Function创建函数对象时,会
被创建的函数对象.__proto__ = Function.prototype
而且函数对象在创建对象时,会
被创建的对象.__proto__ = 创建对象的函数对象.prototype
总结一下,只要new的时候,就会
实例.__proto__ = 创建者.prototype
即只要我创建了你,你就拥有了我的公共区域。
回顾我的校长生涯,在我即将开工给我和每位老师建图书室之前,我又想到:
每个老师的图书室里的书,也有很多是重复的,不如把这些重复的都放到元老级教师孔仔的图书室吧。
可是学生只能进各自老师的图书室,怎么让他们也能进孔仔老师的图书室呢?
还记得睿智的小强校长是怎么做的吗:
在建每件图书室的同时,修一条隧道可以到达孔仔老师的图书室,即Function创建对象的同时会
被创建的函数对象.prototype.__proto__ = Object.prototype
例如
Array.prototype.__proto__ === Object.prototype // true
没错,对于javascript来说,Object就是孔仔老师,Object的prototype就是孔仔老师的图书室。
为了减少不同函数对象的prototype的重复,我们把很通用的对象的方法都放到Object的prototype中,并通过上述方法让其它函数都的prototype都拥有它。
这样,每个对象都拥有函数的prototype,每个函数的prototype又都拥有Object的prototype,那么每个对象也都拥有Object的prototype,即对象总能找到早就给它们精心准备好的方法的;
不同原型之间这个不依不饶的关系,就是原型链。
对于学生来讲,他们可以去的办公的总和、以及先后顺序,就相当于我们的原型链。
如图
关于“隧道”的建成,还有一些重要问题:
实际上,被创建的函数对象.prototype.__proto__ = Object.prototype,并非Function做到的,而是Object做到的。
这里,Object其实是使用的这一规则:
只要我创建了你,你就拥有了我的公共区域
因为javascript的创建方式是new,要想实现被创建的函数对象.prototype.__proto__ = Object.prototype,需要先有
被创建的函数对象.prototype = new Object()
即所有对象的prototype都是Object创建的,都是Object的实例。
其它函数对象的prototype因此才能拥有Object的prototype,即实现这种效果
被创建的函数对象.prototype.__proto__ = Object.prototype
那么,如果你是类比我的校长生涯来理解原型的,你就会想:
new在故事中不是“培训”的意思吗,这是不是说,其它的图书室都是孔仔“培训”出来的?
其实,这里我们自圆其说灵活理解就行了,毕竟例子只是用来帮助理解的。
所以,我们的“new”在这时候就是“创建”的意思。
在javascript中,所有对象的prototype都是Object创建的,那么
在我的校长生涯中,故事可能就是这样的:
这个时刻,学校还只是我(小强校长)和孔仔两个人。
我说,孔仔,为了实现资源的共享,我有一个想法。
孔仔说,啥想法?
我说,把你培养出来后,我已经很累了,但是我们这个学校将来肯定会得到好心人的捐助,也一定会培养更多的老师,老师们也一定需要教学经验方面的学习素材。如果每人我都发一套的话,就有点贵。所以我想给自己建造一个图书室,专门放这些资料,这样不管是你,还是以后培养出来的老师,就都可以在这里共享这些资源了。你觉得怎样,孔仔?
孔仔说,小强校长真抠门英明啊!您看这样如何,每个老师也都建设一个自己的图书室,这样,很多给学生的教材就不用重复买了,他们想用想看的时候去老师的图书室里找就行了,更省钱。
我说,只给学生们开一个图书室不可以吗,每个老师都有图书室会不会浪费?
孔仔说,不太好吧,毕竟不同学科的学生,使用教材的差异还是挺大的,不是很通用,还是建议每个老师都有各自的图书室,方便管理。
我说,有道理,但是总有一些通用的教材,比如《小学生必读》这种,没必要每个老师的图书室里都放一套,不够省钱。
孔仔说,您的意思是?
我说,放你那里吧。
孔仔已经跟不上我的思路,问,其他老师的学生都是去各自老师的图书室啊,这样只有我的学生能看到《小学生必读》这类书吧?
我说,这还不简单,每个老师的图书室都修一条隧道通向你的图书室。
孔仔沉默。
我说,修图书室的事情,就由你来负责吧!先给本校长来一个图书室吧。
孔仔说,不能,我要先给自己修建一个图书室,否则给你修建的时候不知道隧道通向哪里。
我说,好吧。
从此,每当我培养出一个老师,孔仔就帮忙修建一个图书室,同时挖一条隧道通往他自己的图书室。
我们的学校也越来越好。
- Function.prototype.__proto __ === Object.prototype ?
对。建设每个图书室的时候,孔仔都挖隧道通向自己的办公室了,包括校长的办公室。
- Function.prototype.__proto === null ?
对。我们说好把通用资源都放在孔仔办公室,如今已经来到孔仔办公室,还想去哪里?
- 先有的Function还是先有的Object?
先有的Function。孔仔是我培养出来的,当然是先有的本校长。
- 先有的Function.prototype还是先有的Object.prototype?
先有的Object.prototype。不然孔仔修建图书室的时候,怎么知道把隧道通向哪里。
- constructor是什么?
原型的constructor放的是该原型所属的函数对象,即每个图书室里都放在该图书室所属老师的联系方式,比如电话号码。
- 小强校长帅吗?
颜值爆表。
在网上,关于闭包的文章众多。
MDN文档中说:
闭包是函数和声明该函数的词法环境的组合
很多文章中说:
闭包是指有权访问另一个函数作用域中的变量的函数
还有一篇文章,总结了闭包的四种定义。
最后,我决定去请教我的一个经验丰富的同事。
他说:
闭包就是闭着的包子
......
我发现闭包的最大难点,就是没有一个明确的定义。
于是,我去其精华、取其糟粕,写下这篇关于闭包但完全不去定义闭包的文章。
function outter(){
var name = '小强'
function inner(){
console.log(name)
}
return inner
}
var foo = outter()
foo() // '小强'上面这段代码,就是一个闭包。
(无论闭包的定义是什么,这段代码基本上是通行的)
首先,如果套用这个定义:
闭包是指有权访问另一个函数作用域中的变量的函数
那么,函数inner就是闭包,因为我们知道:
定义在函数内部的函数,是可以访问外部函数的作用域的。
简写一下:
function outter(){
var name = '小强'
function inner(){
console.log(name)
}
inner()
}这种结构下,inner函数还是有权访问outter函数作用域中的变量的,所以这是不是闭包?
(我也不知道)
上面代码,是一种最常见的函数嵌套。
当outter函数执行时,会创建一个属于outter的执行环境及变量对象。
当inner函数执行时,又会创建一个inner的执行环境及变量对象。
它们的相同点是:
执行完毕之后,各自的执行环境及变量对象都会被销毁。
尽管函数是一等公民,但是它们执行完毕后、变得“没用”,JS很快将它们“灭门”,这就是JS垃圾回收机制。
它们的联系是:
inner函数可以访问到outter函数的变量对象。
变量对象,顾名思义,就是保存该函数自身变量的一个对象。
内部函数保存所有外层函数的变量对象,形成了自己的作用域。
即inner函数的作用域,包括自身的变量对象、outter的变量对象和window的变量对象。
为什么要保存别人的变量对象?
因为对自己有用,自身没有的话就可以去用外层的。
可以说,外层函数的变量服务于内部函数。
再回到这种形式:
function outter(){
var name = '小强'
function inner(){
console.log(name)
}
return inner
}
var foo = outter()
foo() // '小强'不同于普通嵌套,
这里当outter函数执行到最后时,将inner函数return了出去。
显然,outter已经执行完毕了,但是它的执行环境及变量对象都被销毁了吗?
并不是。
有一个幸存者,就是**outter函数的变量对象**。
虽然outter函数在return之后,自身已经执行完毕。
但是,因为它return的是嵌套在自己内部的函数inner,并赋值给全局变量foo,这就导致:
-
一方面,
outter函数执行完毕,outter的变量对象理应被销毁 -
另一方面,
inner函数被赋值给全局变量,随时有可能被调用,那它的作用域不应该被破坏,其中的outter变量对象也就不该被销毁
上面已经说过:
内部函数保存所有外层函数的变量对象,形成了自己的作用域
所以,就是因为还有用,所以outter函数的变量对象并没有在outter执行后被销毁,成为幸存者。
最终,当我执行foo()的时候,
尽管outter函数早已执行完毕,但依然可以打印出其变量name的值'帅哥小强'。
而我想到的,是《辛德勒名单》这部电影。
1939年,波兰在纳粹德国的统治下,党卫军对犹太人进行了隔离统治。
这时,德国商人奥斯卡·辛德勒和德军建立了良好的关系,他的工厂雇用犹太人工作,大发战争财。
犹太人遭到了德军的大屠杀,辛德勒目睹了这一切之后十分震撼。
辛德勒让自己的工厂成为集中营的附属劳役营,在那些疯狂屠杀的日子里,他的工厂也成为了犹太人的避难所。
德国战败前夕,屠杀犹太人的行动越发疯狂,辛德勒向德军军官开出了1200人的名单,倾家荡产买下了这些犹太人的生命。
这个电影很有名,如果没看过建议看一下。
同样,在我们的JS世界中:
当一个函数执行完毕,它的执行环境及变量对象也会遭到一场屠杀,即垃圾回收机制。
在这场屠杀中,辛德勒用一份自己工厂员工的名单,使自己的工厂成为集中营的附属劳役营,更成为犹太人的避难所。
而inner函数,也有一份自己员工的名单,那就是作用域。
这份名单上,就包含了outter函数的变量对象。
inner函数被赋值给全局变量,就好比辛德勒和德军建立了良好关系,
它的作用域就成为变量对象的避难所,
因为outter函数的变量对象被写在inner函数的员工名单(即作用域)中,所以才免遭杀害。
这就是JS版的《辛德勒名单》。
那么在这个过程中,你认为哪部分属于闭包呢?
开发过程中做过的和见到的
很多时候,页面交互需要navigateTo而不是redirectTo,但是微信小程序的页面路径最多只能十层。
到达十层之后,页面的redirect会失效,用户点不动就会很恼火。
我的解决如下:
export function navigateTo({url, ...data}){
var limit = 10 //微信限制的页面路径层数
var pageCount = getCurrentPages().length
if(pageCount < limit - 2){ //真机上需要额外留两层才能完成跳转
wx.navigateTo({ url, ...data })
}else{
// 路径到上限时reLaunch
url = `pages/index/index` // 小程序的首页
// 也可以在首页添加对options的处理,比如设置参数go,如果有的话decodeURIComponent并跳转
// url = `/pages/index/index?go=${encodeURIComponent(url)}`
wx.reLaunch({ url, ...data })
}
}当页面处于非tab页面时,wx.showTabBarRedDot是失效的、不起作用的。
当你调用了wx.showTabBarRedDot却发现没有红点出现时,不要伤心,不要难过,不要想到做傻事,
因为我发现,wx.showTabBarRedDot只有在tab页面才会生效。
你是否是在非tab页面调用的?
这种情况下,就需要借助一下本地存储。
// 假设我们要显示红点的tab的index为1
// 1.非tab页
// 触发:
wx.setStorageSync('show_tab_bar_red_dot', true)
// 2.tab页
// 触发:
wx.showTabBarRedDot({ index: 1 })
// 在onShow中判断非tab页是否有红点触发:
if(wx.getStorageSync('show_tab_bar_red_dot')){
wx.showTabBarRedDot({ index })
}
// 3.红点显示页(tab的index为1的页):
onShow(){
wx.setStorageSync('show_tab_bar_red_dot', false)
}
onTabItemTap(){
wx.hideTabBarRedDot({ index: 1 })
}微信小程序中socket百分百会关闭的情况:
小程序从前台进入后台,过了很短的一段时间后。
所以如果有处理socket异常关闭的需要,可以重点关照一下小程序onShow时的socket连接情况。
很多困扰,来自看文档中错过的一些细节。
navigateTo, redirectTo只能打开非 tabBar 页面。
switchTab只能打开 tabBar 页面。
wx.saveImageToPhotosAlbum()可以实现图片的一键保存。
但是,当图片是GIF的格式时,ios保存下来的图是打死不会动的。
这时候,可以用wx.previewImage展示大图,并引导用户长按保存。
很多困扰来自看文档中错过的一些细节。
微信小程序文档中,关于画布中draw方法的实例中,并没有很明显的突出draw的异步的特性。
所以,如果你想在画布上作图、并导出图片时,很容易写成这样:
ctx.draw()
ctx.canvasToTempFilePath({ canvasId: 'myCanvas' })这样得到的图片就是空白的,所以记得用异步方式使用draw()。
ctx.draw(true, function() {
// do some thing
})