JavaScript中的一等公民: 函数(Function)

38 篇文章 1 订阅
12 篇文章 0 订阅

1. 函数的基本使用

使用函数声明或者函数表达式创建一个函数

foo();  //foo
bar();  //Uncaught ReferenceError: Cannot access 'bar' before initialization
//函数声明
function foo(){
  console.log(foo);
};

//函数表达式
const bar = function(){
  console.log('bar');
};

setTimeOut(function(){
  //匿名函数
});
  • 函数声明会使函数整体提升
  • 函数表达式只会提升声明不会提升函数体

2. 作用域和闭包

2.1 函数的作用域

  • 函数有自己的作用域
  • 通常情况下函数作用域用内的变量存在于函数运行期间,函数执行完毕后销毁
  • 函数内可以使用上级作用域的变量,上层作用域无法使用函数内部的变量
const age = 21;
function foo(){
  const name = 'ian';
  console.log(age); //21
  return name;
};

console.log(foo); //ian
console.log(name); //undefined

2.2 闭包 closure
形成闭包的三个条件

  • 函数嵌套
  • 内部函数引用了外部函数的变量
  • 外部函数在外层作用域被调用(形成内部函数的引用)
function outer(){
 const name = 'ian';
 function inner(){ console.log(name) };
 return inner;
}

const bar = outer();
bar(); //ian

outer()函数执行的时候得到inner函数,inner函数中使用了outer作用域中的name导致outer函数执行完无法正常释放,变量bar又对inner有了引用,形成了闭包。闭包实际上提供了一种有外部访问函数内部变量的方法。

2.3 闭包的使用场景

函数柯理化

function add(x){
  return function(y){
    return  x + y;
  };
};
const add5 = add(5);
const add3 = add(3);

console.log(add5(1)); //6
console.log(add3(1)); //4

缓存
使用闭包缓存列表进行求和

    function sum() {
      const list = [];
      return function (num) {
        if (num) {
          list.push(num);
        }
        else {
          //沒有传递参数时返回求和结果
          let res = 0;
          for (var i = 0; i < list.length; i++) {
            res += list[i];
          };
          return res;
        }
      };
    };
    const add = sum();
    add(1);
    add(2);
    add(3);
    console.log(add()); //6

使用闭包实现防抖函数

function debounce(fn, await) {
  let timer = null;
  return function (...args) {
    if (timer) clearTimeout(timer);
    timer = setTimeout(() => {
      fn.apply(this, args);
      timer = null;
    }, await);
  }
}

通过闭包实现变量/方法的私有化

function user(){
  const user ={
    name: 'ian',
    age: 21,
  };
return function(){
  return name;
}
};

2.4 闭包的优缺点
闭包的优点:

  • 提供了外部作用域访问访问函数内部的方法
  • 延长了变量的声明周期
  • 避免定义全局变量造成污染

闭包的缺点:
闭包延长了变量的生命周期,增加了变量的引用,大量使用闭包有内存泄露的风险

2.5 闭包一定会造成内存泄露吗?

  • 内存泄露是指没有被使用的变量一直被引用没有被释放,导致内存一直被占用。
  • 闭包不一定会造成内存泄露,只是增加了内存泄露的风险。

下面的代码会造成内存泄露

    window.onload=function(){
      const btn = document.getElementById('btn');
      btn.onclick=function(){
        console.log(btn.id);
      }
    };

避免闭包中内存泄漏的两种方法:

  • 将不用的变量设置为null
  • 减少不必要的变量
    window.onload = function () {
      const btn = document.getElementById('btn');
      const id = btn.id;
      btn.onclick = function () {
        console.log(id);
      };
      btn = null;
    };
    
    window.onload = function () {
      const btn = document.getElementById('btn');
      document.getElementById('btn').onclick = function () {
        console.log(document.getElementById('btn').id);
      };
    };

3. 函数的参数(arguments和arguments.callee)

arguments是一个伪数组,用来获取函数的全部参数

    function add(x, y) {
      return x + y;
    };

    function add1() {
      let res = 0;
      for (let i = 0; i < arguments.length; i++) {
        res += arguments[i]
      };
      return res;
    }

在这里插入图片描述
arguments.callee指向参数所属的当前执行的函数

    function foo() {
      console.log(arguments.callee === foo);
    };
    foo();

将Argumnets转为数组

  //使用数组的slice方法将伪数组转为数组
    function add() {
      const nums = [].slice.call(arguments);
      console.log(nums);
    }
    //使用拓展运算符将伪数组转为数组
    function add(x, y) {
      const nums = [...arguments];
    };
    add(1, 2); //[1, 2]

4. 构造函数

4.1 构造函数的使用

  • 构造函数是一种特殊的函数,总是与new关键字一起使用,得到一个对象
  • 构造函数的声明一般以大写字母开头
 function Person(name, age){
  this.name = name;
  this.age = age;
 };
 
 const jack = new Person('jack',21); //{name:'jack', age:21}
 const tom = new Person('tom',22); //{name:'tom', age:22}

4.2 构造函数执行的过程

  1. 创建一个新对象,将this指向这个新对象
  2. 执行代码为这个对象添加属性
  3. 返回这个对象

4.3 构造函数的返回值
构造函数默认返回一个新的对象,如果有手动的return情况如下:

  • 手动返回基础类型将会被忽略,仍然返回默认的对象
  • 手动返回引用类型的数据将覆盖默认的返回值
    function Person(name,age){
      this.name = name;
      this.age =age;
      return 'person';
    };
    const jack = new Person('jack',23); // //{ name: "jack", age: 23 }
    
    function Person1(name,age){
      this.name = name;
      this.age =age;
      return ['name','age'];
    };
    const tom = new Person1('tom',23); //Array [ "name", "age" ]

5. 立即执行函数 IIFE

立即执行函数就是声明一个匿名函数然后立即执行它

5.1 立即执行函数的语法

function foo(){} ();

这个代码会报错,因为function关键字既可作为函数声明也可作为函数表达式,当以function开头的时候会被认为是函数声明,直接使用()去执行会抛出异常,我们在函数外面包上一个括号使之成为函数表达式。正确的写法如下:

(function(){
  console.log('立即执行函数');
}());

(function(){
   console.log('立即执行函数');
})();

甚至你可以这样写:

+ function(){}(console.log('这是一个函数表达式'));
- function(){}(console.log('这是一个函数表达式'));
! function(){}(console.log('这是一个函数表达式'));
~ function(){}(console.log('这是一个函数表达式'));

5.2 立即执行函数的作用

  • 立即执行函数只会执行一次,而且是匿名函数无法被手动调用
  • 立即执行函数会形成一个内部的作用域,用来存放变量和方法,起到保护作用
  • 避免对全局变量的污染

5.3 立即执行函数的经典使用场景

    //点击按钮输出都是5
    const btns = document.getElementsByClassName('btn');
    for(var i = 0;i<btns.length;i++){
      btns[i].onclick=function(){
        console.log(i);
      }
    };

    //使用立即执行函数解决
    for (var i = 0; i < btns.length; i++) {
      btns[i].onclick = (function (i) {
        return function(){
          console.log(i);
        };
      }(i));
    };
  </script>

使用立即执行函数后,每次for循环都会得到一个新的函数,i从1~5分别被保存到5个不同的函数里,因此点击按钮的时候能依次输出1到5。

6. 递归

递归就是函数调用自身。递归的两个必要条件:

  • 函数调用自身
  • 设置出口条件

利用递归实现斐波那契数列

    function fibonacci(n) {
      if (n <= 2) {
        return 1
      } else {
        return fibonacci(n - 1) + fibonacci(n - 2)
      }
    };

    fibonacci(1); //1
    fibonacci(2); //1
    fibonacci(3); //2
    fibonacci(4); //3
    fibonacci(5); //5

7. 高阶函数

高阶函数的定义: 函数接收函数作为入参

const nums = [1,2,3];
nums.filter(function(n){ returen n>2} );

高阶函数的好处是将具体的计算以函数参数的形式抽离到函数外部,能更灵活的增强函数的功能

8. 函数式编程

  • 函数编程的前提是函数必须幂等:同样的输入有同样的输出
  • 函数式编程设计到的知识点有函数柯理化(curring)、函数组合(compose)等
  • 更详细的请参考:JavaScript中的函数式编程

9. eval

eval接收一个字符串,将这个字符串当作JS语句执行

      const a = 1;
      eval('console.log(a)'); //1

非严格模式下eval能声明和复写变量的值

let a = 1;
eval('a=2; b = 3;')
console.log(a,b); //  2,3

使用别名调用eval的时候,eval使用全局作用域

const exec = eval;
const a = 1;
funtion foo(){
const a = 2;
exec('console.log(a)'); //1
};

总结

函数的基础使用

  • 使用函数表达式或者函数声明去创建一个函数

  • 函数声明整体提升,函数表达式只提升变量

  • 匿名函数

  • 构造函数

    • 构造函数返回一个对象,使用new操作符执行构造函数
    • 构造函数执行的过程:1. 生成一个对象将this绑定到这个对象 2. 执行构造函数的代码 3.返回结果
    • 构造函数默认返回一个对象,手动返回应用类型的数据将代替默认的返回结果
  • 闭包

    • 闭包产生的三个条件:1. 函数嵌套 2.内部函数使用了外部函数作用域内的变量 3.被返回的内部函数在外部作用域有被引用
    • 闭包的主要功能: 1. 提供了函数外部访问函数内部作用域的方法 2. 延长了变量的声明周期,外部函数执行完里面的变量不会立即销毁
    • 闭包的使用场景:缓存、保存私有属性/方法 、柯理化、防抖
    • 闭包的缺点: 增加了内存的占用,外部函数没执行一次都会增加一次引用,可能会造成内存泄漏
    • 如何避免闭包造成内存泄漏: 不使用的变量手动设置为null
  • 函数的arguments和arguments.callee

    • arguments用来获取函数的全部参数是个伪数组
    • 使用Array.prototype.slice(argument)或者[…arguments]将伪数组转为数组
    • arguments.callee指向参数所属的正在执行的函数
  • 立即执行函数(IIFE)

    • 立即执行函数用途:1. 避免污染全局变量 2.保存私有属性/方法
    • 常见的立即执行函数语法: (function(){}())、 (function(){})()
  • 递归

    • 递归就是函数调用自身
    • 递归的2个必要条件: 1.函数调用自身 2.设置出口
  • eval

    • eval接受一个字符串,讲字符串当作js语句执行
    • 非严格模式下eval可以声明和修改变量
    • 使用别名调用eval时,eval的作用域为全局
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值