You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
按照我们的思想,在打印a的时候,a根本就没声明,那应该会报错Uncaught ReferenceError: a is not defined,而实际上返回的是undefined,这就是变量声明提升。JavaScript将声明语句提到了头部,但赋值语句并没提升,所以a只声明未赋值,得到undefined
其实际代码相当于:
1 函数的5种声明
1.1 具名函数
1.2 匿名函数
1.3 具名函数赋值
1.4 window.Function
这种方式的参数全部为字符串形式,除最后一个参数是函数的主体之外,其余参数全是参数
1.5 箭头函数
2 调用函数
在JavaScript中,函数有几种调用方式
其实前两种都是语法糖,最后一种函数调用模式才是正常的调用形式
前两种都可以改写为第三种模式
3 arguments、this是个什么东西
以这个语句为例
3.1 arguments
arguments
就是由参数1
到参数n
组成的一个伪数组
,有key-value
、length
,但是其__proto__
不指向Array.prototype
3.2 this
这里只简单的讲一下
this
在函数调用中代表着什么this
其实就是参数0
,其数据类型根据参数0
数据类型的不同而不同这里要分两种情况
普通模式
和严格模式
当
参数0
为undefined
或者null
时,浏览器会将this
自动变成window
当
参数0
为基本类型时,this
会变成复杂类型,就是其__proto__
指向数据类型.protortpe
,其值为参数0
当
参数0
为简单类型的值时,this
就是简单类型数据,且值为参数0
当
参数0
为复杂类型的值时,this
就是复杂类型数据,且值为参数0
4 Call Stack/调用栈
Call Stack
是调用栈,是计算机科学中存储有关正在运行的子程序的消息的栈用一个递归函数来说下在函数执行过程中,栈内存是如何变化的
从图中可以看到,在代码执行的过程中,每一个出现的子程序都会出现在已有程序的上方。如果一个子程序执行完毕了,那么这个子程序就会从
Call Stack
中被清除,堆在上面的子程序必定会比下面的子程序先被清除栈溢出
栈内存是有限的,如果同时存在的子程序过多,超过了栈内存的承受上限,就会发生栈溢出
例如上面的递归代码,如果求
sum,call(undefined,100000)
,由于递归的原因,栈内存从下到上会储存sum,call(undefined,100000)
、sum,call(undefined,99999)
、……、sum,call(undefined,1)
,但由于栈内存无法将这些全部放下,就会发生栈溢出5 Scope/作用域
5.1概述
这里我用俗语说下,就是如果有一个层层嵌套的函数,而且在嵌套函数中定义了几个同名变量,那么在调用一个变量时,就需要了解变量的作用域来确定调用的是在哪里定义的变量
其作用域示意图表示为:
上述代码我们从上到下依次来看,看
console.log(a)
中的a
到底指的是在哪里定义的a
console.log(a)
这个
console.log(a)
是在f1
函数当中的,所以首先在f1
函数的范围中寻找,看有没有定义a
,如果没有再到它的上一级范围内去寻找- 第二个`console.log(a)` 这个`console.log(a)`是在`f2`函数当中的,所以首先在`f2`函数的范围中寻找;如果`f2`中没有定义`a`,则去`f1`中去寻找,如果还没有定义`a`,则再往上一级范围去寻找
- 第三个`console.log(a)` 这个`console.log(a)`是在全局函数当中的,所以直接在全局函数中寻找
个人总结:
在判断所调用的变量是在哪里定义时,要注意变量声明提升
5.2 变量声明提升
在JavaScript中,有一个特性叫做变量声明提升;简单的说,就是不管你在哪里声明一个变量,JavaScript总将声明提升到当前区域的头部
按照我们的思想,在打印
a
的时候,a
根本就没声明,那应该会报错Uncaught ReferenceError: a is not defined
,而实际上返回的是undefined
,这就是变量声明提升。JavaScript将声明语句提到了头部,但赋值语句并没提升,所以a
只声明未赋值,得到undefined
其实际代码相当于:
5.3 几个例子
在这里,打印
a
时,现在f1
函数中寻找a
,由于变量声明提升,其实在console.log(a)
之前是声明了a
的,只是未赋值,这样就不用到上级全局函数中去寻找a
了这里很容易将
console.log(a)
想成是打印f1
函数中的那个a
,其实不然,虽然f1
调用了f2
,但是f2
是处于全局范围内的,它在f1
之外,所以实际引用的是全局范围定义的a
点击第3个
li
时,打印 2 还是打印 6?答案是6,因为代码在运行之后,
for
循环迅速完成,此时i
早已变成了6,所以不管你点第几个li
,都会打印65.4 小心
eval
eval
接受字符串为参数,然后将字符串当作程序的代码来执行,就像真的在程序中写了代码所以这就会导致一些变量会随着
eval
的执行而被定义,不过这只在普通模式下有效,在严格模式下还是不会定义变量正是由于
eval
的存在,导致在这里将s
的字符串内容执行了,声明并定义了a = 3;
,所以此时console.log(a);
打印的是3在严格模式下,执行
eval
并未声明变量a
,所以console.log(a);
的a
是由变量声明提升所声明的a
,所以打印的是undefined
6 Closure/闭包
6.1 概念
在MDN上的定义为:闭包是函数和声明该函数的词法环境的组合。
其中函数
displayName()
可以访问到函数之外的name
值,而且name
是init()
函数生成的局部变量,并不是全局变量以我自己的理解,闭包就是函数以及这个函数能访问到的其他函数局部变量的集合
6.2 闭包的作用
闭包常用来间接访问一个变量
也就是这个变量没出现在这个函数中,但这个函数可以访问到,相对于这个函数来说,这个变量被隐藏了
就上面的代码来看,函数
displayName()
并没有任何地方声明了name
,但它就是可以访问得到这个变量让变量的值始终保存在内存中
由于垃圾回收机制,如果一个函数在调用完之后,就会被回收,下次再调用时函数声明的变量又是崭新的状态。而闭包的使用让函数声明的变量值一直存在内存中
按“直觉”来说,在调用一次函数之后,函数就会被回收,下一次再调用时,又是新的状态,但是两次调用打印出来的
n
并不相同,这也就是说,n
的值被保存了,并未被垃圾回收机制清除从代码中,
r
其实就是闭包函数f2
,f1
是f2
的父函数,而f2
被赋值给了r
这个全局变量,导致f2
一直在内存中,并未被回收,而f2
是依赖f1
存在的,这就导致f1
和f2
一直存在内存之中,并未被回收6.3 使用闭包的注意点
由于闭包会使得函数的变量一直在内存中,所以不能滥用闭包,会造成性能问题。
解决的办法是在退出函数之前,将不用的局部变量清除
闭包会在父函数外部,改变父函数内部变量的值。所以如果你把父函数当作对象使用,把闭包当作它的公用方法,把内部变量当作它的私有属性,这时一定要小心,不要随便改变父函数内部变量的值
目前了解的还不够多,如有错误之处,欢迎指出
The text was updated successfully, but these errors were encountered: