前端面试宝典【Javascript篇】【2】

在这里插入图片描述

欢迎来到《前端面试宝典》,这里是你通往互联网大厂的专属通道,专为渴望在前端领域大放异彩的你量身定制。通过本专栏的学习,无论是一线大厂还是初创企业的面试,都能自信满满地展现你的实力。

核心特色:

  • 独家实战案例:每一期专栏都将深入剖析真实的前端面试案例,从基础知识到前沿技术,从算法挑战到框架运用,让你在实战中迅速成长。
  • 深度技术解析:不再只是表面文章,我们将带你深入技术的核心,理解每一个知识点背后的原理与- - 应用场景,让你在面试中不仅知其然,更知其所以然。
  • 高质量内容保证:每一期内容都经过精心策划与打磨,确保信息的准确性和实用性,拒绝泛泛而谈,只提供真正有价值的内容。
  • 持续更新迭代:持续关注前端领域的最新动态,及时更新专栏内容,确保你始终站在技术的最前沿。

4. 深拷贝和浅拷贝是两种不同的对象复制策略,主要用于复制包含复杂数据结构的对象,如对象和数组。它们之间的主要区别在于如何处理对象中的引用类型属性。

浅拷贝(Shallow Copy)

浅拷贝只会复制对象的第一层属性,对于对象内部的引用类型(如数组、对象、函数等),它只会复制这些类型的引用,而不是复制这些类型的内容。这意味着,如果原始对象和拷贝对象中的引用类型被修改,它们都会反映相同的数据变化,因为它们指向的是同一个内存地址。

深拷贝(Deep Copy)

深拷贝会递归地复制对象的所有层级,包括对象内部的引用类型。这意味着拷贝后的对象和原始对象完全独立,修改其中一个对象不会影响另一个对象。

实现深拷贝

在JavaScript中,可以使用递归的方式来实现深拷贝。下面是一个使用JSON方法的简单深拷贝实现,适用于基本数据类型和可序列化的对象类型:

function deepCopy(obj) {
    return JSON.parse(JSON.stringify(obj));
}

但是,这种方法有一些限制:

  • 它不能处理循环引用的对象。

  • 它不能正确复制函数和正则表达式。

  • 它不能复制对象的原型链上的属性。

为了克服这些限制,可以使用递归函数来实现一个更完整的深拷贝:

function deepCopy(obj, hash = new WeakMap()) {
    if (obj instanceof Map) return deepCopyMap(obj);
    if (obj instanceof Set) return deepCopySet(obj);
    if (obj instanceof RegExp) return new RegExp(obj);
    if (obj instanceof Date) return new Date(obj);
    if (obj === null || typeof obj !== 'object') return obj;
    if (hash.has(obj)) return hash.get(obj);

    let copy = Array.isArray(obj) ? [] : {};
    hash.set(obj, copy);

    for (let key in obj) {
        if (obj.hasOwnProperty(key)) {
            copy[key] = deepCopy(obj[key], hash);
        }
    }
    return copy;
}

function deepCopyMap(map) {
    const copy = new Map();
    for (let [key, value] of map.entries()) {
        copy.set(deepCopy(key), deepCopy(value));
    }
    return copy;
}

function deepCopySet(set) {
    const copy = new Set();
    for (let value of set) {
        copy.add(deepCopy(value));
    }
    return copy;
}


WeakMap 和 WeakSet

WeakMap和WeakSet是弱引用集合,它们的键必须是对象,而且这些键在垃圾回收时会被自动清除。由于它们的弱引用性质,你无法遍历它们的所有键和值,因此通常认为它们是不可拷贝的。在实际应用中,如果你需要保存WeakMap或WeakSet的状态,可能需要考虑重新构建它们,而不是深拷贝。

函数

函数是不可深拷贝的,因为函数是代码的一部分,而不是数据。当你试图“拷贝”一个函数时,实际上你得到的是对原函数的引用。如果需要在不同上下文中使用相似的功能,应该考虑将函数定义为模块或库的一部分,而不是尝试拷贝它们。

这个函数首先检查传入的对象是否是正则表达式或日期对象,如果是,则创建相应的实例。接着,它检查对象是否是基本类型或null,如果是,则直接返回。对于引用类型,它使用WeakMap来存储已经处理过的对象,以防止无限递归。最后,它递归地复制对象的所有属性。

5. 柯里化函数实现

柯里化(Currying)是函数式编程中的一个重要概念,它允许我们将多参数函数转换为一系列单参数函数的链式调用。下面是一个通用的柯里化函数实现的例子,使用JavaScript编写:

function curry(fn) {
    const arity = fn.length; // 获取函数期望的参数数量
    function _curryInner(args) {
        // 如果提供的参数数量小于函数期望的参数数量,
        // 并且下一个参数不是一个函数(意味着用户不打算提前执行函数),
        // 则返回一个新的函数。
        if (args.length < arity) {
            return function(...moreArgs) {
                return _curryInner(args.concat(moreArgs));
            };
        }
        // 如果提供了足够的参数,或者用户传递了一个函数作为最后一个参数,
        // 则调用原始函数。
        return fn.apply(null, args);
    }

    return function(...initialArgs) {
        return _curryInner(initialArgs);
    };
}

使用这个curry函数,你可以将任何多参数函数转换为柯里化形式。例如,假设我们有一个求和函数:

function sum(a, b, c) {
    return a + b + c;
}

我们可以将其柯里化:

const curriedSum = curry(sum);

然后,我们可以按需逐步传递参数:

const result1 = curriedSum(1)(2)(3); // 结果是 6
const result2 = curriedSum(1)(2, 3); // 结果同样是 6
const result3 = curriedSum(1, 2, 3); // 结果同样是 6
柯里化函数升级版

比如要实现多个数值的相加,参数数量不定:

function curry(fn) {
    let args = [];
    // 无限制接收参数
    function _curryInner(...a) {
        args = args.concat(a);
        return _curryInner
    }
    // 终止函数,计算并返回结果
    _curryInner.endF = function(cb) {
        const res = fn(...args);
        cb(res);
    };
    return _curryInner
}
// 求和函数
function s(...args) {
    return args.reduce((accumulator, currentValue) => accumulator + currentValue, 0);
}
const curriedSum = curry(s);
// 可以传入任意数量的参数
const result1 = curriedSum(1)(2,67,5,4)(3)(2,1,2,3)(3)
    .endF(res => {
    console.log(res);
})
柯里化函数应用场景
  1. 参数预填充(Partial Application): 柯里化可以用来预先设定函数的一些参数,从而创建特定功能的版本。例如,如果你有一个计算税费的函数,你可以预先设定税率,创建一个特定税率的计算税费的函数。
  2. 配置函数: 当你需要创建一组具有特定配置的函数时,柯里化可以派上用场。例如,你可以创建一个配置了特定数据库连接的查询函数。
  3. 事件处理: 在事件驱动的编程中,柯里化可以用来创建特定事件的处理器。例如,你可以创建一个处理点击事件的函数,其中按钮ID已经被预设。
  4. 数据处理管道: 柯里化函数可以链接在一起形成数据处理管道,每个函数负责数据流的一个特定阶段。这对于数据清洗、转换和分析特别有用。
  5. API设计: 在设计API时,柯里化可以提供更优雅的接口。例如,一个搜索API可以先设定搜索条件,然后再执行搜索,而不是一次提供所有参数。
  6. 库和框架: 许多库和框架使用柯里化来提供更灵活的API。例如,Lodash库中的许多函数都可以被柯里化。
  7. 函数组合: 柯里化函数可以更容易地与其他函数组合,形成复杂的逻辑流程。这是函数式编程中常见的模式。
  8. 减少重复代码: 通过柯里化,你可以避免在多个地方重复相同的函数调用模式,从而提高代码的可维护性。
  9. 延迟执行: 柯里化可以用来延迟函数的执行,直到所有必要的参数都被提供。这对于异步编程和需要按顺序处理参数的情况特别有用。
  10. 优化性能: 在某些情况下,柯里化可以帮助优化性能,尤其是当函数的参数集相对固定时,可以缓存中间结果以避免重复计算。

6. 如何让 (a == 1 && a == 2 && a == 3) 的值为true

a定义为一个对象,重写对象的toString方法,定义一个计数器,调用一次toString,+1,这样就可以实现这个需求

let counter = 0;
let a = {
    toString: () => {
        if (counter === 0) {
            counter++;
            return '1';
        } else if (counter === 1) {
            counter++;
            return '2';
        } else if (counter === 2) {
            counter++;
            return '3';
        } else {
            // 防止无限循环,当counter超过预期值时返回一个确定的值
            return '1';
        }
    }
}

console.log((a == 1 && a == 2 && a == 3))
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

bdawn

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值