JS函数
函数是一种特殊的对象
具名函数
- 格式
function 函数名(形式参数1,形式参数2){
语句
return 返回值
}
- 例
function fn(x,y){
return x+y
}
匿名函数
- 格式 上面的具名函数去掉函数名就是匿名函数
let a = function (x,y){
return x+y
}
上面这个格式也叫函数表达式 如果
let a = function fn(x,y){
return x+y
}
fn(1,2)
此时我们调用fn(1,2),他的结果是不等于3的。因为function是在等号的右边,他的作用域就只有最右边;他的正确的表示应该是把右边这个函数的表达式赋值给a变量,所以我们调用a(1,2)他就等于3.
箭头函数
- 格式
let f1= x =>x*x
- 例
let f = 1=>1*1
返回1
let f2= (x,y)=>x+y//固定的括号不能省
- 例
let f2 = (1,2)=>1+2
返回 3
let f3 = (x,y)=>{console.log('hi');return x+y}
- 例
let f3 = (1,2)=>{
console.log('hi');
return 1+2
}//注意:当函数箭头有两个语句时,要加上花括号;且语句一和语句二用‘;’隔开,return也不能省。
let f4= (x,y)=>({name:'x',age:'y'})//当声明一个函数,给他一个函数值,他返回一个对象时,一定要用括号将花括号围起来。
- 例
let f4= (justin,23)=>({name:'justin',age:23})
let f = new Function('x','y',console.log(\'hi\');'return x+y')
- 例
let f = new Function('1','2'console.log(\'hi\');'retuan 1+2')
返回 hi 3
上面这种函数基本没有人用,但是可以让你清楚的知道函数是由谁构造的。
- 总结
所有的函数都是有Function构造的,包括Function、Array、Object等等。
函数自身VS函数调用(fnVSfn())
函数自身
- 代码
let fn = ()=>console.log('hi')
输入fn不会有任何效果,因为没有执行;但我们输入fn(),就会打出‘hi’这里是fn()就是调用。
- 例
let fn = ()=>console.log('hi')
let fn2=fn
fn2()//打出‘hi’
解析:fn不是这个函数,他只是一个变量,但他保存了后面匿名函数的地址,然这个地址被复制给fn2;当我们输入fn()时,就是调用匿名函数,所以会打出结果。fn和fn2都是只引用了匿名函数而已,他们俩并不是函数本身,真正的函数是变量fn后面箭头函数表达式。
函数的要素
- 调用时机
- 作用域
- 闭包
- 形式参数
- 返回值
- 调用栈
- 函数提升
- argument
- this
调用时机
- 例
let a = 1
function fn(){
console.log(a)
}
fn()//打印出1
let a=1
function fn(){
setTimeout(()=>{console.log(a)};0)
fn()
a=2
}//打印出的值是2
解析:这里需要注意的是setTimeout有等一会的意思;如(你正在打游戏吃鸡,你妈叫你吃饭。你肯定回答:好!马上来!但实际你需要打完这把游戏才去吃饭。同理,这里也是这样,相当于等a=2后,再调用fn())
- 再来一个例子
let i = 0
for(i=0;i<6;i++){
setTimeout(1)=>{
console.log(i)
},0)}//打印出6个6
解析:这里是一个循环语句,再加上setTimeout有等会儿执行的意思,所以就先执行循环,等循环执行完再开始打印。(此时,循环执行完后i已经是6 ,再打印6次)
- 对立实例
for(let i=0;i<6;i++){
setTimeout(){
console.log(i)
},0)
}//打印出0、1、2、3、4、5
解析:当for循环let一起引用的时候,会把i复制一份;所以,这个例子中每当for循环一次就把i复制一份,再打出来。
作用域
function fn(){
let a=2
}
console.log(a)//a不存在
解析:这里的a是一个局部变量,他的作用域就只有{}内,所以不管你是执行fn()还是console.log(a)都是无法访问到a的。
全局变量VS局部变量
- 全局变量 在顶级作用域里声明声明的变量是全局变量
- 例
let a =2
function fn(){
console.log(a)
}//打印出2
1.1 Window的属性是全局变量
- 例
Window.b =3
function fn(){
console.log(b)
}
fn()//打印出3
当然,也可以把window放在函数内,因为他是window属性。如:
function fn(){
window.b=3;
console.log(b)
}//打印出3
总之,window属性下的变量想放哪里就放那里,都是可以被调用得到地。
- 局部变量
- 例
function fn(){
let b=1;
console.log(b)
}
fn()//打印出1
function fn(){
let b =1
}
console.log(b)
fn()//打印出undefined
console.log(b)//报错,未定义
所以,此时的b只作用于{}内,出了这里谁也调用不了,这就叫局部变量。
- 局部作用域可套局部作用域(函数可嵌套)
function f1(){
let a=1
function f2{
let a=2
console.log(a)
}
console.log(a)
a=3
f2()
}
f1()//1
解析:函数f2的console.log打印出来的a=2,因为他的作用域就是f2内;f1的打印出来的a=1,他的作用域为f1;至于a=3,因为他在console.log(a)后,所以无意义,所以f1的值也是1.
- 作用域规则 如果多个作用域有同变量a,那么查找a的声明时,就向上取最近的作用域,简称就近原则。查找a的过程与函数执行无关,但a的值与函数执行有关。
闭包
- 例
function fn(){
let a =1
function fn2(){
let a =2
function fn3(){
console.log(a)
}
a=22
fn3()
console.log(a)
a=100
fn2()
}
}
let a=2
function fn3(){
console.log(a)
}//构成一个闭包
- 定义 如果函数用到外部的变量,那么函数加这个变量就叫闭包。
形式参数
形式参数的意思就是非实际参数
- 例
function add(x,y){
return x+y
}
//其中x和y就是形参,因为并不是实际的参数
//调用add时,1和2实际参数,会被赋值给x和y
- 形参可认为是变量的声明
function add(){
var x =arguments[0]
var y = arguments[1]
return x+y
}
返回值
每个函数都有返回值
- 例
function hi(){console.log('hi')}
hi()//返回值就是undefined
- 函数执行完后才会返回,只有函数有返回值
调用栈
- 什么是调用栈 JS引擎在调用一个函数前,需要把函数所在的环境push到一个数组里,这个数组就叫做栈;等函数执行完后,就会把环境弹(pop)出来,然后return到之前的环境,继续执行后续代码。
递归函数
- 例
阶乘
function fn(n){
return n===1? 1:n*fn(n-1)
}
- 理解递归
fn(4)=4*fn(3)
=4*(3*fn(2))
=4*3*(2*fn(1))
=4*3*2*1
=24
//递归就是先运算步骤,再加总最后的答案;运算步骤层层递进称为递,加总就是归总
递归函数的调用栈
- 递归函数的调用栈很长
- 压多少次栈,弹多少次栈
- 调用栈最长的有多少
- Chrome 12578
- Firefox 26773
- Node 12536
爆栈
如果调用栈中压入的帧过多,程序就会崩溃
测试大法
function computeMaxCallstackSize(){
try{
return 1+computeMaxCallstackSize();}catch(e)
{//报错 stack overflow}
return
}
}
函数提升
function fn(){}
不管你把具名函数声明在哪里,他都会跑到第一行
- 李 如果我们直接a(1,2)会报错,那么
a(1,2)
function fn(x,y){return x+y}//返回值是3
解析:因为函数永远会跑到第一行,不管你把它放在哪里
什么是函数提升
let fn = function (){}这是赋值,右边的匿名函数声明不会提升
- 例
fn(1,2)
let fn=function(x,y){return x+y}//报错,因为他不能提升,且这个匿名函数是赋值
//正确的写法
let fn = function (x,y){return x+y}
fn(1,2)
argumrnts和this(每个函数都有,除了箭头函数)
arguments
- 例
function fn(){
console.log(arguments)
}
fn()
返回的是一个length为0的主对象;下面再传一个值fn(1),返回一个数组[1,length:1];再传一个fn(1,'b'),返回一个数组[1,'b',length:2];在这个时候很多人认为arguments是一个包含了所有参数的数组(只答对一半,他应该是一个伪数组)
this
- 例
function fn(){
console.log(this)
}
fn()//返回一个小写的window
//如果不给任何条件,this默认指向window
如何上传this
目前可以用fn.call(xxx,1,2,3)传this和arguments,而且xxx 会被自动转化成对象(这就是JS的糟粕)
- 传一个fn(1) 返回Number{1} 注:如果你传的这个值不只是对象,JS会自动将它封装成对象;一般我们都不用。 那么我们怎么才可以让传一个fn(1),返回一个数字呢?
function fn(){
'use strict'//意思是不要乱加东西
console.log(this)
}
但是,事宜愿为;一般情况都不加‘use strict’,所以我们传fn(1)会打出对象{1}
- 例
function fn(){
console.log(arguments)
console.log(this)
}
fn(1,2,3,4)//返回Number{1} arguments(3) [2,3,4]
所以,我们还可以得出一个结论(this 是隐藏参数,arguments是普通参数) 下面我们讨论一下this的理解
let person={
name:'frank',
sayHi(){}console.log(`你好,我叫`+person.name)}
}
分析:我们可以直接用保存了对象地址的变量获取'name'我们把这种方法叫引用。 引用:如果一个变量保存了一个对象的地址。
JS如何获取哪位还未知的对象的?
在每一个函数加this
- 例
let person = {
name:'frank'
sayHi(){console.log(`你好,我叫`+this.name)}
}
person.sayHi()相当于person.sayHi(person)//表示把person这个对象的地址传给person,person再传给this,这样每个函数都能用this获取一个未知对象的引用 person.sayHi()会隐藏地把person作为this传给sayHi(),方便SayHi获取person对应的对象。
- 关于两张调用方法 小白用法 person.sayHi()会自动把person传到函数里,作为this 大师调用方法 person.sayHi.call(person)需要自己手动把person传到函数里,作为this。
- 例
let person ={
name:'frank'
sayHi(){ console.log(this.name)}
}
person.sayHi.call({name:'1'})//我传什么,this就是什么
打印出1
通常情况下,我们都只会使用对象内的。(如:person.sayHi.call(person))
- 箭头函数没有arguments和this,及时函数中有也是外面的this
console.log(this)//是window
let fn=()=>console.log(this)//打印出是window
fn()//是window
就算你通过fn.call({name:'frank'}),他依然还是window。因为对于箭头函数就没有this这一说法。