Harmony鸿蒙开发0基础入门到精通Day09--JavaScript篇

继承

在面向对象编程中,继承 是核心特性之一,核心目的是实现代码复用 (子类复用父类的属性和方法)并建立类之间的层次关系 (如 "学生" 是 "人" 的子类,"狗" 是 "动物" 的子类)。ES6 通过 extendssuper 关键字简化了继承实现,比 ES5 的 "原型链继承" 更直观、更不易出错。

继承的本质是:子类(派生类)通过继承父类(基类),获得父类的所有属性和方法,同时可以添加自己的属性 / 方法,或重写父类的方法

  • 复用:减少重复代码(父类定义通用逻辑,子类直接使用);
  • 扩展:子类在父类基础上增加新功能(符合 "开闭原则":对扩展开放,对修改封闭)。
原型链继承(最基础,有缺陷)

原理 :让子类的原型(Sub.prototype)指向父类的实例(new Parent()),使子类实例能通过原型链访问父类的属性和方法。

javascript 复制代码
// 父类构造函数
function Parent(name) {
  this.name = name; // 父类实例属性
  this.hobbies = ["reading"]; // 父类引用类型属性
}

// 父类原型方法(需要复用的方法)
Parent.prototype.sayName = function() {
  console.log(this.name);
};

// 子类构造函数
function Child(age) {
  this.age = age; // 子类实例属性
}

// 核心:子类原型指向父类实例(建立原型链)
Child.prototype = new Parent();
// 修复子类原型的 constructor 指向(否则会指向 Parent)
Child.prototype.constructor = Child;

// 子类原型新增方法(仅子类可用)
Child.prototype.sayAge = function() {
  console.log(this.age);
};
javascript 复制代码
const child1 = new Child(18);
child1.name = "Alice"; // 给父类属性赋值
child1.hobbies.push("running"); // 修改父类的引用类型属性

console.log(child1.name); // "Alice"(继承父类属性)
child1.sayName(); // "Alice"(继承父类原型方法)
child1.sayAge(); // 18(子类自己的方法)

// 问题1:子类实例修改引用类型属性会影响其他实例
const child2 = new Child(20);
console.log(child2.hobbies); // ["reading", "running"](被 child1 影响)

// 问题2:无法向父类构造函数传递参数(创建 Child 时不能直接传 name 给 Parent)

缺点

  • 子类实例共享父类的引用类型属性(修改一个会影响所有);
  • 无法在创建子类实例时向父类构造函数传递参数;
  • 子类原型的 constructor 会被篡改,需手动修复。
借用构造函数(解决属性继承问题)

原理 :在子类构造函数中,通过 Parent.call(this, 参数) 调用父类构造函数,强制将父类的属性绑定到子类实例上(避免共享引用类型)。

javascript 复制代码
function Parent(name) {
  this.name = name;
  this.hobbies = ["reading"]; // 引用类型属性
}

Parent.prototype.sayName = function() {
  console.log(this.name);
};

function Child(name, age) {
  // 核心:借用父类构造函数,给子类实例绑定父类属性
  Parent.call(this, name); // 相当于:this.name = name; this.hobbies = ["reading"]
  this.age = age;
}

// 子类原型方法(需单独定义,无法复用父类原型方法)
Child.prototype.sayAge = function() {
  console.log(this.age);
};
javascript 复制代码
const child1 = new Child("Alice", 18);
child1.hobbies.push("running");
console.log(child1.hobbies); // ["reading", "running"]

const child2 = new Child("Bob", 20);
console.log(child2.hobbies); // ["reading"](不被 child1 影响,解决引用类型共享问题)

child1.sayName(); // 报错:child1.sayName is not a function(未继承父类原型方法)

优点

  • 解决了引用类型属性共享的问题(每个子类实例有独立的属性);
  • 能向父类构造函数传递参数(Child 中通过 Parent.callname)。

缺点

  • 父类原型上的方法无法被继承(子类实例不能调用 Parent.prototype 上的方法);
  • 父类构造函数中的方法会被每个子类实例重复创建(不共享,浪费内存)。
组合继承(原型链 + 借用构造函数,常用但有优化空间)

原理 :结合 "原型链继承" 和 "借用构造函数",用借用构造函数继承属性 ,用原型链继承方法,兼顾两者优点。

javascript 复制代码
function Parent(name) {
  this.name = name;
  this.hobbies = ["reading"];
}

Parent.prototype.sayName = function() {
  console.log(this.name);
};

function Child(name, age) {
  // 1. 借用构造函数:继承属性(解决共享问题+传参)
  Parent.call(this, name); 
  this.age = age;
}

// 2. 原型链:继承方法(复用父类原型方法)
Child.prototype = new Parent(); 
Child.prototype.constructor = Child; // 修复 constructor

// 子类原型新增方法
Child.prototype.sayAge = function() {
  console.log(this.age);
};
javascript 复制代码
const child = new Child("Charlie", 19);
child.sayName(); // "Charlie"(继承父类原型方法)
child.sayAge(); // 19(子类方法)
console.log(child.hobbies); // ["reading"]

const child2 = new Child("Dave", 20);
child2.hobbies.push("coding");
console.log(child2.hobbies); // ["reading", "coding"](不影响 child)

优点

  • 既继承了属性(独立不共享),又继承了方法(共享复用);
  • 支持向父类构造函数传递参数。

缺点

  • 父类构造函数会被调用两次:一次是 new Parent() 给子类原型赋值时,一次是 Parent.call(this) 给子类实例赋值时;
  • 子类原型上会有多余的父类属性(new Parent() 产生的,虽不影响实例,但冗余)。
拷贝继承

"拷贝继承" 是一种通过复制父类(或父对象)的属性和方法到子类(或子对象) 实现继承的方式,核心是 "直接拷贝成员" 而非依赖原型链关联。它不涉及复杂的原型链操作,实现简单,但也存在明显的局限性。

实例拷贝:拷贝父类实例成员到子类实例
javascript 复制代码
// 父类构造函数
function Parent(name, age) {
  this.name = name;
  this.age = age;
  this.init = function() {
    console.log(`Parent init: ${this.name}`);
  };
}
Parent.prototype.sayHi = function() {
  console.log(`Hi, I'm ${this.name}`);
};

// 子类构造函数(实例拷贝)
function Child(name, age, num) {
  // 1. 创建父类实例
  const parent = new Parent(name, age);
  // 2. 拷贝父类实例的所有可枚举成员到子类实例(this)
  for (const key in parent) {
    this[key] = parent[key]; // 包括 name、age、init(自身成员)和 sayHi(原型成员)
  }
  // 3. 子类自身属性
  this.num = num;
}

// 测试
const child = new Child("Alice", 18, 1001);
console.log(child.name); // "Alice"(拷贝自父类实例)
child.init(); // "Parent init: Alice"(拷贝自父类实例)
child.sayHi(); // "Hi, I'm Alice"(拷贝自父类原型)
原型拷贝:拷贝父类成员到子类原型
javascript 复制代码
// 父类同上(Parent)

// 子类构造函数(原型拷贝)
function Child(num) {
  this.num = num; // 子类自身属性
}

// 1. 创建父类实例
const parent = new Parent("Bob", 20);
// 2. 拷贝父类成员到子类原型(所有子类实例共享)
for (const key in parent) {
  Child.prototype[key] = parent[key];
}

// 测试
const child1 = new Child(1002);
const child2 = new Child(1003);

console.log(child1.name); // "Bob"(共享父类实例的固定值)
console.log(child2.name); // "Bob"(同一份拷贝,值固定)
child1.sayHi(); // "Hi, I'm Bob"
优点:
  1. 实现简单:无需理解原型链,通过循环拷贝即可实现继承,对新手友好;
  2. 避免原型链陷阱:不依赖原型链,不会出现 "子类实例修改原型属性影响所有实例" 的问题(实例拷贝时,每个实例有独立的父类成员);
  3. 直接访问父类成员 :子类实例可直接通过 this 访问父类成员,无需原型链查找。
缺点(核心局限性):
  1. 父类属性参数固定(原型拷贝的致命问题) 原型拷贝时,父类实例是固定的(如 new Parent("Bob", 20)),所有子类实例的父类属性(nameage)都是同一个值,无法在创建子类实例时动态传递参数(比如想让 child1 叫 "Alice",child2 叫 "Bob" 做不到)。

  2. **引用类型成员仍可能共享(浅拷贝问题)**拷贝是 "浅拷贝":如果父类有引用类型成员(如数组、对象),拷贝的是引用地址,子类实例修改后会影响其他实例(实例拷贝也可能有此问题)。

    javascript 复制代码
    function Parent() {
      this.hobbies = ["reading"]; // 引用类型
    }
    function Child() {
      const parent = new Parent();
      for (const key in parent) {
        this[key] = parent[key]; // 浅拷贝,hobbies 是引用
      }
    }
    const c1 = new Child();
    const c2 = new Child();
    c1.hobbies.push("running");
    console.log(c2.hobbies); // ["reading", "running"](被 c1 影响)
  3. 效率低,重复拷贝 实例拷贝时,每次创建子类实例都会重复创建父类实例并拷贝成员(比如创建 100 个 Child 实例,会执行 100 次拷贝),浪费内存和性能。

  4. 无法继承父类原型的动态更新 如果后续修改父类原型的方法(如 Parent.prototype.sayHi = function() { ... }),子类实例不会同步更新(因为拷贝的是旧版本方法)。

  5. 可能拷贝不必要的成员 for...in 循环会遍历父类原型链上的所有可枚举成员(包括继承的属性),可能拷贝不需要的内容(可通过 hasOwnProperty 过滤,但增加复杂度)。

ES6 用 extends 声明继承关系,用 super 关联父类,语法简洁且语义清晰。
javascript 复制代码
// 父类(基类):定义通用属性和方法
class Person {
  // 父类构造方法:初始化通用属性
  constructor(name, age) {
    this.name = name; // 姓名(通用属性)
    this.age = age;   // 年龄(通用属性)
  }

  // 父类实例方法:通用行为
  sayHi() {
    console.log(`我是${this.name},${this.age}岁`);
  }

  // 父类静态方法:通用工具方法
  static create(name, age) {
    return new Person(name, age); // 工厂方法:创建实例
  }
}

// 子类(派生类):用 extends 继承 Person
class Student extends Person {
  // 子类构造方法:初始化子类特有属性
  constructor(name, age, grade) {
    // 必须先调用 super() 才能使用 this(super 代表父类的构造方法)
    super(name, age); // 调用父类构造方法,传递 name 和 age
    this.grade = grade; // 年级(子类特有属性)
  }

  // 子类实例方法:扩展新行为
  study() {
    console.log(`${this.name}在${this.grade}年级学习`);
  }

  // 子类重写父类方法:覆盖父类的 sayHi,添加子类逻辑
  sayHi() {
    super.sayHi(); // 调用父类的 sayHi 方法(复用父类逻辑)
    console.log(`我在${this.grade}年级`); // 新增子类逻辑
  }

  // 子类静态方法:继承父类静态方法,也可新增
  static createStudent(name, age, grade) {
    return new Student(name, age, grade);
  }
}
extends:声明继承关系

class 子类 extends 父类 表示 "子类继承自父类",子类会自动获得父类的所有实例属性、实例方法、静态方法(私有成员除外)。

javascript 复制代码
// 子类实例可以访问父类的属性和方法
const student = new Student("Alice", 16, 10);
console.log(student.name); // "Alice"(继承父类的 name 属性)
student.sayHi(); // 调用重写后的 sayHi(同时复用父类逻辑)
student.study(); // 调用子类新增的 study 方法
super:连接父类的 "桥梁"

super 是继承的核心,有两种关键用法:

  • super(参数) :在子类 constructor 中调用,代表父类的构造方法 ,用于初始化父类的属性。✅ 必须在子类构造方法中先调用 super(),再使用 this(否则报错,因为子类实例的创建依赖父类初始化)。

  • super.方法名() :在子类方法中调用,代表父类的原型方法,用于复用父类的方法逻辑。

私有成员的继承限制

父类的私有成员(用 # 定义)不会被子类继承,子类无法访问(即使在内部也不行),确保父类的封装性。

javascript 复制代码
class Parent {
  #privateProp = "父类私有属性"; // 私有属性
  getPrivate() {
    return this.#privateProp; // 父类内部可访问
  }
}

class Child extends Parent {
  tryAccessParentPrivate() {
    // return this.#privateProp; // 报错:子类无法访问父类私有属性
  }
}

const child = new Child();
console.log(child.getPrivate()); // "父类私有属性"(通过父类公共方法间接访问)

闭包

闭包(Closure)是 JavaScript 中函数及其关联的作用域链 的组合,核心特性是:内部函数能够访问并操作其外部函数(即使外部函数已经执行完毕)中的变量。简单说,闭包让函数 "记住" 了它诞生时所处的环境(外部变量)。

形式:函数套函数

特点:包含最里层的变量不受外界影响

面试题:

<script>

function fun(n, o) {

console.log(o)

const obj = {

fun: function (m) {

return fun(m, n)

}

}

return obj

}

var a = fun(0)//undefined

a.fun(1)//0

a.fun(2)//0

a.fun(3)//0

</script>

先写个简单的案例,让我们来看看闭包的使用

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <input type="button" class="left" value="-">
    <input type="text" class="t" value="0">
    <input type="button" class="right" value="+">
</body>
<script>
    let oLeft = document.querySelector('.left')
    let oRight = document.querySelector('.right')
    let oT = document.querySelector('.t')
    function fn(v){
        let n = v;
        const obj = {
            add:()=>++n,
            reduce:()=>--n
        }
        return obj
    }
    
    let res = fn(oT.value)
    oLeft.onclick = ()=>oT.value = res.reduce()
    oRight.onclick = ()=>oT.value = res.add()
</script>
</html>

闭包的形成需要同时满足以下 3 个条件,缺一不可:

  1. 嵌套函数:存在内部函数(子函数)嵌套在外部函数(父函数)中;
  2. 引用外部变量:内部函数引用了外部函数中的变量(或参数);
  3. 外部访问内部函数:内部函数被外部函数返回,或通过其他方式暴露到外部(使得外部函数执行后,内部函数仍能被调用)。

直观理解:闭包的经典示例

通过一个 "计数器" 例子,直观感受闭包的作用:

javascript 复制代码
// 外部函数:创建计数器环境
function createCounter() {
  let count = 0; // 外部函数的局部变量(被内部函数引用)

  // 内部函数:引用并操作外部变量 count
  function increment() {
    count++; // 访问外部函数的 count
    return count;
  }

  // 返回内部函数(暴露到外部)
  return increment;
}

// 调用外部函数,得到内部函数(此时 createCounter 已执行完毕)
const counter = createCounter(); 

// 多次调用内部函数,发现它仍能访问 count
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3

关键现象createCounter 执行完毕后,理论上其内部的 count 变量应该被销毁(函数执行上下文出栈),但由于内部函数 increment 引用了 count 且被外部的 counter 保存,count 被 "保留" 下来,这就是闭包的作用 ------延长外部变量的生命周期

闭包的本质:作用域链的持久化

JavaScript 中,函数在创建时会关联一个作用域链(包含自身作用域、外部函数作用域、全局作用域)。当内部函数被外部引用时,这个作用域链不会被销毁,因此内部函数始终能通过作用域链访问外部变量。

简单说:闭包 = 内部函数 + 外部函数的作用域(含变量)

闭包的典型用途(为什么需要闭包?)

闭包的核心价值是 **"封装私有变量""保存状态"**,在实际开发中有 3 个高频场景:

1. 实现私有变量(模块化)

JavaScript 没有原生的 "私有变量" 语法(ES6 的 # 私有属性是后来新增的),闭包可模拟私有变量 ------ 外部无法直接访问,只能通过内部函数暴露的接口操作。

javascript 复制代码
function createUser(name) {
  // 私有变量(外部无法直接访问)
  let age = 18; 

  // 暴露操作私有变量的接口(闭包)
  return {
    getName: () => name, // 访问私有变量 name
    getAge: () => age,   // 访问私有变量 age
    grow: () => age++    // 修改私有变量 age
  };
}

const user = createUser("Alice");
console.log(user.getName()); // "Alice"(通过接口访问)
console.log(user.getAge());  // 18
user.grow();
console.log(user.getAge());  // 19

// 无法直接访问私有变量
console.log(user.age); // undefined(外部访问不到)
2.保存函数状态(如防抖、节流、柯里化)

闭包能 "记住" 函数上一次执行的状态,这是防抖、节流、函数柯里化的核心原理。

示例:简单防抖(限制高频操作)

javascript 复制代码
function debounce(fn, delay) {
  let timer = null; // 闭包保存定时器状态

  // 内部函数(闭包)
  return function(...args) {
    // 清除上一次的定时器
    if (timer) clearTimeout(timer); 
    // 重新计时
    timer = setTimeout(() => {
      fn.apply(this, args);
    }, delay);
  };
}

// 使用:输入框输入后 500ms 才执行搜索
const input = document.querySelector("input");
input.oninput = debounce(function(e) {
  console.log("搜索:", e.target.value);
}, 500);

这里 timer 被闭包保存,每次输入时能 "记住" 上一次的定时器,从而实现防抖。

3. 回调函数中保留上下文

在异步回调(如 setTimeout、事件监听)中,闭包可保留回调执行时需要的上下文变量。

javascript 复制代码
function setup() {
  const message = "Hello, 3秒后执行";

  // 定时器回调是闭包,记住 message
  setTimeout(function() {
    console.log(message); // 3秒后输出 "Hello, 3秒后执行"
  }, 3000);
}

setup(); // 调用后,message 被闭包保留,3秒后仍能访问

沙箱

在 JavaScript 中实现沙箱的核心目标是隔离不可信代码 ,限制其对全局环境、敏感 API(如 localStoragefetch、DOM 操作)的访问,防止恶意行为。根据运行环境(浏览器 / Node.js)和隔离强度的不同,有多种实现方案,以下是最常用的几种实践方式:

浏览器端沙箱实现(前端场景)

浏览器端沙箱主要解决 "用户脚本污染全局作用域、篡改页面或窃取浏览器资源" 的问题,常用方案如下:

1. iframe 沙箱(隔离性最强,推荐)

利用 iframe 的原生隔离能力和 sandbox 属性,为不可信代码创建独立的全局环境,限制其权限。原理iframe 拥有独立的 windowdocumentsandbox 属性可精确控制允许的操作(如是否允许脚本、表单提交、同源访问等)。

示例代码

html 复制代码
<!-- 主页面 -->
<button id="runBtn">运行用户代码</button>
<div id="output"></div>

<!-- 隐藏的 iframe 作为沙箱容器 -->
<iframe 
  id="sandboxFrame" 
  style="display: none"
  sandbox="allow-scripts"  <!-- 仅允许执行脚本,其他权限(如表单、同源访问)默认禁用 -->
></iframe>

<script>
  const frame = document.getElementById('sandboxFrame');
  const output = document.getElementById('output');
  const runBtn = document.getElementById('runBtn');

  // 初始化 iframe 沙箱环境
  frame.onload = () => {
    // 向沙箱注入必要的工具(如输出函数)
    frame.contentWindow.log = (msg) => {
      output.textContent += `\n${msg}`; // 沙箱内代码通过 log 输出到主页面
    };
  };
  // 加载空白页面(确保沙箱环境干净)
  frame.src = 'about:blank';

  // 运行用户代码
  runBtn.onclick = () => {
    const userCode = `
      // 用户提交的代码(在沙箱内执行)
      log('用户代码执行中...');
      log('沙箱内的 window:', window === parent.window); // false(独立 window)
      
      // 尝试访问敏感 API(会被 sandbox 限制)
      try {
        localStorage.setItem('test', '123'); // 报错:localStorage 被禁用
      } catch (e) {
        log('禁止访问 localStorage:', e.message);
      }
    `;

    // 在 iframe 中执行代码
    frame.contentWindow.eval(userCode);
  };
</script>

核心配置(sandbox 属性)

  • allow-scripts:允许 iframe 内执行 JavaScript(必须显式开启,否则脚本无法运行);
  • allow-same-origin:允许 iframe 访问与主页面同源的资源(谨慎开启,可能突破隔离);
  • allow-top-navigation:允许 iframe 导航到顶层页面(禁止开启,防止恶意跳转)。

优点 :隔离性强(原生浏览器支持),安全性高;缺点 :与主页面通信需通过 postMessage 或注入函数,略复杂。

2. Web Workers 沙箱(适合计算密集型代码)

Web Workers 是独立于主线程的线程,与主线程通过消息通信,天然隔离 DOM 和主线程全局变量,适合执行耗时或不可信的计算逻辑。

原理 :Worker 线程无法访问 windowdocument 等 DOM 相关对象,只能通过 postMessage 与主线程交互,避免篡改页面。

示例代码

html 复制代码
<!-- 主页面 -->
<input type="text" id="codeInput" placeholder="输入代码,如 1+1">
<button id="runBtn">执行</button>
<div id="result"></div>

<script>
  const runBtn = document.getElementById('runBtn');
  const codeInput = document.getElementById('codeInput');
  const result = document.getElementById('result');

  // 创建 Worker 沙箱(独立线程)
  const worker = new Worker('sandbox-worker.js');

  // 接收 Worker 的执行结果
  worker.onmessage = (e) => {
    result.textContent = `结果:${e.data}`;
  };

  // 运行用户代码
  runBtn.onclick = () => {
    const userCode = codeInput.value;
    worker.postMessage(userCode); // 发送代码到 Worker 执行
  };
</script>

<!-- sandbox-worker.js(Worker 脚本) -->
self.onmessage = (e) => {
  const code = e.data;
  try {
    // 限制代码仅为表达式(避免复杂逻辑)
    const result = eval(`(${code})`); // 用括号包裹确保表达式执行
    self.postMessage(result); // 发送结果回主线程
  } catch (err) {
    self.postMessage(`错误:${err.message}`);
  }
};

限制

  • 无法访问 DOM(documentwindow 均不可用);
  • 无法访问主线程的全局变量,只能通过消息传递数据;
  • 适合纯计算场景(如公式计算、数据处理),不适合需要 DOM 操作的代码。

优点 :非阻塞主线程,隔离性好;缺点:功能受限(无 DOM 访问),通信成本高。

3. Proxy + 作用域隔离(轻量隔离,适合简单场景)

通过 Proxy 拦截全局变量访问,结合函数作用域限制代码只能操作 "允许的资源",模拟一个受限的全局环境。

原理 :用 Proxy 包装一个 "伪全局对象",代码执行时通过 with 语句将作用域绑定到该对象,所有变量访问都会被 Proxy 拦截,禁止访问敏感资源。

示例代码

javascript 复制代码
// 创建沙箱函数
function createSandbox() {
  // 允许访问的全局变量白名单
  const allowedGlobals = new Set(['console', 'Math', 'Date']);

  // 沙箱内的伪全局对象
  const fakeGlobal = {
    console: { log: (...args) => console.log('[沙箱]', ...args) }, // 包装 console
    Math,
    Date
  };

  // 用 Proxy 拦截变量访问
  const proxy = new Proxy(fakeGlobal, {
    get(target, key) {
      if (!allowedGlobals.has(key)) {
        throw new Error(`禁止访问全局变量:${key}`); // 拦截敏感变量
      }
      return target[key];
    },
    set(target, key, value) {
      if (!allowedGlobals.has(key)) {
        throw new Error(`禁止修改全局变量:${key}`); // 拦截敏感变量修改
      }
      target[key] = value;
      return true;
    }
  });

  // 执行沙箱内代码的函数
  return function runCode(code) {
    // 用 with 绑定作用域到 proxy,限制变量查找范围
    with (proxy) {
      // 用立即执行函数包裹,避免变量泄漏
      (function() {
        eval(code);
      })();
    }
  };
}

// 使用沙箱
const runInSandbox = createSandbox();

// 测试安全代码
runInSandbox(`
  console.log('计算结果:', Math.max(1, 2, 3)); // 允许
  console.log('当前时间:', new Date().toLocaleString()); // 允许
`);

// 测试恶意代码(会被拦截)
try {
  runInSandbox(`
    window.alert('恶意弹窗'); // 禁止访问 window
    localStorage.setItem('test', '123'); // 禁止访问 localStorage
  `);
} catch (e) {
  console.error('被拦截:', e.message); // 输出:禁止访问全局变量:window
}

注意

  • with 语句在严格模式('use strict')下会被禁止,需确保代码在非严格模式执行;
  • 无法完全阻止闭包逃逸(若代码中形成闭包引用外部作用域变量,仍可能突破隔离)。

优点 :轻量、无需额外 DOM 元素,适合简单脚本;缺点:隔离性较弱(存在闭包逃逸风险),不适合高风险场景。

防抖与节流

防抖(Debounce)和节流(Throttle)是 JavaScript 中优化高频事件触发 的核心技术,用于减少不必要的函数执行,提升性能(如减少网络请求、避免 DOM 频繁操作)。两者核心差异在于:防抖是 "等待稳定后执行",节流是 "固定间隔内只执行一次"

一、防抖(Debounce):等待稳定后执行

1. 核心原理

高频事件(如输入、窗口缩放)触发后,等待指定时间(n 秒)再执行函数 ;若 n 秒内事件再次触发,则重置定时器,重新等待 n 秒。形象理解:"等用户停下来再做事",比如搜索框输入时,用户停止输入后才发请求,避免输入过程中频繁请求。

2. 实现代码
javascript 复制代码
// 模拟搜索请求
function search(keyword) {
  console.log(`搜索关键词:${keyword}`);
  // 实际场景:axios.get(`/api/search?kw=${keyword}`)
}

// 给输入框绑定防抖事件
const input = document.querySelector('input');
input.addEventListener('input', debounce(search, 500)); 
// 效果:输入时不触发,停止输入500ms后执行搜索
适用场景
  • 搜索框输入联想(停止输入后发请求);
  • 窗口 resize 事件(窗口调整稳定后再计算布局);
  • 文本编辑器自动保存(停止输入后保存);
  • 按钮防重复点击(立即执行版:点击一次后,n 秒内再次点击无效)。
javascript 复制代码
    //自己定义一个标识用来判断定时器是否应该要重新执行
    let flag = true
    oInpt3.oninqut = function () {
        // clearTimeout(timer2)
        if (flag == false) return
        flag = false
        timer2 = setTimeout(function () {
            console.log(`3秒后输出结果${oInpt3.value}`)
            //这是定时器结束后需要还原回去
            flag = true
        }, 3000)
    }
相关推荐
lly20240612 小时前
HTML与CSS:构建网页的基石
开发语言
一只会写代码的猫12 小时前
面向高性能计算与网络服务的C++微内核架构设计与多线程优化实践探索与经验分享
java·开发语言·jvm
2013编程爱好者13 小时前
Vue工程结构分析
前端·javascript·vue.js·typescript·前端框架
是小胡嘛13 小时前
C++之Any类的模拟实现
linux·开发语言·c++
csbysj202014 小时前
Vue.js 混入:深入理解与最佳实践
开发语言
不羁的fang少年15 小时前
前端常见问题(vue,css,html,js等)
前端·javascript·css
change_fate15 小时前
el-menu折叠后文字下移
前端·javascript·vue.js
Gerardisite16 小时前
如何在微信个人号开发中有效管理API接口?
java·开发语言·python·微信·php
Want59516 小时前
C/C++跳动的爱心①
c语言·开发语言·c++
coderxiaohan16 小时前
【C++】多态
开发语言·c++