慢慢更新
深拷贝
面试手写
思路上大体分为两部分:
- 基本数据类型
- 引用数据类型
- 日期
- 正则
- html元素
- 考虑了循环引用
function deepClone(target, hash = new WeakMap()) {
if (target === null) return target
if (target instanceof Date) return new Date(target)
if (target instanceof RegExp) return new RegExp(target)
if (target instanceof HTMLElement) return target
if (typeof target !== 'object') return target
let cloneTarget = new target.constructor()
if (hash.has(target)) {
return hash.get(target)
}
hash.set(target,cloneTarget)
Reflect.ownKeys(target).forEach(key => {
cloneTarget[key] = deepClone(target[key], hash)
})
return cloneTarget
}
let a = {
aa: [1, 2, 3]
}
let b = deepClone(a)
b.aa[0] = 1111
console.log(b)
console.log(a)
日常使用
懂得都懂
JSON.parse(JSON.stringify(obj))
数组转树形结构
这里使用浅拷贝就可以了,可以避免修改原有数组
const arr = [
{ 'id': '29', 'pid': '', 'name': '总裁办' },
{ 'id': '2c', 'pid': '', 'name': '财务部' },
{ 'id': '2d', 'pid': '2c', 'name': '财务核算部' },
{ 'id': '2f', 'pid': '2c', 'name': '薪资管理部' },
{ 'id': 'd2', 'pid': '', 'name': '技术部' },
{ 'id': 'd3', 'pid': 'd2', 'name': 'Java研发部' }
]
function listToTree(arr) {
let res = []
let map = {}
arr.forEach((item) => {
map[item.id] = { // 浅拷贝,避免修改原有数组
...item,
children: []
}
})
arr.forEach((item) => {
let parent = map[item.pid]
if (parent) {
parent.children.push(map[item.id])
} else {
res.push(map[item.id])
}
})
return res
}
const treeList = listToTree(arr)
console.log(treeList);
树形结构转数组
function flatten(tree, res = []) {
tree.forEach((item) => {
let { children, ...obj } = item
res.push(obj)
if (children) {
flatten(children, res)
}
})
return res
}
数组去重
只有数字的简单去重
function uniqueArr(arr) {
return Array.from(new Set(arr))
}
有引用类型的复杂去重
使用 JSON.stringify 解决
let arr = [
{
name: '1',
age: 1
},
{
name: '2',
age: 2
},
{
name: '1',
age: 1
},
123,
1,
123,
[1, 2, 3],
[1],
[1, 2, 3]
]
function uniqueArr(arr) {
let res = []
let set = new Set()
arr.forEach((item) => {
if (typeof item === 'object') {
if (!set.has(JSON.stringify(item))) {
res.push(item)
set.add(JSON.stringify(item))
}
} else {
if (!set.has(item)) {
res.push(item)
set.add(item)
}
}
})
return res
}
console.log(uniqueArr(arr))
结果:
数组扁平化
forEach + 递归
function copyFlat(arr, depth) {
let res = []
function flat(arr, depth) {
arr.forEach((item) => {
if (Array.isArray(item) && depth > 0) { // 注意递归的终止条件应该写在这里
flat(item, depth - 1)
} else {
res.push(item)
}
})
}
flat(arr, depth)
return res
}
console.log(copyFlat([1,[2, [3]]], 1))
function copyFlat(arr, depth) {
if (depth === 0) return arr
return arr.reduce((res, cur) => {
return res.concat(Array.isArray(cur) ? copyFlat(cur, depth - 1) : cur)
}, [])
}
console.log(copyFlat([1,[2, [3]]], 1))
防抖和节流
防抖
function debounce(fn, delay = 1000) {
let timer = null
return function () {
clearTimeout(timer)
timer = setTimeout(() => {
fn.apply(this, arguments)
}, delay)
}
}
节流
function throttle(fn, delay = 1000) {
let timer = 0
return function () {
let now = Date.now()
if (now - timer >= delay) {
fn.apply(this, arguments)
timer = now
}
}
}
继承
组合继承
组合继承 = 原型链继承 + 构造函数继承
function Father() {
}
function Son() {
Father.call(this) // 原型链继承
}
Son.prototype = new Father() // 构造函数继承
优点:
- 可以拿到父类的属性和方法
- 并且属性和方法不会与其他子类实例共享
缺点:
- 调用了两次父类构造,所以属性上可以理解为父类上有的属性,子类上有一份相同的,总共有两份,子类上的属性会覆盖父类,这样不是特别好,最好的情况应该是父类身上没有属性,只有子类身上有继承过来的父类属性,那么父类就不应该被构造调用
- 子类的原型对象是父类了,所以构造函数也指向父类,不是我们想要的结果
寄生组合式继承
function inheritPrototype(subType, superType) {
let prototype = Object.create(superType.prototype)
prototype.constructor = subType
subType.prototype = prototype
}
function Father() {
}
function Son() {
Father.call(this)
}
inheritPrototype(Son, Father)
目前最好的继承方式,优点就是解决了组合式继承的问题
判断一个数组是否包含另一个数组
function arrInclude(arr1, arr2) {
return arr2.every(item => arr1.includes(item))
}
console.log(arrInclude([2, 3, 4, 5, 6, 7, 8, 9, 10], [2, 3]))
获取 url 参数
function getUrlParam(sUrl, sKey) {
let paramStr = sUrl.indexOf('#') === -1 ? sUrl.slice(sUrl.indexOf('?') + 1) : sUrl.slice(sUrl.indexOf('?') + 1, sUrl.indexOf('#'))
let params = paramStr.split('&')
let paramObj = {}
params.forEach((item) => {
let [key, value] = item.split('=')
if (paramObj[key]) {
paramObj[key] = [].concat(paramObj[key], value)
} else {
paramObj[key] = value
}
})
if (sKey) {
return paramObj[sKey] ? paramObj[sKey] : ''
}
return paramObj
}
函数柯里化
参数定长
function curry(fn) {
let len = fn.length
let outArg = [].slice.call(arguments, 1)
return function () {
let inArg = [].slice.call(arguments)
let allArg = [...outArg, ...inArg]
if (allArg.length >= len) {
return fn.apply(null, allArg)
} else {
return curry.call(null, fn, ...allArg)
}
}
}
function sum(a,b,c) {
return a + b + c
}
let cur = curry(sum)
console.log(cur(1)(2)(3))
手写 call、apply、bind
call
Function.prototype.mycall = function (context, ...args) { // call 以对象形式进行传参,非严格模式下会对包装类型对象进行包装
let isStrict = (function () { return this === undefined })()
if (!isStrict) {
let type = typeof context
if (type === 'string') {
context = new String(context)
} else if (type === 'number') {
context = new Number(context)
} else if (type === 'boolean') {
context = new Boolean(context)
}
}
let fn = this
if (!context) {
return fn(...args)
}
let symbol = Symbol()
context[symbol] = fn
let res = context[symbol](...args)
delete context[symbol]
return res
}
apply
相比于 call,只是把形参变了一下
Function.prototype.myapply = function (context, args = []) {
let isStrict = (function () { return this === undefined })()
if (!isStrict) {
let type = typeof context
if (type === 'number') {
context = new Number(context)
} else if (type === 'boolean') {
context = new Boolean(context)
} else if (type === 'string') {
context = new String(context)
}
}
let fn = this
if (!context) {
return fn(...args)
}
let symbol = Symbol()
context[symbol] = fn
let res = context[symbol](...args)
delete context[symbol]
return res
}
bind
Function.prototype.mybind = function (context, ...args) {
let fn = this
let newFn = function (...newArgs) {
if (this instanceof newFn) {
return fn.call(this, ...args, ...newArgs)
}
return fn.call(context, ...args, ...newArgs)
}
newFn.prototype = Object.create(fn.prototype)
return newFn
}
function foo(something) {
this.a = something;
}
var obj1 = {};
var bar = foo.mybind(obj1);
bar(2); // 让foo此时指向obj1
console.log(obj1.a); // 2
var baz = new bar(3); // new修改了this指向,指向新创建出来的foo对象,并将该新创建的foo对象的a赋值为3
console.log(obj1.a); // 2,因为上面this指向新的对象,所以obj1的a没有再被修改
console.log(baz.a); // 3