【JavaScript入门笔记05 函数进阶Ⅱ setTimeout 和 setInterval】

本文深入探讨JavaScript中的函数对象,包括函数的name属性、length属性、自定义属性和命名函数表达式(NFE)。此外,讲解了new Function的语法及其与闭包的关系。最后,详细阐述了setTimeout和setInterval的使用,包括它们的工作原理、参数、应用场景及注意事项,特别是嵌套使用和零延时技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

笔记参考javascript.info中文站

函数对象,NFE

我们知道,函数是一种值,而且函数的类型是对象,因此,我们可以把函数看作一种 “行为对象” ,我们还可以对函数这个对象进行 “增/删属性”,“按引用传递” 等操作

1. 属性 “name”

函数的名字可以通过属性 “name” 来访问:

function sayHi() {
  alert("Hi");
}

alert(sayHi.name); // sayHi

事实上,如果函数声明时没有显式的赋予函数名字,Javascript 引擎会自动根据上下文给函数命名:

function f(sayHi = function() {}) {
  alert(sayHi.name); // sayHi(生效了!)
}

而且不可更改:

let sayHi = function() {
  console.log('Hi');
};

sayHi.name = 'balabala';
console.log(sayHi.name); // sayHi(没变)

名字也可能是空字符串:

// 函数是在数组中创建的
let arr = [function() {}];

alert( arr[0].name ); // <空字符串>

2. 属性 “length”

内建属性 “length” 返回函数入参的个数:

function f1(a) {}
function f2(a, b) {}
function many(a, b, ...more) {}

alert(f1.length); // 1
alert(f2.length); // 2
alert(many.length); // 2,rest 参数不参与计数

这个属性常见于多态性函数中,库中的函数会根据函数被调用时传入参数的个数决定哪一套逻辑将被使用

3. 自定义属性

我们也可以添加我们自己的属性

举个例子:

function sayHi() {
  alert("Hi");

  // 计算调用次数
  sayHi.counter++;
}
sayHi.counter = 0; // 初始值

sayHi(); // Hi
sayHi(); // Hi

alert( `Called ${sayHi.counter} times` ); // Called 2 times

我们现在可以利用函数的自定义属性将上一张讲的 “闭包” 修改一下:

function makeCounter() {

  function counter() {
    return counter.count++;
  };

  counter.count = 0;
  return counter;
}

let counter = makeCounter();
alert( counter() ); // 0
alert( counter() ); // 1

counter.count = 10;
alert( counter() ); // 10

现在我们的外部变量也可以随意修改内部的函数的变量了,如果我们不希望外部修改内部,那么还是闭包更合适

4. 命名函数表达式(NFE,Named Function Expression)

命名函数表达式指带有名字的函数表达式

我们将函数赋值给一个变量的时候往往不会给函数增加名字,即使加了名字,这段代码也只是函数表达式的复制过程,而非一个函数的正常声明过程

但是,NFE有其独特之处:函数内部可以调用函数本身,此调用对外部不可见

普通的函数声明也可以做到用函数本身这一点,而且对外部可见,但有时候我们就是需要函数表达式来做一些事,这时候我们并不能直接通过调用赋值的变量来调用函数本身,因为这个变量可能在函数外部被修改,从而导致整个函数从此以后不能使用,即使这个函数在外部变量修改之前已经被赋值给了其他变量:

let sayHi = function(who) {
  if (who) {
    alert(`Hello, ${who}`);
  } else {
    sayHi("Guest"); // Error: sayHi is not a function
  }
};
sayHi(); // Hello, Guest
func(); // Error, func is not defined(在函数外不可见)

let welcome = sayHi; // 赋值给了其他变量
sayHi = null; // 原变量被修改

welcome(); // Error,嵌套调用 sayHi 不再有效!

NFE 就没这个问题:

let sayHi = function func(who) {
  if (who) {
    alert(`Hello, ${who}`);
  } else {
    func("Guest"); // 现在一切正常
  }
};

let welcome = sayHi;
sayHi = null;

welcome(); // Hello, Guest(嵌套调用有效)


“new Function” 语法

“new Function” 语法是一种创建函数的方法,不常使用

1. 语法
let func = new Function ([arg1, arg2, ...argN], functionBody);

举两个例子:

let sum = new Function('a', 'b', 'return a + b');
alert( sum(1, 2) ); // 3

let sayHi = new Function('alert("Hello")');
sayHi(); // Hello

请注意,参数和函数体部分都是字符串

这样的设定方便我们把从服务器中接收到的代码字符串直接使用在我们的函数中:

let str = ... 动态地接收来自服务器的代码 ...

let func = new Function(str);
func();

2. 闭包

闭包是指使用一个特殊的属性 [[Environment]] 来记录函数自身的创建时的环境的函数,指向函数创建之初的词法环境

但是使用 “new Function” 语法创建的函数的 [[Environment]] 指向全局环境

因此,此类函数只能访问全局变量

function getFunc() {
  let value = "test";

  let func = new Function('alert(value)');

  return func;
}

getFunc()(); // error: value is not defined

“new Function” 语法创建的函数是从服务器中直接接受来的,因此我们在编写代码时并不知道我们将接收到什么样的代码,不可控性非常高,因此我们要尽可能避免服务器代码对内部函数以及变量进行修改



setTimeout 和 setInterval

  • setTimeout 允许我们将函数推迟到一段时间间隔之后再执行。
  • setInterval 允许我们重复运行一个函数,从一段时间间隔之后开始运行,之后以该时间间隔连续重复运行该函数。
1. setTimeout
let timerId = setTimeout(func|code, [delay], [arg1], [arg2], ...)

func|code 指希望延迟的代码或函数,如果传入的是字符串,JavaScript 会自动为其创建一个函数:

setTimeout("alert('Hello')", 1000);
// 我们也可以使用箭头函数代替它们
setTimeout(() => alert('Hello'), 1000);

delay 指延迟的时间,毫秒单位
后面的 arg1...... 指被延迟的函数的参数,由函数来决定

举个例子:

function sayHi(phrase, who) {
  alert( phrase + ', ' + who );
}

setTimeout(sayHi, 1000, "Hello", "John"); // Hello, John

setTimeout 在调用时会返回一个“定时器标识符(timer identifier)”,我们可以使用 clearTimeout() 来取消这个调度,但是标识符不会因此而改变:

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

clearTimeout(timerId); // 取消调度,导致什么都没有发生
alert(timerId); // 还是这个标识符(并没有因为调度被取消了而变成 null)

2. setInterval

setInterval 方法和 setTimeout 的语法相同,所有参数的意义也相同

唯一的区别在于,setInterval 方法会每隔 delay 毫秒就重新执行一次函数:

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

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

为了避免 setTimeoutsetInterval 在等待过程中函数没有引用而导致被垃圾回收系统清理, setTimeoutsetInterval 中的函数会被创建一个内部引用,这样就不会被误删了。


3. 嵌套的 setTimeout

我们也可是采用嵌套的 setTimeout 来实现周期调度;

let timerId = setTimeout(function tick() {
  alert('tick');
  timerId = setTimeout(tick, 2000); // (*)
}, 2000);

这样做比 setInterval 函数更加灵活,因为变量的值可以继承,不会重新初始化

我们甚至可以需修改每次 setTimeout 的参数,比如根据情况动态修改延迟时间

除此之外还有一个硬件问题:
setInterval 函数的延迟时间实际上是吧函数执行时间也包含在内的,因此实际等待的时间比 delay 设置的要短一点点

但是嵌套的 setTimeout 没有这个问题,一定是等函数运行结束再进行 delay 时间的等待

4. 零延时的 setTimeout

setTimeout(func, 0),或者仅仅是 setTimeout(func) 可以使用零延时的 setTimeout

这种写法可以让我们在整个脚本运行完之后的瞬间在运行某段代码:

setTimeout(() => alert("World"));

alert("Hello");
// 最终结果是“Hello”、“World”

实际上零延迟并不是真的零延迟,在 5 重嵌套定时器之后,时间间隔被强制设定为至少 4 毫秒

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值