23·灵魂前端工程师养成-JavaScript函数

2022-10-31 18:00:10 浏览数 (1)

  • 函数是对象

  • 函数的要素
  • call指定this
  • 箭头函数
  • 立即执行函数

-曾老湿, 江湖人称曾老大。


-多年互联网运维工作经验,曾负责过大规模集群架构自动化运维管理工作。 -擅长Web集群架构与自动化运维,曾负责国内某大型金融公司运维工作。 -devops项目经理兼DBA。 -开发过一套自动化运维平台(功能如下): 1)整合了各个公有云API,自主创建云主机。 2)ELK自动化收集日志功能。 3)Saltstack自动化运维统一配置管理工具。 4)Git、Jenkins自动化代码上线及自动化测试平台。 5)堡垒机,连接Linux、Windows平台及日志审计。 6)SQL执行及审批流程。 7)慢查询日志分析web界面。


函数是对象


定义函数1

代码语言:javascript复制
//具名函数
function 函数名(形式参数1,形式参数2){
  语句
  return 返回值
}

//匿名函数,也叫做函数表达式
let f = function(x,y){return x y}
代码语言:javascript复制
function fn(x,y){
    return x y
}

let a = function(x,y){
    return x y
}

//合并
let f = function fn(x,y){
    return x y
}

//如果此时调用fn(1,2),是否能成功?

很明显,没有成功,为什么呢?如果使用上面的方式定义函数,fn只能作用在等于号的右边,出了作用域就失败。

代码语言:javascript复制
f(1,2)
3


定义函数2

代码语言:javascript复制
//箭头函数
let f1 = x => x * x

f1(2)
4

let f2 = (x,y) => x * y

f2(2,3)
6

let f3 = (x,y) => {
    console.log('hi')
    return x * y //如果使用了大括号,那么return就不能省略
}

f3(6,8)
hi
48

//直接返回一个对象的时候,必须用括号,把对象括起来
let f4 = x => ({name:x})

f4('zls')
{name: "zls"}


//构造函数(没人用)
let fn1 = new Function('x','y','console.log('hi');return x y')

fn1(2,3)
hi
5

函数的要素

每个函数都有以下几点要素: 1.调用时机 2.作用域 3.闭包 4.形参 5.返回值 6.调用栈 7.函数提升 8.arguments(除了箭头函数) 9.this(除了箭头函数)


调用时机

如果下面的几段代码,都能完美的答出正确答案,那么你对调用时机,已经掌握的很好了。

代码语言:javascript复制
//请问下面的函数执行结果是什么?
let a = 1
function fn(){
  console.log(a)
}

//请问下面的函数执行结果是什么?
let a = 1
function fn(){
  console.log(a)
}

fn()

//请问下面的函数执行结果是什么?
let a = 1
function fn(){
  console.log(a)
}

a = 2
fn()

//请问下面的函数执行结果是什么?
let a = 1
function fn(){
  console.log(a)
}

fn()
a = 2

//请问下面的函数执行结果是什么?
let a = 1
function fn(){
  setTimeout(()=>{
    console.log(a)
  },0)
}

fn()
a = 2

//请问下面的函数执行结果是什么?
let i = 0
for(i=0;i<6;i  ){
  setTimeout(()=>{
    console.log(i)
  },0)
}

//请问下面的函数执行结果是什么?
for(let i=0;i<6;i  ){
  setTimeout(()=>{
    console.log(i)
  },0)
}

作用域

每个函数都会创建一个作用域

代码语言:javascript复制
// 请问打印出的结果是多少?
function fn(){
  let a = 1
}
console.log(a)

// 请问打印出的结果是多少?
function fn(){
  let a = 1
}
fn()
console.log(a)

所以a就是一个局部变量

在顶级作用域中声明的变量,是全局作用域 挂在window上的属性,都是全局作用域

代码语言:javascript复制
// 请问打印出的结果是多少?
function f1(){
  let a = 1
  
  function f2(){
    let a = 2
    console.log(a)
  }
  console.log(a)
  a = 3
  f2()
}

f1()

如果多个作用域,有同名的变量a 1.向上取最近的作用域 2.作用域和函数的执行无关(静态作用域 也叫:词法作用域 )

代码语言:javascript复制
// 请问打印出的结果是多少?
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()

闭包

讲完了...

代码语言:javascript复制
// 请问打印出的结果是多少?
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 和 f3 组成了闭包


参数

形参的意思:形式参数,非实际参数

代码语言:javascript复制
function add(x,y){
  return x y
}

其中x和y就是形参,因为并不是实际的参数

代码语言:javascript复制
add(1,2)

调用add时,1和2是实际参数,会被赋值给x和y

形参可认为是变量的声明,上面的代码等价于下面的代码

代码语言:javascript复制
function add(){
  var x = arguments[0]
  var y = arguments[1]
  return x y
}

function add(x){
  return x   arguments[1]
}

形参可多可少


返回值

每个函数都有返回值

代码语言:javascript复制
//那么请问,下面这个函数,返回值是什么?
function hi(){
  console.log('hi')
}

hi()

//那么请问,下面这个函数,返回值是什么?
function hi(){
  return console.log('hi')
}

hi()

注意: 1.函数执行完了后,才会返回 2.只有函数有返回值

代码语言:javascript复制
1 2返回值是3   ???  这个 说法是错误的

1 2的值是3


就好比,linux命令:ifconfig 它的返回值是什么?是0
但是他的结果,是好几个IP地址...

递归,调用栈,爆栈

什么是调用栈?

JS引擎在调用一个函数之前,需要把函数所在的环境push到一个数组里,这个数组叫做调用栈 等函数执行完了,就会把这个环境pop出来 然后return到之前的环境,继续执行后续代码

代码语言:javascript复制
console.log(1)
console.log('1 2的结果为'   add(1,2))
console.log(2)

递归函数

代码语言:javascript复制
//阶乘
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

//压栈次数 11434,使用递归函数测调用栈

function computeMaxCallStackSize(){
  try{
    return 1   computeMaxCallStackSize();
  }catch (e){
    //报错就说明 stack  overflow了
    return  1;
  }
}

函数提升

什么是函数提升?

代码语言:javascript复制
function fn(){}

不管把这个函数声明在哪里,它都会跑到第一行

代码语言:javascript复制
add(1,2)


add(1,2)

function add(x,y){
  return x y
}
3

这段代码,可以看出,就算是先调用函数,但是如果使用function add(){}的方式,就可以调用到这个函数,因为这样函数会自动跑到第一行。

如果同时有一个变量和一个函数怎么办?

代码语言:javascript复制
let add = 1
function add(){}

//会发现上面这段代码,报错,这就是因为我们喜欢使用let而不是使用var

//但是如果用var会出现什么问题?
var add = 1
function add(){}

add
1

//function是会提升函数,但是let赋值,永远不会提升
add(1,2)
let add = function(x,y){ return x y}


arguments和this

JS 三座大山的第二座:this

代码语言:javascript复制
function fn(){
  console.log(arguments)
}

从上面我们看出,arguments是一个数组...

No ! ! ! ! !

代码语言:javascript复制
fn(1,'b')

不包含数组的原型链的数组都是伪数组,这个可以看出,他没有push pop等数组的方法...

this

JS的千古奇案,如果不给任何条件,this默认指向window

代码语言:javascript复制
function fn(){
  console.log(this)
}

如果 要指定this只能通过call来指定

代码语言:javascript复制
 fn.call(1)
 //如果 传的this不是对象,JS会自动帮你封装成对象

但是我们又不想让他给我瞎封装,我们不想要这个功能怎么办?

那么就只能,在生命函数的时候,加上'use strict'

代码语言:javascript复制
function fn(){
  'use strict'
  console.log(this)
}
fn.call(1)

但是,没有人写代码,都会写'use strict'

call传参会被分成两段,第一段是this 剩下的是arguments

代码语言:javascript复制
function fn(){
    console.log(arguments)
    console.log(this)
}

fn.call(1,2,3,4)

假设没有this

代码语言:javascript复制
let person = {
  name: 'zls',
  sayHi(){
    console.log(`你好,我叫:`   person.name)
  }
}

如果用class,这样我们就没有办法调用person

我们需要得到一个对象,如何在没有对象名字 的时候,拿到那个对象呢?

土办法,用参数

代码语言:javascript复制
//对象中
let person = {
  name: 'zls',
  sayHi(p){
    console.log(`你好,我叫:`   p.name)
  }
}

person.sayHi(person)

代码语言:javascript复制
//类中
class Person{
    constructor(name){ this.name = name }
    sayHi(p){console.log(`你好,我叫:`   p.name)}
}

谁会用这种方法,,,,python就用了...

代码语言:javascript复制
class Person:
  def __init__(self,name):
    self.name = name
  def sayHi(self):
    print('你好,我叫:'   self.name)
    
person = Person('zls')
person.sayHi()

特点: 每个函数 都接收一个额外的 self 这个 self 就是传进来的对象 只不过 Python 会偷偷帮你传对象 person.sayHi() 等价于 person.sayHi(person) person 就被传给 self 了

所以...JS没有走Python的路,他选择了另一种,更难的路...这就是第二座大山this

使用this获取那个未来的对象,JS在每个函数中都加了this

代码语言:javascript复制
let person = {
  name: 'zls',
  sayHi(-this-){
    console.log(`你好,我叫:`   this.name)
  }
}

person.sayHi()
你好,我叫:zls

代码语言:javascript复制
//小白调用方法,会自动把person传到函数里,作为this
person.sayHi()

//大师调用方法,需要自己手动把person传到函数里,作为this
person.sayHi.call(person)

let person = {
    name: 'zls',
    sayHi(){console.log(this.name)}
}

person.sayHi.call({name:1})
1

call指定this


回顾之前的例子

代码语言:javascript复制
// 没有指定this
function add(x,y){
  return x y
}

add.call(undefined,1,2)
add.call('zls',1,2)
add.call('fuck',1,2)

两种传递方式

1.隐式传递

代码语言:javascript复制
fn(1,2) //等价于 fn(undefined,1,2)
obj.child.fn(1) //等价于 fn(obj.child,1)

2.显示传递

代码语言:javascript复制
fn.call(undefined,1,2)
fn.apply(undefined,[1,2])

绑定this

使用bind可以让this不被改变

代码语言:javascript复制
function f1(p1,p2){
  console.log(this,p1,p2)
}

let f2 = f1.bind({name: 'zls'})
//那么 f2 就是 f1 绑定了 this 之后的新函数

f2() //等价于 f1.call({name: 'zls'})

.bind还可以绑定其他参数

代码语言:javascript复制
let f3 = f1.bind({name: 'zls'},'hi')
f3() //等价于 f1.call({name: 'zls'},hi)

箭头函数

箭头函数中,没有 argumentsthis,JS最新版,把这俩东西干掉了... 父爱

立即执行函数

我们以前想要声明一个局部变量

代码语言:javascript复制
var a = 1
function fn(){
  var a = 2
}
console.log(a)

代码语言:javascript复制
//声明 一个匿名函数,直接调用
function(){
  var a = 2
  console.log(a)
}()


  function(){
  var a = 2
  console.log(a)
}()
2

- function(){
  var a = 2
  console.log(a)
}()
2


//最终,JS想要生成一个局部变量,使用立即执行函数
! function(){
  var a = 2
  console.log(a)
}()
2
true


//但是在新版函数中,我们只需要一个代码块
{
  let a = 10
  console.log(a)
}
console.log(a)
10

0 人点赞