本博客介绍的内容如下:
1、函数的五种声明方式;
2、函数的name属性;
3、函数的本质;
4、Function和function的区别;
5、this和arguments;
6、call stack调用栈;
7、作用域;
8、什么是闭包;
9、逗号操作符
1、函数的五种声明方式
函数developer.mozilla.org声明函数用到关键字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
打印这个名字,会显示为函数
(2)匿名函数
function
但是匿名函数不能单独使用,如果单独使用就会报错
匿名函数需要赋值给另一个变量
var
这样就不会报错啦
(3)具名函数赋值给一个变量
var
它与直接用具名函数不赋值给一个变量的区别是函数名字y已经无法打印了,会报错。
这是JavaScript的不一致性,这需要强行记忆。
此时y的作用域被限制在自己函数里面。
(4)用window.Function函数构造函数赋值给一个变量
Functiondeveloper.mozilla.org该方法很少用。以下代码的new可以删除也是一样的效果,因为Function是复杂类型的对象,所以加不加new都不影响它是一个对象。
new
写入三个参数,而且全是字符串形式传入
如果赋值给一个变量后,传入两个参数就可以使用啦
如果return里面传入一个值,比如
var n=1
f=new Function('x','y','return x+'+n+'+y')
这样就可以把n的声明和赋值带入到函数里面,函数里面的n就会变成1,传入参数1,2后会得到4
如果把n两边的单引号变成双引号就会成为一个字符串
如果把n两边的引号去掉,那么函数里面就是n本身,但是传入参数1,2之后结果还是4
MDN上不推荐此方法来构造函数
(5)箭头创建匿名函数
箭头函数developer.mozilla.org箭头函数都是匿名的,需要赋值给一个变量
可以省略很多代码比如
sum
使用sum(1,2)就可以得到3
如果花括号{}里面只有一句话,一个return,那么就可以同时且必须同时删除花括号{}和return使用简写。比如
sum=(x,y)=>x+y
这样代码变简单很多
如果参数只有一个,比如只有一个参数n,就还可以继续简化,把小括号()也删除掉,比如
sum=n=>n*n
这样就更加简单了
箭头函数在函数和箭头之间不能换行,不然会报错
箭头函数大括号里面如果有多行,可以用分号分隔
2、函数的name属性
Function.namedeveloper.mozilla.orgname属性返回的都是以字符串形式返回的。
(1)具名函数的name就是"函数的名字"
(2)匿名函数赋值给一个变量后,它的name是这个"变量名"
(3)具名函数赋值给一个变量后,它的name属性是这个"函数的名字"
(4)用new Function构造的函数赋值给一个变量后,它的name属性是"anonymous"
anonymous这个单词就叫做匿名的意思。
3、函数的本质
函数是一种对象,就是一段能否反复调用( 调用的英语叫call)的代码块,函数还能接受不同的参数,不同的参数返回不同的值。
简单来说就是一种可以执行代码的对象,就叫做函数。
一段很长的代码来求三角形的面积,比如
我们用函数包括进来,然后传入两个参数就可以直接调用函数来求三角形面积,而且还可以反复传入参数来调用求三角形面积。
那么函数在内存中是如何存储的?是跟简单类型一样存储,还是与复杂类型对象存地址呢?
通过使用toString()可以看到他就把自己当做字符串来储存,跟对象得到的"[object Object]"不太一样。
内存图中的显示可以看到函数它的公用属性里面有一个call()方法:
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/calldeveloper.mozilla.org可以用一个普通对象来是想函数的效果,便于理解,这其中需要用到JavaScript 标准库里面的 eval()这个函数属性。
eval()是全局对象的一个函数属性。
eval()developer.mozilla.org通过eval()就直接执行获得最后的值。
如果用console.log()只能到他的字符串,说明这里并不能执行。
所以函数也属于对象,函数的调用过程就是eval()函数体的过程,也就是把函数体作为一段代码执行的过程。
因此我们引出了f.call()。
那么f(1,2)和f.call(undefined,1,2)有什么不同呢?
他的不同在于f.call(undefined,1,2)可以看出this是什么。
而他的使用方法就是第一个参数需要一个undefined,然后后面传入相应的参数,比如
4、Function和function的区别
(1)function
是关键字,类似的关键字还有var、if、else等;
ECMAScript 关键字www.w3school.com.cn JavaScript 保留关键字www.w3cschool.cn它的作用是第一章已经说到过,声明函数用到关键字function,它与var一样都是关键字,用于声明一个对象,该对象就是函数对象。
与var不同的是它只能声明函数,而var可以声明七种数据类型的变量。
(2)Function
ECMAScript Function 对象(类)www.w3school.com.cn Functiondeveloper.mozilla.org他可以作为一个全局对象,也可以作为一个构造函数,也是window全局对象可以直接访问到它——window.Function。
只是刚好Function也可以按照他的Function构造函数来构造一个函数对象。
然后用一个变量(比如var f)来引用它。
下面可加new,也可以不加new,因为他是一个复杂类型的对象。
var
前面也说明过MDN不推荐此方法来构造函数。
5、this和arguments
Arguments 对象developer.mozilla.org前面章节说到f.call(undefined,1,2)2)可以看出this和arguments是什么。
this可以得到call的第一个参数undefined;
用arguments可以得到call的第一个参数后面的参数[1,2]。
(1)arguments
Arguments 对象developer.mozilla.org第一个参数后面的会组成一个伪数组。伪数组说明见前面的博客:
bomber:JavaScript数组及部分API介绍zhuanlan.zhihu.com(2)this
函数的this关键字在 JavaScript 中的表现略有不同,此外,在严格模式和非严格模式之间也会有一些差别。
thisdeveloper.mozilla.org(2-1)this在非严格模式(一般模式)中
第一个参数是undefined或null会转换为window全局函数
undefined
null
第一个参数是数字、布尔、字符串会转换为new来包装的对象
bomber:《Global Object、公用属性(原型)、prototype和__proto__》zhuanlan.zhihu.com数字
布尔
字符串
(2-2)this在严格模式中
严格模式developer.mozilla.org放一个特定字符串语句 "use strict"或'use strict'就可以启动严格模式
该模式下面,this不会转换,call的第一个参数写的是什么,就是什么。
6、call stack调用栈
Call stackdeveloper.mozilla.org调用栈是解释器(就像浏览器中的javascript解释器)追踪函数执行流的一种机制。当执行环境中调用了多个函数时,通过这种机制,我们能够追踪到哪个函数正在执行,执行的函数体中又调用了哪个函数。
它叫做栈,在前面博客的数据结构中有说到,栈就是先进后出的。
bomber:算法和数据结构zhuanlan.zhihu.comfunction
- 每调用一个函数,解释器就会把该函数添加进调用栈并开始执行。
- 正在调用栈中执行的函数还调用了其它函数,那么新函数也将会被添加进调用栈,一旦这个函数被调用,便会立即执行。
- 当前函数执行完毕后,解释器将其清出调用栈,继续执行当前执行环境下的剩余的代码。
从图形中来看
一开始,我们得到一个空空如也的调用栈。随后,每当有函数被调用都会自动地添加进调用栈,执行完函数体中的代码后,调用栈又会自动地移除这个函数。最后,我们又得到了一个空空如也的调用栈。
简约的来说就是调用=>被调用的函数加入调用栈=>执行剩余代码=>把该函数从调用栈中清除,并且先进入调用栈的后清除。
也可以通过打印的顺序来了解
function
运行上面代码后,从代码输出也可以看到先进去的后出来
函数里面调用函数自己,就可以实现递归,也就是有多个一样的函数压入调用栈
function
输入参数就会显示结果
每台电脑的栈的都有一定的容量,如果压入的次数太多就会爆掉,就是stack overflow
比如我的电脑在9666次没有报错,但是9667次就报错了。
也有一个关于stack overflow的网站,专门用来讨论各种语言stack overflow或者编程的BUG 的问答
Stack Overflow - Where Developers Learn, Share, & Build Careersstackoverflow.com另一个C语言报错的叫做segment fault,也有一个开发者社区叫做segment fault
人类身份验证 - SegmentFaultsegmentfault.com三种调用栈的动态演示
普通调用
普通调用latentflip.com嵌套调用
嵌套调用latentflip.com递归调用
递归调用latentflip.com7、作用域
作用域属于前面算法与结构博客中的树。
(1)下面左边代码中,用图形表示的红色和蓝色框很像一个树的结构
1、如果在12行里面写了a=3,就代表在全局范围(作用域)里面写的a=3;
2、如果在3到4行之间里面插入一句a=3,就代表在f1范围(作用域)里面写的a=3。
(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赋值。
(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
从这个代码中所在的区域,可以划分为三个分区1,2,3,并且找声明都是按照往父级区域中最近原则的优先级别找声明,到最外层的全局范围(作用域),如果找到了声明就代表是该声明的赋值,如果找不到声明,就说明是给全局范围(作用域)声明并赋值。
(4)第一次变形后的代码看看执行到第五行代码的时候打印的a是什么?
var
图形的第五行
通过控制台打出来可以看到,这是的答案是undefined
其实这里需要考虑到声明提升
(5)第二次变形后,看看console.log(a)打印出应该是什么?
var
打出来可以看到结果是2
如果把函数function f4放到全局范围的下面
var
打出来可以看到结果是1
这就是前面说的树形结构的作用域,子作用域的的a只在自身范围或者父作用域有效。
(6)第三次变形后,如果f4.call()前面有一句代码把前面的var a=1 的a赋值覆盖掉了,看看console.log(a)打印出应该是什么?
var
我们可以看到这里如果被a=2覆盖,那么打印的结果是2
所以这里f4里面的console.log(a)里面的a是第一行的var a,但是它的值是后面的a=2。
跟我们用for循环的时候一样的情况:
在点击所有选项打印的i结果都是6,
因为i会声明提升为全局变量,后面的赋值会把前面的赋值覆盖,全局变量最后一个赋值就是6,那么点击所有的选项结果都是6。
for循环,在onclick之前已经循环完了,最后一个值就是6
也就是用户点击的速度肯定比电脑处理的速度要慢。因为电脑是的速度是纳秒级别
声明提升后的效果
当然这里如果改用let,那么结果就是每次点击的索引下标了,因为let作用在代码块内有效,当前的i只在本轮循环有效,所以每一次循环的i其实都是一个新的变量,所以最后输出的就是你点击的选项的索引下标。
8、什么是闭包
闭包developer.mozilla.org如果一个函数,使用了它范围(作用域)外的变量,那么(这个函数+这个变量)就叫做闭包)
详细请看方方的博客:
方应杭:「每日一题」JS 中的闭包是什么?zhuanlan.zhihu.com9、逗号操作符
逗号操作符developer.mozilla.org逗号操作符 对它的每个操作数求值(从左到右),并返回最后一个操作数的值。
更多函数相关知识可以查看
你真的懂函数吗 - 写代码啦!xiedaimala.com本文为本人的原创文章,著作权归本人和饥人谷所有,转载务必注明来源