1. 函数是什么?
根据代码大全的定义
js 中只有函数和方法
函数
function fn() {
// 函数
console.log(1);
}
- 方法
var obj = {
fn: fn, // 方法
};
2. 数学领域也有函数
- 数学中的函数
- 函数在数学中为两个非空集合间的一种对应关系
- 定义域中的每项元素皆对应一项值域中的元素
- 什么意思
- 假设函数为 f
- 假设输入为 x1,那么必有一个输出 y1: f(x1)=y1
- 假设输入为 x2,那么对应的输出可以是有,也可不是
- 不管输入多少次 x1,输出都是也
- f()在数学里是不合法的,因为没有输入,除非另有说明
- 函数式编程
- 如果你的函数符合数学函数的定义,就是函数式的
3. JS 的函数和数学函数不同
- 接下来只讨论 JS 的函数
let n = 0;
function add() {
n = n + 1;
return n;
}
add(); // 1
add(); // 2
4. 函数的返回值由什么确定
- 影响因素
- 调用时输入的参数 parmas
- 定义时的环境 env
- 举例,这个例子说明 a 是定义时的 a,而不是执行时的 a
let x = "x";
let a = "1";
function f1(x) {
return x + a;
}
{
let a = "2";
f1("x"); // 值为多少,"x1"
}
- 要根据最新的 a 来决定环境变量
let x = "x";
let a = "1";
function f1(x) {
return x + a;
}
a = "3";
{
let a = "2";
f1("x"); // 值为多少,"x3"
}
- 另一个例子,面试常考
let x = "x";
let a = "1";
function f1(c) {
c();
}
{
let a = "2";
function f2() {
console.log(x + a);
}
f1(f2); // 打印出多少,x2
}
- 并不是所有语言都可以在函数访问外部变量
- ruby 需要 lambda 关键字
5. 闭包
- 如果在函数里面可以访问外面的变量,那么这个函数+这些变量=闭包
- 一个很常见的考题,闭包和时间结合
for(var i=0;i<6;i++){
setTimeout(
()=>console.log(i) // 箭头函数访问了i
)
}
// 打印6个6,因为6个函数共用一个i
- 如何打印 0 ~ 5
- 把 var 改成 let ,let 会做一些处理,每次循环都会生成一个新的 i 给闭包
- 也可以用立即执行函数(已经非常过时,推荐用 let)
6. 闭包总结
- 闭包的特点
- 能让一个函数维持住一个变量
- 但并不能维持这个变量的值
- 尤其是变量的值会变化的时候
var obj = {
i: 0,
fn() {
console.log(this.i);
},
}; // 对象
const handle = function () {
var i = 0;
return function () {
console.log(i);
};
}; // 闭包
- 对象是穷人的闭包
- 对象也可以来维持住一个变量
- 如果一门语言不支持闭包,可以用对象代理
- 闭包是穷人的对象
- 如果一门语言不支持对象,可以用闭包代理
// 解释闭包是穷人的对象
function createPerson(age, name) {
return function (key) {
if (key === "name") return name;
if (key === "age") return age;
};
}
person("name"); // frank
person("age"); // 18
7. 声明一个函数
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;
// 其中, f1 f2 f3 是ES6之前的语法,支持this/arguments/new,而f4是ES6新出的语法,
// 不支持this/arguments/new
// PS 这里没有提到generator函数和async函数
- 举例箭头函数不支持 this
const f = () => console.log(this);
f(); // window
f.call({ name: "frank" });
// window
- 代码 1
const a = 233
const f2 => ()=> console.log(a)
- 代码 2
console.log(this);
const f1 = () => console.log(this);
- 箭头函数如何处理 a,就如何处理 this
- 即箭头函数把 this 当作外部的变量,仅此而已,但是非箭头函数的 this 有很多特殊处理
- 箭头函数不支持 this 指的就是箭头函数对 this 与其他变量一视同仁,不会特殊对待
8. 普通函数 this 是参数
- 根据剃刀定理,如无必要,勿增实体
- this 就是一个隐式参数而已
- this 的确定,把隐示 this,改成 call,或者 bind
- 显示 this
fn.call(asThis, 1, 2);
fn.bind(asThis, 1, 2)();
obj.methods.call(obj, "hi");
- 隐示 this
fn(1, 2);
// fn.call(undefined, 1, 2) 浏览器里是window
obj.method("hi");
// obj.method.call(obj, 'hi')
array[0]("hi");
// array[0].call(array, 'hi')
- 测试一下
button.onclick = function (e) {
console.log(this);
};
// this是参数, 答 button 是错的
// 正确答案: this的值无法确定,要看this是如何调用的,如果是用户点击按钮的时候,浏览器调用,浏览器一定会把
// button作为this传进来,作为call的第一个参数,如果通过其他的方式调用的,那么就要看其他方式是怎么调用的
- 真实面试题
let length = 10;
function fn() {
console.log(this.length);
}
let obj = {
length: 5,
method(fn) {
fn(); // 改写成fn.call(undefined) , 指window.length即不是let,是指当前页面iframe有几个
arguments[0](); // arguments.0.call(arguments),即 fn.call(arguments),即实参的长度2
},
};
obj.method(fn, 1); // 输出什么?,输出3(这个不确定),2
9. 递归与调用栈
- 一个简单的递归阶乘,见下图
- 代码表示
j = (n)=> n===1 ? 1: n * j(n-1)
- 带入法求值
j(4)
= 4 * j(3)
= 4 * (3 * j(2))
= 4 * (3 * (2 * j(1)))
= 4 * (3 * (2 * 1 )))
= 4 * (3 * 2)
= 4 * 6
= 24
- 见递进回归图
- 调用栈,压栈和弹栈图
- 复杂点的例子
- 斐波那契数列,每一项都是前两项的和
fib = (n) =>
n === 0 ? 0 :
n === 1 ? 1 :
fib(n-1) + fib(n-2)
- 斐波那契数列示意图,从右往左算
- 带入法
f(4)
= f(3) + f(2)
= (f(2) + f(1)) + (f(1) + f(0) )
= ((f(1) + f(0)) + f(1)) + (1 + 0)
= ((1 + 0) + 1) + 1
= ( 1 + 1 ) + 1
= 3
- 调用栈图
调用栈用来记忆[回到哪]
- 如果需要记忆的过多,就会爆栈
如何降低压栈/计算次数
- 尾递归优化或者记忆函数
尾递归
- 在函数的尾巴进行递归
使用迭代代替递归
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没有尾递归优化
- 所有的递归都可以改写成循环
f = (n) => {
let array = [0,1]
for(let i = 0; i<= n-2; i++){
array[i+2] = array[i+1] + array[1]
}
return array[array.length-1]
}
- js递归没有做很好的处理,记忆化解决方案
- 曾经算了就不再去算,lodash,_.memoize案例
- 记忆化可以减少重复计算,大大减少压栈次数
- React.memo减少计算,用usememo优化
- 一个新问题onClick每次都会重新创建,用useCallback优化