关于 JS 函数的一切
本文基于: 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 函数的一切的更多相关文章
- 3.3 js函数
1.函数语法: 函数声明的方式:function 函数名(参数1,参数2-){//函数体;}函数调用:函数名(参数1,参数2-); 函数内不一定都指定返回值. 如果需要指定返回值,可用 return ...
- Js函数function基础理解
正文:我们知道,在js中,函数实际上是一个对象,每个函数都是Function类型的实例,并且都与其他引用类型一样具有属性和方法.因此,函数名实际上是指向函数对象的指针,不与某个函数绑定.在常见的两种定 ...
- js函数表达式和函数声明的区别
我们已经知道,在任意代码片段外部添加包装函数,可以将内部的变量和函数定义"隐 藏"起来,外部作用域无法访问包装函数内部的任何内容. 例如: var a = 2; function ...
- 通用js函数集锦<来源于网络> 【二】
通用js函数集锦<来源于网络> [二] 1.数组方法集2.cookie方法集3.url方法集4.正则表达式方法集5.字符串方法集6.加密方法集7.日期方法集8.浏览器检测方法集9.json ...
- 通用js函数集锦<来源于网络/自己> 【一】
通用js函数集锦<来源于网络/自己>[一] 1.返回一个全地址2.cookie3.验证用户浏览器是否是微信浏览器4.验证用户浏览器是否是微博内置浏览器5.query string6.验证用 ...
- 100多个基础常用JS函数和语法集合大全
网站特效离不开脚本,javascript是最常用的脚本语言,我们归纳一下常用的基础函数和语法: 1.输出语句:document.write(""); 2.JS中的注释为//3.传统 ...
- JS函数
1.document.write(""); 输出语句2.JS中的注释为//3.传统的HTML文档顺序是:document->html->(head,body)4.一个浏 ...
- js函数和运算符
函数是由事件驱动或者它被调用时执行可重复使用的代码块. <script> function myFunction(){ Alert(“hello World!”): } </scri ...
- JavaScript学习03 JS函数
JavaScript学习03 JS函数 函数就是包裹在花括号中的代码块,前面使用了关键词function: function functionName() { 这里是要执行的代码 } 函数参数 函数的 ...
- JSF页面中使用js函数回调后台bean方法并获取返回值的方法
由于primefaces在国内使用的并不是太多,因此,国内对jsf做系统.详细的介绍的资料很少,即使有一些资料,也仅仅是对国外资料的简单翻译或者是仅仅讲表面现象(皮毛而已),它们的语句甚至还是错误的, ...
随机推荐
- exp解析
1 #pragma once 2 #include <string> 3 #include <functional> 4 #include <type_traits> ...
- 记一次NACOS开放公网访问导致服务器被挖矿的解决流程 [kdcflush] acosd
前言 事情的起因是这样的,昨天领导找到我说服务器内存满了,影响其他程序正常运行了,让我把测试服务器上之前启动的六个JAVA程序停一下,接着我就登上服务器执行docker compose down把服务 ...
- mysql执行步骤口诀:发连缓分析,优化执行器
mysql执行步骤口诀:发连缓分析,优化执行器 mysql执行步骤: 1.我们在客户端发起一个SQL的查询: 2.连接器判断用户登录以及用户权限: 3.缓存命中,走缓存(mysql缓存不是redis缓 ...
- 【Mycat】01 概述
什么是Mycat? 数据库中间件 中间件:是一类连接软件组件和应用的计算机软件,以便于软件各部件之间的沟通. 例子:Tomcat,web中间件. 数据库中间件:连接java应用程序和数据库 为什么要用 ...
- 【SpringBoot】整合Swagger 接口文档
前言 可能运用的开发模式: SSM -> SpringMVC + Spring + Mybatis SSMP -> SpringMVC + Spring + MybatisPlus SM ...
- 再探 游戏 《 2048 》 —— AI方法—— 缘起、缘灭(4) —— state-of-the-art
<2048>游戏在线试玩地址: https://play2048.co/ 该游戏的解法比较不错的资料为外网的一个讨论帖子: What is the optimal algorithm fo ...
- CF1992场题解
Only Pluses 算法:数学. 题意简述:有三个数,每次选择一个数 \(x\),使得 \(x\) 增加一,至多操作 \(5\) 次,最后求出这三个数的乘积最大值. 简单题,一眼秒了.考虑把这 \ ...
- lbs 地理位置
lbs 地理位置 https://caorong.github.io/2018/05/04/lbs/ https://www.cnblogs.com/lbser/p/3310455.html http ...
- Managing Difficulties
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define FOR(i,n,m) for(int i=n;i<=m;i++) ...
- VMware 安装 OpenWrt
准备 OpenWrt VMDK 固件映像 你可以直接下载 VMDK 版本的 OpenWrt 固件映像,或者自己构建一个,或者从 IMG 映像文件转换.一般来说 IMG 映像文件较为容易获取,因此下面介 ...