JavaScript 数组拼接打印_JavaScript核心之函数

本博客介绍的内容如下:

1、函数的五种声明方式;

2、函数的name属性;

3、函数的本质;

4、Function和function的区别;

5、this和arguments;

6、call stack调用栈;

7、作用域;

8、什么是闭包;

9、逗号操作符


1、函数的五种声明方式

函数​developer.mozilla.org
ad1b536e571e59aeb3c4322119419168.png

声明函数用到关键字function,它与var一样都是关键字,用于声明一个对象,该对象就是函数对象。

与var不同的是它只能声明函数,而var可以声明七种数据类型。

从console.log()的源代码来了解一个函数。console.log()其实就是随便打印一个信息,然后返回undefined

console

此时如果,输入console.log(1)就会打印出1,然后返回undefined。该函数永远返回的是undefined,它的返回值和打印出的信息没有任何关系

所有函数都会有一个return undefined,返回值为undefined,return如果不写,函数会默认自动加上return undefined

函数可以不传入参数,但是必须有return,忘记写return,默认也会加上return undefined。

(1)具名函数

function 

打印这个名字,会显示为函数

5faba57e7226d765d7aa47069802d57d.png

(2)匿名函数

function 

但是匿名函数不能单独使用,如果单独使用就会报错

f41f4c581bba007c24c74f0c474ac7a9.png

匿名函数需要赋值给另一个变量

var 

这样就不会报错啦

6d6616f6de9879e0e9654b65e76fffef.png

(3)具名函数赋值给一个变量

var 

它与直接用具名函数不赋值给一个变量的区别是函数名字y已经无法打印了,会报错。

这是JavaScript的不一致性,这需要强行记忆。

6068016db6e9d2fb9d5276731af00e54.png

此时y的作用域被限制在自己函数里面。

c6ef3359e23825e415cb54eb9c6ec21b.png

(4)用window.Function函数构造函数赋值给一个变量

Function​developer.mozilla.org
ad1b536e571e59aeb3c4322119419168.png

该方法很少用。以下代码的new可以删除也是一样的效果,因为Function是复杂类型的对象,所以加不加new都不影响它是一个对象。

new 

写入三个参数,而且全是字符串形式传入

b041ee45e72f885d834fbb70e42c8048.png

如果赋值给一个变量后,传入两个参数就可以使用啦

e8c60fa4f18d2d287a0c5b4150e08763.png

如果return里面传入一个值,比如

var n=1
f=new Function('x','y','return x+'+n+'+y')

这样就可以把n的声明和赋值带入到函数里面,函数里面的n就会变成1,传入参数1,2后会得到4

4088766c6c5c0c3996107dc6b83ae0ba.png

如果把n两边的单引号变成双引号就会成为一个字符串

05bb82a40ade2dd374740306ad48ed69.png

如果把n两边的引号去掉,那么函数里面就是n本身,但是传入参数1,2之后结果还是4

8a17853abaa88b23823923e396c6d94a.png

MDN上不推荐此方法来构造函数

a05568701cf0eaa010e83b3a1c3478ed.png

(5)箭头创建匿名函数

箭头函数​developer.mozilla.org
ad1b536e571e59aeb3c4322119419168.png

箭头函数都是匿名的,需要赋值给一个变量

可以省略很多代码比如

sum

使用sum(1,2)就可以得到3

2eaa4a0912dd1bb2419592932fdc2b15.png

如果花括号{}里面只有一句话,一个return,那么就可以同时且必须同时删除花括号{}和return使用简写。比如

sum=(x,y)=>x+y

这样代码变简单很多

c8b4e91052f15efeee03280e2e1cc0e2.png

如果参数只有一个,比如只有一个参数n,就还可以继续简化,把小括号()也删除掉,比如

sum=n=>n*n

这样就更加简单了

8d57d3511db361146c7862a1f11589ae.png

箭头函数在函数和箭头之间不能换行,不然会报错

6c5bf0b5d43041d6828321d03ef3e852.png

箭头函数大括号里面如果有多行,可以用分号分隔

196c7d9baba3527cb7ef18cf182771c3.png

2、函数的name属性

Function.name​developer.mozilla.org
ad1b536e571e59aeb3c4322119419168.png

name属性返回的都是以字符串形式返回的。

(1)具名函数的name就是"函数的名字"

0bb248f31d9b246acf7f83e0a4d67a3c.png

(2)匿名函数赋值给一个变量后,它的name是这个"变量名"

c8bb6168feeeec75510ad49dbeae6961.png

(3)具名函数赋值给一个变量后,它的name属性是这个"函数的名字"

23794b24562b07c6f7441c27ff19523f.png

(4)用new Function构造的函数赋值给一个变量后,它的name属性是"anonymous"

85e9b68ea7374b01ca12b13a41ade980.png

anonymous这个单词就叫做匿名的意思。


3、函数的本质

函数是一种对象,就是一段能否反复调用( 调用的英语叫call)的代码块,函数还能接受不同的参数,不同的参数返回不同的值。

简单来说就是一种可以执行代码的对象,就叫做函数。

一段很长的代码来求三角形的面积,比如

61b3cdfc6f41fd0f7a8bf62c4d094bbf.png

我们用函数包括进来,然后传入两个参数就可以直接调用函数来求三角形面积,而且还可以反复传入参数来调用求三角形面积。

d2a408eb6ab1535284daf893cbf28963.png

那么函数在内存中是如何存储的?是跟简单类型一样存储,还是与复杂类型对象存地址呢?

通过使用toString()可以看到他就把自己当做字符串来储存,跟对象得到的"[object Object]"不太一样。

da918042dc2a5a0762ad397b7ee8603b.png

内存图中的显示可以看到函数它的公用属性里面有一个call()方法

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/call​developer.mozilla.org

ed68f54e21000bd8c782fa12e15f05e6.png

可以用一个普通对象来是想函数的效果,便于理解,这其中需要用到Java​Script 标准库里面eval()这个函数属性。

eval()是全局对象的一个函数属性。

eval()​developer.mozilla.org
ad1b536e571e59aeb3c4322119419168.png

通过eval()就直接执行获得最后的值

b40779424f00b9b62256e866042a5f66.png

如果用console.log()只能到他的字符串,说明这里并不能执行。

f63704a5e52ab6fa33fa4f449df93e36.png

所以函数也属于对象,函数的调用过程就是eval()函数体的过程,也就是把函数体作为一段代码执行的过程

3aaef6bf37af0c8fedb9df0b6f3b63cc.png

因此我们引出了f.call()。

那么f(1,2)和f.call(undefined,1,2)有什么不同呢?

他的不同在于f.call(undefined,1,2)可以看出this是什么。

而他的使用方法就是第一个参数需要一个undefined,然后后面传入相应的参数,比如

87c882adfa9b339c215a2499315577da.png

4、Function和function的区别

(1)function

关键字,类似的关键字还有var、if、else等;

ECMAScript 关键字​www.w3school.com.cn JavaScript 保留关键字​www.w3cschool.cn
77fed3bd8da1d247902b99a245d1b2d7.png

它的作用是第一章已经说到过,声明函数用到关键字function,它与var一样都是关键字,用于声明一个对象,该对象就是函数对象。

与var不同的是它只能声明函数,而var可以声明七种数据类型的变量。

(2)Function

ECMAScript Function 对象(类)​www.w3school.com.cn Function​developer.mozilla.org
ad1b536e571e59aeb3c4322119419168.png

他可以作为一个全局对象,也可以作为一个构造函数,也是window全局对象可以直接访问到它——window.Function。

只是刚好Function也可以按照他的Function构造函数来构造一个函数对象

然后用一个变量(比如var f)来引用它。

下面可加new,也可以不加new,因为他是一个复杂类型的对象。

var 

前面也说明过MDN不推荐此方法来构造函数。

a05568701cf0eaa010e83b3a1c3478ed.png

5、this和arguments

Arguments 对象​developer.mozilla.org
ad1b536e571e59aeb3c4322119419168.png
this​developer.mozilla.org
ad1b536e571e59aeb3c4322119419168.png

前面章节说到f.call(undefined,1,2)2)可以看出this和arguments是什么。

this可以得到call的第一个参数undefined;

用arguments可以得到call的第一个参数后面的参数[1,2]。

9984a8de92da778c4c42b7b93cdbcfc3.png

(1)arguments

Arguments 对象​developer.mozilla.org
ad1b536e571e59aeb3c4322119419168.png

第一个参数后面的会组成一个伪数组。伪数组说明见前面的博客:

bomber:JavaScript数组及部分API介绍​zhuanlan.zhihu.com

5e8195f02963d6945fa6cea1be620335.png

(2)this

函数的this关键字在 JavaScript 中的表现略有不同,此外,在严格模式和非严格模式之间也会有一些差别。

this​developer.mozilla.org
ad1b536e571e59aeb3c4322119419168.png

(2-1)this在非严格模式(一般模式)中

第一个参数是undefined或null会转换为window全局函数

undefined

ccb5d6338065863e7a26254dd73b874b.png

null

d0c238e3906992e61341cdd6eda90f3d.png

第一个参数是数字、布尔、字符串会转换为new来包装的对象

bomber:《Global Object、公用属性(原型)、prototype和__proto__》​zhuanlan.zhihu.com

数字

c0c9e95daea301690cc41746c636bc35.png

布尔

721a1cb64a6ec7b351ffdf1fc431ef4b.png

字符串

779fec2c3105633e1b865bc8dfcf9a36.png

(2-2)this在严格模式中

严格模式​developer.mozilla.org
ad1b536e571e59aeb3c4322119419168.png

放一个特定字符串语句 "use strict"或'use strict'就可以启动严格模式

该模式下面,this不会转换,call的第一个参数写的是什么,就是什么。

64b2b20a0bfa5b0decfbe18f1be7320e.png

6、call stack调用栈

Call stack​developer.mozilla.org
ad1b536e571e59aeb3c4322119419168.png

调用栈是解释器(就像浏览器中的javascript解释器)追踪函数执行流的一种机制。当执行环境中调用了多个函数时,通过这种机制,我们能够追踪到哪个函数正在执行,执行的函数体中又调用了哪个函数。

它叫做栈,在前面博客的数据结构中有说到,栈就是先进后出的。

bomber:算法和数据结构​zhuanlan.zhihu.com
function 
  • 调用一个函数,解释器就会把该函数添加进调用栈并开始执行
  • 正在调用栈中执行的函数还调用了其它函数,那么新函数也将会被添加进调用栈,一旦这个函数被调用,便会立即执行
  • 当前函数执行完毕后,解释器将其清出调用栈,继续执行当前执行环境下的剩余的代码

从图形中来看

d266d3d482dc25bae1bb40bf5a012513.png

一开始,我们得到一个空空如也的调用栈。随后,每当有函数被调用都会自动地添加进调用栈,执行完函数体中的代码后,调用栈又会自动地移除这个函数。最后,我们又得到了一个空空如也的调用栈。

简约的来说就是调用=>被调用的函数加入调用栈=>执行剩余代码=>把该函数从调用栈中清除,并且先进入调用栈的后清除

也可以通过打印的顺序来了解

function 

运行上面代码后,从代码输出也可以看到先进去的后出来

60cd9568300beedd83c51bc0c8fd71a2.png

函数里面调用函数自己,就可以实现递归,也就是有多个一样的函数压入调用栈

function 

输入参数就会显示结果

007b189c334da2e124d1acfada505866.png

每台电脑的栈的都有一定的容量,如果压入的次数太多就会爆掉,就是stack overflow

比如我的电脑在9666次没有报错,但是9667次就报错了。

ddf926b97e48c2517d196aa5a1fb71d9.png

也有一个关于stack overflow的网站,专门用来讨论各种语言stack overflow或者编程的BUG 的问答

Stack Overflow - Where Developers Learn, Share, & Build Careers​stackoverflow.com
9d853161c3fe2f0b9dc400e3a1fb0482.png

另一个C语言报错的叫做segment fault,也有一个开发者社区叫做segment fault

人类身份验证 - SegmentFault​segmentfault.com

三种调用栈的动态演示

普通调用

普通调用​latentflip.com

嵌套调用

嵌套调用​latentflip.com

递归调用

递归调用​latentflip.com

7、作用域

作用域属于前面算法与结构博客中的树。

(1)下面左边代码中,用图形表示的红色和蓝色框很像一个树的结构

1、如果在12行里面写了a=3,就代表在全局范围(作用域)里面写的a=3;

2、如果在3到4行之间里面插入一句a=3,就代表在f1范围(作用域)里面写的a=3。

c40b2129cdf817890adb20cec1a25507.png

(2)如果f1中没有写var

1、如果把第三行的var删除掉,只留下a=2,那么这里的a是它的树形结构中父范围(作用域)里面的var a,因为a=2优先表示他是一个赋值操作,把3赋值给a,这里的a是全局范围(作用域)里面var声明的a;

2、a=2优先会找当前所在的f1范围(作用域)里面有没有a的声明,也就是var a,这里很明显没有,所以就去它的父范围(作用域)找a的声明,也就是var a,父范围(作用域)里面有a的声明,所以是给全局范围(作用域)的a赋值。

65aa8c4a37d6cf184cd1ae1766ee1f2c.png

(3)如果f1中没有写var,并且全局作用域中也没有声明a,也就是var a

1、如果把第三行的var删除掉,只留下a=2,并且全局范围(作用域)中没有var a,那么这里的a是它的树形结构中全局范围(作用域)里面的声明a并赋值为2

2、a=2优先会找当前所在的f1范围(作用域)里面有没有a的申明,也就是var a,这里很明显没有,所以就去它的父范围(作用域)找a的声明,也就是var a,这里的父范围(作用域)是全局范围(作用域),里面也没有a的声明,所以是给全局范围(作用域)的a声明并赋值为2

70d5706834f91c82205e6aebad753136.png

从这个代码中所在的区域,可以划分为三个分区1,2,3,并且找声明都是按照往父级区域中最近原则的优先级别找声明,到最外层的全局范围(作用域),如果找到了声明就代表是该声明的赋值,如果找不到声明,就说明是给全局范围(作用域)声明并赋值

f9e8a1c94e8d3207237e92150859644a.png

(4)第一次变形后的代码看看执行到第五行代码的时候打印的a是什么?

var 

图形的第五行

45924380b99a1a6ecc8cc97408a28f8f.png

通过控制台打出来可以看到,这是的答案是undefined

a0c4c38bdd20f046534ff5b0ae1608a1.png

其实这里需要考虑到声明提升

37b7e582e4cc69f0a15501463151e144.png

(5)第二次变形后,看看console.log(a)打印出应该是什么?

var 

打出来可以看到结果是2

dd80050cf6d0afe195a029d5b43aa16c.png

如果把函数function f4放到全局范围的下面

var 

打出来可以看到结果是1

118962f6d1baa9c9043b9bbb226f8273.png

这就是前面说的树形结构的作用域,子作用域的的a只在自身范围或者父作用域有效

(6)第三次变形后,如果f4.call()前面有一句代码把前面的var a=1 的a赋值覆盖掉了,看看console.log(a)打印出应该是什么?

var 

我们可以看到这里如果被a=2覆盖,那么打印的结果是2

28a28b98188b69e627382869358454dc.png

所以这里f4里面的console.log(a)里面的a是第一行的var a,但是它的值是后面的a=2

跟我们用for循环的时候一样的情况:

f193fdb1832fad498b52ccebc20ef44b.png

在点击所有选项打印的i结果都是6,

f187640de44b8a568525dbab7136a0bd.png
https://www.zhihu.com/video/1104117865623502848

因为i会声明提升为全局变量,后面的赋值会把前面的赋值覆盖,全局变量最后一个赋值就是6,那么点击所有的选项结果都是6。

for循环,在onclick之前已经循环完了,最后一个值就是6

也就是用户点击的速度肯定比电脑处理的速度要慢。因为电脑是的速度是纳秒级别

769702b454b2f664fee5094236695edd.png

声明提升后的效果

f74b06f0da159e7850dd7af522bd98d7.png
https://www.zhihu.com/video/1104118185065795584

当然这里如果改用let,那么结果就是每次点击的索引下标了,因为let作用在代码块内有效,当前的i只在本轮循环有效,所以每一次循环的i其实都是一个新的变量,所以最后输出的就是你点击的选项的索引下标。

581b71aa8d044a86b4139af30396a722.png
https://www.zhihu.com/video/1104119313140645888

8、什么是闭包

闭包​developer.mozilla.org
ad1b536e571e59aeb3c4322119419168.png

如果一个函数,使用了它范围(作用域)外的变量,那么(这个函数+这个变量)就叫做闭包)

d87db08ce3bda738161a53ed3fcf81e6.png

详细请看方方的博客:

方应杭:「每日一题」JS 中的闭包是什么?​zhuanlan.zhihu.com
a2f8b22e6d0746c055c2bc4fb03e7c18.png

9、逗号操作符

逗号操作符​developer.mozilla.org
ad1b536e571e59aeb3c4322119419168.png

逗号操作符 对它的每个操作数求值(从左到右),并返回最后一个操作数的值。

b96d78abad8a14c2320ef7f39dcbcd30.png

更多函数相关知识可以查看

你真的懂函数吗 - 写代码啦!​xiedaimala.com

本文为本人的原创文章,著作权归本人和饥人谷所有,转载务必注明来源

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值