面试题-5 2020-07-06 前端,Javascript 暂无评论 190 次阅读 ### 45. 什么是`Set`对象,它是如何工作的? Set 对象允许你存储任何类型的唯一值,无论是原始值或者是对象引用。 我们可以使用Set构造函数创建Set实例。 ```js const set1 = new Set(); const set2 = new Set(["a","b","c","d","d","e"]); ``` 我们可以使用add方法向Set实例中添加一个新值,因为add方法返回Set对象,所以我们可以以链式的方式再次使用add。如果一个值已经存在于Set对象中,那么它将不再被添加。 ```js set2.add("f"); set2.add("g").add("h").add("i").add("j").add("k").add("k"); // 后一个“k”不会被添加到set对象中,因为它已经存在了 ``` 我们可以使用has方法检查Set实例中是否存在特定的值。 ```js set2.has("a") // true set2.has("z") // true ``` 我们可以使用size属性获得Set实例的长度。 ```js set2.size // returns 10 ``` 可以使用clear方法删除 Set 中的数据。 ```js set2.clear(); ``` 我们可以使用Set对象来删除数组中重复的元素。 ```js const numbers = [1, 2, 3, 4, 5, 6, 6, 7, 8, 8, 5]; const uniqueNums = [...new Set(numbers)]; // [1,2,3,4,5,6,7,8] ``` 另外还有`WeakSet`, 与 `Set` 类似,也是不重复的值的集合。但是 `WeakSet` 的成员只能是对象,而不能是其他类型的值。`WeakSet` 中的对象都是弱引用,即垃圾回收机制不考虑 `WeakSet`对该对象的引用。 - Map 数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。 - WeakMap 结构与 Map 结构类似,也是用于生成键值对的集合。但是 WeakMap 只接受对象作为键名( null 除外),不接受其他类型的值作为键名。而且 WeakMap 的键名所指向的对象,不计入垃圾回收机制。 ### 46. 什么是Proxy? Proxy 用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程”,即对编程语言进行编程。 Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。 ```! 高能预警⚡⚡⚡, 以下47~64条是JavaScript中比较难的高级知识及相关手写实现,各位看官需慢慢细品 ``` ### 47. 写一个通用的事件侦听器函数 ``` const EventUtils = { // 视能力分别使用dom0||dom2||IE方式 来绑定事件 // 添加事件 addEvent: function(element, type, handler) { if (element.addEventListener) { element.addEventListener(type, handler, false); } else if (element.attachEvent) { element.attachEvent("on" + type, handler); } else { element["on" + type] = handler; } }, // 移除事件 removeEvent: function(element, type, handler) { if (element.removeEventListener) { element.removeEventListener(type, handler, false); } else if (element.detachEvent) { element.detachEvent("on" + type, handler); } else { element["on" + type] = null; } }, // 获取事件目标 getTarget: function(event) { return event.target || event.srcElement; }, // 获取 event 对象的引用,取到事件的所有信息,确保随时能使用 event getEvent: function(event) { return event || window.event; }, // 阻止事件(主要是事件冒泡,因为 IE 不支持事件捕获) stopPropagation: function(event) { if (event.stopPropagation) { event.stopPropagation(); } else { event.cancelBubble = true; } }, // 取消事件的默认行为 preventDefault: function(event) { if (event.preventDefault) { event.preventDefault(); } else { event.returnValue = false; } } }; ``` ### 48. 什么是函数式编程? JavaScript的哪些特性使其成为函数式语言的候选语言? 函数式编程(通常缩写为FP)是通过编写纯函数,避免共享状态、可变数据、副作用 来构建软件的过程。数式编程是声明式 的而不是命令式 的,应用程序的状态是通过纯函数流动的。与面向对象编程形成对比,面向对象中应用程序的状态通常与对象中的方法共享和共处。 函数式编程是一种编程范式 ,这意味着它是一种基于一些基本的定义原则(如上所列)思考软件构建的方式。当然,编程范式的其他示例也包括面向对象编程和过程编程。 函数式的代码往往比命令式或面向对象的代码更简洁,更可预测,更容易测试 - 但如果不熟悉它以及与之相关的常见模式,函数式的代码也可能看起来更密集杂乱,并且 相关文献对新人来说是不好理解的。 ### 49. 什么是高阶函数? 高阶函数只是将函数作为参数或返回值的函数。 ```js function higherOrderFunction(param,callback){ return callback(param); } ``` ### 50. 为什么函数被称为一等公民? 在JavaScript中,函数不仅拥有一切传统函数的使用方式(声明和调用),而且可以做到像简单值一样: * 赋值(`var func = function(){}`)、 * 传参(`function func(x,callback){callback();}`)、 * 返回(`function(){return function(){}}`), 这样的函数也称之为第一级函数(`First-class Function`)。不仅如此,JavaScript中的函数还充当了类的构造函数的作用,同时又是一个Function类的实例(instance)。这样的多重身份让JavaScript的函数变得非常重要。 ### 51. 手动实现 `Array.prototype.map 方法` map() 方法创建一个新数组,其结果是该数组中的每个元素都调用一个提供的函数后返回的结果。 ```js function map(arr, mapCallback) { // 首先,检查传递的参数是否正确。 if (!Array.isArray(arr) || !arr.length || typeof mapCallback !== 'function') { return []; } else { let result = []; // 每次调用此函数时,我们都会创建一个 result 数组 // 因为我们不想改变原始数组。 for (let i = 0, len = arr.length; i < len; i++) { result.push(mapCallback(arr[i], i, arr)); // 将 mapCallback 返回的结果 push 到 result 数组中 } return result; } } ``` ### 52. 手动实现`Array.prototype.filter`方法 `filter() `方法创建一个新数组, 其包含通过所提供函数实现的测试的所有元素。 ```js function filter(arr, filterCallback) { // 首先,检查传递的参数是否正确。 if (!Array.isArray(arr) || !arr.length || typeof filterCallback !== 'function') { return []; } else { let result = []; // 每次调用此函数时,我们都会创建一个 result 数组 // 因为我们不想改变原始数组。 for (let i = 0, len = arr.length; i < len; i++) { // 检查 filterCallback 的返回值是否是真值 if (filterCallback(arr[i], i, arr)) { // 如果条件为真,则将数组元素 push 到 result 中 result.push(arr[i]); } } return result; // return the result array } } ``` ### 53. 手动实现`Array.prototype.reduce`方法 [reduce()]() 方法对数组中的每个元素执行一个由您提供的reducer函数(升序执行),将其结果汇总为单个返回值。 ```js function reduce(arr, reduceCallback, initialValue) { // 首先,检查传递的参数是否正确。 if (!Array.isArray(arr) || !arr.length || typeof reduceCallback !== 'function') { return []; } else { // 如果没有将initialValue传递给该函数,我们将使用第一个数组项作为initialValue let hasInitialValue = initialValue !== undefined; let value = hasInitialValue ? initialValue : arr[0]; 、 // 如果有传递 initialValue,则索引从 1 开始,否则从 0 开始 for (let i = hasInitialValue ? 0 : 1, len = arr.length; i < len; i++) { value = reduceCallback(value, arr[i], i, arr); } return value; } } ``` ### 54. js的深浅拷贝 JavaScript的深浅拷贝一直是个难点,如果现在面试官让我写一个深拷贝,我可能也只是能写出个基础版的。所以在写这条之前我拜读了收藏夹里各路大佬写的博文。具体可以看下面我贴的链接,这里只做简单的总结。 * **浅拷贝:** 创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 ,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。 * **深拷贝:** 将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象。 **浅拷贝的实现方式:** * **Object.assign() 方法:** 用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。 * **Array.prototype.slice():**slice() 方法返回一个新的数组对象,这一对象是一个由 begin和end(不包括end)决定的原数组的浅拷贝。原始数组不会被改变。 * **拓展运算符`...`:** ```js let a = { name: "Jake", flag: { title: "better day by day", time: "2020-05-31" } } let b = {...a}; ``` **深拷贝的实现方式:** * **乞丐版:** JSON.parse(JSON.stringify(object)),缺点诸多(会忽略undefined、symbol、函数;不能解决循环引用;不能处理正则、new Date()) * **基础版(面试够用):** 浅拷贝+递归 (只考虑了普通的 object和 array两种数据类型) ```js function cloneDeep(target,map = new WeakMap()) { if(typeOf taret ==='object'){ let cloneTarget = Array.isArray(target) ? [] : {}; if(map.get(target)) { return target; } map.set(target, cloneTarget); for(const key in target){ cloneTarget[key] = cloneDeep(target[key], map); } return cloneTarget }else{ return target } } ``` * **终极版:** ```js const mapTag = '[object Map]'; const setTag = '[object Set]'; const arrayTag = '[object Array]'; const objectTag = '[object Object]'; const argsTag = '[object Arguments]'; const boolTag = '[object Boolean]'; const dateTag = '[object Date]'; const numberTag = '[object Number]'; const stringTag = '[object String]'; const symbolTag = '[object Symbol]'; const errorTag = '[object Error]'; const regexpTag = '[object RegExp]'; const funcTag = '[object Function]'; const deepTag = [mapTag, setTag, arrayTag, objectTag, argsTag]; function forEach(array, iteratee) { let index = -1; const length = array.length; while (++index < length) { iteratee(array[index], index); } return array; } function isObject(target) { const type = typeof target; return target !== null && (type === 'object' || type === 'function'); } function getType(target) { return Object.prototype.toString.call(target); } function getInit(target) { const Ctor = target.constructor; return new Ctor(); } function cloneSymbol(targe) { return Object(Symbol.prototype.valueOf.call(targe)); } function cloneReg(targe) { const reFlags = /\w*$/; const result = new targe.constructor(targe.source, reFlags.exec(targe)); result.lastIndex = targe.lastIndex; return result; } function cloneFunction(func) { const bodyReg = /(?<={)(.|\n)+(?=})/m; const paramReg = /(?<=\().+(?=\)\s+{)/; const funcString = func.toString(); if (func.prototype) { const param = paramReg.exec(funcString); const body = bodyReg.exec(funcString); if (body) { if (param) { const paramArr = param[0].split(','); return new Function(...paramArr, body[0]); } else { return new Function(body[0]); } } else { return null; } } else { return eval(funcString); } } function cloneOtherType(targe, type) { const Ctor = targe.constructor; switch (type) { case boolTag: case numberTag: case stringTag: case errorTag: case dateTag: return new Ctor(targe); case regexpTag: return cloneReg(targe); case symbolTag: return cloneSymbol(targe); case funcTag: return cloneFunction(targe); default: return null; } } function clone(target, map = new WeakMap()) { // 克隆原始类型 if (!isObject(target)) { return target; } // 初始化 const type = getType(target); let cloneTarget; if (deepTag.includes(type)) { cloneTarget = getInit(target, type); } else { return cloneOtherType(target, type); } // 防止循环引用 if (map.get(target)) { return map.get(target); } map.set(target, cloneTarget); // 克隆set if (type === setTag) { target.forEach(value => { cloneTarget.add(clone(value, map)); }); return cloneTarget; } // 克隆map if (type === mapTag) { target.forEach((value, key) => { cloneTarget.set(key, clone(value, map)); }); return cloneTarget; } // 克隆对象和数组 const keys = type === arrayTag ? undefined : Object.keys(target); forEach(keys || target, (value, key) => { if (keys) { key = value; } cloneTarget[key] = clone(target[key], map); }); return cloneTarget; } module.exports = { clone }; ``` 参考文章: [如何写出一个惊艳面试官的深拷贝](https://mp.weixin.qq.com/s/vXbFsG59L1Ba0DMcZeU2Bg) [深拷贝的终极探索(99%的人都不知道)](https://segmentfault.com/a/1190000016672263) 标签: js面试题 本作品采用 知识共享署名-相同方式共享 4.0 国际许可协议 进行许可。
评论已关闭