草庐IT

关于 JS 函数的一切

hhsk 2023-03-28 原文

本文基于: Bilibili - 自由的加百利

前置条件:

  • 需掌握函数的编写、传参、返回、调用
  • 理解作用域、掌握定时器的用法
  • 知道引用类型和基本数据类型的区别
  • 知道函数也是引用类型
  • 听说过同步异步的概念
  • 了解类和对象的关系

匿名函数

来看一下一个函数的基本属性:

匿名函数的自运行

我们可以将一个普通函数去掉它的名字,这样就成功的创建了一个匿名函数,并且编译器不会报错。

那么这个函数既然没有名字,我们又该怎么调用它呢?这时只需要使用一个小括号包裹住整个函数,再在函数体的末尾添加一个小括号就可以在创建函数之后立即执行这个函数。

这种写法,也叫作 匿名函数的自运行

其与直接在外部书写函数体内部的语句相比,优点就是不会造成变量污染,会在匿名函数内形成一个 封闭的作用域

小括号的作用

在匿名函数的外部加上一个小括号,实际的作用是 将该函数的声明变成了一个优先计算的表达式

( function(){...} )()

而表达式的运算结果就是这个 匿名函数 本身。拿到了函数本身之后,就可以在其后面加上一个小括号来调用它了。

把函数变成表达式?

既然小括号的作用是将函数的声明变成表达式,那么在函数周围加上运算符会不会有同样的效果呢?

+function(){...}()
!function(){...}()
~function(){...}()
void function(){...}()
delete function(){...}()

以上的几种写法都可以成功执行匿名函数,而且使用 +function(){...}() 这种方式执行函数自运行的效率是最高的。

递归函数

递归函数 是指一个函数直接或间接的调用自身,并在特定的情况下结束并放回运行结果

这里我们举一个 阶乘 的例子:

function F(N) {
  return N * F(N - 1);
}

表面看上去,这个函数可以接收一个参数,并计算出这个数的阶乘。但是仔细想想就会发现不对劲,当 N = 1 时函数并没有停止自身的继续传递,也就是说这个函数没有停止条件,最终便会陷入一个死循环。结果就是 会在某一时刻,大量的函数将内存空间占满导致内存溢出。

也就是说我们上面写的这个函数,只有 没有

改造递归

我们尝试改变一下上面的 递归函数
首先要弄清楚,我们需要计算的是一个数 它的阶乘是多少。计算一个数字的阶乘便是让这个数每次乘以比他自身小 1 的数,直到乘到1。(说得不是很清楚,大家自行理解)

那么关键点就在于这个 直到

我们不能让它无止境的传递下去,在上面的例子中,参与递归的 N 为 1 时还在继续向内传递,0, -1, -2, -3... 我们所要做的就是当函数传递到 N = 1 时停止向内传递,直接返回 1 自身,将其自己交给外部的函数来调用,代码更改如下:

function F(N) {
  if (N == 1) return 1;
  return N * F(N - 1);
}

上面 if 语句的作用是:当 N 为 1 时,直接返回 1

这时运行一下就会发现,函数不报错了,而且也得到了我们想要的结果。

回调函数

回调函数,并不是指一种特殊的函数,而是指函数的使用方式

看一下下面的代码:

function f1(){
  console.log(111);
}
function f2(){
  console.log(222);
}
f1();
f2();

输出结果的顺序自然是先输出 111,再输出 222

但是如果我们给 f1() 添加一个定时器呢?

function f1(){
  setTimeout(function(){
    console.log(111);
  }, 1000)
}
function f2(){
  console.log(222);
}
f1();
f2();

这时便会先输出 222,一秒后输出 111。这种含有异步操作的函数就被称为 异步函数 ,异步函数最大的特点就是 后续的代码不需要排队,异步函数时可以和后续的代码并行的。f1() 就是一个典型的异步函数,你无法知道 f1()f2() 哪一个会先结束。

回调函数引出

那么在有异步函数的情况下,如果我希望先输出 111,再输出222,要怎么做呢?

目前看来,唯一的办法是 把函数 f2() 放在 f1() 的内部调用

function f1(){
  setTimeout(function(){
    console.log(111);
    f2();
  }, 1000)
}
function f2(){
  console.log(222);
}
f1();

假设有这样一个场景,项目组里有小白、小黄、小绿三个人,有一个工具函数 getToken()

function getToken(){
  //异步函数......
}

它是一个异步函数,大家都在使用这个函数完成自己的业务,并且每个人都希望在 getToken() 结束后执行自己的代码,于是它们将函数写成了下面这样:

但是这种写法显然是错误的,因为异步函数保证不了函数的执行顺序。那么现在只能想办法将自己所写的函数放在异步函数内部,才有机会在其后面执行。

首先,我们给 getToken() 函数增加一个参数 callback

function getToken(callback){
  //异步函数......
}

之后,三个人的代码就可以改成这样:

把自己的函数传进去,最后在 getToken() 的最后调用这个 callback

function getToken(callback){
  //异步函数......
  callback();
}

现在,所有人的代码都会在异步函数最后执行,这极大的提高了代码的可复用性,降低了开发维护的成本。

这种函数调用的方式就叫回调

字面意思就是:把自己的函数交给别人,回头再调。

构造函数

  • 这一节需要理解 什么是面向对象

一个函数除了可以被当作函数,还可以被当作 class

function fn(){

}
let obj = new fn();
console.log( typeof obj );

我们可以直接使用 new 关键字来声明一个对象,这个时候,我们就说 fn() 是一个构造函数

那么 fn() 明明是一个空函数,这个对象是怎么来的呢?

构造函数的执行流程

问题的关键就在于这个 new 关键字。当你调用函数时在前面加上了 new 关键字,浏览器就会启动 构造函数 的执行流程:

function fn(){
  this = {}
  // 创建一个空对象,将其保存在this关键字中
  
  ...... //your code

  return this;
}
let obj = new fn();

当然了,上面部分代码是不可见的。一个函数到底是普通函数还是构造函数,取决于你来怎么使用它。

但是通常,按照习惯,我们会将构造函数的首字母大写,普通函数的首字母小写。也就是说,如果你看到一个函数的首字母是大写的,在绝大多数的时候,它不应该被直接调用。

function User() {
  ......
}

let user = User();  ×
let user = new User();  √

在最新版的 JavaScript 已经支持了 class 关键字,你可以像 Java 一样定义一个类,并通过构造方法来生成对象。

闭包函数

function a(){
  let x = 1;
  function b(){
    console.log(x);
  }
}

函数 b() 是一个定义在函数 a() 内部的函数,所以其可以访问到变量 x ,变量 x 相对于函数 b() 来说就是一个全局变量。

如果我们把函数 b() 作为函数 a() 的返回值:

function a(){
  let x = 1;
  return function b(){
    console.log(x);
  }
}
let c = a();
c();

我们已知,函数 c() 就是函数 b() ,有由于函数 c() 是全局变量,因此,相当于在全局范围调用了函数 b() ,打破了函数 b() 只能在局部使用的限制,最终我们打印出了变量 x

在这里,函数 a() 所形成的作用域,叫做 闭包,函数 b() 被称作 闭包函数

函数的柯里化

这一节来源于知乎:https://zhuanlan.zhihu.com/p/163838720#:~:text=函数柯里化,就是,后,才执行原函数

function add(a, b) {
  return a + b
}

function curry(fn) {
  return function (a) {
    return function (b) {
      return fn(a, b)
    }
  }
}
let fn  = curry(add)(1)(2)

有关关于 JS 函数的一切的更多相关文章

  1. ruby - 在没有 sass 引擎的情况下使用 sass 颜色函数 - 2

    我想在一个没有Sass引擎的类中使用Sass颜色函数。我已经在项目中使用了sassgem,所以我认为搭载会像以下一样简单:classRectangleincludeSass::Script::FunctionsdefcolorSass::Script::Color.new([0x82,0x39,0x06])enddefrender#hamlengineexecutedwithcontextofself#sothatwithintemlateicouldcall#%stop{offset:'0%',stop:{color:lighten(color)}}endend更新:参见上面的#re

  2. ruby-on-rails - 在 ruby​​ 中使用 gsub 函数替换单词 - 2

    我正在尝试用ruby​​中的gsub函数替换字符串中的某些单词,但有时效果很好,在某些情况下会出现此错误?这种格式有什么问题吗NoMethodError(undefinedmethod`gsub!'fornil:NilClass):模型.rbclassTest"replacethisID1",WAY=>"replacethisID2andID3",DELTA=>"replacethisID4"}end另一个模型.rbclassCheck 最佳答案 啊,我找到了!gsub!是一个非常奇怪的方法。首先,它替换了字符串,所以它实际上修改了

  3. ruby - 在 Ruby 中有条件地定义函数 - 2

    我有一些代码在几个不同的位置之一运行:作为具有调试输出的命令行工具,作为不接受任何输出的更大程序的一部分,以及在Rails环境中。有时我需要根据代码的位置对代码进行细微的更改,我意识到以下样式似乎可行:print"Testingnestedfunctionsdefined\n"CLI=trueifCLIdeftest_printprint"CommandLineVersion\n"endelsedeftest_printprint"ReleaseVersion\n"endendtest_print()这导致:TestingnestedfunctionsdefinedCommandLin

  4. ruby - 在 Ruby 中按名称传递函数 - 2

    如何在Ruby中按名称传递函数?(我使用Ruby才几个小时,所以我还在想办法。)nums=[1,2,3,4]#Thisworks,butismoreverbosethanI'dlikenums.eachdo|i|putsiend#InJS,Icouldjustdosomethinglike:#nums.forEach(console.log)#InF#,itwouldbesomethinglike:#List.iternums(printf"%A")#InRuby,IwishIcoulddosomethinglike:nums.eachputs在Ruby中能不能做到类似的简洁?我可以只

  5. C51单片机——实现用独立按键控制LED亮灭(调用函数篇) - 2

    说在前面这部分我本来是合为一篇来写的,因为目的是一样的,都是通过独立按键来控制LED闪灭本质上是起到开关的作用,即调用函数和中断函数。但是写一篇太累了,我还是决定分为两篇写,这篇是调用函数篇。在本篇中你主要看到这些东西!!!1.调用函数的方法(主要讲语法和格式)2.独立按键如何控制LED亮灭3.程序中的一些细节(软件消抖等)1.调用函数的方法思路还是比较清晰地,就是通过按下按键来控制LED闪灭,即每按下一次,LED取反一次。重要的是,把按键与LED联系在一起。我打算用K1来作为开关,看了一下开发板原理图,K1连接的是单片机的P31口,当按下K1时,P31是与GND相连的,也就是说,当我按下去时

  6. ruby-on-rails - 将字符串转换为 ruby​​-on-rails 中的函数 - 2

    我需要一个通过输入字符串进行计算的方法,像这样function="(a/b)*100"a=25b=50function.something>>50有什么方法吗? 最佳答案 您可以使用instance_eval:function="(a/b)*100"a=25.0b=50instance_evalfunction#=>50.0请注意,使用eval本质上是不安全的,尤其是当您使用外部输入时,因为它可能包含注入(inject)的恶意代码。另请注意,a设置为25.0而不是25,因为如果它是整数a/b将导致0(整数)。

  7. ruby - 在 ruby​​ 中使用 .try 函数和 .map 函数 - 2

    我需要从json记录中获取一些值并像下面这样提取curr_json_doc['title']['genre'].map{|s|s['name']}.join(',')但对于某些记录,curr_json_doc['title']['genre']可以为空。所以我想对map和join()使用try函数。我试过如下curr_json_doc['title']['genre'].try(:map,{|s|s['name']}).try(:join,(','))但是没用。 最佳答案 你没有正确传递block。block被传递给参数括号外的方法

  8. ruby-on-rails - 关于 Ruby 的一般问题 - 2

    我在我的rails应用程序中安装了来自github.com的acts_as_versioned插件,但有一段代码我不完全理解,我希望有人能帮我解决这个问题class_eval我知道block内的方法(或任何它是什么)被定义为类内的实例方法,但我在插件的任何地方都找不到定义为常量的CLASS_METHODS,而且我也不确定是什么here,并且有问题的代码从lib/acts_as_versioned.rb的第199行开始。如果有人愿意告诉我这里的内幕,我将不胜感激。谢谢-C 最佳答案 这是一个异端。http://en.wikipedia

  9. ruby - 是否可以从也在该模块中的类内部调用模块函数 - 2

    在这段Ruby代码中:ModuleMClassC当我尝试运行时出现“'M:Module'的未定义方法'helper'”错误c=M::C.new("world")c.work但直接从另一个类调用M::helper("world")工作正常。类不能调用在定义它们的同一模块中定义的模块函数吗?除了将类移出模块外,还有其他解决方法吗? 最佳答案 为了调用M::helper,你需要将它定义为defself.helper;结束为了进行比较,请查看以下修改后的代码段中的helper和helper2moduleMclassC

  10. ruby - 将运算符传递给函数? - 2

    也许这听起来很荒谬,但我想知道这对Ruby是否可行?基本上我有一个功能...defadda,bc=a+breturncend我希望能够将“+”或其他运算符(例如“-”)传递给函数,这样它就类似于...defsuma,b,operatorc=aoperatorbreturncend这可能吗? 最佳答案 两种可能性:以方法/算子名作为符号:defsuma,b,operatora.send(operator,b)endsum42,23,:+或者更通用的解决方案:采取一个block:defsuma,byielda,bendsum42,23,

随机推荐