前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版
  • JavaScript Part 1

    • Ajax总结篇
    • Canvas 绘制八大行星
    • DOM编程之API学习总结篇
    • JavaScript事件机制
    • JavaScript代码片段100个
    • JavaScript作用域分析总结
    • JavaScript原型链回顾
    • JavaScript及jQuery中的各种宽高属性图解
    • JavaScript工程项目的一系列最佳实践
    • JavaScript常用API合集
    • JavaScript数组、字符串、对象常用方法
    • JavaScript数组方法总结篇
    • JavaScript深浅拷贝
    • JavaScript运动框架之速度时间版本
    • JavaScript运行机制Event Loop
    • JavaScript防抖节流原理
    • Javascript中的复制粘贴功能
    • Javascript数组详解
    • OOP之原型与原型链
    • Object.defineProperty详解
    • V8源码浅析JS数组常见方法
    • await 在 forEach 中不生效解决方案
    • iframe+表单跨域提交POST请求
    • javascript笔记总结篇
    • 业务中处理数据结构常用的JS方法
    • 作用域
    • 你真的掌握变量和类型了吗
    • 前后端分离之数据Mock
    • 原型与原型链
    • 原生JS补给(上)
    • 如何写出一个惊艳面试官的深拷贝
    • 带你填一些JS容易出错的坑
    • 彻底弄懂 JavaScript 执行机制
    • 执行上下文 执行栈
    • 正则回顾总结
    • 正则基础知识
    • 正则完整篇
    • 正则表达式
    • 浅析Promise原理
    • 浅谈JavaScript中的异步处理
    • 深拷贝 vs 浅拷贝
    • 聊一聊typeof instanceof 实现原理.
    • 聊一聊闭包
    • 高阶函数map reduce filter
  • JavaScript Part 2

  • CSS

  • HTML

  • Jquery

  • ES6

  • 小程序

  • Vue

  • React

  • 深入React

  • React Native

  • NodeJS

  • Angular

  • TypeScript

  • Webpack

  • 浏览器

  • 移动端

  • 前端工程化

  • Electron

  • HTTP

  • Nginx

  • Linux

  • 数据结构与算法

  • LeetCode算法题

  • 综合

完整面试题地址:
作者:程序员poetry
扫码关注作者公众号:「前端进阶之旅」 每天分享技术干货
前端进阶之旅公众号二维码

高阶函数|博客系列

# 1. 函数式编程能解决的问题

  • 可扩展性–我是否需要不断地重构代码来支持额外的功能?
  • 易模块化–如果我更改了一个文件,另一个文件是否会受到影响?
  • 可重用性–是否有很多重复的代码?
  • 可测性–给这些函数添加单元测试是否让我纠结?
  • 易推理性–我写的代码是否非结构化严重并难以推理?

# 2. 学习之前你需要了解的一些概念

# 函数输入

在数学中,函数总是获取一些输入值,然后给出一个输出值。

但在程序中,它或许有许多个输入值,或许没有。它或许有一个输出值( return 值),或许没有。

从上述的定义出发,所有的函数都需要输入。

大多数情况下,人们把函数的输入值称为 “arguments” 或者 “parameters” 。所以它到底是什么?

arguments 是你输入的值(实参), parameters 是函数中的命名变量(形参),用于接收函数的输入值。例子如下:

function fn (x, y) {
	console.log(x, y)
}
fn(3, 4)

// 3, 4
@前端进阶之旅: 代码已经复制到剪贴板

3 和 4是函数 fn(..) 调用的 arguments。x 和 y 是 parameters,用于接收参数值(分别为 3 和 4 )。

在javascript中定义的形参和实参可以是不等的:

function fn (x, y) {
	console.log(x, y)
}
fn(3)

// 3, undefined
@前端进阶之旅: 代码已经复制到剪贴板

你传入少于声明形参个数的实参,所有缺少的参数将会被赋予 undefined 变量,意味着你仍然可以在函数作用域中使用它,但值是 undefined。

# 输入计数

一个函数所“期望”的实参个数是取决于已声明的形参个数,即你希望传入多少参数。

如以下函数:

function fn (x, y, z) {
	console.log(x, y, z)
}
@前端进阶之旅: 代码已经复制到剪贴板

fn期望三个参数,因为它声明了三个形参。这里有一个特殊的术语:Arity。Arity 指的是一个函数声明的形参数量。 fn(..) 的 Arity 是 3。

# 函数的length

你可能需要在程序运行时获取函数的 Arity,使用函数的 length 属性即可。

function fn (x, y, z) {
	console.log(x, y, z)
}
fn.length  // 3
@前端进阶之旅: 代码已经复制到剪贴板

提示: 函数的 length 属性是一个只读属性,并且它是在最初声明函数的时候就被确定了。它应该当做用来描述如何使用该函数的一个基本元数据。

引入ES6特性的一些函数的length:

function foo(x,y = 2) {
	// ..
}

function bar(x,...args) {
	// ..
}

function baz( {a,b} ) {
	// ..
}

foo.length;				// 1
bar.length;				// 1
baz.length;				// 1
@前端进阶之旅: 代码已经复制到剪贴板

# arguments

上面我们已经知道了,可以用函数的length属性来获取到这个函数的形参个数,但是在实际使用时,我们更需要知道的是函数传入的实参个数,此时可以使用每个函数都有的arguments对象(类数组),来获取到传入的实参个数。

如下面这个例子:

function foo(x,y,z) {
	console.log( arguments.length );	// 2
}

foo( 3, 4 );
@前端进阶之旅: 代码已经复制到剪贴板

由于 ES5(特别是严格模式下)的 arguments 不被一些人认同,很多人尽可能地避免使用。尽管如此,它永远不会被移除,这是因为在 JS 中我们“永远不会”因为便利性而去牺牲向后的兼容性,但是大多数人还是不建议使用它。

所以你只需要知道以下几点:

  1. 当你需要知道参数个数的时候,arguments.length 还是可以用的。
  2. 不要通过 arguments[1] 访问参数的位置。只要记住 arguments.length。

# …ES6获取剩余参数

当你需要像数组那样访问参数,很有可能的原因是你想要获取的参数没有在一个规范的位置。我们如何处理?

ES6 救星来了!让我们用 ... 操作符声明我们的函数,也被当做 “spread”、“rest” 或者 “gather” (我比较偏爱)提及。

function foo(x,y,z,...args) {
	// ..
}
@前端进阶之旅: 代码已经复制到剪贴板

看到参数列表中的 ...args 了吗?那就是 ES6 用来告诉解析引擎获取所有剩余的未命名参数,并把它们放在一个真实的命名为 args 的数组。args 无论是不是空的,它永远是一个数组。但它不包含已经命名的 x,y 和 z 参数,只会包含超出前三个值的传入参数.

function foo(x,y,z,...args) {
	console.log( x, y, z, args );
}

foo();					// undefined undefined undefined []
foo( 1, 2, 3 );			// 1 2 3 []
foo( 1, 2, 3, 4 );		// 1 2 3 [ 4 ]
foo( 1, 2, 3, 4, 5 );	// 1 2 3 [ 4, 5 ]
@前端进阶之旅: 代码已经复制到剪贴板

你甚至可以直接在参数列中使用 ... 操作符,没有其他正式声明的参数也没关系:

function foo(...args) {
	console.log(args)
}

foo(1, 2);     // [1, 2]
@前端进阶之旅: 代码已经复制到剪贴板

现在 args 是一个由参数组成的完整数组,你可以尽情使用 args.length 来获取传入的参数。你也可以安全地使用 args[1] .

# 关于实参的小技巧

如果你希望调用函数的时候只传一个数组代替之前的多个参数,该怎么办?

function foo(...args) {
	console.log( args[3] );
}

var arr = [ 1, 2, 3, 4, 5 ];

foo( ...arr );						// 4
@前端进阶之旅: 代码已经复制到剪贴板

…操作符在这里也被用到了。

在形参中使用的时候function foo(…args){},是将形参整合,形成一个数组:

function foo(x, y){}
// 经过...之后变为
function foo([x, y]){}
@前端进阶之旅: 代码已经复制到剪贴板

在实参中使用的时候foo(…arr),是将实参展开:

foo([1, 2, 3])
// 经过...之后变为
foo(1, 2)
@前端进阶之旅: 代码已经复制到剪贴板

你甚至可以多个值和…一起来使用:

function foo(...args) {
	console.log( args[3] );
}

var arr = [ 2 ];

foo( 1, ...arr, 3, ...[4,5] );		// 4
// 相当于是foo(1, 2, 3, 4, 5); 所以获取到的args[3]为4
@前端进阶之旅: 代码已经复制到剪贴板

# 关于形参的小技巧

默认参数

在 ES6 中,形参可以声明默认值。当形参没有传入到实参中,或者传入值是 undefined,会进行默认赋值的操作:

function foo(x = 3) {
	console.log( x );
}

foo();					// 3
foo( undefined );		// 3
foo( null );			// null
foo( 0 );				// 0
@前端进阶之旅: 代码已经复制到剪贴板

解构

现在有这么一个函数:

function foo(params) {
	console.log(params)
}

foo([1, 2, 3])
@前端进阶之旅: 代码已经复制到剪贴板

我在拿到args数组之后,想要命名传入数组的第 1、2 个值,也许你可以这么做:

// example1
function foo(params) {
	console.log(params) // [1, 2, 3]
	var x = params[0] // 1
	var y = params[1] // 2
	var args = params.slice( 2 ) // [3] (args就是剩余的参数集合)
}

foo([1, 2, 3])
@前端进阶之旅: 代码已经复制到剪贴板

现在你可以用更酷的ES解构的方式来写这个函数:

// example
function foo( [x,y,...args] = [] ) {
	console.log(x) // 1
	console.log(y) // 2
	console.log(args) // [3]
}

foo( [1,2,3] );
@前端进阶之旅: 代码已经复制到剪贴板

上面的这种[x, y, …args] = []就是一种数组解构,解构是通过你期望的模式来描述数据(对象,数组等),并分配(赋值)值的一种方式。

在这里例子中,解构告诉解析器,一个数组应该出现的赋值位置(即参数)。这种模式是:拿出数组中的第一个值,并且赋值给局部参数变量 x,第二个赋值给 y,剩下的则组成 args。

同样这种解构也可以用在对象中,称为对象解构:

function foo( {x,y} = {} ) {
	console.log( x, y );
}

foo( {
	y: 3
} );					// undefined 3
@前端进阶之旅: 代码已经复制到剪贴板

我们传入一个对象作为一个参数,它解构成两个独立的参数变量 x 和 y,从传入的对象中分配相应属性名的值。我们不在意属性值 x 到底存不存在对象上,如果不存在,它最终会如你所想被赋值为 undefined。

通过上面的学习,我们需要认识到很重要的一个原则:声明性代码通常比命令式代码更干净。

声明性代码也就是上面的example2,在定义形参的时候就给参数命好名,而example1就是命令式代码,它在拿到形参之后再进行命名。

# 随着输入而变化的函数

现在有这么一个函数:

function foo(x,y) {
	if (typeof x == "number" && typeof y == "number") {
		return x * y;
	}
	else {
		return x + y;
	}
}
@前端进阶之旅: 代码已经复制到剪贴板

明显地,这个函数会根据你传入的值而有所不同,比如:

foo( 3, 4 );			// 12

foo( "3", 4 );			// "34"
@前端进阶之旅: 代码已经复制到剪贴板

程序员这样定义函数的原因之一是,更容易通过同一个函数来重载不同的功能。乍一看这样设计一个函数好像很方便,使得我们的函数可以有很多不同的行为,其实通过不同的输入值让一个函数重载拥有不同的行为的技巧叫做特定多态(ad hoc polymorphism)。但在函数式编程中,要对方便的诱惑有警惕之心。因为你可以通过这种方式设计一个函数,即使可以立即使用,但这个设计的长期成本可能会让你后悔。

# 函数输出

在 JavaScript 中,函数只会返回一个值。下面的三个函数都有相同的 return 操作。

function foo() {}

function bar() {
	return;
}

function baz() {
	return undefined;
}
@前端进阶之旅: 代码已经复制到剪贴板

如果你没有 return 值,或者你使用 return;,那么则会隐式地返回 undefined 值。

如果想要尽可能靠近函数式编程的定义:使用函数而非程序,那么我们的函数必须永远有返回值。这也意味着他们必须明确地 return 一个值,通常这个值也不是 undefined。

← JS面向对象最后一弹_多态篇进阶柯里化 →

fe
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版
  • JavaScript Part 1

    • Ajax总结篇
    • Canvas 绘制八大行星
    • DOM编程之API学习总结篇
    • JavaScript事件机制
    • JavaScript代码片段100个
    • JavaScript作用域分析总结
    • JavaScript原型链回顾
    • JavaScript及jQuery中的各种宽高属性图解
    • JavaScript工程项目的一系列最佳实践
    • JavaScript常用API合集
    • JavaScript数组、字符串、对象常用方法
    • JavaScript数组方法总结篇
    • JavaScript深浅拷贝
    • JavaScript运动框架之速度时间版本
    • JavaScript运行机制Event Loop
    • JavaScript防抖节流原理
    • Javascript中的复制粘贴功能
    • Javascript数组详解
    • OOP之原型与原型链
    • Object.defineProperty详解
    • V8源码浅析JS数组常见方法
    • await 在 forEach 中不生效解决方案
    • iframe+表单跨域提交POST请求
    • javascript笔记总结篇
    • 业务中处理数据结构常用的JS方法
    • 作用域
    • 你真的掌握变量和类型了吗
    • 前后端分离之数据Mock
    • 原型与原型链
    • 原生JS补给(上)
    • 如何写出一个惊艳面试官的深拷贝
    • 带你填一些JS容易出错的坑
    • 彻底弄懂 JavaScript 执行机制
    • 执行上下文 执行栈
    • 正则回顾总结
    • 正则基础知识
    • 正则完整篇
    • 正则表达式
    • 浅析Promise原理
    • 浅谈JavaScript中的异步处理
    • 深拷贝 vs 浅拷贝
    • 聊一聊typeof instanceof 实现原理.
    • 聊一聊闭包
    • 高阶函数map reduce filter
  • JavaScript Part 2

  • CSS

  • HTML

  • Jquery

  • ES6

  • 小程序

  • Vue

  • React

  • 深入React

  • React Native

  • NodeJS

  • Angular

  • TypeScript

  • Webpack

  • 浏览器

  • 移动端

  • 前端工程化

  • Electron

  • HTTP

  • Nginx

  • Linux

  • 数据结构与算法

  • LeetCode算法题

  • 综合