Zijue/blog

函数

Opened this issue · 0 comments

Zijue commented

最近学习前端知识看到一个大佬写的 blog 感觉特别的棒,于是在阅读和学习之后打算总结( 😂 )到自己的知识体系中,建议直接看大佬的总结

函数是 JavaScript 世界里的一等公民,具有一个特别重要的作用:定义作用域,在 ES6 之前,只有函数才有这个功能,ES6 之后才有 let

函数常见的四种形态

// 函数的声明形态
function func0(){
    console.log("函数的声明形态");
}

// 函数的表达式形态(一)
var func1 = function(){
    console.log("函数的表达式形态");
}
// 函数的表达式形态(二)
(function func2() {})  // 这种函数的声明形态主要用于立即执行函数(IIFE) 

// 函数的嵌套形态
var func3 = function(){
    console.log("函数的嵌套形态");
    let func4 = function(){
        console.log("func4 嵌套在 func3 中");
    }
    func4();
}

// 函数的闭包形态
var func5 = function(){
    let a = "func5";
    return function(){
        console.log("闭包形态函数:" + a);
    }
}

所有的函数都通过一对括号“()”调用,我们称之为调用括号对

函数声明提升

只有声明形态的函数,才具有提升的特性(意思就是代码的执行顺序提升至最前面)

console.log(func0); //>> func0() {return 0}
console.log(func1); //>> undefined
//函数的声明形态
function func0() {
  return 0;
}
//函数的表达式形态
var func1 = function() {
  return 1;
};

IIFE与匿名函数、有名函数

IIFE(Immediately-Invoked Function Expression,立即执行函数)形式的函数调用方式,非常适合匿名函数调用。有两种写法(等效):

(function(){
    console.log("我是立即运行的匿名函数");
})();

(function(){
    console.log("我也是立即运行的匿名函数");
}());

有名函数方便递归,递归需要函数调用自身,函数如果没有名字,就无法有效地通过一个标志符找到函数自身以便供调用。函数的名字可以通过name属性读取到

//函数调用自身称为递归,函数名为“func”
(function func(i){
    console.log("函数名为"+func.name+",第"+i+"次调用")
    if(i<3){//递归出口
        func(++i);//递归
    }
})(1);
//>> 函数名为func,第1次调用
//>> 函数名为func,第3次调用
//>> 函数名为func,第3次调用

匿名函数不利于调试栈追踪,有名函数根据名字可以很快在调试的时候定位代码位置

箭头函数

(参数) => { 表达式 } 这种写法声明一个函数,就叫箭头函数(也叫lamda表达式)。主要意图是定义轻量级的内联回调函数

(function(i){
    console.log(i);
})(1);
// 这两种函数的声明方式等效
((i)=>{
    console.log(i);
})(1);

箭头函数不暴露arguments对象,如下所示:

((a)=>{
    console.log(a);//>> 1
    
    console.log(arguments.length);//>> Uncaught ReferenceError: arguments is not defined
})(1);

箭头函数一个明显作用就是可以保持 this 的指向,总是指向定义它时所在的上下文环境

function func() {
  // 返回一个箭头函数
  return a => {
    //this 继承自 func()
    console.log(this.a);
  };
}
var obj1 = {
  a: 2
};
var obj2 = {
  a: 3
};

var bar = func.call(obj1);
bar.call(obj2); //>> 2         不是 3 !

// func() 内部创建的箭头函数会捕获调用时 func() 的 this。
// 由于 func() 的 this 绑定到 obj1, bar(引用箭头函数)的 this 也会绑定到 obj1,
// this一旦被确定,就不可更改,所以箭头函数的绑定无法被修改。(new 也不行!)

箭头函数不能作为构造函数,因此无法被 new 操作,也就没有 new.target

var Foo = () => {};
var foo = new Foo(); // TypeError: Foo is not a constructor

高阶函数(Higher-order function)

如果一个函数可以接收另一个函数作为参数 或 将另一个函数作为返回值,该函数就称之为高阶函数。
高阶函数最常见的形式之一就是回调函数

function fn1(callback){
    if(callback){
        callback();
    }
}

fn1(function(){
    console.log("高阶函数");//>> 高阶函数
});

函数重载

所谓重载(overload),就是函数名称一样,但是随着传入的参数个数不一样,调用的逻辑或返回的结果会不一样。jQuery之父John Resig曾经提供了一个非常巧妙的思路实现重载,代码如下:

(() => {//IIFE+箭头函数,把要写的代码包起来,避免影响外界,这是个好习惯

    // 当函数成为对象的一个属性的时候,可以称之为该对象的方法。
  
    /**
    * @param {object}  一个对象,以便接下来给这个对象添加重载的函数(方法)
    * @param {name}    object被重载的函数(方法)名
    * @param {fn}      被添加进object参与重载的函数逻辑
    */
    function overload(object, name, fn) {
      var oldMethod = object[name];//存放旧函数,本办法灵魂所在,将多个fn串联起来
      object[name] = function() {
        // fn.length为fn定义时的参数个数,arguments.length为重载方法被调用时的参数个数
        if (fn.length === arguments.length) {//若参数个数匹配上
          return fn.apply(this, arguments);//就调用指定的函数fn
        } else if (typeof oldMethod === "function") {//若参数个数不匹配
          return oldMethod.apply(this, arguments);//就调旧函数
                                                  //注意:当多次调用overload()时,旧函数中
                                                  //又有旧函数,层层嵌套,递归地执行if..else
                                                  //判断,直到找到参数个数匹配的fn
        }
      };
    }
  
    // 不传参数时
    function fn0() {
      return "no param";
    }
    // 传1个参数
    function fn1(param1) {
      return "1 param:" + param1;
    }
    // 传两个参数时,返回param1和param2都匹配的name
    function fn2(param1, param2) {
      return "2 param:" + [param1, param2];
    }
  
    let obj = {};//定义一个对象,以便接下来给它的方法进行重载
    
    overload(obj, "fn", fn0);//给obj添加第1个重载的函数
    overload(obj, "fn", fn1);//给obj添加第2个重载的函数
    overload(obj, "fn", fn2);//给obj添加第3个重载的函数
  
    console.log(obj.fn());//>> no param
    console.log(obj.fn(1));//>> 1 param:1
    console.log(obj.fn(1, 2));//>> 2 param:1,2
})();