MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

JavaScript函数方法的代码优化示例

2021-04-183.3k 阅读

一、JavaScript 函数基础回顾

在深入探讨函数方法的代码优化之前,先简单回顾一下 JavaScript 函数的基础知识。JavaScript 中的函数是一等公民,这意味着函数可以像其他数据类型(如字符串、数字)一样被使用。函数可以作为变量的值,可以作为参数传递给其他函数,也可以作为其他函数的返回值。

(一)函数定义方式

  1. 函数声明 函数声明是最常见的定义函数的方式。语法如下:
function add(a, b) {
    return a + b;
}

这里定义了一个名为 add 的函数,接受两个参数 ab,并返回它们的和。函数声明会被提升,这意味着在代码执行之前,函数声明就已经被解析并可以在其作用域内的任何位置调用,即使在声明之前调用也不会报错。例如:

console.log(add(2, 3)); 
function add(a, b) {
    return a + b;
}

上述代码虽然在函数声明之前调用了 add 函数,但依然可以正常输出结果 5

  1. 函数表达式 函数表达式是将函数定义为一个表达式的值。语法如下:
const subtract = function (a, b) {
    return a - b;
};

这里使用 const 定义了一个变量 subtract,其值是一个匿名函数。与函数声明不同,函数表达式不会被提升。如果在定义之前调用,会导致 ReferenceError。例如:

console.log(subtract(5, 3)); 
const subtract = function (a, b) {
    return a - b;
};

上述代码会报错,因为在 console.log 调用时,subtract 变量还未被赋值。

  1. 箭头函数 ES6 引入了箭头函数,提供了一种更简洁的函数定义方式。语法如下:
const multiply = (a, b) => a * b;

箭头函数没有自己的 thisargumentssupernew.target。它的 this 取决于外层作用域。例如:

const obj = {
    value: 10,
    getValue: function () {
        return () => this.value;
    }
};
const getValueFunc = obj.getValue();
console.log(getValueFunc()); 

这里箭头函数 () => this.value 中的 this 指向 obj,所以会输出 10

二、函数方法优化方向

(一)提升性能

  1. 减少函数调用开销 在 JavaScript 中,每次函数调用都有一定的开销,包括创建执行上下文、参数传递等。如果在循环中频繁调用函数,可能会影响性能。例如,下面的代码在循环中调用 Math.sqrt 函数:
for (let i = 0; i < 1000000; i++) {
    const result = Math.sqrt(i);
    // 其他操作
}

这里每次循环都调用 Math.sqrt 函数。如果 Math.sqrt 函数的结果不会改变,可以将其提取到循环外部,减少函数调用次数:

const sqrt = Math.sqrt;
for (let i = 0; i < 1000000; i++) {
    const result = sqrt(i);
    // 其他操作
}

这样,sqrt 变量指向 Math.sqrt 函数,在循环中调用 sqrt 函数的开销就会相对小一些,因为减少了通过 Math 对象查找 sqrt 函数的过程。

  1. 避免不必要的闭包 闭包是指函数可以访问其外部作用域的变量。虽然闭包非常强大,但过度使用或不当使用闭包可能会导致性能问题。例如:
function outer() {
    const largeArray = new Array(1000000).fill(1); 
    return function inner() {
        return largeArray[0];
    };
}
const innerFunc = outer();

在上述代码中,inner 函数形成了闭包,它可以访问 outer 函数作用域中的 largeArray。由于 inner 函数引用了 largeArraylargeArray 不会被垃圾回收机制回收,即使 outer 函数执行完毕。如果 inner 函数不需要使用 largeArray,可以将其修改为:

function outer() {
    const largeArray = new Array(1000000).fill(1); 
    const value = largeArray[0];
    return function inner() {
        return value;
    };
}
const innerFunc = outer();

这样,largeArrayouter 函数执行完毕后可以被垃圾回收,从而节省内存。

(二)提高代码可读性与可维护性

  1. 合理命名函数 函数名应该清晰地表达函数的功能。例如,下面的函数:
function f(a, b) {
    return a * b;
}

函数名 f 没有明确表达函数的功能。将其改为更有意义的名字,如 multiplyNumbers

function multiplyNumbers(a, b) {
    return a * b;
}

这样,代码的可读性大大提高,其他开发人员可以很容易理解该函数的作用。

  1. 分解复杂函数 如果一个函数的功能过于复杂,应该将其分解为多个小函数。例如,下面的函数用于处理用户注册逻辑:
function registerUser(username, password, email) {
    if (!username ||!password ||!email) {
        throw new Error('用户名、密码和邮箱不能为空');
    }
    const hashedPassword = hashPassword(password); 
    const user = {
        username,
        hashedPassword,
        email
    };
    const isUserExists = checkUserExists(user); 
    if (isUserExists) {
        throw new Error('用户已存在');
    }
    saveUserToDatabase(user); 
    return user;
}

上述函数包含了输入验证、密码哈希、检查用户是否存在以及保存用户到数据库等多个功能。可以将这些功能分解为单独的函数:

function validateUserInput(username, password, email) {
    if (!username ||!password ||!email) {
        throw new Error('用户名、密码和邮箱不能为空');
    }
}
function hashPassword(password) {
    // 实际的哈希逻辑
    return password; 
}
function checkUserExists(user) {
    // 实际的检查逻辑
    return false; 
}
function saveUserToDatabase(user) {
    // 实际的保存逻辑
}
function registerUser(username, password, email) {
    validateUserInput(username, password, email);
    const hashedPassword = hashPassword(password); 
    const user = {
        username,
        hashedPassword,
        email
    };
    const isUserExists = checkUserExists(user); 
    if (isUserExists) {
        throw new Error('用户已存在');
    }
    saveUserToDatabase(user); 
    return user;
}

这样,每个函数的功能单一,代码的可读性和可维护性都得到了提升。

三、具体函数方法优化示例

(一)数组操作函数优化

  1. map 方法优化 map 方法用于创建一个新数组,其结果是该数组中的每个元素都调用一个提供的函数后返回的结果。例如,将数组中的每个元素乘以 2:
const numbers = [1, 2, 3, 4, 5];
const doubledNumbers = numbers.map(function (number) {
    return number * 2;
});

使用箭头函数可以使代码更简洁:

const numbers = [1, 2, 3, 4, 5];
const doubledNumbers = numbers.map(number => number * 2);

如果 map 方法中的回调函数逻辑较为复杂,可以将其提取为一个单独的函数,以提高代码的可读性。例如:

function transformNumber(number) {
    if (number % 2 === 0) {
        return number * 2;
    } else {
        return number + 1;
    }
}
const numbers = [1, 2, 3, 4, 5];
const transformedNumbers = numbers.map(transformNumber);
  1. filter 方法优化 filter 方法用于创建一个新数组,其包含通过所提供函数实现的测试的所有元素。例如,过滤出数组中的偶数:
const numbers = [1, 2, 3, 4, 5];
const evenNumbers = numbers.filter(function (number) {
    return number % 2 === 0;
});

同样,使用箭头函数可以简化代码:

const numbers = [1, 2, 3, 4, 5];
const evenNumbers = numbers.filter(number => number % 2 === 0);

如果过滤逻辑复杂,也可以提取为单独的函数。例如,过滤出大于 10 且为偶数的数:

function filterNumbers(number) {
    return number > 10 && number % 2 === 0;
}
const numbers = [5, 12, 15, 20];
const filteredNumbers = numbers.filter(filterNumbers);
  1. reduce 方法优化 reduce 方法对数组中的每个元素执行一个由您提供的 reducer 函数(升序执行),将其结果汇总为单个返回值。例如,计算数组元素的总和:
const numbers = [1, 2, 3, 4, 5];
const sum = numbers.reduce(function (acc, number) {
    return acc + number;
}, 0);

使用箭头函数简化:

const numbers = [1, 2, 3, 4, 5];
const sum = numbers.reduce((acc, number) => acc + number, 0);

如果 reduce 的逻辑复杂,可以提取为单独的函数。例如,计算数组中每个数的平方和:

function sumOfSquares(acc, number) {
    return acc + number * number;
}
const numbers = [1, 2, 3, 4, 5];
const sum = numbers.reduce(sumOfSquares, 0);

(二)字符串操作函数优化

  1. split 方法优化 split 方法用于把一个字符串分割成字符串数组。例如,将字符串按空格分割:
const sentence = 'Hello world';
const words = sentence.split(' ');

如果分割符是固定的,且代码中多次使用 split 方法,可以将分割符提取为常量,提高代码的可维护性。例如:

const SPACE = ' ';
const sentence1 = 'Hello world';
const sentence2 = 'JavaScript is awesome';
const words1 = sentence1.split(SPACE);
const words2 = sentence2.split(SPACE);
  1. replace 方法优化 replace 方法用于在字符串中用一些字符替换另一些字符,或替换一个与正则表达式匹配的子串。例如,将字符串中的所有空格替换为下划线:
const sentence = 'Hello world';
const newSentence = sentence.replace(/ /g, '_');

如果替换逻辑复杂,比如不仅要替换空格,还要根据不同情况进行其他处理,可以将替换逻辑封装到一个函数中。例如:

function customReplace(str) {
    return str.replace(/ /g, function (match) {
        if (match === ' ') {
            return '_';
        } else {
            return match;
        }
    });
}
const sentence = 'Hello world';
const newSentence = customReplace(sentence);

(三)事件处理函数优化

  1. DOM 事件绑定优化 在 JavaScript 中,为 DOM 元素绑定事件是常见的操作。例如,为按钮添加点击事件:
const button = document.getElementById('myButton');
button.addEventListener('click', function () {
    console.log('按钮被点击了');
});

使用箭头函数可以使代码更简洁:

const button = document.getElementById('myButton');
button.addEventListener('click', () => console.log('按钮被点击了'));

如果点击事件的处理逻辑复杂,可以将其封装为一个函数。例如:

function handleButtonClick() {
    const message = '按钮被点击了';
    console.log(message);
    // 其他复杂逻辑
}
const button = document.getElementById('myButton');
button.addEventListener('click', handleButtonClick);
  1. 事件委托优化 事件委托是一种优化事件处理的技术,通过将事件处理程序绑定到父元素,利用事件冒泡机制处理子元素的事件。例如,有一个列表,每个列表项都有点击事件:
<ul id="myList">
    <li>列表项1</li>
    <li>列表项2</li>
    <li>列表项3</li>
</ul>
const listItems = document.querySelectorAll('#myList li');
listItems.forEach(function (listItem) {
    listItem.addEventListener('click', function () {
        console.log('列表项被点击了');
    });
});

使用事件委托优化:

const list = document.getElementById('myList');
list.addEventListener('click', function (event) {
    if (event.target.tagName === 'LI') {
        console.log('列表项被点击了');
    }
});

这样,只需要为父元素 ul 绑定一个点击事件,而不是为每个 li 元素分别绑定事件,减少了内存占用和事件处理的开销。

四、函数性能分析工具

(一)Chrome DevTools

Chrome DevTools 是一个强大的前端开发工具,其中的 Performance 面板可以用于分析函数的性能。

  1. 录制性能数据 打开 Chrome DevTools,切换到 Performance 面板,点击录制按钮,然后在页面上执行需要分析的操作(例如点击按钮触发函数),最后停止录制。

  2. 分析函数性能 录制完成后,会生成一个性能报告。在报告中,可以查看每个函数的执行时间、调用次数等信息。例如,找到某个函数,查看其在整个操作过程中的执行时间占比。如果某个函数执行时间过长,可以针对性地进行优化。

(二)Lighthouse

Lighthouse 是一个开源的自动化工具,用于提高网络应用的质量。它不仅可以分析页面的性能,还能检测代码中的一些最佳实践。

  1. 运行 Lighthouse 在 Chrome 浏览器中,打开需要分析的页面,然后在 Chrome DevTools 中切换到 Lighthouse 面板,点击“Generate report”按钮,Lighthouse 会对页面进行分析,并生成报告。

  2. 查看函数相关建议 在 Lighthouse 报告中,可能会包含一些与函数性能和代码质量相关的建议。例如,如果某个函数导致页面响应时间过长,Lighthouse 会提示优化该函数。

五、优化后的代码维护与持续优化

(一)代码审查

  1. 定期审查优化后的代码 在对函数方法进行优化后,需要定期进行代码审查。审查过程中,检查优化后的代码是否仍然满足业务需求,是否引入了新的问题。例如,在优化数组操作函数时,检查 mapfilterreduce 等方法的使用是否正确,结果是否符合预期。

  2. 遵循团队代码规范 代码审查还应确保优化后的代码遵循团队的代码规范。例如,函数命名规范、缩进风格等。如果团队规定函数名采用驼峰命名法,那么在优化函数时,也要遵循这个规范。

(二)持续关注性能指标

  1. 监控应用性能 使用性能监控工具(如上述提到的 Chrome DevTools 和 Lighthouse)持续监控应用的性能指标。关注页面加载时间、交互响应时间等关键指标。如果发现性能指标下降,可能是之前优化的函数出现了问题,或者有新的代码影响了性能。

  2. 根据性能变化进行调整 根据性能监控的结果,及时对代码进行调整。如果发现某个函数在特定场景下性能变差,可以进一步分析原因并进行优化。例如,随着数据量的增加,之前优化的 reduce 方法可能变得效率低下,这时就需要重新审视 reduce 方法的实现,考虑是否可以采用更高效的算法。

通过以上对 JavaScript 函数方法的优化示例、性能分析工具以及优化后代码的维护与持续优化的探讨,希望能帮助开发者编写出更高效、更易读、更易维护的 JavaScript 代码。在实际开发中,要根据具体的业务场景和性能需求,灵活运用这些优化技巧。