JavaScript基础篇-函数

110 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第7天,点击查看活动详情

1. 防抖函数

在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时,重新出发定时器。

function debounce(func,ms = 1000){
  let timer;
  return function(...args){
    if(timer){
      clearTimeout(timer); //清除计时器
    }
    timer = setTimeout(()=>{
      func.apply(this,args); 
    },ms)
  }
}
const task = ()=>{ 
  console.log('run  task')
}
const debounceTask = debounce(task,1000);
window.addEventListener('scroll', debounceTask)

从下图可以看出,当滚动在进行中时,不会输出"run task",当停止滚动1s后就输出。

GIF.gif

2. 节流函数

规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效。

function throttle(func,ms = 1000){
  let canRun = true;
  return function(...args){
    if(!canRun) return;
    canRun = false;
    setTimeout(()=>{
      func.apply(this,args);
      canRun = true;
    },ms)
  }
}
const task = ()=>{ console.log("run task2") }
const throttleTask = throttle(task,1000);
window.addEventListener('scroll', throttleTask)

从下图可以看出,当滚动在进行中时,不会疯狂输出"run task2",而是有规律的输出,每1s输出一次。

GIF.gif

3. 高阶函数

高阶函数是一个接收函数作为参数或者将函数作为返回值输出的函数。

  • Array.prototype.map
  • Array.prototype.filter
  • Array.prototype.reduce
  • Array.prototype.sort 以上是Javascript中内置的高阶函数。它们接受一个函数作为参数,并应用这个函数到列表的每一个元素。
let list = [1,2,3] 
let result = list.reduce((total,num)=>{ return total+num },0) // result => 6

4. 函数柯里化

柯里化就是把具有较多参数的函数转换成具有较少参数的函数的过程。

//普通函数
function sum(a, b, c) {
  return a + b + c;
}
// 调用
sum(1, 2, 3); // 6

上述函数实现的是将a,b,c三个参数相加,改写为柯里化函数如下:

function sum(a){
  return function(b){
    return function(c){
       return a + b + c
    }
  }
}
//调用
let sum1 = sum(1);
let sum2 = sum1(2);
sum2(3); // 6

那么问题来了,上面改写后的柯里化函数和原函数比起来代码多了不少,而且也不如原函数好理解,柯里化函数到底有什么用呢?

确实,柯里化函数在这里看起来的确是很臃肿,不实用,但在很多场景下他的作用是很大的,甚至很多人在不经意间已经在使用柯里化函数了。举一个简单的例子:

设我们有一批的长方体,我们需要计算这些长方体的体积,实现一个如下函数:

function volume(length, width, height) {
  return length * width * height;
}
volume(200, 100, 200);
volume(200, 150, 100);
volume(200, 50, 80);
volume(100, 50, 60);

如上计算长方体的体积函数会发现存在很多相同长度的长方体,我们再用柯里化函数实现一下:

function volume(length, width, height) {
  return function(width) {
    return function(height) {
      return length * width * height;
    }
  }
}
let len200 = volume(200);
len200(100)(200);
len200(150)(100);
len200(50)(80);
volume(100)(50)(60);
//如上,通过实现一个len200函数我们统一处理长度为200的长方体的体积,这就实现了参数复用。

手写柯里化通用函数

function curry(func){
  return function curried(...args){
    //关键知识点: function.length 用来获取函数的形参个数
    //补充: arguments.length 获取的是实参个数
    if(args.length >= func.length){
      return func.apply(this,args)
    }
    return function(...args2){
      return curried.apply(this,args.concat(args2))
    }
  }
}

5. 闭包函数

闭包函数:闭包是指有权访问另一个函数作用域中的变量的函数

闭包的好处:

  • 保护函数内的变量安全,实现封装,防止变量流入其他环境发生命名冲突。
  • 在内存中维持一个变量,可以做缓存。(但使用多了同时也是一项缺点,消耗内存)
  • 匿名自执行函数可以减少内存消耗。

闭包的弊端:

  • 被引用的私有变量不能被销毁,增大了内存消耗,造成内存泄漏,解决方法是可以在使用完变量后手动为它赋值为null。
  • 其次由于闭包涉及跨域访问,所以会导致性能损失,我们可以通过把跨作用域变量存储在局部变量中,然后直接访问局部变量,来减轻对执行速度的影响。
var global = "global scope"; //全局变量
function checkscope() {
    var scope = "local scope"; //局部变量
    function f() {
        return scope; //在作用域中返回这个值
    };
    return f();
}
checkscope(); // 返回"local scope"

严格来说,闭包需要满足三个条件:

  1. 访问所在作用域;
  2. 函数嵌套;
  3. 在所在作用域外被调用;

使用闭包实现一个计数器

function outerFunction () {
     var counter = 0;
     function innerFunction (){
         return counter += 1;
     }
     return innerFunction;
}
var add = outerFunction();
add(); //1
add(); //2
add(); //3

//当不使用该闭包,需释放内存
add = null;

这时候的add就形成了一个闭包。一个闭包由两部分组成,函数和创建该函数的环境。环境是由环境中的局部变量组成的。对于闭包add来说,它由函数innerFunction和变量counter组成,所以这时候add是可以访问变量counter的。

6. 立即执行函数

声明一个函数,并马上调用这个匿名函数就叫做立即执行函数;

(function () {alert("我是匿名函数")}()) //用括号把整个表达式包起来 
(function () {alert("我是匿名函数")})() //用括号把函数包起来 
!function () {alert("我是匿名函数")}() //求反,我们不在意值是多少,只想通过语法检查 
+function () {alert("我是匿名函数")}() 
-function () {alert("我是匿名函数")}() 
~function () {alert("我是匿名函数")}() 
void function () {alert("我是匿名函数")}() 
new function () {alert("我是匿名函数")}()

立即执行函数的作用

  • 可以不为函数命名,避免污染全局变量。
  • 函数内部形成一个单独的作用域,可以封装一些外部无法读取的私有变量。

一道经典面试题

function test(){
    for( var index = 0; index < 3; index++ ){
        setTimeout(function(){
            console.log('index'+index)        
        })    
    }
}
test();
//输出3个index3

image.png

通过闭包+立即执行函数解决问题

function test(){
    for( var index = 0; index < 3; index++ ){
        (index=>{
            setTimeout(()=>{
                console.log("index"+index)            
            })        
        })(index)    
    }
}
test(); //输出 "index0" "index1" "index2"

image.png

也可以使用ES6 let定义变量

function test(){
    for( let index= 0; index < 3; index++ ){
        setTimeout(()=>{
            console.log('index'+index)        
        })    
    }
}
test() //输出 "index0" "index1" "index2"

image.png