函数也是一种对象
具名函数
function 函数名(形式参数1,形式参数2){
语句
return 返回值
}
匿名函数
- 上面的具名函数去掉函数名就是匿名函数
let a = function(){}函数仍为匿名函数,只不过变量a存了匿名函数的地址- 也叫做函数表达
- 考点
let add = function fn(){
return 1
}
//将具名函数赋值给变量add,此时只能通过add调用
fn()
//使用函数名调用会报错
箭头函数
let f1 = x => x+1
//参数只有一个,语句也只有一句可省略括号,
//return及花括号
f1(1) //2
let f2 = (x,y) =>{
let temp = x+y+1
return temp
}
f2(1,2) //4
//参数大于一个时需要使用括号包起来
//语句大于一句时,需要{}括起来且手动return设定返回值
let f3 = x=>{name:'ben'} //{}将视为代码块而不是对象
改写为:
let f3 = x=>({name:'ben'})
//可返回一个对象
构造函数
let fn = new Function('x','y','return x+y')
//没有人使用的语法,但是对了解函数有帮助
//所有函数都是由Function构造出来的
//包括Object,Array,Function
fn和fn()区别
- fn是函数本身,fn()是调用函数fn
let fn = ()=>1
let fn2 = fn
fn2()
//解析步骤
fn存了匿名函数的地址
fn2也存了匿名函数的地址
fn()会调用匿名函数
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
let a = 1
function fn(){
setTimeout(()=>{
console.log(a)
},0)
}
a=2
fn()
//请问打印出什么
//打印出2
调用时机写了一篇相关博客,可以参考 Javascript调用时机
作用域
- 每个函数都会创建一个作用域
function fn(){
let a = 0
}
console.log(a)
//报错,无法访问到fn函数作用域里的a
//let的作用域只在最近的花括号包围中
全局变量和局部变量
- 在顶级作用域声明的是全局变量
- window的属性是全局变量
- 其他都是局部变量
- 函数可以嵌套
function f1(){
let a = 1
function f2(){
let a = 2
function f3(){
let a = 3
console.log(a)
}
f3()
}
f2()
console.log(a)
}
f1()
//打印f3函数作用域中的a,和f1函数作用域中的a
//3 1
作用域规则
如果多个作用域有同名变量a
- 那么查找a的声明时,就向上取最近的作用域
- 简称就近原则
- 查找a的过程与函数无关
- 但a的值y与函数执行有关
闭包
- 如果一个函数用到了外部的变量,那么这个函数加这个变量就叫做闭包
- let a和f3组成了闭包
function f1(){
let a = 1 //a和f3组成了闭包
function f3(){
console.log(a)
}
f3()
}
f1()
参数
形式参数 就是非实际参数
function fn(x){
return x
}
//x就是形式参数,并不是实际的参数
fn(1)
//调用fn时,1是实际参数,会被赋值给x
//形参可认为是变量声明
function fn(){
var x = arguments[0]
return x
}
let o = { name :'mike' }
function f(obj){
obj.name = 'ben'
return obj
}
f(o)
o //{name:'ben'}
//参数为对象时,传入的为对象的地址
形参可多可少
function fn(a){
return a + arguments[1]
}
fn(1,2)
//3
function fn2(a,v,b){
return a+v
}
fn2(1,2)
//3
//JavaScript的语法比较随意
返回值
- 每一个函数都有返回值
- 没写return,会返回undefined
- 只有执行函数后才存在返回值,若不执行不存在返回值
- 只有函数才有返回值 就是return后面的值
调用栈
什么是调用栈
- JS引擎在调用一个函数前
- 需要把函数所在的环境push(压栈)到一个数组里
- 这个数组就叫做调用栈
- 等函数执行完了,就会把环境pop(弹栈)出来
- 然后return到之前的环境,继续执行后续代码
function add(x,q){
return x+q
}
console.log(1+add(1,2))
//console.log的所在环境压栈,进入函数
//add的所在环境压栈,进入函数
//执行完add函数后弹栈,回到add的所在环境,也就是console.log函数中
//执行完console.log函数后打印4,弹栈回到console.log的所在环境,也就是顶级作用域
递归函数
乘阶
function f(n){
return n!==1?n * f(n-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
//先递进再回归
//f(10000) 就要压10000次栈
栈有多大?
function f(n){
return n!==1?n + f(n-1):1
}
f(20000) //爆栈
//VM189:1 Uncaught RangeError: Maximum call stack size exceeded
//可用此函数测试不同环境下的调用栈有多大
function computeMaxCallStackSize(){
try{
return 1 + computeMaxCallStackSize()
}catch(e){
return 1
}
}
函数提升
- 不管把具名函数放到哪里,都会跑到第一行
a()
function a(){
return 1
}
//执行后返回1
let b = 1
function b(){}
//-------相当于以下代码
function b(){}
let b = 1
//使用let会报错,不允许重复声明
var a = 1
function a(){}
a //1 因为函数会跑到第一行,a的值后续才被赋值为1
let m = function(){}
//这是赋值,不是变量提升
this和arguments (箭头函数没有)
arguments
function a(){
console.log(arguments)
}
a() //输出一个伪数组
a(1,2,3) //arguments就是包含[1,2,3]的伪数组
this
function b(){
console.log(this)
}
b() //window 如果不给任何条件,this默认指定为window
//可以用call传入this和arguments,第一个参数为this,后面的为arguments
function c(){
console.log(this)
console.log(arguments)
}
c.call(1,2,3)
//Number对象 如果传入的this不是对象,js会自动封装成对象
//包含[2,3]的伪数组
this是隐藏的参数,arguments是普通参数
假设没有this
let obj = {
name : 'ben'
sayName(){
console.log('my name is : '+ obj.name)
}
}
obj.sayName() //my name is : ben
//我们可以用直接保存了对象地址的变量获取'name',这种方式叫做引用
//如果obj改名了,函数就挂了
//sayName函数可能在另一个文件中
//需要一种方式拿到对象的引用
//土办法,用参数,python用了这种方式
let obj = {
name : 'ben'
sayName(o){
console.log('my name is : '+ o.name)
}
}
obj.sayName(obj) //my name is : ben
js在每个函数中加了this
- 用this可以获取还未知道名字的对象
let obj = {
name : 'ben'
sayName(){
console.log('my name is : '+ this.name)
}
}
obj.sayName()
//相当于
obj.sayName(obj) //obj的地址被传给了this
//这样每个函数都能用this获取一个未知对象的引用了
obj.sayName()会隐式的把obj作为this传给sayName,this就是最终调用sayName的对象
两种调用
- obj.sayName()
- 会自动的把obj传到函数里,作为this
- obj.sayName.call(obj),如果this不重要可用null或undefined占位,一定要传入this,apply和caal使用一致,入参改为数组形式
- 手动的将obj传到函数里,作为this
重写forEach
Array.prototype.forEachTest = function(fn){
for(let i = 0;i<this.length;i++){
fn(this[i],i)
}
}
[1,2,3].forEachTest(function(value){
console.log(value)
})
[1,2,3].forEachTest.call([1,2,3],function(value){
console.log(value)
})
//输出123
绑定this
function f1(p1,p2){
console.log(this)
}
let f2 = f1.bind({name:'ben'}) //指定this为{name:'ben'}
//f2相当于就是f1绑定了this的新函数
f2() //等价于f1.call({name:'ben'})
箭头函数没有arguments和this
- 里面的this就是外面的this
console.log(this) //window
let fn()=()=>{console.log(this)}
fn() //window
fn.call({name:'ben'}) //call指定也没用
let b = 2
let fn2 = ()=>{
console.log(b)
}
//如同b的值,是外面的值
let fn3 = ()=>{
console.log(arguments)
}
fn3() //报错,因为没有arguments
立即执行函数
只有js才有的东西,目前用的比较少,作用在不想生成全局变量
//语法前面需要有操作符(!,-,+,())
! function(){
var a = 2
}()
新版js造局部变量
{
let a = 1
}
console.log('1')
(function(){
var a = 2
}())
//因为console.log返回值为undefined,所以相当于undefined(function(){})