10 函数

63 阅读6分钟
  1. 函数是对象,函数名也是变量,里面值存储的是函数的引用,且值是可以修改的
  2. 函数对象多了其他属性,如apply()、name
  3. 函数并没有签名,即调用函数的时候,参数是无法约束函数的

构造

  1. function f2(一堆参数){}
  2. const f3 = function(一堆参数) {}
  3. const f4 = (一堆参数)=>{}
  4. const f1 = new Function(一堆参数,函数体不需要大括号);

属性与方法

  • name:存储着函数的标识符,即函数名的字符串化
  • length:存储着函数命名参数的个数
  • prototype:存储着函数实例的原型,指向Function.prototype,箭头函数没有该属性
  • apply/call():以指定的this来调用函数,前者接受this值和参数数组,后者接受this值和一堆参数、
  • bind():返回新函数:原函数中this指定为传入参数的第一个

函数声明

function f2(一堆参数){}

1.函数名

  • 函数名就是指向函数的指针,可以变换,也可以赋值给其他变量从而其他变量也指向了函数实例
  • 函数名()则会调用函数,函数名则会访问函数对象实例
function f1(){
  console.log('这是函数体');
}
f1();//调用函数
console.log(f1.name);//f1,访问的是函数实例

2.实名参数

  • js既不关心参数类型,也不关心参数数量
  • 传递实名参数时,可以使用拓展符
    • 调用时对数组使用拓展符,则会将数组分开传入
    • 函数声明中使用拓展符,则会将对应的多个参数转为数组
const array = [1,2,3];
function f1(a,b){
  console.log(a,b);
}
f1(array);//[ 1, 2, 3 ] undefined,参数数量少也可调用
f1(...array);//1 2,参数过多也可以调用
function f2(arg1,...args){
  console.log(arg1,args);
}
f2(4,5,6);//4 [ 5, 6 ]
  • 默认参数:默认参数中,前者不能使用后者来赋值,存在着暂时性死区

3.函数体

arguments

  • 一个类数组对象,存储着传入的参数,不是Array实例,但是Array操作基本上都能使用
  • callee属性:指向arguments的函数实例的指针,用于递归,因为函数名可能中间会指向其他函数,所以不能像其他语言一样过度依赖函数名调用
function factorial(num){
  if(num<=1) return 1;
  return num*arguments.callee(num-1);
}
let trueFactorial = factorial;//传递函数对象引用
factorial = function(){//改变指向
  return 0;
}

console.log(trueFactorial(5));//120
console.log(factorial(5));//0

this

指的是把函数当成方法调用的上下文对象,全局中则是global/window对象

global.color = 'red';
function sayColor(){
  console.log(this.color);
}
const o = {
  color:'blue',
  sayColor
}
sayColor();   //red
o.sayColor(); //blue
//注意上述两者调用的函数是一样的,只是执行上下文环境不同

caller

指向调用当前函数的函数,如果在全局作用中调用这是null

new.target

如果函数new调用,则指向构造函数,否则为undefined,其实就是arguments.callee特供版

function  Obj(){
  console.log(new.target === arguments.callee);
}
new Obj();//true

函数表达式

const f3 = function(一堆参数) {}

  • 与函数声明的区别
    • 函数提升:函数声明是会将所有函数定义提升到最前面,而函数表达式并不会

箭头函数

const f4 = (一堆参数)=>{}

  • 简写
    • 参数只有一个时可不写括号
    • 函数体只有一条语句时可不写大括号
  • 与函数声明的区别
    • 函数体不支持arguments
    • 不能使用与创建对象相关的内容:super、new.target以及不存在属性prototype
    • this指向的是定义时的上下文对象,而不是调用时

函数特殊使用

递归

正如前面而言,函数内部尽量不要使用函数名()来调用自身,而是使用arguments.callee(),但是在严格模式下会出错,所以使用如下取别名方法

const factorial = function f1(num){//取别名
  if(num<= 1) return 1;
  return num*f1(num-1);
}
console.log(factorial(5));//120

闭包

引用了另一个函数作用域中变量的函数,通常是在一个函数内部定义并返回一个新函数(闭包),该新函数使用了外部函数的变量

function outer(value1,value2){
  const temp = arguments;
  return function(){
    console.log(arguments);//只能访问本身的[Arguments] {}
    console.log(temp);//访问外部函数的[Arguments] { '0': 1, '1': 2 }
    return value1<value2;
  }
}
const bibao = outer(1,2);
console.log(bibao());//true

bibao = null;//此时闭包函数对象被销毁,outer(1,2)活动对象才被销毁
  • 问题:外部函数执行上下文的作用域链会销毁,但是活动对象依然保存在内存中,直到闭包函数对象被销毁
  • 如果闭包函数希望访问外部函数的this和arguments,则需要先将这两个赋值给其他变量,才能在闭包中访问

尾调用

在外部函数return时调用另一个函数

  • 这种情况一般会把外部函数保留在栈中,等到另一个函数返回之后再把外部函数弹出栈
  • 优化条件:尾调用函数不是外部函数的闭包,且外部函数返回并不会对尾调用函数的返回进行额外操作

立即调用的函数表达式

此针对匿名函数,紧跟在第一组括号后面的第二组括号会立即调用前面的函数表达式

(()=>{console.log('diaoyong')})(); //diaoyong

私有变量/私有方法

js中并没有私有成员的概念,与之类似的有个私有变量:定义在函数或块中的变量,都可以认为是私有的

特权函数(对象中的方法)

能够访问函数私有变量及私有函数的公有方法,即在函数内部定义对象方法

  • 定义特权方法的方式
  1. 直接使用this.特权方法
    function Person(name){//name是私有变量
      function sayName(){//私有方法,也是闭包
        console.log(name);
      }
      this.getName = ()=>{//特权函数,也是闭包
        sayName();
        return name;
      }
    }
    const person1 = new Person('lisi');
    console.log(person1.getName());//lisi  lisi
    
    • 不足
      • 由于直接使用this,所以外部函数就是构造方法,每次调用构造函数创建对象的时候都会重新创建一套变量和方法,这样会导致每个私有变量/私有方法都会在每个实例上创建,而没有做到共享
  2. 定义在其他对象上
    2.1 静态私有变量

    定义在构造函数的原型对象上

    (function(name){
      function sayName(){//私有方法,也是闭包
        console.log(name);
      }
      Person = function(value){name = value};//构造函数,因为前面没有修饰符,所以是定义在全局的
      Person.prototype.getName = ()=>{//特权函数,也是闭包
        sayName();
        return name;
      }
    })()
    const person1 = new Person('lisi');
    console.log(person1.getName());//lisi  lisi
    const person2 = new Person('zhangsan');
    console.log(person1.getName());//zhangsan zhangsan,因为共享,所以跟着一起变
    console.log(person2.getName());//zhangsan zhangsan
    
    2.2 模块模式

    定义在单例对象(只有一个实例的对象,一般使用对象字面量创建)上

    let singleton = (function(name){
      function sayName(){
        console.log(name);
      }
      return {//直接返回单例对象
        publicName:'public',
        getName(){//特权方法定义在单例对象中
          sayName();
          return name;
        }
      }
    })('lisi');
    console.log(singleton.getName());//lisi  lisi
    
    2.3 模块增强模式

    定义在自创建的对象上,同时对其进行增强(添加修改属性)

    //singleton其实就是obj的增强版本:增加了静态变量
    let singleton = (function(name){
      function sayName(){
        console.log(name);
      }
      let obj = new Object();//创建对象
      obj.publicName = 'public';//对象增强
      obj.getName = ()=>{//特权函数定义在对象上
        sayName();
        return name;
      }
      return obj;//返回对象
    })('lisi');
    console.log(singleton.getName());//lisi  lisi