💙 《JavaScript高级程序设计》 | 函数

33 阅读5分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 1 天,点击查看活动详情

函数是对象,函数名是指向函数对象的指针。

函数声明的 4 种方式:

  • 函数声明的方式定义,比如:function sum (num1, num2) { return num1 + num2; }
  • 函数表达式,比如:let sum = function(num1, num2) { return num1 + num2; };
  • “箭头函数”(arrow function),比如:let sum = (num1, num2) => { return num1 + num2; };
  • 使用 Function 构造函数,比如:let sum = new Function("num1", "num2", "return num1 + num2"); // **不推荐**

箭头函数

任何可以使用函数表达式的地方,都可以使用箭头函数。

箭头函数不能使用 argumentssupernew.target,也不能用作构造函数。

箭头函数没有 prototype 属性。

箭头函数和普通函数的区别

  1. 箭头函数比普通函数更加简洁
  2. 箭头函数没有自己的 this
  3. 箭头函数继承来的 this 指向永远不会改变
  4. call()apply()bind() 等方法不能改变箭头函数中 this 的指向
  5. 箭头函数不能作为构造函数使用
  6. 箭头函数没有自己的 arguments
  7. 箭头函数没有 prototype
  8. 箭头函数不能用作 Generator 函数,不能使用 yeild 关键字

一个函数可以拥有多个函数名。

理解参数

在使用 function 关键字定义(非箭头)函数时,可以在函数内部访问 arguments 对象,从中取得传进来的每个参数值。

arguments 对象是一个类数组对象。

通过 arguments 对象的 length 属性检查传入的参数个数。

默认参数值

ECMAScript 6 支持显式定义默认参数。

arguments 对象的值不反映参数的默认值,只反映传给函数的参数。

默认参数作用域与暂时性死区

参数初始化顺序遵循“暂时性死区”规则,即前面定义的参数不能引用后面定义的。

参数也存在于自己的作用域中,它们不能引用函数体的作用域。


小知识点:

没有重载:同名函数会被后定义的覆盖先定义的。

扩展操作符既可以用于调用函数时传参,也可以用于定义函数参数。

先读取函数声明,并在执行上下文中生成函数定义。而函数表达式必须等到代码执行到它那一行,才会在执行上下文中生成函数定义。

把函数作为参数传给另一个函数,而且还可以在一个函数中返回另一个函数。


函数内部

函数内部存在两个特殊的对象:argumentsthisECMAScript 6 又新增了 new.target 属性。

arguments

arguments 是一个类数组对象,包含调用函数时传入的所有参数。

arguments 对象有一个 callee 属性,是一个指向 arguments 对象所在函数的指针。

使用 arguments.callee 就可以让函数逻辑与函数名解耦

// 阶乘函数
function factorial(num) { 
 if (num <= 1) { 
 return 1; 
 } else { 
 return num * factorial(num - 1); 
 } 
}
// 使用 **arguments.callee**
function factorial(num) { 
 if (num <= 1) { 
 return 1; 
 } else { 
 return num * arguments.callee(num - 1); 
 } 
}

this

this 引用的是把函数当成方法调用的上下文对象,这时候通常称其为 this 值(在网页的全局上下文中调用函数时,this 指向 windows)。

在箭头函数中,this 引用的是定义箭头函数的上下文。

caller

arguments.caller 在严格模式下访问它会报错,在非严格模式下则始终是 undefined

严格模式下还有一个限制,就是不能给函数的 caller 属性赋值,否则会导致错误。

new.target

ECMAScript 6 新增了检测函数是否使用 new 关键字调用的 new.target 属性。

如果函数是正常调用的,则 new.target 的值是 undefined;如果是使用 new 关键字调用的,则 new.target 将引用被调用的构造函数。

函数属性与方法

prototype 是保存引用类型所有实例方法的地方,这意味着 toString()、valueOf() 等方法实际上都保存在 prototype

函数还有两个方法:apply()和 call()。

apply() 方法接收两个参数:函数内 this 的值和一个参数数组。第二个参数可以是 Array 的实例,但也可以是 arguments 对象。

注意: 在严格模式下,调用函数时如果没有指定上下文对象,则 this 值不会指向 window。 除非使用 apply()call() 把函数指定给一个对象,否则 this 的值会变成 undefined

call() 方法:第一个参数跟 apply() 一样,也是 this 值,而剩下的要传给被调用函数的参数则是逐个传递的。

bind() 方法会创建一个新的函数实例,其 this 值会被绑定到传给 bind() 的对象。

函数表达式

定义函数有两种方式:函数声明函数表达式

函数声明的关键特点是函数声明提升,即函数声明会在代码执行之前获得定义。

递归

递归函数通常的形式是一个函数通过名称调用自己。

在写递归函数时使用 arguments.callee 可以避免这个问题。

arguments.callee 就是一个指向正在执行的函数的指针。

尾调用优化

“尾调用”,即外部函数的返回值是一个内部函数的返回值。

尾调用优化要求:直接 return 返回、用返回后必须转型为字符串、是一个闭包。

闭包

闭包指的是那些引用了另一个函数作用域中变量的函数,通常是在嵌套函数中实现的。

调用一个函数时,会为这个函数调用创建一个执行上下文,并创建一个作用域链。

this 如果在全局函数中调用,则 this 在非严格模式下等于 window,在严格模式下等于 undefined

注意: thisarguments 都是不能直接在内部函数中访问的。如果想访问包含作用域中的 arguments 对象,则同样需要将其引用先保存到闭包能访问的另一个变量中。

不合理的使用闭包,从而导致某些变量一直被留在内存当中,会导致内存泄漏

立即调用的函数表达式

函数可以在创建之后立即调用,执行其中代码之后却不留下对函数的引用。

立即调用的函数表达式如果不在包含作用域中将返回值赋给一个变量,则其包含的所有变量都会被销毁。

私有变量

私有变量包括函数参数局部变量,以及函数内部定义的其他函数

特权方法可以使用构造函数或原型模式通过自定义类型中实现,也可以使用模块模式或模块增强模式在单例对象上实现。

私有变量和私有函数是由实例共享的。

模块模式是在单例对象基础上加以扩展,使其通过作用域链来关联私有变量和特权方法。

模块模式使用了匿名函数返回一个对象。