> 文章列表 > 前端面试高频手写代码题

前端面试高频手写代码题

前端面试高频手写代码题

前端面试高频手写代码题

  • 一、实现一个解析URL参数方法
    • 方法一:String和Array的相关API
    • 方法二: Web API 提供的 URL
    • 方法三:正则表达式+string.replace方法
  • 二、Call 、Apply、 Bind
    • call:
    • apply:
    • bind
  • 三、instanceof
  • 四、数组去重
    • 方法一、new Set()
    • 方法二、Map 或 对象
    • 方法三、indexOf 或者 includes
    • 方法四、filter + indexOf
    • 扩展:map set 常用方法和 object的常用方法
  • 五、数组扁平化:
  • 六、防抖与节流:
  • 七、new
  • 八、create
  • 补充:对象的三种创建方法:
  • 九、3种继承:
    • 1、 function + call
    • 2、class + extends
    • 3、原型
    • 十、深拷贝与浅拷贝

一、实现一个解析URL参数的方法

方法一:String和Array的相关API

第一种方法主要是利用字符串分割和数组操作,拿到关键的字符串,再做一下类型转换,组成最终的结果。主要技术点是要熟悉String和Array的相关API,灵活运用。

const getUrlParams = (url) => {const arrURL = url.split('?').pop().split('#').shift().split('&');console.log(arrURL)let obj = {};arrURL.forEach(item => {const [k,v] = item.split('=');obj[k] = v;})return obj;};const url = 'http://sample.com/?a=1&b=2&c=xx&d=2#hash';console.log(getUrlParams(url))

方法二: Web API 提供的 URL

第二种方法利用了 Web API 提供的 URL 和 URLSearchParams 对象实现的,用起来非常简单,缺点是兼容性不是很完美,不兼容IE系列浏览器。

const getUrlParams2 = (url) => {const u = new URL(url)const s = u.searchParamslet obj = {};// 这里的s.forEach((v,k) => {obj[k] = v;})console.log(obj)return obj;};const url = 'http://sample.com/?a=1&b=2&c=xx&d=2#hash';console.log(getUrlParams2(url))

方法三:正则表达式+string.replace方法

第三种方法通过正则表达式(捕获组,匹配不在集合中的字符)解析标准的query-string键值对字符串,然后巧妙地利用 string.replace方法的第二个参数可以作为回调函数进行一些操作,得到最终结果,写起来是最简洁的,但是写正则真的是头疼。

正则表达式 /([?&=]+)=([&]+)/g 匹配 URL 中的查询参数。其中 [^?&=]+ 匹配参数名,= 匹配参数名和参数值之间的等号,[^&]+ 匹配参数值。

调用 replace 方法将匹配到的字符串替换为箭头函数的返回值。这里的第一个参数 _ 表示匹配到的整个字符串,不需要使用,用下划线表示忽略该参数。第二个参数 k 表示匹配到的参数名,第三个参数 v 表示匹配到的参数值。

const getUrlParams3 = (url) => {// 定义一个 parse url.search 的方法url = url.split('#').shift();const obj = {};url.replace(/([^?&=]+)=([^&]+)/g, (_, k, v) => (obj[k] = v));return obj;};const url = 'http://sample.com/?a=1&b=2&c=xx&d=2#hash';console.log(getUrlParams3(url))

二、Call 、Apply、 Bind

call:

改变 this 指向用的,可以接收多个参数

delete: fn如果不及时删除,可能会导致内存泄漏,因此需要使用 delete 关键字将其从上下文对象中删除。
唯一的 Symbol 值 fn,并将其作为属性添加到 ctx 对象中,这样就避免了属性名的冲突。

Function.prototype.mycall = function(ctx, ...args){ctx = ctx || window;let fn = Symbol();ctx[fn] = this;//this是函数的调用者,这里是foolet result = ctx[fn](...args);delete ctx[fn];//释放内存return result
}
let obj = {name:"张三"
}
function foo(){return this.name;
}
// 就是把 foo 函数里的 this 指向,指向 obj
console.log(foo.mycall(obj))

apply:

原理同上,只不过 apply 接收第二个参数是数组,不支持第三个参数

使用 arguments 对象来判断是否传递了第二个参数,第一个参数是ctx,如果传递了,将其作为参数列表使用展开运算符 … 解构到一个数组中

Function.prototype.mycall = function(ctx){ctx = ctx || window;let fn = Symbol();ctx[fn] = this;//this是函数的调用者,这里是foolet result;if(arguments[1]){result = ctx[fn](...arguments[1]);}else{result = ctx[fn]();}delete ctx[fn];//释放内存return result
}
let obj = {name:"张三"
}
function foo(){return this.name;
}
// 就是把 foo 函数里的 this 指向,指向 obj
console.log(foo.apply(obj))

bind

bind 不会立即执行,会返回一个函数
1、函数可以直接执行并且传参,如 foo.myBind(obj, 1)(2, 3),所以需要 [ …args, …arguments ]合并参数
2、函数也可以 new,所以要判断原型 this instanceof fn

Function.prototype.myBind = function (ctx, ...args) {const self = thisconst fn = function(){}const bind = function(){const _this = this instanceof fn ? this : ctxreturn self.apply(_this, [...args, ...arguments])}fn.prototype = this.prototypebind.prototype = new fn()return bind
}

call、apply、bind的区别
1、都可以改变 this 指向
2、call 和 apply 会立即执行,bind 不会,而是返回一个函数
3、call 和 bind 可以接收多个参数,apply 只能接受两个,第二个是数组
bind 参数可以分多次传入

三、instanceof

接受两个参数,判断第二个参数是不是在第一个参数的原型链上

function myInstanceof(left, right) {// 获得实例对象的原型 也就是 left.__proto__let left1 = Object.getPrototypeOf(left)// 获得构造函数的原型let prototype = right.prototype// 判断构造函数的原型 是不是 在实例的原型链上while (true) {// 原型链一层层向上找,都没找到 最终会为 nullif (left1 === null) return falseif (prototype === left1) return true// 没找到就把上一层拿过来,继续循环,再向上一层找left1 = Object.getPrototypeOf(left1)}}let arr = [1,2,3]let str = '123'
// 第一个参数是实例对象,第二个参数是构造函数console.log(myInstanceof(str,Array));

四、数组去重

方法一、new Set()

function unique(arr){return Array.from(new Set(arr))
}
let arr = [1,1,2,2,3,3,4,4]
console.log(unique(arr))

方法二、Map 或 对象

用空对象 let obj ={}利用对象属性不能重复的特性

map方法:
function unique(arr){let map = new Map() // 或者用空对象 let obj ={}利用对象属性不能重复的特性let ar = []arr.forEach((item) => {if(!map.has(item)){// 如果是对象的话就判断 !obj[item]map.set(item,true) // 如果是对象的话就 obj[item] = true  其他一样ar.push(item)}})return ar;
}
let arr = [1,1,2,2,3,3,4,4]
console.log(unique(arr))

对象方法:

function unique(arr){let obj = {}let ar = []arr.forEach((item) => {if(!obj[item]){// 如果是对象的话就判断 !obj[item]obj[item] = true // 如果是对象的话就 obj[item] = true  其他一样ar.push(item)}})return ar;
}
let arr = [1,1,2,2,3,3,4,4]
console.log(unique(arr))

方法三、indexOf 或者 includes

includes方法:

function unique(arr){let ar = [];arr.forEach((item) => {if(!ar.includes(item)){ar.push(item)}})return ar;
}
let arr = [1,1,2,2,3,3,4,4]
console.log(unique(arr))

indexOf方法:

function unique(arr){let ar = [];arr.forEach((item) => {if(ar.indexOf(item) == -1){ar.push(item)}})return ar;
}
let arr = [1,1,2,2,3,3,4,4]
console.log(unique(arr))

方法四、filter + indexOf

function unique(arr){let ar = []ar = arr.filter((item,index) => {return arr.indexOf(item) == index})return ar;
}
let arr = [1,1,2,2,3,3,4,4]
console.log(unique(arr))

扩展:map set 常用方法和 object的常用方法

1.Set数据类型:

集合 :里面的元素不能重复

Set是 一组key的集合,但不存储value。由于key不能重复,所以,在Set中,没有重复的key。

常用的方法:

add : 添加一个set元素

delete : 删除一个set元素

​ has : 查看元素是否存在

​ size属性 : 查看set中的元素数量

​ clear : 清空set集合

Symbol.iterator :迭代数据

foreach:循环遍历数据

2.Map数据类型
map与Object非常类似。map与Object的最大区别是:map的key可以是任何数据类型,object的可以只能是字符串,JavaScript支持的所有类型都可以当作Map的key

常用的方法:

set : 添加一个map元素 内置两个参数 第一个是键 第二个是值

​ delete : 删除一个map元素

​ has : 查看元素是否存在

​ size属性 : 查看map中的元素数量

get : 获取一个map集合元素

​ clear : 清空map集合

foreach:循环遍历数据

3.map 与 set 的关系

1.Map是键值对,Set是值的集合,当然键和值可以是任何的值;

2.Map可以通过get方法获取值,而set不能因为它只有值;

3.都能通过迭代器进行for…of或者foreach遍历;

4.Set的值是唯一的可以做数组去重,Map由于没有格式限制,可以做数据存储

4.ES6中 Object 的常用方法:

Object.is() 方法判断两个值是否是相同的值
Object.assign() 方法用于将所有可枚举属性的值从一个或多个源对象分配到目标对象。
Object.getOwnPropertyDescriptors() 回指定对象所有自身属性(非继承属性)的描述对象。
Object.setPrototypeOf() 为现有对象设置原型,返回一个新对象
Object.getPrototypeOf() 方法返回指定对象的原型
Object.keys() 返回所有可遍历( enumerable )属性的键名。
Object.values(),返回所有的可遍历属性的内容。
Object.entries() 方法返回一个给定对象自身可枚举属性的键值对数组。

五、数组扁平化:

就是把多维数组变成一维数组


let arr = [1,4,[1,[2,[3]]]]
let brr = arr.flat(Infinity);console.log((brr))

六、防抖与节流:

防抖:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>debounce</title>
</head>
<body><p>debounce</p>搜索 <input id="input1"><script>// 默认延迟200毫秒,如果传入的有值,就优先传入的值function debounce(fn, delay = 200) { let timer = null;return function () { if(timer) clearTimeout(timer)timer = setTimeout(()=>{fn.apply(this, arguments)//透传this和参数timer = null;},delay)}}const input1 = document.getElementById('input1')input1.addEventListener('keyup', debounce(()=>{console.log("搜索", input1.value)}),300)</script>
</body>
</html>

节流:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>throttle</title>
</head>
<body><p>throttle</p><div id="div1" draggable="true" style="width: 100px; height: 50px; background-color: #ccc; padding: 10px;">可拖拽</div><script>// 默认延迟200毫秒,如果传入的有值,就优先传入的值function throttle(fn, delay = 200) { let timer = null;return function () { if(timer) returntimer = setTimeout(()=>{fn.apply(this, arguments)//透传this和参数timer = null;},delay)}}const div1 = document.getElementById('div1')div1.addEventListener('drag', throttle((e)=>{console.log("鼠标的位置", e.offsetX, e.offsetY)}),300)</script>
</body>
</html>

七、new

function myNew(fn,...args){// 不是函数不能 newif(typeof fn !== "function"){throw new Error('TypeError')}// 创建一个继承 fn 原型的对象const newObj = Object.create(fn.prototype);// 将 fn 的 this 绑定给新对象,并继承其属性,然后获取返回结果const result = fn.apply(newObj, args);// 根据 result 对象的类型决定返回结果return result && (typeof result === "object" || typeof result == "function") ? result : newObj;
}

八、create

function mycreate(obj) { function fn(){}fn.prototype = obj;return new fn()}

补充:对象的三种创建方法:

1、字面量方法: let obj = {} 、Object.create方法、new方法

2、三者的区别:

  • 字面量和new关键字创建的对象是Object的实例,原型指向Object.prototype,继承内置对象Object
  • Object.create(arg, pro)创建的对象的原型取决于arg,arg为null,新对象是空对象,没有原型,不继承任何对象;arg为指定对象,新对象的原型指向指定对象,继承指定对象

九、3种继承:

1、 function + call

function parent() { this.name = '张三';}function child(){this.age = 19;parent.call(this);}let obj1 = new child()console.log(obj1)

2、class + extends

一定要记得写super()

注意:使用了class的是ES6继承,function的原型继承和call继承都是ES5继承

class parent{constructor(){this.name = '张三'}
}class child extends parent{constructor(){super()this.age = 19}
}let obj = new child();console.log(obj)

3、原型

在子类的原型上new一个父类

如果光打印子类可能看不出来继承了,要特地去打印子类没有但是父类有的属性,会发现子类也有,那么就是实现继承了

function parent() { this.name = '张三';}function child(){this.age = 19;}child.prototype = new parent()
let obj1 = new child()console.log(obj1,obj1.name)

十、深拷贝与浅拷贝

1、加了{} , 使用Object.assign({},obj)层次大于2的都是浅拷贝,第一层都是深拷贝

2、如果不加{} — Object.assign(obj) — 全都浅拷贝

let obj = {name:'张三', age:19,work:['it']}
let obj1 = Object.assign({},obj);
// let obj1 = Object.assign(obj)// 浅拷贝 -- 使用Object.assign({},obj); 层次大于2的都是浅拷贝
obj1.work[0] = 'web'
console.log(obj)
console.log(obj1)// 深拷贝--Object.assign({},obj);第一层都是深拷贝
obj1.name = 'lisi'
console.log(obj)
console.log(obj1)// 如果不加{} ---  Object.assign(obj) --- 全都浅拷贝