函数可以使用 var、let 或 const 关键字声明,就像声明字符串、数字等变量一样。
var log = function(message) {
console.log(message);
}
const log = message => {
console.log(message);
};
由于函数是变量,那就可以把函数添加到对象中。
const obj = {
message: "xx",
log(message) {
console.log(message);
}
};
obj.log(obj.message);
还可以把函数添加到 JavaScript 数组中。
const message = [
"xx",
message => console.log(message),
"yy",
message => console.log(message)
];
message[1](message[0]);
message[3](message[2]);
函数可以像常规变量一样作为参数发给其他函数。
const insideFn = logger => {
logger("They can be sent to other functions as arguments");
}
insideFn(message => console.log(message));
函数也可以像变量一样由其他函数返回。
const createScream = function(logger) {
return function(message) {
logger(message.toUpperCase() + "!!!");
};
};
const scream = createScream(message => console.log(message));
scream("functions can be returned from other functions");
scream("createScream returns a function");
scream("scream invokes that returned function");
const createScream = logger => message => {
logger(message.toUpperCase() + "!!!");
};
const scream = createScream(message =>
console.log(message)
);
scream("ES6 can createScream with less");
函数式编程是一个更大编程范式的一部分,即声明式编程。声明式编程是一种编程风格,采用这种风格开发的应用有个显著特点:重点描述该做什么,而不管怎么做。
React 是声明式的。
函数式编程的一些核心概念:不可变性、纯函数、数据转换、高阶函数和递归。
在采用函数式风格的程序中,数据是不可变的,永不更改。我们不直接更改原始数据结构,而是创建数据结构的副本,所有操作都使用副本。
在 JavaScript 中,函数的参数是对真正数据的引用。
// 例子1:
let color_lawn = {
title: "lawn",
color: "#00FF00",
rating: 0
};
function rateColor(color, rating) {
color.rating = rating;
return color;
}
console.log(rateColor(color_lawn, 5).rating); // 5
console.log(color_lawn.rating); // 5
// 例子2:
let color_lawn = {
title: "lawn",
color: "#00FF00",
rating: 0
};
const rateColor = function(color, rating) {
return Object.assign({}, color, { rating: rating });
};
console.log(rateColor(color_lawn, 5).rating); // 5
console.log(color_lawn.rating); // 0
// 例子3:
const rateColor = (color, rating) => ({
...color,
rating
});
Array.push 不是一个不可变函数。如需不更改原数组,必须使用 Array.concat。Array.concat 的作用是拼接数组。
// 例子1:
let list = [
{ title: "Rad Red" },
{ title: "Lawn" },
{ title: "Party Pink" }
];
var addColor = function(title, colors) {
colors.push({ title: title });
return colors;
};
console.log(addColor("Glam Green", list).length); // 4
console.log(list.length); // 4
// 例子2:
let list = [
{ title: "Rad Red" },
{ title: "Lawn" },
{ title: "Party Pink" }
];
const addColor = (title, array) => array.concat({ title });
console.log(addColor("Glam Green", list).length); // 4
console.log(list.length); // 3
与复制对象一样,还可以使用展开运算符拼接数组。
let list = [
{ title: "Rad Red" },
{ title: "Lawn" },
{ title: "Party Pink" }
];
const addColor = (title, list) => [
...list,
{ title }
];
console.log(addColor("Glam Green", list).length); // 4
console.log(list.length); // 3
纯函数指基于参数做计算,并返回一个值的函数。纯函数至少接受一个参数,而且始终返回一个值或另一个函数。这种函数没有副作用、不设置全局变量,也不更改应用的状态。纯函数把参数视为不可变数据。
var frederick = {
name: "Frederick Douglass",
canRead: false,
canWrite: false
};
const selfEducate = person => ({
...person,
canRead: true,
canWrite: true
});
console.log(selfEducate(frederick)); // {name: 'Frederick Douglass', canRead: true, canWrite: true}
console.log(frederick); // {name: 'Frederick Douglass', canRead: false, canWrite: false}
在 React 中,UI 使用纯函数表达。
建议遵守下面三条规则编写函数:
在函数式编程中,数据从一种格式变成另一种格式。我们要做的式使用函数产出转换后的副本。这样的函数减少了代码的命令式属性,从而能降低复杂度。若想精通 JavaScript 函数式编程,必须掌握两个核心函数,即 Array.map 和 Array.reduce。
我们可以使用 Array.join 函数得到一个以逗号分割的清单或其他格式的字符串。Array.join 是 JavaScript 内置的数组方法,作用是从数组中提取出以指定符号分割的字符串。
const schools = [
"Yorktown",
"Washington & Lee",
"Wakefield"
];
console.log(schools.join(", ")); // Yorktown, Washington & Lee, Wakefield
如果想定义一个函数,创建一个新数组,存储名称以”W“开头的学校,可以使用 Array.filter 方法。Array.filter 是 JavaScript 内置的函数,根据源数组产出一个新数组。该函数只接受一个参数,这个参数是一个断言,即一个始终返回布尔值 true 或 false 的函数。Array.filter 在数组的每一个元素上调用断言,元素作为参数传给断言,返回值决定是否把元素添加到新建的数组中,
const schools = [
"Yorktown",
"Washington & Lee",
"Wakefield"
]
const wSchools = schools.filter(school => school[0] === "W")
console.log( wSchools ) // ['Washington & Lee', 'Wakefield']
若想从数组红删除元素,不建议使用 Array.pop 或 Array.splice,应该使用 Array.filter,因为 Array.filter 执行的是不可变操作。
Array.map 函数接受的参数是一个函数,这个函数是数组的每一个元素上调用一次,不管返回什么都添加到新数组中。
const schools = [
"Yorktown",
"Washington & Lee",
"Wakefield"
]
const highSchools = schools.map(school => ({ name: school }))
console.log(highSchools) // [{name: 'Yorktown'}, {name: 'Washington & Lee'}, {name: 'Wakefield'}]
如果想把数组转换成对象,可以结合 Array.map 和 Object.keys。Object.keys 方法从对象中获取所有键,返回由键构成的数组。
const schools = {
"Yorktown": 10,
"Washington & Lee": 2,
"Wakefield": 5
}
const schoolArray = Object.keys(schools).map(key => ({
name: key,
wins: schools[key]
}))
console.log(schoolArray)
reduce 和 reduceRight 函数可用于把数组转换成任何值,包括数组、字符串、布尔值、对象,甚至是函数。
const ages = [21,18,42,40,64,63,34]
const maxAge = ages.reduce((max, age) => {
console.log(`${age} > ${max} = ${age > max}`)
if (age > max) {
return age
} else {
return max
}
}, 0)
console.log('maxAge', maxAge)
21 > 0 = true
18 > 21 = false
42 > 21 = true
40 > 42 = false
64 > 42 = true
63 > 64 = false
34 > 64 = false
maxAge 64
// 可简化为:
const max = ages.reduce((max, value) => (value > max) ? value : max, 0)
Array.reduceRight 的作用与 Array.reduce 相同,只不过不从数组开头开始归约,而是从数组末尾开始。
把数组转换成对象。
const colors = [
{
id: '-xekare',
title: "rad red",
rating: 3
},
{
id: '-jbwsof',
title: "big blue",
rating: 2
},
{
id: '-prigbj',
title: "grizzly grey",
rating: 5
},
{
id: '-ryhbhsl',
title: "banana",
rating: 1
}
]
const hashColors = colors.reduce((hash, {id, title, rating}) => {
hash[id] = {title, rating}
return hash
}, {})
console.log(hashColors)
使用 reduce 甚至可以把数组转换成完全不同的数组。
const colors = ["red", "red", "green", "blue", "green"];
const uniqueColors = colors.reduce(
(unique, color) =>
(unique.indexOf(color) !== -1) ? unique : [...unique, color],
[]
)
console.log(distinctColors)
高阶函数指用于处理其他函数的函数,其参数可以是函数,也可以返回函数,或者二者兼而有之。
第一类高阶函数是接受其他函数为参数的函数。Array.map、Array.filter 和 Array.reduce 都接受函数作为参数,因此它们是高阶函数。
const invokeIf = (condition, fnTrue, fnFalse) =>
(condition) ? fnTrue() : fnFalse()
const showWelcome = () => console.log("Welcome!!!")
const showUnauthorized = () => console.log("Unauthorized!!!")
invokeIf(true, showWelcome, showUnauthorized) // Welcome!!!
invokeIf(false, showWelcome, showUnauthorized) // Unauthorized!!!
返回一个函数的高阶函数有助于我们处理 JavaScript 复杂的异步操作,创建我们在必要时需要使用或重用的函数。
const getFakeMembers = count => new Promise((resolves, rejects) => {
const api = `https://api.randomuser.me/?nat=US&results=${count}`
const request = new XMLHttpRequest()
request.open('GET', api)
request.onload = () =>
(request.status === 200) ?
resolves(JSON.parse(request.response).results) :
reject(Error(request.statusText))
request.onerror = (err) => rejects(err)
request.send()
})
const userLogs = userName => message =>
console.log(`${userName} -> ${message}`)
const log = userLogs("grandpa23")
log("attempted to load 20 fake members")
getFakeMembers(20).then(
members => log(`successfully loaded ${members.length} members`),
error => log("encountered an error loading members")
)
grandpa23 -> attempted to load 20 fake members
grandpa23 -> successfully loaded 20 members
或
grandpa23 -> attempted to load 20 fake members
grandpa23 -> encountered an error loading members
递归技术指创建重新调用自身的函数。如果要解决的问题涉及循环,往往就可以转而使用递归函数。
const countdown = (value, fn) => {
fn(value)
return (value > 0) ? countdown(value-1, fn) : value
}
countdown(10, value => console.log(value))
递归模式特别适合在异步操作中使用。函数在做好准备时调用本身,例如在数据可用时,或者当计时器结束时。
const countdown = (value, fn, delay=1000) => {
fn(value)
return (value > 0) ?
setTimeout(() => countdown(value-1, fn), delay) : value
}
const log = value => console.log(value)
countdown(10, log)
递归技术适合在搜索数据结构中使用。可以递归迭代子文件夹,直到找出仅包含文件的文件夹为止。也可以递归迭代 HTML DOM,直到找到一个没有子节点的元素为止。
const dan = {
type: "person",
data: {
gender: "male",
info: {
id: 22,
fullname: {
first: "Dan",
last: "Deacon"
}
}
}
}
const deepPick = (fields, object={}) => {
const [first, ...remaining] = fields.split(".")
return (remaining.length)
? deepPick(remaining.join("."), object[first])
: object[first]
}
console.log( deepPick("type", dan) ) // person
console.log( deepPick("data.info.fullname.first", dan) ) // Dan
函数式程序是把逻辑拆分成一系列关注特定任务的小型纯函数。最终,你要把这些小型函数整合到一起。合成的方式、模式和技术有很多种,你可能熟悉的一种是串接。在 JavaScript 中,函数可以使用点号记法串接在一起,依次处理前一个函数的返回值。
字符串有 replace 方法。replace 方法返回一个模块字符串,而模板字符串也有 replace 方法。因此,可以使用点号记法把 replace 方法串接在一起处理字符串。
const template = "hh:mm:ss tt"
const clockTime = template.replace("hh", "03")
.replace("mm", "33")
.replace("ss", "33")
.replace("tt", "PM")
console.log(clockTime) // 03:33:33 PM
console.log(template) // hh:mm:ss tt
当一个函数充当了在两个函数之间传递数据的管道,但像下面的例子,这样的句法难以理解,也不易维护和扩充。
const createClockTime = date => ({date})
const appendAMPM = ({date}) =>
({
date,
ampm: (date.getHours() >= 12) ? "PM" : "AM"
})
const civilianHours = clockTime => {
const hours = clockTime.date.getHours()
return {
...clockTime,
hours: (hours > 12) ?
hours - 12 :
hours
}
}
const removeDate = clockTime => {
let newTime = {...clockTime}
delete newTime.date
return newTime
}
// Not the best way to compose all of these into one function
const oneFunction = date =>
removeDate(
civilianHours(
appendAMPM(
createClockTime(date)
)
)
)
console.log(oneFunction(new Date()))
一种更为优雅的方法是创建一个高阶函数,把多个函数合成为一个大型函数。
const createClockTime = date => ({date})
const appendAMPM = ({date}) =>
({
date,
ampm: (date.getHours() >= 12) ? "PM" : "AM"
})
const civilianHours = clockTime => {
const hours = clockTime.date.getHours()
return {
...clockTime,
hours: (hours > 12) ?
hours - 12 :
hours
}
}
const removeDate = clockTime => {
let newTime = {...clockTime}
delete newTime.date
return newTime
}
// A more elegant approach to composition
const compose = (...fns) =>
(arg) =>
fns.reduce(
(composed, f) => f(composed),
arg
)
const oneFunction = compose(
createClockTime,
appendAMPM,
civilianHours,
removeDate
)
console.log(oneFunction(new Date())) // { "ampm": "PM", "hours": 8 }
在函数式程序中,应该尽量使用函数,而非值。需要时,我们可以调用函数来获取值。
const oneSecond = () => 1000
const getCurrentTime = () => new Date()
const clear = () => console.clear()
const log = message => console.log(message)