JavaScript开发:函数在实际开发中的使用总结(1)

本文以《JavaScript高级程序设计》第4版作为基础参考,整理使用JavaScript开发过程中,函数使用相关的知识点。

本文是开发知识点系列第六篇。

  1. 第一篇:JavaScript开发中变量、常量声明的规矩总结
  2. 第二篇:JavaScript开发:数据类型知识总结
  3. 第三篇:JavaScript开发:使用Number数据类型需要注意的问题
  4. 第四篇:JavaScript开发:操作符在实际开发中的使用总结
  5. 第五篇:JavaScript开发:流程控制语句在实际开发中的使用总结

函数在JavaScript开发中视为一等公民,足见其重要性,不可不察。

终于写到函数了,前面的五篇可以说是JavaScript内置规则,也可以说是JavaScript语言的基础,给予开发者发挥的空间并不多。一直到函数,开发者可以发挥的余地增大,开发变得异彩纷呈,变得有意思;当然能够与函数在这方面旗鼓相当的还有后面的对象。

什么是函数?函数是具备一定功能的可以被重复调用的代码块。函数可以接收输入(称为参数),并返回一个结果(称为返回值)

函数的基本性质

函数声明&定义

函数可以通过多种方式定义:

  1. 函数声明(Function Declaration):
javascript 复制代码
function myFunction(a, b) {
  return a + b;
}
  1. 函数表达式(Function Expression):
javascript 复制代码
const myFunction = function(a, b) {
  return a + b;
};
  1. 箭头函数(Arrow Function):
javascript 复制代码
const myFunction = (a, b) => {
  return a + b;
};

如果箭头函数函数体只有一条语句,可以简写为:

javascript 复制代码
const myFunction = (a, b) => a + b;
  1. 使用Function构造函数:
javascript 复制代码
const myFunction = new Function('a', 'b', 'return a + b');

这种因为会影响性能,所以不被推荐。

需要注意的是,普通函数声明会被JavaScript引擎提升(hoisted),这意味着你可以在声明之前调用函数。而函数表达式和箭头函数则不会被提升,必须在调用函数之前定义函数。

此外,箭头函数和其他两种方式定义的函数在行为上也有一些差异,例如箭头函数没有自己的thisthis的值在箭头函数被定义时就已经确定,等于箭头函数定义时所在的上下文。

另外,箭头函数不能使用argumenssupernew.target,也不能作为构造函数,也没有prototype属性。

函数名

ECMAScript 6的所有函数对象都会暴露一个只读的name属性。多数情况下这个属性保存的是函数标识符,或者是一个字符串化的变量名。如果没有名称即匿名函数,则显示成空字符串。如果函数是使用Function构造函数创建的,则会标识成"anonymous"。

如果函数是一个获取函数、设置函数或者使用bind()实例化了,则标识符前面会加上一个前缀:对于获取函数(getter)和设置函数(setter),name属性返回 "get " 或 "set " 加上函数的名称。对于使用bind()方法创建的函数,name属性返回 "bound " 加上原函数的名称。

  1. 普通函数:
javascript 复制代码
function myFunction() {}
console.log(myFunction.name); // 输出:"myFunction"
  1. 获取函数和设置函数:
javascript 复制代码
var obj = {
  get value() {},
  set value(v) {}
};

var descriptor = Object.getOwnPropertyDescriptor(obj, 'value');

console.log(descriptor.get.name); // 输出:"get value"
console.log(descriptor.set.name); // 输出:"set value"
  1. 使用bind()创建的函数:
javascript 复制代码
function myFunction() {}
var boundFunction = myFunction.bind(null);

console.log(boundFunction.name); // 输出:"bound myFunction"

此外,name属性的值不一定能反映函数的当前名称,因为函数的名称可以在运行时被改变。

参数

JavaScript中的函数参数不是必须写的,提前写只是为了方便。

非箭头函数可以用arguments获取到参数,其本身就是一个类数组。可通过arguments[0]arguments[1]arguments[2]......获取到参数。

箭头函数因为不能使用arguments,也就不能通过arguments获取参数。但是箭头函数可以通过使用剩余参数的方式,获取参数,后面会说到。

关于函数的默认值,ECMAScript 6可以显式的定义参数的默认值了。不仅限于普通函数,箭头函数也可以。

另外因为参数是按照顺序初始化的,所以后定义默认值的参数可以引用先定义的参数。

js 复制代码
function greet(name = 'World', message = `Hello, ${name}!`) {
  console.log(message);
}

扩展运算符:扩展运算符可以将数组展开,数组成员直接作为参数传递给函数,不仅限于普通函数。

js 复制代码
let numbers = [1, 2, 3];

function sum(a, b, c) {
  return a + b + c;
}

console.log(sum(...numbers));  // 输出 6

收集参数/剩余参数:在ES6中,你可以使用剩余参数(rest parameters)语法来表示任意数量的参数。剩余参数在参数列表中的最后一个参数前面加上...,在函数内部,剩余参数表现为一个数组。

js 复制代码
function sum(...numbers) {
  return numbers.reduce((a, b) => a + b, 0);
}

console.log(sum(1, 2, 3));  // 输出 6

没有重载

所谓重载在传统编程中,比如Java开发中,一个函数名相同的函数可以有两个甚至以上的定义,只要参数的类型或者数量不同就可以。但是JavaScript不可以,只要函数名相同后面定义的会覆盖前面定义的。

函数的属性和方法

在JavaScript中,函数是一种特殊的对象,除了上面提到的name属性,它还有一些其它的属性和方法。

  1. length:返回函数的参数个数,即在函数定义时给出的形参个数。

  2. prototype:非箭头函数都有一个prototype属性,它指向函数的原型对象。当使用new操作符创建一个新对象时,新对象会从它的构造函数的原型对象上继承属性和方法。

下面则是一些函数对象的内置方法:

  1. call():调用一个函数,并将函数的this值设置为提供的值。所有的参数都会作为函数的参数传递。

  2. apply():调用一个函数,并将函数的this值设置为提供的值。接受一个数组或类数组对象,其中的元素会作为函数的参数传递。

  3. bind():创建一个新的函数,新函数的this值和参数被预设为bind()的参数。

  4. toString():返回一个表示函数源代码的字符串。

需要注意的是,虽然函数是对象,但它们和普通的对象有一些重要的区别。最重要的区别是函数可以被调用执行,而普通的对象不能。

还有箭头函数this因为一开始就确定好了,所以不可以通过callapplybind改变。

函数内部

函数内部有一些特殊的对象和变量,它们在函数执行时被创建,并在函数执行完毕后被销毁。除了上面提到的arguments,还有以下特殊对象和变量:

  1. this:这是一个特殊的变量,它引用了函数被调用时的上下文对象。this的值在函数被调用时确定,取决于函数的调用方式。例如,当一个函数作为对象的方法被调用时,this引用了那个对象;当一个函数直接被调用(即不通过对象或其他函数调用)时,this引用了全局对象(在非严格模式下)或undefined(在严格模式下)。当然箭头函数因为this被提前确定了,所以不具备这样的特性。

  2. return:这是一个关键字,用于指定函数的返回值。当执行到return语句时,函数会立即停止执行,并返回return后面的表达式的值。如果函数没有return语句,或者return后面没有表达式,那么函数会返回undefined

  3. 局部变量:在函数内部声明的变量是局部变量,它们只在函数内部可见。每次函数被调用时,都会创建新的局部变量。

  4. 内部函数:在函数内部可以声明其他的函数,这些函数被称为内部函数或嵌套函数。内部函数可以访问外部函数的变量和参数,这称为词法作用域或静态作用域。

函数的种类

除了上面函数声明提到的三种函数:function声明式函数,函数表达式以及箭头函数,还有以下几种函数类型:

  1. 立即调用的函数表达式(Immediately Invoked Function Expression,IIFE):这种函数在定义后立即调用。
javascript 复制代码
(function() {
  console.log('Hello, world!');
})();
  1. 生成器函数(Generator Function):生成器函数是ES6引入的新特性,它可以返回一个生成器对象。生成器对象可以按需产生一系列的值。
javascript 复制代码
function* idGenerator() {
  let id = 0;
  while (true) {
    yield id++;
  }
}
  1. 构造函数(Constructor Function):构造函数用于创建新的对象。构造函数通常首字母大写,使用new关键字调用。
javascript 复制代码
function Person(name) {
  this.name = name;
}
let john = new Person('John');
  1. 方法(Method):方法是定义在对象或类中的函数。
javascript 复制代码
var obj = {
  greet: function() {
    console.log('Hello, world!');
  }
};

递归函数和尾调用优化

递归函数

递归函数是一种调用自身的函数。

javascript 复制代码
function factorial(n) {
  if (n === 0) {
    return 1;
  } else {
    return n * factorial(n - 1);
  }
}

console.log(factorial(5)); // 输出:120

factorial函数接受一个参数n,并返回n的阶乘。如果n等于0,那么函数返回1;否则,函数返回n乘以factorial(n - 1)的结果。这里,factorial(n - 1)是一个递归调用,它计算(n - 1)的阶乘。

递归函数必须有一个终止条件,否则函数会无限递归,导致栈溢出错误。

尾调用优化

尾调用优化是一种编程语言的特性,它允许在函数的最后一步调用另一个函数时,不增加调用栈。这意味着,即使在无限递归的情况下,也不会出现栈溢出错误。

以下是一个使用尾调用的例子:

javascript 复制代码
function factorial(n, acc = 1) {
  if (n === 0) {
    return acc;
  } else {
    return factorial(n - 1, n * acc);
  }
}

console.log(factorial(5)); // 输出:120

在这个例子中,factorial函数在最后一步调用了自身,这是一个尾调用。这里使用了一个累积器acc来保存中间结果,这样在每次递归调用时,都只需要计算一次乘法,而不需要保存中间的乘法结果。这使得函数可以被尾调用优化。

尾调用优化失败的一些例子:

js 复制代码
function foo(x) {
  return bar(x) + 1;  // 尾调用后还有加法操作,无法优化
}

function foo(x) {
  bar(x);  // 尾调用的结果不是函数的返回值,无法优化
}

function foo(x) {
  let y = x * 2;
  return function() { return y; }  // 尾调用函数是一个闭包,无法优化
}

function foo(x) {
  return new bar(x);  // 尾调用函数是一个构造函数,无法优化
}

function foo(x) {
  return obj.bar(x);  // 尾调用函数是一个方法,无法优化
}

function factorial(n) {
  if (n === 0) {
    return 1;
  } else {
    // 尾调用自身,但是调用后还有乘法操作,所以不能被尾调用优化
    return n * factorial(n - 1);
  }
}

需要注意的是,只有在严格模式下,JavaScript引擎才会进行尾调用优化。通过在文件或函数的开始处添加'use strict';来开启严格模式。

总结一下

虽然说到了函数就有意思了,但这篇主要写的还是函数的基本内置规则。好吧,总结一下要点:

  1. 函数是一个具有一定功能的代码块,可以有输入值参数,也可以有返回值,默认返回undefined
  2. 函数声明有几种,注意区别,根据实际开发需要使用
  3. 函数参数可以写也可以不写,写是为了方便,推荐写
  4. 非箭头函数可以通过arguments获得参数
  5. 函数参数可以通过剩余参数收集参数
  6. JavaScript函数没有重载
  7. 注意非箭头函数中的this指向,这个需要总结,一句话总结的话:决定于调用环境
  8. 箭头函数this提前确定,不可以被bindcallapply改变,没有property属性,没有arguments,也不能作为构造函数
  9. 函数的种类有立即调用函数、构造函数还有生成器函数
  10. 递归和尾调用函数学会使用

本文完。

相关推荐
achene_ql9 分钟前
基于 WebRTC 的一对一屏幕共享项目(一)——项目简介
javascript·websocket·node.js·webrtc·html5
七月shi人13 分钟前
Canvas设计图片编辑器全讲解(一)Canvas基础(万字图文讲解)
前端·javascript·canvas
患得患失94936 分钟前
【css】【面试提问】css经典问题总结
前端·css
非著名架构师1 小时前
2025版 JavaScript性能优化实战指南从入门到精通
开发语言·javascript·性能优化
程序员Bears1 小时前
React深度解析:Hooks体系与Redux Toolkit现代状态管理实践
前端·react.js·前端框架
FungLeo1 小时前
浏览器原生 Web Crypto API 实现 SHA256 Hash 加密
前端·算法·哈希算法·sha256·web crypto api
编程大全1 小时前
47道ES67高频题整理(附答案背诵版)
开发语言·javascript·ecmascript
技术与健康2 小时前
Chrome 插件网络请求的全面指南
前端·chrome
夏天想2 小时前
vue页面目录菜单有些属性是根据缓存读取的。如果缓存更新了。希望这个菜单也跟着更新。
前端·vue.js·缓存
神秘的t2 小时前
Spring Web MVC————入门(3)
前端·后端·spring·mvc