Skip to content

zhg11/note-sm

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

57 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

笔记

JavaScript自我实现系列

点击跳转


从赋值看基本类型和引用类型的区别

赋值就是把某一个值赋给变量。

我凭什么要把值赋给变量?

变量相当于名字。

拿我举例,如果我没有名字,当别人叫我帮忙的时候,就只能说:

“那个个头不高、颜值爆表、头发很硬、坐在角落的小哥哥,过来帮我一下呗!”

而有名字的情况是:

“小强快来!”

可见变量赋值的意义在于便于使唤

基本类型的赋值

基本类型的赋值,好比在每个盒子里放东西。

直接赋值

例如,

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一样,都能访问这把钥匙对应的房间了。

所以引用对象的赋值都是操作钥匙。

插播广告:{name: '小强'} == {name: '小强'}吗?

答案是否定的。

这相当于在问,这两个房子的钥匙相同吗?

这两个房子只是在装修上极其相似,我们不能通过将自己的房子布置得和邻居的房子一样、就能得到邻居家的房钥匙,

程序也是如此。

引用类型赋值面试题

例一、

var a = {n: 1}
var b = a
a.x = a = {n: 2}
console.log(a.x)    // undefined
console.log(b.x)    // {n: 2}

逐句翻译吧:

  1. var a = {n: 1}

取一个盒子名a,建一个房子,钥匙放到盒子a里面;

房子里有个盒子n,放着1。

  1. var b = a

取一个盒子名b,盒子b偷看一下盒子a,哇哦,一把钥匙,

盒子b里面也有了这把钥匙,也能去访问这个房间了。

  1. 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}

再逐句翻译:

  1. var xiaoMing = {moneyBox: 1}

小明有一把房钥匙,这个房子里有个钱柜,里面放着1元钱。

  1. var xiaoQiang = xiaoMing

小强偷偷复制了一把小明的房钥匙,从此他也可以进出小明的房子。

  1. 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的培训方式

在javascript中,Function扮演的就是校长的角色。

首先,他不想亲自去培养学生,所以他培养出了第一个老师孔仔。

Object就相当于孔仔一样可以培养出学生,即通过new Object可以创建对象。

但是,校长为了学生的多样化,又培养了语文老师、数学老师、思想品德老师等老师,而且还可以按需要培养更多老师;

同样,开发者想要创建多样化的数据、符合需要的对象,诸如StringNumberBoolean这些函数对象也必不可少的,而且也可以根据需要写各种构造函数。

可以看到,校长培训出老师,老师再培训出学生;

Function创建函数对象,函数对象再创建对象;

只不过javascript的培训方式是new

javascript的图书室

校长为了避免给每位老师买教学经验方面的书,所以创建了自己的图书室,用来让自己培养的老师共享这些资源;

而我们的Function,也并不想把那些常用的方法都复制一份,保存到ObjectStringArray...以及后来新创建的N多构造函数当中,于是新增一个属性prototype(即原型对象),把一些公共的方法存在里面;

Function让它创建出来的函数对象共享prototype的方式,就是在通过new Function创建函数对象的同时,会使

被创建的函数对象.__proto__ = Function.prototype

比如,在创建Object的时候,会使

Object.__proto__ = Function.prototype

这个场景似曾相识?

没错,这就是我小强校长在创建自己的图书室后,将图书室钥匙送给孔仔老师一把的情景。

此后,孔仔老师就可以来我的图书室,查阅教学经验相关的书籍。

同理,Object也因此可以访问Functionprototype,使用Function为他们精心准备的函数对象的方法。

验证

Object.__proto__ === Function.prototype     // true

这里的true表示的就是,钥匙已拿到,Object已拥有Functionprototype

接下来,可能会更多用到“拥有”这个词语

为什么呢?

虽然,根据开篇对引用类型的介绍,可知Object.__proto__ = Function.prototype所做的事情:

无非就是,Function把公共的方法都放在某个房间里,再将钥匙存在自己的属性prototype里;

在赋值时,就是复制了一把prototype里的钥匙,然后存在Object的属性__proto__中。

从这个过程可以看出,原型也不过是引用类型的赋值,就是通过一种联系方式间接拥有某个东西。

本篇文章开始说过,

即使是间接拥有,也是拥有。

首先,对引用类型的及其赋值的理解是必要的。

但是,在理解原型和原型链过程中,可以不必太关注引用类型赋值过程这种细节。

因为原型的目的,就是让实例们都拥有有一个公共区域,方便使用一些常用的方法。

所以类似Object.__proto__ === Function.prototype这种吓人的结构,它要表达的不过就是这个意思:

前者已经拥有了后者提供的公共区域

就像我的校长生涯那样,当我发现建图书室的作用之后,并不止为自己培养的老师建了图书室。

只要是老师,都要有一个自己的图书室,存放自己学生通常会用到的书或器材

这样,把一些学生的教材也变成共享,就又免得给每个学生都重复买教材。

也就是,不仅Function创建函数对象时,会

被创建的函数对象.__proto__ = Function.prototype

而且函数对象在创建对象时,会

被创建的对象.__proto__ = 创建对象的函数对象.prototype

总结一下,只要new的时候,就会

实例.__proto__ = 创建者.prototype

只要我创建了你,你就拥有了我的公共区域

javascript的隧道

回顾我的校长生涯,在我即将开工给我和每位老师建图书室之前,我又想到:

每个老师的图书室里的书,也有很多是重复的,不如把这些重复的都放到元老级教师孔仔的图书室吧。

可是学生只能进各自老师的图书室,怎么让他们也能进孔仔老师的图书室呢?

还记得睿智的小强校长是怎么做的吗:

在建每件图书室的同时,修一条隧道可以到达孔仔老师的图书室,即Function创建对象的同时会

被创建的函数对象.prototype.__proto__ = Object.prototype

例如

Array.prototype.__proto__ === Object.prototype      // true

没错,对于javascript来说,Object就是孔仔老师,Objectprototype就是孔仔老师的图书室。

为了减少不同函数对象的prototype的重复,我们把很通用的对象的方法都放到Objectprototype中,并通过上述方法让其它函数都的prototype都拥有它。

这样,每个对象都拥有函数的prototype,每个函数的prototype又都拥有Objectprototype,那么每个对象也都拥有Objectprototype,即对象总能找到早就给它们精心准备好的方法的;

不同原型之间这个不依不饶的关系,就是原型链。

对于学生来讲,他们可以去的办公的总和、以及先后顺序,就相当于我们的原型链。

如图

关于“隧道”的建成,还有一些重要问题:

实际上,被创建的函数对象.prototype.__proto__ = Object.prototype,并非Function做到的,而是Object做到的。

这里,Object其实是使用的这一规则:

只要我创建了你,你就拥有了我的公共区域

因为javascript的创建方式是new,要想实现被创建的函数对象.prototype.__proto__ = Object.prototype,需要先有

被创建的函数对象.prototype = new Object()

即所有对象的prototype都是Object创建的,都是Object的实例。

其它函数对象的prototype因此才能拥有Objectprototype,即实现这种效果

被创建的函数对象.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 })
  }
}

bug记录

wx.showTabBarRedDot失效

当页面处于非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百分百会关闭的情况:

小程序从前台进入后台,过了很短的一段时间后。

所以如果有处理socket异常关闭的需要,可以重点关照一下小程序onShow时的socket连接情况。

navigateTo失效或redirectTo失效

很多困扰,来自看文档中错过的一些细节。

navigateTo, redirectTo只能打开非 tabBar 页面。

switchTab只能打开 tabBar 页面。

ios保存动图

wx.saveImageToPhotosAlbum()可以实现图片的一键保存。

但是,当图片是GIF的格式时,ios保存下来的图是打死不会动的。

这时候,可以用wx.previewImage展示大图,并引导用户长按保存。

draw是异步的

很多困扰来自看文档中错过的一些细节。

微信小程序文档中,关于画布中draw方法的实例中,并没有很明显的突出draw的异步的特性。

所以,如果你想在画布上作图、并导出图片时,很容易写成这样:

ctx.draw()
ctx.canvasToTempFilePath({ canvasId: 'myCanvas' })

这样得到的图片就是空白的,所以记得用异步方式使用draw()

ctx.draw(true, function() {
    // do some thing
})

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • HTML 92.0%
  • JavaScript 8.0%