JavaScript(第五节续)

229 阅读9分钟

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局部变量

  1. 全局变量 在顶级作用域里声明声明的变量是全局变量
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属性下的变量想放哪里就放那里,都是可以被调用得到地。

  1. 局部变量
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
  • 函数执行完后才会返回,只有函数有返回值

调用栈

  1. 什么是调用栈 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
     //递归就是先运算步骤,再加总最后的答案;运算步骤层层递进称为递,加总就是归总
递归函数的调用栈
  • 递归函数的调用栈很长
  • 压多少次栈,弹多少次栈
  • 调用栈最长的有多少
  1. Chrome 12578
  2. Firefox 26773
  3. 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这一说法。