创建函数
函数是一种特殊的函数
// 具名函数function fn(x, y) {return x + y}// 匿名函数function (x, y) {return x + y}// 箭头函数let a = x => x * xlet a2 = (x, y) => {console.log('hi!');return x + y}let a3 = x => ({name: x})
函数的要素
调用时机
let i = 0for(i = 0; i<6; i++){setTimeout(()=>{console.log(i)},0) // 马上处理完循环 再执行 console.log(i)}// 会打印 6 个 6
我们先了解setTimeout()方法:它是设置一个定时器,该定时器在定时器到期后执行一个函数或指定的一段代码。setTimeout()是异步任务 for循环是同步任务,只有先把同步任务执行完在执行异步任务。这里就是先把循环执行完 ,执行完循环后的 i = 6 ,然后再执行setTimeout里面的箭头函数。打印出i 也就是6 。也就是打印出6个6。
让他打印 0、1、2、3、4、5
执行下面代码
for( let i = 0; i<6; i++){setTimeout(()=>{console.log(i)},0)}
在for循环中使用let的情况下,由于块级作用域的影响,导致每次迭代过程中的 i 都是独立的存在。也就是说这6次循环i独立出了 0、1、2、3、4、5、6这7个数 。
其他方法
function fn1() {for (var i = 0; i < 6; i++) {function fn2(i) {setTimeout(() => {console.log(i)}, 0)}fn2(i);}}
我的理解如下这里fn2引用了外面函数的变量形成了闭包,在闭包的状态下使得变量i始终保存在内存中,每一次调用都是在上一次调用的基础上进行计算。
作用域
JavaScript 只有两种作用域:一种是全局作用域,变量在整个程序中一直存在,所有地方都可以读取;另一种是函数作用域,变量只在函数内部存在。
在顶级作用域申明的变量是全局变量
window的属性是全局变量
其他的都是局部变量
局部变量
function fn(x){let a = 1x=a}console.log(a);// a 会报错 a is not defined
全局变量
var a = 1;function f() {console.log(a);}f()// 1
作用域与函数执行无关的是静态作用域
var a = 1;var x = function () {console.log(a);};function f() {var a = 2;x();}f() // 1
函数x是在函数f外部声明的,所以他的作用域绑定外层 函数x 的内部变量是不会在f函数内部取值。
闭包
闭包简单理解成“定义在一个函数内部的函数”。
如果一个函数用到了外部的变量,那么这个函数加这个变量就叫做闭包。
正常情况下,函数外部无法读取函数内部声明的变量。如果出于种种原因,需要得到函数内的局部变量。正常情况下,这是办不到的,只有通过变通方法才能实现。那就是在函数的内部,再定义一个函数。
父对象的所有变量,对子对象都是可见的,反之则不成立。
function f1(){let a = 1function f2(){let a = 2function f3(){console.log(a) // 这里的a 调用了外面的a}a = 22f3()}console.log(a)a = 100f2()}f1()// 1// 22
上面代码只解释以下 f2 和 f3 ;函数f3在函数f2里面 这时 f2 内部的所有局部变量对f3是可见的。
既然f3可以读取f2的局部变量,那么只要把f3作为返回值,我们不就可以在f1外部读取它的内部变量。
闭包的最大用处有两个,一个是可以读取外层函数内部的变量,另一个就是让这些变量始终保持在内存中,即闭包可以使得它诞生环境一直存在。
function fn(item) {return function () {return item++;};}var inc = fn(5);inc() // 5inc() // 6inc() // 7
上述代码 item是fn 的内部变量 ,在闭包的状态下被保留 ,每一次调用都是在上一次调用的基础上进行计算。闭包inc使得函数fn的内部环境,一直存在。所以,闭包可以看作是函数内部作用域的一个接口。
形式参数
函数运行的时候,有时需要提供外部数据,不同的外部数据会得到不同的结果,这种外部数据就叫参数。
function add(x, y){return x+y}// 其中 x 和 y 就是形参,因为并不是实际的参数add(1,2)// 调用 add 时,1 和 2 是实际参数,会被赋值给 x y
形参的本质就是变量申明
返回值
每个函数都有返回值 执行完才有返回值
没有写return 就会返回 undefined
调用栈
什么是调用栈
- JS 引擎在调用一个函数前
- 需要把函数所在的环境 push 到一个数组里
- 这个数组叫做调用栈
- 等函数执行完了,就会把环境弹(pop)出来
然后 return 到之前的环境,继续执行后续代码

爆栈
如果调用栈中压入的帧过多,程序就会崩溃
函数名提升
f()function fn(){}// 不管你把具名函数声明在哪里,它都会跑到第一行
表面上,上面代码好像在声明之前就调用了函数f。但是实际上,由于“变量提升”,函数f被提升到了代码头部,也就是在调用之前已经声明了。但是,如果采用赋值语句定义函数,JavaScript 就会报错。
f();var f = function (){};// TypeError: undefined is not a functionvar f;f();f = function () {};
上面代码第二行,调用f的时候,f只是被声明了,还没有被赋值,等于undefined,所以会报错。
arguments(除了箭头函数)
arguments 是一个伪数组
由于 JavaScript 允许函数有不定数目的参数,所以需要一种机制,可以在函数体内部读取所有参数。这就是arguments对象的由来。
this(除了箭头函数)
如果不给this任何条件 this会默认指向window
function fn(){console.log(this)}fn()// 输出 Window {window: Window, self: Window, document: document, name: "", location: Location, …}
如果传的这个this不是对象那个JS会自动封装成一个对象
目前可以用 fn.call(xxx, 1,2,3) 传 this 和 arguments
function fn(){'use strict' ; // 让this 不要乱来 开启严格模式 这里就不会变成对象console.log(this)}
this是隐藏参数
arguments是普通参数
let person = {name: 'frank',sayHi(this){console.log(`你好,我叫` + this.name)}}person.sayHi()//相当于person.sayHi(person)//person.sayHi()会隐式地把 person 作为 this 传给 sayHi//然后 person 被传给 this 了(person 是个地址)//这样,每个函数都能用 this 获取一个未知对象的引用了
this就是调用函数的对象
.call
function add(x,y){return x+y}add.call(null,1,2)
为什么要多写一个 undefined
- 因为第一个参数要作为 this
- 但是代码里没有用 this
- 所以只能用 undefined 占位
- 其实用 null 也可以
Array.prototype.forEach = function(){console.log(this);}let arr = [1,2,3]arr.forEach.call(arr) // 这里的arr就是this// forEach 源代码 遍历当前数组Array.prototype.forEach = function(fn){for(let i = 0;i<this.length;i++){fn(this[i],i)}}arr.forEach.call(arr.(item)=> conaole.log(item))
箭头函数
console.log(this) // windowlet fn = () => console.log(this)fn() // window
立即执行函数
!function () { /* code */ }();~function () { /* code */ }();-function () { /* code */ }();+function () { /* code */ }();
