button执行onclick函数_高级前端养成41js专精09之函数全解上

1. 函数是什么?

  • 根据代码大全的定义97febdb3be9fb697aa17b7a72cee2175.png

  • 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. 递归与调用栈

  • 一个简单的递归阶乘,见下图
28409d83c203c2f413393938708c9683.png
阶乘
  • 代码表示
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
  • 见递进回归图
5b93c5ffe28e7f7e9f53b2aa3ea566c5.png
递归图示
  • 调用栈,压栈和弹栈图
bc05c639a4ba73310b55667c843687c3.png
调栈图
  • 复杂点的例子
    • 斐波那契数列,每一项都是前两项的和
fib = (n) => 
n === 0 ? 0 : 
n === 1 ? 1 : 
fib(n-1) + fib(n-2) 
  • 斐波那契数列示意图,从右往左算
c1c999a18d2646ee7a851aacf492a29a.png
斐波那契数列示意图
  • 带入法
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
  • 调用栈图
57de0b7656f4c5390d8e9c5a2df32869.png
调用栈图
  • 调用栈用来记忆[回到哪]

    • 如果需要记忆的过多,就会爆栈
  • 如何降低压栈/计算次数

    • 尾递归优化或者记忆函数
  • 尾递归

    • 在函数的尾巴进行递归
  • 使用迭代代替递归

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)
// 这就是尾递归
// 为什么尾递归可以优化?因为不用[回头]了
  • 见尾递归图
fe43e1b03473ca051cfec5a72e82b89f.png
尾递归图
  • 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优化
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值