函数是什么?
数学里的函数
- 函数在数学中为两个非空集合间的一种对应关系
- 定义域中的每项元素皆对应一项值域中的元素
- 假设函数为f
- 假设输入为x1,那么必有一个输出y1:f(x1)=y1
- 假设输入为x2,那么对应输出可以是y1也可以不是
- 不管你输入多少次x1,输出都是有
- f()在数学里不合法,因为没有输入,除非另说明
- 函数式编程
- 如果你的函数符合数学含义的函数,那么就是函数式的
JS的函数与环境
函数的返回值由什么确定
- 调用时输入的参数params
- 定义时的环境env
举例
let x = 'x'
let a = '1'
function f1(x){
return x+a
}
{
let a = '2'
f1('x')//值为x1 这个a是定义时的a而不是执行时的a
}
let x = 'x'
let a = '1'
function f1(x){
return x+a
}
a = '3'
{
let a = '2'
f1('x')//值为x3 a在定义环境中是3
}
let x = 'x'
let a = '1'
function f1(x){
return x+a
}
a = '3'
{
let a = '2'
f1('x')//值为x3 执行在a变成4之前
}
a = '4'
let x = 'x'
let a = '1'
function f1(c){
c()
}
{
let a = '2'
function f2(){
console.log(x + a)
}
f1(f2)//打印出x2 f2定义的环境中 a = '2',x会从外层找到'x'
}
面试题
for(var i = 0;i<6;i++){
setTimeout(
()=>console.log(i)
)
}
打印出6个6,因为6个函数共用一个i
for(let i = 0;i<6;i++){
setTimeout(
()=>console.log(i)
)
}
打印出0 1 2 3 4 5 ,因为6个函数各自一个i
for(var i = 0;i<6;i++){
!function(j){
setTimeout(
()=>console.log(i)
)
}(i)//通过立即执行函数也可以解决这个问题
}
闭包
特点
- 能让一个函数维持住一个变量
- 但并不能维持这个变量的值
- 尤其是变量的值会变化的时候
对象是穷人的闭包
- 对象也可以来维持住一个闭包
- 如果一门语言不支持闭包,可以用对象处理
闭包是穷人的对象
- const f1 = new Function( ‘x’,’y’,’return x + y’ )
- function f2(x,y){ return x + y }
- const f3 = function (x,y){ return x + y }
- const f4 = (x,y)=> x + y
其中,1 2 3 都是ES6之前的语法,支持this/arguments/new
而4是ES6的新语法,不支持this/arguments/new
箭头函数不支持this
const a = 233
const f1 = ()=>console.log(a) /233
console.log(this)
const f2 = ()=>console.log(this) //Window
- 箭头函数把this当做外部的变量,仅此而已
- 但非剪头函数有很多特殊处理
- 箭头函数不支持this指的就是剪头函数对this与其他变量一视同仁,不会特殊对待
非箭头函数的this
this其实就是一个隐式的参数
显式this
fn.call(asThis,1,2)
fn.bind(asThis,1,2)()
obj.method.call(obj,'hi')
隐式this
fn(1,2) //fn.call(undefined,1,2)
obj.method('hi') //obj.method.call(obj,'hi')
array[0]('hi') //array[0].call(array,'hi')
button.onclick = function(e){
console.log(this)
}
//this是参数,不同情况不一样
const vm = new Vue({
date:{
message:'hi'
},
methods:{
sayHi(){
console.log(this.message)
//不确定 也是参数
//正常使用是vm
}
}
})
面试题
let length = 10
function fn(){console.log(this.length)}
let obj = {
length:5,
method(fn){
fn()
arguments[0]()
}
}
obj.method(fn,1)//输出什么?答案密码 123456
小总结
- this是call的第一个参数
- new重新设计了this
- 剪头函数不接受this
递归与调用栈
递归阶乘
const j = (n) => n === 1 ? 1 : n * j(n-1)
代入求值
先递进,后回归
斐波那契数列
每一项都是前两项的和
const fib = (n) =>
n === 0 ? 0 :
n === 1 ? 1 :
fib(n-1) + fib(n-2)
代入求值
降低压栈/计算次数
尾递归优化(迭代)
f = (n) => f_inner(2,n,1,0)
f_inner = (start,end,prev1,prev2)=>
start === end ? prev1 + prev2 :
f_inner(start + 1, end, prev1 + prev2, prev1)
代入求值
这就是尾递归,可以不用“回头”,不压栈从而达到优化效果
但是js中,没有尾递归优化,因为js还是会压栈去创造一些环境栈
在有尾递归优化的语言中就可以使用
所有的递归都可以改成循环
f = (n) => {
let array = [0,1]
for(let i = 0; i <= n-2; i++){
array[i+2] = array[i+1] + array[i]
}
return array[array.length-1]
}
记忆化函数与React优化
记忆化可以减少重复计算,大大降低压栈次数
const memo = (fn) => {
const memoed = function(key) {
if (!(key in memoed.cache)) {
memoed.cache[key] = fn.apply(this, arguments)
// 注意 memo 只会以第一个参数作为记忆点,但是还是会用 arguments 来调用 fn
}
return memoed.cache[key]
}
memoed.cache = {}
return memoed
}
const x2 = memo((x) => {
console.log('执行了一次')
return x * 2
})
// 第一次调用 x2(1)
console.log(x2(1)) // 打印出执行了,并且返回2
// 第二次调用 x2(1)
console.log(x2(1)) // 不打印执行,并且返回上次的结果2
// 第三次调用 x2(1)
console.log(x2(1)) // 不打印执行,并且返回上次的结果2
Lodash的实现
_.memoize = function(func, hasher) {
var memoize = function(key) {
var cache = memoize.cache;
var address = '' + (hasher ? hasher.apply(this, arguments) : key)
if (!has(cache, address))
cache[address] = func.apply(this, arguments)
return cache[address];
};
memoize.cache = {};
return memoize;
};
React.memo
React.useCallback
这个函数需要根据m的变化来进行更新,所以不会在没变化的时候去产生新的onClick函数
点击查看【codepen】
柯里化Currying
让所有函数都只接受一个函数,基于单一参数函数衍生出非常多理论知识,比如λ运算
用闭包(对象)来让函数支持两个参数
单参数函数接受两个参数
用对象的形式
const add = ({a,b})=> a + b
add({a:1,b:2})
柯里化
const add = a => b => a + b
add(1)(2)
面试题
如何把三参数函数add(1,2,3)变成curriedAdd(1)(2)(3)形式?
const curriedArr =
a =>
b =>
c =>
add(a,b,c)
假设 addTwo接受两个参数 addThree接受三个参数 addFore接受四个参数 请写出一个currify函数,使得它们分别接受2,3,4次参数,比如 currify(addTwo)(1)(2) //3 currify(addThree)(1)(2)(3) //6 currify(addFore)(1)(2)(3)(4) //10 也就是说 currify 能将任意接受固定个参数的函数变成单一参数的函数
currify = (fn, params = [])=>
(...args) =>
params.length+args.length === fn.length
? fn(...params, ...args)
: currify(fn, [...params, ...args])
addTwo = (a,b)=>a+b
addThree = (a,b,c)=>a+b+c
newAddTwo = currify(addTwo)
newAddThree = currify(addThree)
newAddTwo(1)(2)
newAddThree(1)(2)(3)
高阶函数
JS内置的高阶函数
- Function.prototype.bind
- Function.prototype.apply
- Function.prototype.call
- Array.prototype.sort
- Array.prototype.map
- Array.prototype.filter
- Array.prototype.reduce
函数的组合
function doubleSay (str) {
return str + ", " + str;
}
function capitalize (str) {
return str[0].toUpperCase() + str.substring(1);
}
function exclaim (str) {
return str + '!';
}
let result = exclaim(capitalize(doubleSay("hello")));
result //=> "Hello, hello!"
使用pipe操作
let result = "hello"
|> doubleSay
|> capitalize
|> exclaim;
result //=> "Hello, hello!"