5. 函数

本文介绍了JavaScript中的函数基础知识,包括创建、调用、参数和返回值。深入讨论了作用域和闭包的概念,以及如何通过回调函数处理异步操作。此外,还探讨了防抖和节流技术在优化函数执行频率中的应用。函数的柯里化让函数调用更灵活,并展示了设计模式,如工厂模式和单例模式,以增强代码复用和组织结构。
摘要由CSDN通过智能技术生成

函数:

函数是数据运算和运行的工厂



函数基础:

  • 创建函数: 使用 function 关键字来创建一个函数。

    function functionName() {
      // 函数体
    }
    
  • 调用函数: 使用函数名和括号来调用一个函数。

    functionName();
    
  • 参数: 在函数定义中使用形参来接受外部传入的数据。

    function myFunction(param1, param2) {
      // 使用 param1 和 param2
    }
    
  • 返回值: 使用 return 关键字来返回函数的值。

    function add(a, b) {
      return a + b;
    }
    
  • 匿名函数: 创建一个没有函数名的函数,可以赋值给一个变量或作为回调函数。

    let myFunction = function() {
      // 函数体
    };
    


作用域和闭包:

  • 作用域: 定义变量的可访问范围。

    let x = 10; // 全局作用域
    
    function myFunction() {
      let y = 20; // 局部作用域
    }
    
  • 闭包: 函数和其相关的变量构成的封闭空间,可以在外部访问内部变量。

    function outerFunction() {
      let x = 10;
    
      function innerFunction() {
        console.log(x); // 在内部函数中访问 x
      }
    
      return innerFunction;
    }
    
    let myFunction = outerFunction();
    myFunction(); // 输出 10
    

回调函数

回调函数是指将一个函数作为参数传递给另外一个函数,并在这个函数执行完后,将这个函数作为参数传递的函数调用,以实现在函数执行完后通知调用者的目的。

回调函数常用于异步编程中,比如Ajax请求、定时器、事件绑定等场景,因为这些场景中需要等待某个事件完成后再进行下一步操作,而回调函数可以在事件完成后被调用,使得主程序不需要一直等待事件完成。

下面是一个使用回调函数的例子,演示了如何通过回调函数处理异步请求:

function makeRequest(url, callback) {
  var xhr = new XMLHttpRequest();
  xhr.open('GET', url, true);
  xhr.onreadystatechange = function() {
    if (xhr.readyState === 4 && xhr.status === 200) {
      var data = xhr.responseText;
      callback(data);
    }
  };
  xhr.send();
}

function handleData(data) {
  console.log('Received data:', data);
}

makeRequest('https://api.example.com/data', handleData);

在上面的例子中,makeRequest函数使用XMLHttpRequest对象发送Ajax请求,并在请求完成后判断响应状态和数据,并将响应数据传递给回调函数callback。然后,我们定义了handleData函数作为回调函数,该函数会在makeRequest函数执行完成后被调用,并处理响应数据。最后,我们通过makeRequest函数并将handleData函数作为参数调用来发送Ajax请求。


防抖(Debounce)和节流(Throttle)

防抖和节流是两种常用的优化函数执行频率的方式。它们在处理高频率触发的事件或函数调用时非常有用。下面是它们的表格对比:

防抖(Debounce)节流(Throttle)
定义在指定的时间间隔内,函数被连续触发时,只执行最后一次操作。在指定的时间间隔内,函数被连续触发时,按照固定频率执行操作。
特点适用于高频率触发的事件,如搜索框输入、窗口大小调整等。适用于连续触发的事件,如滚动事件、鼠标移动事件等。
实现方式利用计时器和事件回调函数实现。利用计时器和函数执行时间戳实现。
实现示例function debounce(func, delay) {...}function throttle(func, delay) {...}

防抖的实现示例:

function debounce(func, delay) {
  let timeoutId;
  return function(...args) {
    clearTimeout(timeoutId);
    timeoutId = setTimeout(() => {
      func.apply(this, args);
    }, delay);
  };
}

节流的实现示例:

function throttle(func, delay) {
  let lastInvokeTime = 0;
  return function(...args) {
    const now = Date.now();
    if (now - lastInvokeTime >= delay) {
      func.apply(this, args);
      lastInvokeTime = now;
    }
  };
}

需要注意的是,防抖和节流的具体实现方式可能会根据具体需求和使用场景而略有不同。上述示例只是常见的实现方式之一。


函数柯里化

函数柯里化是指将接受多个参数的函数转换成一系列只接受一个参数的嵌套函数的过程。柯里化可以让函数更加灵活,可以分步传参数,在函数组合和处理过程中发挥重要作用。柯里化也可以简化函数的调用方式,使得代码更加清晰易懂。

下面是一个柯里化的例子,演示了如何使用柯里化将一个函数分步传递参数:

function add(x) {
  return function(y) {
    return x + y;
  };
}

var add5 = add(5);
console.log(add5(2)); // 7
console.log(add(5)(2)); // 7

在上面的例子中,我们定义了一个add函数,该函数返回一个内部函数,并保存了传入的第一个参数x。内部函数也接受一个参数y,实现了将x和y相加的功能。然后,我们通过传入不同的第一个参数x,可以得到多个不同的内部函数,这些内部函数都保存了不同的x值,并可以重复调用,实现了函数分步处理的功能。

函数柯里化在应用中还有其他的用途和好处,比如可以把原来需要接受多个参数的函数进行切片,然后统一处理共性部分,增加函数复用性;可以为一般处理流程进行定制加强等。



函数的遍历

函数的遍历方式有多种,可以根据不同需求选择适合的方式。以下是常见的函数遍历方式的比较表格:

for…in 循环Object.keys()Object.getOwnPropertyNames()Reflect.ownKeys()
说明遍历对象的可枚举属性和原型链上的可枚举属性返回对象自身的可枚举属性返回对象自身的所有属性,包括不可枚举属性返回对象自身的所有属性,包括符号属性
遍历顺序任意顺序属性插入顺序属性插入顺序属性插入顺序
遍历继承的属性
遍历符号属性
遍历方法
优先顺序原型链上的属性 -> 自身属性符号属性 -> 字符串属性

需要根据具体的需求选择适用的遍历方式。

使用 for...in 循环来遍历函数:

for (let key in obj) {
  if (typeof obj[key] === 'function') {
    // 处理函数
  }
}

使用 Object.keys() 来遍历函数:

Object.keys(obj).forEach(function(key) {
  if (typeof obj[key] === 'function') {
    // 处理函数
  }
});

使用 Object.getOwnPropertyNames() 来遍历函数:

Object.getOwnPropertyNames(obj).forEach(function(key) {
  if (typeof obj[key] === 'function') {
    // 处理函数
  }
});

使用 Reflect.ownKeys() 来遍历函数:

Reflect.ownKeys(obj).forEach(function(key) {
  if (typeof obj[key] === 'function') {
    // 处理函数
  }
});


下面是一些常见的JavaScript模式及其应用场景,以表格形式展示:

设计模式定义
工厂模式在不暴露对象创建逻辑的前提下,让客户端根据自己的需要创建对象。创建者和被创建者分离。
单例模式保证一个类仅有一个实例,并且提供访问该实例的全局访问点。
观察者模式定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动更新。
实例模式抽象与不同的实现分离,使得相同的实现能被不同的抽象使用。
发布/订阅模式又称观察者模式或消息队列,定义了一种一对多的关系,让多个观察者对象同时监听某一个主体对象,当主体对象状态发生改变时,所有相关的观察者都可以得到通知。
代理模式为其他对象提供一个代理以控制对这个对象的访问。
外观模式对外隐藏一些复杂的处理过程,提供一个简单接口以方便访问。
装饰者模式动态地给一个对象添加一些额外的职责,同时又不改变其原始功能。

设计模式应用场景
工厂模式1.创建对象需要复杂的逻辑或多步骤操作;
2.需要创建大量相似的对象。
单例模式1.全局只需要一个实例对象;
2.需要频繁创建销毁一个对象,造成性能浪费;
3.实例化需要消耗很多资源,比如数据库连接、大型外部文件等。
观察者模式1.一个对象的改变需要同时改变其他对象的状态;
2.一个抽象模型有两个方面,其中一个方面依赖于另一个方面;
3.当一个对象必须通知其他对象,但又不能假定其他对象是谁。
实例模式1.想要一个类能够拥有多个实例;
2.创建对象的场景,需要用到的一些信息只有运行时才能得到;
3.需要用同样的方式处理多个不同的对象。
发布/订阅模式1.需要解耦多个对象之间的复杂关系;
2.希望能在一个对象状态改变时通知其他对象,而又不希望这些对象直接耦合在一起;
3.需要在系统中创建一个中心控制点,来管理应用的不同部分之间的通信。
代理模式1.需要在某个对象运行之前或之后执行一些额外的操作;
2.使用一个对象来代替另一个对象的功能;
3.保护目标对象或进行缓存等优化。
外观模式1.用于简化底层操作和复杂工具的操作;2.在向客户端提供 API 的时候,提供简单稳定的接口;
3.当需要构建一个层次结构的子系统时,装饰者模式比外观模式更合适。
装饰者模式1. 对象需要被动态地扩展功能,而不影响其他对象;
2. 需要派生一个系列的对象,例如:一个设计良好的游戏可以用装饰者模式来实现不同的游戏角色。

工厂模式(Factory Pattern)

  • 定义一个工厂函数,用于创建实例。
  • 工厂函数返回实例对象。
function createPerson(name, age) {
  const obj = {};
  obj.name = name;
  obj.age = age;
  obj.sayHello = function () {
    console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
  };
  return obj;
}
const person1 = createPerson("Alice", 25);
const person2 = createPerson("Bob", 30);
person1.sayHello(); // 输出:Hello, my name is Alice and I am 25 years old.
person2.sayHello(); // 输出:Hello, my name is Bob and I am 30 years old.

单例模式(Singleton Pattern)

  • 定义一个单例对象。
  • 单例对象的所有属性和方法都是全局共享的。
const singleton = {
  name: "Alice",
  age: 25,
  sayHello: function () {
    console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
  }
};
singleton.sayHello(); // 输出:Hello, my name is Alice and I am 25 years old.

观察者模式(Observer Pattern)

  • 定义一个主题对象和观察者对象。
  • 主题对象维护一个观察者列表,当主题对象状态发生改变时,通知所有观察者更新状态。
class Subject {
  constructor() {
    this.observers = [];
  }
  addObserver(observer) {
    this.observers.push(observer);
  }
  removeObserver(observer) {
    const index = this.observers.indexOf(observer);
    if (index !== -1) {
      this.observers.splice(index, 1);
    }
  }
  notify(data) {
    this.observers.forEach(observer => {
      observer.update(data);
    });
  }
}
class Observer {
  constructor() {}
  update(data) {
    console.log(`Data updated: ${data}`);
  }
}
const subject = new Subject();
const observer1 = new Observer();
const observer2 = new Observer();
subject.addObserver(observer1);
subject.addObserver(observer2);
subject.notify("new data"); // 输出:Data updated: new data Data updated: new data

实例模式(Prototype Pattern)

  • 定义一个原型对象。
  • 使用 Object.create() 方法在原型对象上创建实例对象。
const personPrototype = {
  name: "Alice",
  age: 25,
  sayHello: function () {
      console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
  }
};
const person1 = Object.create(personPrototype);
person1.name = "Bob";
person1.age = 30;
person1.sayHello(); // 输出:Hello, my name is Bob and I am 30 years old.

发布/订阅模式(Publish/Subscribe Pattern)

  • 定义一个事件发布者对象和事件订阅者对象。
  • 发布者对象维护一个事件列表,可以订阅和发布事件,订阅者对象可以订阅事件并在事件发生时自动更新。
class Publisher {
  constructor() {
    this.events = {};
  }
  subscribe(event, callback) {
    if (!this.events[event]) {
      this.events[event] = [];
    }
    this.events[event].push(callback);
  }
  unsubscribe(event, callback) {
    if (!this.events[event]) {
      return;
    }
    const index = this.events[event].indexOf(callback);
    if (index !== -1) {
      this.events[event].splice(index, 1);
    }
  }
  publish(event, data) {
    if (!this.events[event]) {
      return;
    }
    this.events[event].forEach(callback => {
      callback(data);
    });
  }
}
class Subscriber {
  constructor() {}
  update(data) {
    console.log(`Data updated: ${data}`);
  }
}
const publisher = new Publisher();
const subscriber = new Subscriber();
publisher.subscribe("newData", subscriber.update);
publisher.publish("newData", "new data"); // 输出:Data updated: new data

代理模式(Proxy Pattern)

  • 定义一个实际对象和代理对象。
  • 代理对象充当中间人,处理客户端通过代理对象访问原始对象时的请求。
class RealSubject {
  constructor() {};
  request(data) {
    console.log(`Data request: ${data}`);
  }
}
class ProxySubject {
  constructor() {
    this.realSubject = new RealSubject();
  }
  request(data) {
    if (this.checkAccess()) {
      this.realSubject.request(data);
    }
  }
  checkAccess() {
    return true;
  }
}
const proxy = new ProxySubject();
proxy.request("new data"); // 输出:Data request: new data

外观模式(Facade Pattern)

  • 定义一个系统对象和外观对象。
  • 外观对象提供一个接口来访问子系统中的一组接口,客户端通过外观对象来访问子系统。
class System {
  constructor() {}
  methodA(data) {
    console.log(`System A: ${data}`);
  }
  methodB(data) {
    console.log(`System B: ${data}`);
  }
}
class Facade {
  constructor() {
    this.system = new System();
  }
  execute(data) {
    this.system.methodA(data);
    this.system.methodB(data);
  }
}
const facade = new Facade();
facade.execute("new data"); // 输出:System A: new data System B: new data

装饰者模式(Decorator Pattern)

  • 定义一个基础组件和装饰者对象,以及具体装饰者对象。
  • 装饰者对象通过继承基础组件对象来包装对象,从而为对象添加新的功能。
class Component {
  constructor() {}
  operation() {
    console.log(`Base operation.`);
  }
}
class Decorator extends Component {
  constructor(component) {
    super();
    this.component = component;
  }
  operation() {
    this.component.operation();
  }
}
class ConcreteDecoratorA extends Decorator {
  constructor(component) {
    super(component);
  }
  operation() {
    super.operation();
    console.log(`Decorator A operation.`);
  }
}
class ConcreteDecoratorB extends Decorator {
  constructor(component) {
    super(component);
  }
  operation() {
    super.operation();
    console.log(`Decorator B operation.`);
  }
}
const component = new Component();
const decoratorA = new ConcreteDecoratorA(component);
const decoratorB = new ConcreteDecoratorB(decoratorA);
decoratorB.operation();
// 输出:
// Base operation.
// Decorator A operation.
// Decorator B operation.
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值