定义一个函数
具名函数(具有名字的函数)
function函数名(形式参数1,形式参数2){
语句
return(返回值)
}
匿名函数(具名函数去掉名字就是匿名函数)
let a = function(x,y){
return x+y
}
也叫函数表达式,等号右边是函数表达式,等号左边是声明一个变量a并把它赋值
let a = function(x,y){return x+y}
fn(1,2)
会报错,fn的作用域只在等号右边
箭头函数
let f1 = x =>x*x x 输入参数,x*x 输出参数
let f2 =(x,y)=> x+y 括号不能省略
let f3 =(x,y)=>{return x+y} 花括号不能省略,有花括号就必须加return,要不然JS不知道
let f4 =(x,y)=>({name:x, age:y}) 直接返回对象JS会报错,需要加圆括号
构造函数
let f =new Function('x','y','return x+y')
所有的函数都是Function构造出来的,包括 Object Array Function
函数自身和函数调用,fn和fn()的区别
let fn=()=>console.log('hi')
fn 没有效果,因为fn没有执行
fn() 才有结果,有圆括号才是调用
let fn=()=>console.log('hi')
let fn2 = fn 只保留了函数的地址
fn2()
fn保存了匿名函数的地址,这个地址被复制给了fn2
fn2调用了匿名函数
fn和fn2都是匿名函数的引用而已,真正的函数既不是fn也不是fn2
函数的要素
调用时机
作用域
闭包
形式参数
返回值
调用栈
函数提升
arguments(除了箭头函数)
this(除了箭头函数)
调用时机(时机不同,结果不同)
let a =1
function fn(){ console.log(a)}
打印出多少?
不知道,因为还没有调用函数代码
let a =1
function fn(){ console.log(a)}
fn()
打印出多少?
1
let a =1
function fn(){ console.log(a)}
a=2
fn()
打印出多少?
2(调用函数时a=2)
let a =1
function fn(){ console.log(a)}
fn()
a=2
打印出多少?
1(先调用的函数)
let a =1
function fn(){
setTimeout(()=>
console.log(a)},)
fn()
a=2
打印出多少?
2(fn的执行时机是在a=2之后)
setTimeout过一段时间在执行,先把当前所有代码执行完,才做等一会做的事情
let i=0
for(i=0; i<6; i++){
setTimeout(()=>{
console.log(i)},0)}
打印出多少?
6个6
先执行整个循环,最后i=6,要打印6次,所以是6个6
for(let i=0; i<6; i++){
setTimeout(()=>{
console.log(i)},0)}
打印出多少?
0,1,2,3,4,5
因为JS在for和let一起用时会加东西,每次循环时会多创建一个i
作用域(每个函数都会创建一个作用域)
function fn(){let a = 1}
console.log(a)
a不存在
是不是因为fn没有执行?
就算fn执行了,也访问不到作用域里面的a
let的作用域范围:let往前找第一个花括号,往后找第一个花括号,就算let唯一一个作用域
全局变量VS局部变量
在顶级作用域声明的变量是全局变量
window的属性是全局变量,其它的都是局部变量
函数可以嵌套,作用域也可以嵌套
function f1(){
let a =1
function f2(){
let a = 2
function f3(){
console.log(a)
}
a = 22
f3()
}
console.log(a)
a = 100
f2()
}
f1()
作用域的确定跟函数执行无关,这种跟函数执行无关的作用域叫静态作用域,也叫做词法作用域
作用域的规则
如果多个作用域有同名变量a
那么查找a的声明时,就向上取最近的作用域,简称(就近原则)
查找a的过程与函数执行无关,但a的值与函数执行有关
闭包
如果一个函数用到了外部变量,那么这个函数加这个变量,就叫做闭包
左边的a和f3 组成了闭包
function f1(){
let a =1
function f2(){
let a = 2
function f3(){
console.log(a)
}
a = 22
f3()
}
console.log(a)
a = 100
f2()
}
f1()
形式参数
形式参数的意思是非实际参数,形式参数可以认为是变量声明,本质是变量声明
function add(x,y){return x+y}
x,y并不知道是多少,只是代表了这个函数,还没有赋值
其中x和y就是形式参数,因为并不是实际的参数
add(1,2)
调用add时,1和2就是实际参数,会赋值值给x,y
当我们赋值的时候,我们只是把x的地址复制到形式参数
没有什么值和地区的区别,所有东西根据内存图全部复制
function add(){
var x = arguments[0]
var x = arguments[1]
return x+y
}
arguments[]拿到所有参数
等价于上面代码,形式参数可多可少(形式参数只是给参数取名字)
function add(x){
return x+arguments[1]
}
add(1,2)=3
function add(x,y,z){
return x+y}
add(3,4) = 7
形式参数声明了可以不使用
返回值(return后面的值是返回值)
每个函数都有返回值
function hi(){ console.log('hi')}
hi()
没写return,所以返回值是undefined
function hi(){ return console.log('hi')}
hi()
返回值为console.log('hi')的值,即undefined
返回值是你在执行之后才会使用的,如果不执行就不存在返回值,函数执行完了才会返回
只有函数有返回值
1+2 返回值为3, 错的因为没有return
1+2 值为3, 值不是返回值
调用栈
JS引擎在调用一个函数前,需要把函数所在的环境push到一个数组里,这个数组叫调用栈
等函数执行执行完了,就把环境弹(pop)出来,然后return到之前的环境,继续执行后续的代码
JS每次进入一个函数都要记录回到哪,所以要把回到的地址写到栈里(压栈)
等把函数执行完,就弹栈(知道回到哪)
递归函数
阶乘
function f(n){
return n!== 1? n*f(n-1):1
}
阶加
function f(n){
return n!== 1? n+f(n-1):1
}
理解递归:n不等于1,就返回 n*(fn - 1),否则返回1
f(4)
=4*f(3)
=4*(3*f(2))
=4*(3*(2*f(1)))
=4*(3*(2*(1)))
=4*(3*(2))
=4*(6)
=24
先递进,在回归
阶乘
n!= 1 n=1
n*(n-1)! n>=1时
1!=1
2!=2*1=2
3!=3*2!=6
4!=4*3!=24
函数提升
什么是函数提升
function fn(){}
不管你把具名函数声明在哪里,它都会跑到第一行
什么不是函数提升
let fn = function(){}
这是赋值,右边的匿名函数不会提升
arguments和this
每个函数都有arguments和this,除了箭头函数没arguments和this
function fn(){
console.log(arguments)
console.log(this)
}
如何传arguments,调用fn即可传arguments
fn(1,2,3)那么arguments就是[1,2,3]的伪数组
如何传this
目前可用 fn.call(xxx,1,2,3)传this和arguments
而且xxx会被自动转化成对象
fn.call(1,2,3,4)
call会分成2段:1是this,变成对象;2,3,4是arguments,伪数组
个人结论
this是隐藏参数,this是参数,arguments普通参数
规则一:如果不给this任何条件,那么this默认指向window
规则二:如果传给this不是对象,JS会自动帮你封装成对象
function fn(){'use strict'
consloe.log(this)}
fn.call(1)
1就不会变成对象了
this
JS在每个函数里加了this,用this获取那个对象
let person={
name:'frank'
sayHi(隐藏的this){
console.log(`你好,我叫`+this.name)
}
}
person.sayHi()
相当于 person.sayHi(person)
然后person被传给this了
person是个地址,这样每个函数都能用一个this获取一个未知对象的引用
person.sayHi()会隐式的把person作为this传给sayHi,(方便shyHi获取person 对应的对象)
JS做了两件事
把this给你
把person给this
就相当于把person给你
this就是最终调用sayHi的对象
我们想让函数获取对象的引用,但是并不想通过变量名做到
JS通过额外的this做到:person.sayHi()会把person自动传给sayHi,sayHi可以通过this引用person
this的两种调用方法
person.sayHi()
会自动把person传到函数里,作为this
person.sayHi.call(person)
需要自己手动把person传到函数里,作为this
person.sayHi.call({name:1})
call后面传什么,this接什么
如果使用call传给this,那么不使用this时,需要找东西占位
function add(x,y){
return x+y
}
add.call(undefined,1,2)
add.call('hello',1,2)
array.forEach2.call(array) 用forEach2调用array
array.forEach2() 用array作为thiis调用forEach2
Array.person.forEach2=function(fn){
for(let i=0;i<this.length;i++){
fn(this[i],i,this)
}
}
遍历当前数组,当前数组是this,this可以引用未来的当前数组
this是什么
由于大家使用forEach2的时候总是会用arr.forEach2
所以arr就被自动传给forEach2了
this不一定是数组
Array.prototype.forEach2.call({0:'a',1:'b'})
this的两种使用方法
隐式传递
fn(1,2) 等价于 fn.call(undefined,1,2,)
obj.child.fn(1) 等价于 obj.child.fn.call(obj.child,1)
undefined作为this,占位
obj.child对象里面的一个属性的函数,函数前面这部分(obj.child)当做this
显示传递
fn.call(undefined,1,2)
fn.apply(undefined,[1,2])
绑定this
使用.bind可以让this不被改变
function f1(p1, p2){
console.log(this, p1, p2)
}
let f2 = f1.bind({name:'frank'})
那么 f2 就是 f1 绑定了 this 的新函数
f2()等价于f1.call({name:'frank'})
f2是f1,this的绑定版本
.bind还可以绑定其它参数
let f3 = f1.bind({name:'frank'},'hi')
f3()
等价于 f1.call({name:'frank'}, hi)
箭头函数
没有arguments和this;如果函数里面出现了this,就会当做没有出现;里面的this就是外面的this
console.log(this) window
let fn =() =>console.log(this) 声明这个函数fn
fn() 不管任何时候调用fn,this都是window
fn.call({name:'frank'}) 就算加call都没有this,还是window
箭头函数里面的this就是一个普通变量,外面是什么它是什么
详细资料点击:JavaScript 函数