javascript 函数 -声明+实际应用

我心飞翔 分类:javascript

本篇介绍javascript函数的相关概念

函数声明

函数定义

函数表达式

  var fn = function(){
    console.log(1)
  }
 

function + 函数名字

function fnf(){
  console.log(1)
}
 

new Function()

传递参数 参数名称,参数字符串

var fnc = new Function('name','age','console.log(name,age)');
fnc('33',33)
 

函数的变量提升

  • 函数表达式进行定义的函数,会提升var + 函数名字,而=后的作为参数进行赋值操作
  • 使用function 定义的函数,在变量提升的时候会整体进行提升;
  //函数表达式
  console.log('--函数表达式开始-',fn)
  var fn = function(){
    console.log(1)
  }
  console.log('--函数表达式结束-',fn)

  /** 函数fun */
console.log('--function开始-',fnf)
  function fnf(){
    console.log(1)
  }
console.log('--function结束-',fnf)
 
javascript 函数 -声明+实际应用
图1 函数的变量提升

函数类型

具名函数

具名函数是有函数名字的函数,

var fnNameFn = function(){}
function fnNameFn2(){}
console.log(fnNameFn.prototype)

 
javascript 函数 -声明+实际应用
图2 具名函数的名字

匿名函数

无函数名称、如立即执行函数,

(function(){ }())
 

箭头函数

箭头函数涉及的内容比较多,最常见的就是this指向的问题。箭头函数最常见的使用场景就是回调函数了

let arrowFun = ()=>{}
console.log(arrowFun.prototype) // undefined
 

箭头函数的定义格式

// 直接返回值
let arrfn = ()=> 2;
console.log(arrfn())
//传递参数进行判断
let arrfn2 = (name)=> name !='mfy';
console.log(arrfn2())
 

函数参数

函数的参数分为实参形参

  • 实参 实际传入的参数
  • 形参 只能使用在函数体内部的变量
function add(num1,num2){} // num1 、num2是形参
var a=1,b=3;
add(a,b) // a,b 为实参
 

arguments

  • 普通函数是存在arguments
  • 箭头函数不存在arguments
let funMfy =function(){
 console.log(arguments)
}
funMfy('mfy','23')

//箭头函数
let fnnn = ()=>{
 console.log(arguments)
}
fnnn('mfy',33) //报错
 

收集参数

let fyy = function(...args){
 console.log(args)
}
fyy('mfy',32,[])
 

函数应用

函数的使用在js中非常非常多了,其实以下的相关介绍,无非就是在基础函数中进行变换和使用才有了下面的名此

闭包

一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包(closure)。也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域。在 JavaScript 中,每当创建一个函数,闭包就会在函数创建的同时被创建出来。

function clouser(){
  let a = '666'
  return function(){
    console.log(a)
  }
 }
 let bfn = clouser();
 bfn();//666
 

扩展知识点:变量提升 作用域
闭包应用:工厂函数 立即执行函数

闭包常见问题

  • 循环问题
  • 变量打印问题

高阶函数

高阶函数(higher-order-function)指操作函数的函数,一般地,有以下两种情况:

  • 1、函数可以作为参数被传递
  • 2、函数可以作为返回值输出

javascript 中的函数显然满足高阶函数的条件,在实际开发中,无论是将函数当作参数传递,还是让函数的执行结果返回另外一个函数,这两种情形都有很多应用场景。下面将对这两种情况进行详细介绍;

函数做为参数传递

把函数当作参数传递,代表可以抽离出一部分容易变化的业务逻辑,把这部分业务逻辑放在函数参数中,这样一来可以分离业务代码中变化与不变的部分。其中一个常见的应用场景就是回调函数。

回调函数是最常见的参数传递了在ajax异步请求的应用中,回调函数的使用非常频繁。想在ajax请求返回之后做一些事情,但又并不知道请求返回的确切时间时,最常见的方案就是把callback函数当作参数传入发起ajax请求的方法中,待请求完成之后执行callback函数

function ajax(callback){
    $.ajax( 'http://xx.com/getUserInfo?' + userId, function( data ){
        //不确定什么时候返回,进行回调执行
        if ( typeof callback === 'function' ){
          callback( data );
        }
      });
}
 

很多框架已经异步处理的时候都使用了函数作为参数传入
Vue的this.nextTickReact 的setState

函数作为返回值

闭包工厂函数

function People(){}
function Factort(){
 var instance = null
 return function(){
 if(!instance)
   instance = new People();
 }
 return instance;
}
 

函数柯里化

函数的柯里化(currying)又称部分求值。一个currying的函数首先会接受一些参数,接受了这些参数之后,该函数并不会立即求值,而是继续返回另外一个函数,刚才传入的参数在函数形成的闭包中被保存起来。待到函数被真正需要求值的时候,之前传入的所有参数都会被一次性用于求值.简称闭包;

demo 花费计算问题

//通用的柯里化函数
var curring = function(fn){
    var args = [];
    return function(){
        if(arguments.length ==0){
            return fn.apply(this,args);

        }else{
            [].push.apply(args,arguments);
        }
    }
}
var cost1 = (function(){
    var money = 0;
    return  function(){
        for(var i=0;i<arguments.length;i++){
            money+=arguments[i]
        }
        return money;
    }

})()
var cost3 = curring(cost1);
cost3(100);
cost3(200);
cost3(100);
console.log(cost3())
 

求值柯里化

传递多个参数时候

  /**
  * 求值柯里化
  */

var curring3= function(fn){
   //获取fn外的其他参数
   var args = [].slice.call(arguments,1);
   return function(){
       // 获取fn的所有参数
       var innerArgs =[].slice.call(arguments);
       // 最终参数列表重合展开
       var finnalArgs = args.concat(innerArgs);
       //将参数列表展开,并传入fn中
       return fn.apply(null,finnalArgs)
   }
}
var cost6 = (function(){
   var money = 0;
   return function () {
     for (var i = 0, l = arguments.length; i < l; i++) {
       money += arguments[i];
     } 
     return money;
   }
})()
var costC = curring3(cost6,300,233);
console.log(costC(44))

console.log(costC(2003,444))
 

反柯里化

Array.prototype上的方法原本只能用来操作array对象。但用call和apply可以把任意对象当作this传入某个方法,这样一来,方法中用到this的地方就不再局限于原来规定的对象,而是加以泛化并得到更广的适用性

有没有办法把泛化this的过程提取出来呢?反柯里化(uncurrying)就是用来解决这个问题的。反柯里化主要用于扩大适用范围,创建一个应用范围更广的函数。使本来只有特定对象才适用的方法,扩展到更多的对象。

Function.prototype.unCurring=function(){
    var _this = this; 
    return function(){
        var obj = Array.prototype.shift.call(arguments);
        return _this.apply(obj,arguments); //更改当前的this指向
    }
}

//另一种方法实现

Function.prototype.currying = function() {
    var _this = this;
    return function() {
        return Function.prototype.call.apply(_this, arguments);
    }
} 
 

递归函数

递归函数则是自己调用自己本身,最常见的一个就是对象的深拷贝以及斐波那契

惰性函数

惰性函数表示函数执行的分支只会在函数第一次调用的时候执行,在第一次调用过程中,该函数会被覆盖为另一个按照合适方式执行的函数,这样任何对原函数的调用就不用再经过执行的分支了。
javascript的浏览器事件兼容

function addEvent(type, element, fun) {
    if (element.addEventListener) {
        element.addEventListener(type, fun, false);
    }
    else if(element.attachEvent){
        element.attachEvent('on' + type, fun);
    }
    else{
        element['on' + type] = fun;
    }
}

//添加事件

//第一次绑定
var ele = document.getElementById("bind-event");
addEvent('bind-event',ele,()=>{
    console.log(111)
});
//第二次绑定
var ele1= document.getElementById("bind-event2");
addEvent('click',ele1,()=>{
    console.log(222)
})
 

我们每次绑定的时候都回去判断当前是否支持,而在一个浏览器中,我们只需要进行判断一次即可;
惰性函数的本质就是函数重写,所谓惰性载入,指函数执行的分支只会发生一次,有两种实现惰性载入的方式;

  • 第一种 函数的重写 进行变量绑定赋值
function addEvent(type, element, fun){ 
    if (element.addEventListener) {
        addEvent = function(type, element, fun){
            element.addEventListener(type, fun, false);
        }
    }else if(element.attachEvent){
        addEvent = function(type, element, fun){
            element.attachEvent('on' + type, fun);
        }
    }else{
        addEvent =function(type,element,fun){
            element['on' + type] = fun;
        }
    }
    return addEvent(type, element, fun)
}

 

在这个惰性载入的addEvent()中,if语句的每个分支都会为addEvent变量赋值,有效覆盖了原函数。最后一步便是调用了新赋函数。下一次调用addEvent()时,便会直接调用新赋值的函数,这样就不用再执行if语句了;

  • 第二种是声明函数时就指定适当的函数。把嗅探浏览器的操作提前到代码加载的时候,在代码加载的时候就立刻进行一次判断,以便让addEvent返回一个包裹了正确逻辑的函数;
var addEvent = (function () {
    if (document.addEventListener) {
        return function (type, element, fun) {
            element.addEventListener(type, fun, false);
        }
    }
    else if (document.attachEvent) {
        return function (type, element, fun) {
            element.attachEvent('on' + type, fun);
        }
    }
    else {
        return function (type, element, fun) {
            element['on' + type] = fun;
        }
    }
})();
 

纯函数

简单来说,一个函数的返回结果只依赖于它的参数,并且在执行过程里面没有副作用,我们就把这个函数叫做纯函数。这么说肯定比较抽象,我们把它掰开来看:

  • 返回结果只依赖于它的参数
  • 函数执行过程中是没有副作用的
var a =1;
function ac(b){
  return a+b
}
 

此时不是纯函数,因为此时函数的结果依赖的函数外部的变量a;

const a = 1
const foo = (x, b) => x + b
foo(1, 2) // => 3
 

此时函数依赖函数传递过来的参数,当传递的参数为1,2的时候,无论如何都不会改变函数传出的结果
一个函数的返回结果只依赖于它的参数。

👉🏻 引用类型数据 当作参数传入的时候,如果内部更改了引用类型的数据,那么他就不是纯函数,因为出现了副作用;

防抖和节流

这个高频点,另分出来进行解释

js函数+实际应用面试高频点

  • 函数参数arguments的使用
  • 箭头函数使用点
  • 函数内部this指向问题
  • 闭包问题
    • 闭包概念
    • 常见闭包问题
    • 闭包产生的缺点
    • 涉及作用域
  • 函数柯里化(参数收集过程)
  • 写一个纯函数(可能是编程中,要求写纯函数)
  • 防抖和节流 (面试被问频率非常高)
    • 防抖和节流使用场景
    • 手写防抖和节流

参考文档

  • MDN闭包讲解
  • js纯函数讲解

-《javascript 高级程序设计第四版》

  • 很早之前参考的一篇文章

回复

我来回复
  • 暂无回复内容