欢迎来到《前端面试宝典》,这里是你通往互联网大厂的专属通道,专为渴望在前端领域大放异彩的你量身定制。通过本专栏的学习,无论是一线大厂还是初创企业的面试,都能自信满满地展现你的实力。
核心特色:
- 独家实战案例:每一期专栏都将深入剖析真实的前端面试案例,从基础知识到前沿技术,从算法挑战到框架运用,让你在实战中迅速成长。
- 深度技术解析:不再只是表面文章,我们将带你深入技术的核心,理解每一个知识点背后的原理与- - 应用场景,让你在面试中不仅知其然,更知其所以然。
- 高质量内容保证:每一期内容都经过精心策划与打磨,确保信息的准确性和实用性,拒绝泛泛而谈,只提供真正有价值的内容。
- 持续更新迭代:持续关注前端领域的最新动态,及时更新专栏内容,确保你始终站在技术的最前沿。
目录
- 4. 深拷贝和浅拷贝是两种不同的对象复制策略,主要用于复制包含复杂数据结构的对象,如对象和数组。它们之间的主要区别在于如何处理对象中的引用类型属性。
- 浅拷贝(Shallow Copy)
- 深拷贝(Deep Copy)
- 实现深拷贝
- WeakMap 和 WeakSet
- 函数
- 5. 柯里化函数实现
- 柯里化函数升级版
- 柯里化函数应用场景
- 6. 如何让 (a == 1 && a == 2 && a == 3) 的值为true
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);
})
柯里化函数应用场景
- 参数预填充(Partial Application): 柯里化可以用来预先设定函数的一些参数,从而创建特定功能的版本。例如,如果你有一个计算税费的函数,你可以预先设定税率,创建一个特定税率的计算税费的函数。
- 配置函数: 当你需要创建一组具有特定配置的函数时,柯里化可以派上用场。例如,你可以创建一个配置了特定数据库连接的查询函数。
- 事件处理: 在事件驱动的编程中,柯里化可以用来创建特定事件的处理器。例如,你可以创建一个处理点击事件的函数,其中按钮ID已经被预设。
- 数据处理管道: 柯里化函数可以链接在一起形成数据处理管道,每个函数负责数据流的一个特定阶段。这对于数据清洗、转换和分析特别有用。
- API设计: 在设计API时,柯里化可以提供更优雅的接口。例如,一个搜索API可以先设定搜索条件,然后再执行搜索,而不是一次提供所有参数。
- 库和框架: 许多库和框架使用柯里化来提供更灵活的API。例如,Lodash库中的许多函数都可以被柯里化。
- 函数组合: 柯里化函数可以更容易地与其他函数组合,形成复杂的逻辑流程。这是函数式编程中常见的模式。
- 减少重复代码: 通过柯里化,你可以避免在多个地方重复相同的函数调用模式,从而提高代码的可维护性。
- 延迟执行: 柯里化可以用来延迟函数的执行,直到所有必要的参数都被提供。这对于异步编程和需要按顺序处理参数的情况特别有用。
- 优化性能: 在某些情况下,柯里化可以帮助优化性能,尤其是当函数的参数集相对固定时,可以缓存中间结果以避免重复计算。
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))