JavaScript深入学习系列----函数篇上

函数的概念

  • 函数定义:函数是一段可重复使用的代码块,可以通过函数名来调用。
  • 函数的作用:封装代码、提高代码复用性、模块化编程。

函数的定义方式

1.1函数声明

function命令声明的代码区块,就是一个函数。function命令后面是函数名,函数名后面是一对圆括号,里面是传入函数的参数。函数体放在大括号里面

js 复制代码
function print(s) {
  console.log(s);
}

上面的代码命名了一个print函数,以后使用print()这种形式 就可以调用相应的代码。 这叫做函数的声明(Function Declaration)

1.2 函数表达式

除了用function命令声明函数,还可以采用变量赋值的写法。

js 复制代码
var print = function(s) {
  console.log(s);
};

1.3 Function 构造函数

javascript 复制代码
var add = new Function(
  'x',
  'y',
  'return x + y'
);

// 等同于
function add(x, y) {
  return x + y;
}
函数表达式和函数申明的区别

函数会变量提升

sql 复制代码
console.log(sum(10, 10)); 
function sum(num1, num2) { 
 return num1 + num2; 
} 
javascript 复制代码
console.log(sum(10, 10));   //会报错
let sum = function(num1, num2) { 
 return num1 + num2; 
}; 

函数调用

普通函数调用

  • 直接通过函数名加括号调用。
  • 适用于普通函数声明和函数表达式。
js 复制代码
const person = {
    name: "Alice",
    greet: function() {
        console.log(`Hello, ${this.name}!`);
    }
};
person.greet(); // 输出: Hello, Alice!

方法调用

  • 当函数作为对象的方法时,通过对象调用。
  • 此时,函数内部的 this 指向调用该方法的对象。
js 复制代码
const person = {
    name: "Alice",
    greet: function() {
        console.log(`Hello, ${this.name}!`);
    }
};
person.greet(); // 输出: Hello, Alice!
kotlin 复制代码
const calculator = {
    value: 0,
    add: function(num) {
        this.value += num;
        return this;
    },
    multiply: function(num) {
        this.value *= num;
        return this;
    }
};
calculator.add(5).multiply(2);
console.log(calculator.value); // 输出: 10

间接调用( call apply bind

call()和apply()

语法 :显式指定 this 和参数:

  • func.call(thisArg, arg1, arg2...):参数逐个传递。
  • func.apply(thisArg, [arg1, arg2...]):参数以数组传递。
    this 指向thisArg 参数指定的对象。
javascript 复制代码
function showInfo(age, city) {
  console.log(`${this.name}, ${age}, ${city}`);
}

const obj = { name: "Charlie" };
showInfo.call(obj, 25, "New York"); // Charlie, 25, New York
showInfo.apply(obj, [25, "New York"]); // 同上
bind()

语法const boundFn = func.bind(thisArg),返回一个绑定 this 的新函数。
this 指向 :永久绑定到 thisArg,无法再修改。

ini 复制代码
const boundShow = showInfo.bind(obj);
boundShow(30, "London"); // Charlie, 30, London

箭头函数调用

  • 箭头函数没有自己的 this,继承自外层作用域。
  • 不能通过 callapplybind 改变 this
ini 复制代码
const outerThis = this; // 假设外层 this 是全局对象
const arrowFn = () => {
  console.log(this === outerThis); // true
};
arrowFn.call({}); // 仍然指向外层 this(无法修改)

递归调用

递归指函数内部调用自身的方式。

  • 主要用于数量不确定的循环操作
  • 要有退出时机否则会陷入死循环

我们的第一个主题是 递归(recursion)

递归是一种编程模式,在一个任务可以自然地拆分成多个相同类型但更简单的任务的情况下非常有用。

在一个任务可以简化为一个简单的行为加上该任务的一个更简单的变体的时候可以使用。或者,就像我们很快会看到的那样,处理某些数据结构。

当一个函数解决一个任务时,在解决的过程中它可以调用很多其它函数。在部分情况下,函数会调用 自身 。这就是所谓的 递归

简单起见,让我们写一个函数 pow(x, n),它可以计算 xn 次方。换句话说就是,x 乘以自身 n 次。

有两种实现方式。

  1. 迭代思路:使用 for 循环:
ini 复制代码
function pow(x, n) {
  let result = 1;

  // 在循环中,用 x 乘以 result n 次
  for (let i = 0; i < n; i++) {
    result *= x;
  }

  return result;
}

alert( pow(2, 3) ); // 8

2 递归思路:简化任务,调用自身:

scss 复制代码
function pow(x, n) {
  if (n == 1) {
    return x;
  } else {
    return x * pow(x, n - 1);
  }
}

alert( pow(2, 3) ); // 8

递归思路:简化任务,调用自身:

scss 复制代码
function pow(x, n) {
  if (n == 1) {
    return x;
  } else {
    return x * pow(x, n - 1);
  }
}

alert( pow(2, 3) ); // 8
递归遍历

递归的另一个重要应用就是递归遍历。假设我们有一家公司。人员结构可以表示为一个对象:

javascript 复制代码
let company = { // 简洁起见被压缩了
  sales: [{name: 'John', salary: 1000}, {name: 'Alice', salary: 1600 }],
  development: {
    sites: [{name: 'Peter', salary: 2000}, {name: 'Alex', salary: 1800 }],
    internals: [{name: 'Jack', salary: 1300}]
  }
};

// 用来完成任务的函数
function sumSalaries(department) {
  if (Array.isArray(department)) { // 情况(1)
    return department.reduce((prev, current) => prev + current.salary, 0); // 求数组的和
  } else { // 情况(2)
    let sum = 0;
    for (let subdep of Object.values(department)) {
      sum += sumSalaries(subdep); // 递归调用所有子部门,对结果求和
    }
    return sum;
  }
}

alert(sumSalaries(company)); // 7700

递归结构递归(递归定义的)数据结构是一种部分复制自身的结构。

回调函数调用

参数是一个函数(通常是匿名函数),该函数会在行为(action)完成时运行。这是一个带有真实脚本的可运行的示例:

ini 复制代码
// 这里我们加载一个脚本,设置一个回调函数,和我们说什么时候脚本加载完成
function loadScript(src, callback) {
  let script = document.createElement('script');
  script.src = src;
  script.onload = () => callback(script);
  document.head.append(script);
}

loadScript('https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.2.0/lodash.js', script => {
  alert(`酷,脚本 ${script.src} 加载完成`);
  alert( _ ); // _ 是所加载的脚本中声明的一个函数
});

这被称为"基于回调"的异步编程风格。异步执行某项功能的函数应该提供一个 callback 参数用于在相应事件完成时调用。(译注:上面这个例子中的相应事件是指脚本加载)

回调地狱问题

加载多个异步请求怎么处理

lua 复制代码
loadScript('1.js', function(error, script) {

  if (error) {
    handleError(error);
  } else {
    // ...
    loadScript('2.js', function(error, script) {
      if (error) {
        handleError(error);
      } else {
        // ...
        loadScript('3.js', function(error, script) {
          if (error) {
            handleError(error);
          } else {
            // ...加载完所有脚本后继续 (*)
          }
        });

      }
    });
  }
});

为什么优先判断错误,第一次错了,就不用执行后续情况了,称为Error 优先回(errorfirstcallback 风格。

场景 :将函数作为参数传递给其他函数(如事件处理器、定时器)。
this 的陷阱 :回调函数中的 this 默认指向全局对象(需显式绑定)。

javascript 复制代码
const button = document.querySelector("button");
const handler = {
  message: "Clicked!",
  handleClick() {
    console.log(this.message); // 默认 this 指向 button 元素(需绑定)
  },
};

// 错误示例:this 指向 button 元素
button.addEventListener("click", handler.handleClick); 

// 正确示例:通过 bind 绑定 this
button.addEventListener("click", handler.handleClick.bind(handler));
  1. 回调函数中的 this 丢失
    使用 bind() 或箭头函数解决:
scss 复制代码
setTimeout(handler.handleClick.bind(handler), 1000);
// 使用箭头函数包裹
setTimeout(() => handler.handleClick(), 1000);
ini 复制代码
function User(name) {
  this.name = name;
  this.isAdmin = false;
}

let user = new User("Jack");

alert(user.name); // Jack
alert(user.isAdmin); // false

构造函数

构造函数在技术上是常规函数。不过有两个约定:

  1. 它们的命名以大写字母开头。
  2. 它们只能由 "new" 操作符来执行。
ini 复制代码
function User(name) {
  this.name = name;
  this.isAdmin = false;
}

let user = new User("Jack");

alert(user.name); // Jack
alert(user.isAdmin); // false

当一个函数被使用 new 操作符执行时,它按照以下步骤:

  1. 一个新的空对象被创建并分配给 this
  2. 函数体执行。通常它会修改 this,为其添加新的属性。
  3. 返回 this 的值。

换句话说,new User(...) 做的就是类似的事情:

ini 复制代码
function User(name) {
  // this = {};(隐式创建)

  // 添加属性到 this
  this.name = name;
  this.isAdmin = false;

  // return this;(隐式返回)
}
所以 new User("Jack") 的结果和以下操作的结果相同:
let user = {
  name: "Jack",
  isAdmin: false
};
构造器的 return

通常,构造器没有 return 语句。它们的任务是将所有必要的东西写入 this,并自动转换为结果。

但是,如果这有一个 return 语句,那么规则就简单了:

  • 如果 return 返回的是一个对象,则返回这个对象,而不是 this
  • 如果 return 返回的是一个原始类型,则忽略。
csharp 复制代码
function BigUser() {
  this.name = "John";
  return { name: "Godzilla" };  // <-- 返回这个对象
}
alert( new BigUser().name );  // Godzilla,得到了那个对象

这里有一个 return 为空的例子(或者我们可以在它之后放置一个原始类型,没有什么影响):
function SmallUser() {
  this.name = "John";
  return; // <-- 返回 this
}
alert( new SmallUser().name );  // John
构造器中的方法
ini 复制代码
function User(name) {
  this.name = name;
 this.sayHi = function() {
    alert( "My name is: " + this.name );
  };
}
let john = new User("John");

john.sayHi(); // My name is: John
调度:setTimeout 和 setInterval
  • setTimeout 允许我们将函数推迟到一段时间间隔之后再执行。

clearTimeout 来取消调度

scss 复制代码
let timerId = setTimeout(() => alert("never happens"), 1000);
alert(timerId); // 定时器标识符

clearTimeout(timerId);
alert(timerId); // 还是这个标识符(并没有因为调度被取消了而变成 null)

嵌套的 setTimeout

ini 复制代码
我们要实现一个服务(server),每间隔 5 秒向服务器发送一个数据请求,但如果服务器过载了,

那么就要降低请求频率,比如将间隔增加到 10、20、40 秒等。
let delay = 5000;
let timerId = setTimeout(function request() {
  ...发送请求...

  if (request failed due to server overload) {
    // 下一次执行的间隔是当前的 2 倍
    delay *= 2;
  }

  timerId = setTimeout(request, delay);

}, delay);

setInterval

scss 复制代码
// 每 2 秒重复一次
let timerId = setInterval(() => alert('tick'), 2000);

// 5 秒之后停止
setTimeout(() => { clearInterval(timerId); alert('stop'); }, 5000);

函数参数

概述

函数运行的时候,有时需要提供外部数据,不同的外部数据会得到不同的结果,这种外部数据就叫参数。

参数传递规则

  • 基本类型:值传递(函数内修改不影响外部)
  • 对象类型:引用传递(函数内修改属性影响外部)

形参实参

形参是在函数声明时设置的参数,实参指在调用函数时传递的值。

  • 形参数量大于实参时,没有传参的形参值为 undefined
  • 实参数量大于形参时,多于的实参将忽略并不会报错
javascript 复制代码
// n1,n2 为形参
function sum(n1, n2) {
	return n1+n2;
}
// 参数 2,3 为实参
console.log(sum(2, 3)); //5

当没传递参数时值为undefined

function sum(n1, n2) {
  return n1 + n2;
}
console.log(sum(2)); //NaN

默认参数

sql 复制代码
//total:总价 year:年数
function avg(total, year) {
  year = year || 1;
  return Math.round(total / year);
}
console.log(avg(2000, 3));

// 使用新版本默认参数方式如下

function avg(total, year = 1) {
  return Math.round(total / year);
}
console.log(avg(2000, 3));

下面通过排序来体验新版默认参数的处理方式,下例中当不传递 type 参数时使用默认值 asc。

typescript 复制代码
unction sortArray(arr, type = 'asc') {
	return arr.sort((a, b) => type == 'asc' ? a - b : b - a);
}
console.log(sortArray([1, 3, 2, 6], 'desc'));

默认参数要放在最后面

javascript 复制代码
//total:价格,discount:折扣,dis:折后折
function sum(total, discount = 0, dis = 0) {
  return total * (1 - discount) * (1 - dis);
}
console.log(sum(2000, undefined, 0.3));

函数作为函数值进行传递

scss 复制代码
function callSomeFunction(someFunction, someArgument) { 
 return someFunction(someArgument); 
} 
这个函数接收两个参数。第一个参数应该是一个函数,第二个参数应该是要传给这个函数的值。
任何函数都可以像下面这样作为参数传递:
function add10(num) { 
 return num + 10; 
} 
let result1 = callSomeFunction(add10, 10); 
console.log(result1); // 20 


function getGreeting(name) { 
 return "Hello, " + name; 
} 
let result2 = callSomeFunction(getGreeting, "Nicholas"); 
console.log(result2); // "Hello, Nicholas" 
callSomeFunction()函数是通用的,第一个参数传

展开语法 . . .

展示语法或称点语法体现的就是收/放特性,做为值时是放,做为接收变量时是收。

ini 复制代码
let hd = [1, 2, 3];
let [a, b, c] = [...hd];
console.log(a); //1
console.log(b); //2
console.log(c); //3
[...hd] = [1, 2, 3];
console.log(hd); //[1, 2, 3]

使用展示语法可以替代 arguments 来接收任意数量的参数

scss 复制代码
function hd(...args) {
  console.log(args);
}

hd(1, 2, 3,); //[1, 2, 3, ]

也可以用于接收部分参数

scss 复制代码
function hd(site, ...args) {
  console.log(site, args); // (3) [1, 2, 3]
}

hd( 1, 2, 3);
javascript 复制代码
#### arguments

arguments 是函数获得到所有参数集合,下面是使用 arguments 求和的例子

function sum() {

return [...arguments].reduce((total, num) => { return (total += num); }, 0);

}

console.log(sum(2, 3, 4, 2, 6)); //17

更建议使用展示语法

function sum(...args) {

return args.reduce((a, b) => a + b);

}

console.log(sum(2, 3, 4, 2, 6)); //17

复制代码
相关推荐
爱上妖精的尾巴4 分钟前
3-18 WPS JS宏 颜色设置实例应用(按条件设置单元格颜色)学习笔记
javascript·笔记·学习·excel·wps·js宏·jsa
玺同学1 小时前
从卡顿到流畅:前端渲染性能深度解析与实战指南
前端·javascript·性能优化
我是谁谁1 小时前
一篇文章让你学透在Vue 3 中watch 和 watchEffect的区别
javascript
光影少年1 小时前
vuex中的辅助函数怎样使用
前端·javascript
teeeeeeemo1 小时前
JS数据类型检测方法总结
开发语言·前端·javascript·笔记
懒大王、1 小时前
Vue添加图片作为水印
前端·javascript·vue.js
3Katrina2 小时前
《JavaScript this 指向深度剖析:从基础到复杂场景实战》
前端·javascript
暖苏2 小时前
Vue.js第一节
前端·javascript·css·vue.js·ecmascript
white.tie2 小时前
一个手机请求头的随机库
开发语言·javascript·python
萌萌哒草头将军3 小时前
⚡️⚡️⚡️ 开源了!原来 Vite 加载图片还可以这样啊!🚀🚀🚀
javascript·vue.js·react.js