说在最前。本文内容并非全部原创,只是个人在面试过程中在查阅了众多文章之后的总结,如有记得出处会标明。其一是提供给有需要的同学做复习,其二是自己保存一份以便查阅。希望各位文明观看。
js基础知识
判断数据类型的方式
1、typeof
typeof [] // object, 数组也可单独使用Array.isArray()
typeof {}; // object
typeof new Function(); // function
2、instanceof
[] instanceof Array; // true
3、constructor
[].__proto__.constructor === Array // true, 并不准确,如果继承时没有显示指定constructor会判断出错
4、toString
Object.prototype.toString.call('a') // "[object, string]"
Object.prototype.toString.call([5]) // "[object, Array]"
原型问题
请查阅 这篇文章,讲的很清晰了。原型问题是高频题,一定要去理解,而不是死记硬背
箭头函数和普通函数的区别
async/await实现
0.1 + 0.2 === 0.3 ?
宏任务和微任务
jakearchibald.com/2015/tasks-…
实现new操作符
function myNew (fn) {
var obj = Object.create(fn.prototype)
var args = Array.prototype.slice.call(arguments, 1)
var res = fn.call(obj, ...args)
if (res && typeof res === 'object' || typeof res === 'function') {
return res
}
return obj
}
实现instanceof
function myInstanceOf (obj, fn) {
if (typeof obj !== 'object' || obj === null) return false
const proto = obj.__proto__
if (proto === null) return false
if (proto !== fn.prototype) {
return myInstanceOf(proto, fn)
} else {
return true
}
}
手写call
Function.prototype.myCall = function (target, ...args) {
if (typeof this !== 'function') {
throw new Error('not a function')
}
target = target || window
var fn = Symbol()
target[fn] = this
var res = target[fn](...args)
delete target[fn]
return res
}
手写apply
Function.prototype.myApply = function (target, args) {
if (typeof this !== 'function') {
throw new Error('not a function')
}
target = target || window
var fn = Symbol()
target[fn] = this
var res = target[fn](args)
delete target[fn]
return res
}
手写bind
Function.prototype.myBind = function (context, ...args) {
if (typeof this !== 'function') {
throw TypeError("Bind must be called on a function");
}
context = context || window
var _this = this
var fn = Symbol()
context[fn] = this
var result = function () {
var _args = Array.from(arguments)
if (this instanceof _this) {
this[fn] = _this
this[fn](...[...args, ..._args])
delete this[fn]
} else {
context[fn](...[...args, ..._args]);
delete context[fn];
}
}
// 实现继承,当result作为构造函数使用时, this.__proto__ === result.prototype
// this.__proto__.__proto__ === result.prototype.__proto__ === _this.prototype; //this instanceof _this =>true
result.prototype = Object.create(this.prototype)
return result
}
实现reduce
Array.prototype.myReduce = function(cb, initValue) {
if (!Array.isArray(this)) {
throw new TypeError("not a array")
}
// 数组为空,并且有初始值,报错
if (this.length === 0 && arguments.length < 2) {
throw new TypeError('Reduce of empty array with no initial value')
}
var res = null
var arr = this
if (initValue) {
res = initValue
} else {
res = arr.splice(0, 1)[0]
}
arr.forEach((item, index) => {
res = cb(res, item, index, arr)
})
return res
};
函数柯里化(参数长度固定)
function curry (fn) {
const res = function (...args) {
if (args.length === fn.length) {
return fn(...args)
} else {
return function (...newArgs) {
return res(...args, ...newArgs)
}
}
}
}
函数柯里化(参数长度不固定)
function curry (fn) {
let args = []
return function temp (...newArgs) {
if (newArgs.length) {
args = [...args, ...newArgs]
return temp
} else {
let val = fn.apply(this, args)
args = []
return val
}
}
}
防抖
function debounce (fn ,delay) {
let timer
return function () {
var args = Array.from(arguments)
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(() => {
fn.apply(this, args)
}, delay)
}
}
防抖立即触发
function debounceImm (fn ,delay, immediate) {
let timeout
return function () {
const context = this
const args = Array.from(arguments)
if (timeout) clearTimeout(timeout)
if (immediate) {
const callNow = !timeout
setTimeout(() => {
timeout = null
}, delay)
if (callNow) fn.apply(context, args)
} else {
timeout = setTimeout(() => {
fn.apply(context, args)
}, delay)
}
}
}
节流
function throttle (fn, delay) {
let flag = true
return function () {
var args = Array.from(arguments)
if (!flag) {
return
}
flag = false
setTimeout(() => {
fn.apply(this, args)
flag = true
})
}
}
防抖和节流有各自的应用场景,比如列表滑动使用节流,表单提交使用防抖
用proxy实现数组的负索引, 如arr[-1]就返回arr[arr.length - 1]
var negativeArray = (arr) => {
return new Proxy(arr, {
get: (target, prop, receiver) => {
const key = +prop > 0 ? prop : (target.length + (+prop))
console.log('key', key)
return Reflect.get(target, key, receiver)
}
})
}
var unicon = negativeArray([1,2,3,4])
console.log(unicon[-2])
模拟lodash的_.get()方法
function get (target, path, defaultValue = undefined) {
const paths = path.replace(/\[(\d+)\]/g, '.$1').split('.')
let result = target
for (const p of paths) {
result = Object(result)[p]
if (result === undefined) {
return defaultValue
}
}
return result
}
console.log(get({ a: null }, "a.b.c", 3)); // output: 3
console.log(get({ a: [{ b: 1 }] }, "a[0].b", 3)); // output: 1
深拷贝
function isObject (val) {
return typeof val === 'object' && val !== null
}
function deepClone (obj, map = new WeekMap()) {
if (!isObject(obj)) return obj
// 防止循环引用
if (map.has(obj)) {
return map.get(obj)
}
let target = Array.isArray(obj) ? [] : {}
map.set(obj, target)
Object.keys(obj).forEach(item => {
if (isObject(item)) {
target[item] = deepClone(obj[item], map)
} else {
target[item] = obj[item]
}
})
return target
}
更细致的处理方式请查看github.com/jsmini/clon…
编程题
大数相加
function add(a ,b) {
//取两个数字的最大长度
let maxLength = Math.max(a.length, b.length)
//用0去补齐长度
a = a.padStart(maxLength, 0)//'0000007123434532341'
b = b.padStart(maxLength, 0)//'1234567800000009999'
//定义加法过程中需要用到的变量
let everySum = 0
let carry = 0 //进位
let res = ''
for(let i = maxLength - 1 ; i >= 0 ;i--){
everySum = parseInt(a[i]) + parseInt(b[i]) + carry
carry = Math.floor(everySum / 10)
res = everySum % 10 + res
}
if(carry !== 0){
res = '' + carry + res
}
return res
}
给定一个正整数,返回小于该数的最大2次幂。如输入9,返回8
function getMaxSecondPower (target) {
// 观察一下2的幂数字的二进制规律,2->10, 4 -> 100, 8 -> 1000, 所以 2^n < target < 2^n-1, 那么target转二进制后必定与2^n位数相同
const toBinary = Number(target).toString(2)
return parseInt('1'.padEnd(toBinary.length, 0), 2)
}
实现其他格式转驼峰
如: first_name, FIRST_NAME, FirstName, first_Name 转为 firstName
function smallCamel(value) {
return value.replace(/(_?)([A-Z][a-z]+|[a-z]+|[A-Z]+)/g, function (value, _ ,word, i) {
if (i === 0) {
return word.toLowerCase()
} else {
return word.slice(0, 1).toUpperCase() + word.slice(1).toLowerCase()
}
})
}
虚拟dom转为真实dom
function _render (vnode) {
if (typeof vnode === 'number') {
vnode = String(vnode)
}
if (typeof vnode === 'string') {
return document.createTextNode(vnode)
}
var dom = document.createElement(vnode.tag)
if (vnode.attrs) {
Object.keys(vnode.attrs).forEach(key => {
dom.setAttribute(key, vnode.attrs[key])
})
}
vnode.children.forEach(child => {
return dom.appendChild(_render(child))
})
return dom
}
var vnode = {
tag: 'DIV',
attrs:{
id:'app'
},
children: [
{
tag: 'SPAN',
children: [
{ tag: 'A', children: [] }
]
},
{
tag: 'SPAN',
children: [
{ tag: 'A', children: [] },
{ tag: 'A', children: [] }
]
}
]
}
console.log(_render(vnode))
将对象铺平
function flattenObj (obj) {
if (!isObject(obj)) {
return
}
let res = {}
const dfs = (cur, prefix) => {
if (isObject(cur)) {
if (Array.isArray(cur)) {
cur.forEach((item, index) => {
dfs(item, `${prefix}[${index}]`)
})
} else {
for (var k in cur) {
dfs(cur[k], `${prefix}${prefix ? '.' : ''}${k}`)
}
}
} else {
res[prefix] = cur
}
}
dfs(obj, '')
console.log('flattenObj: ', res)
return res
}
function isObject (val) {
return typeof val === 'object' && val !== null
}
const fobj = {
a: {
b: 1,
c: 2,
d: {e: 5}
},
b: [1, 3, {a: 2, b: 3}],
c: 3
}
flattenObj(fobj)
上两题主要考察递归问题
实现compose函数
function compose (...fn) {
if (!fn.length) return (v) => v
if (fn.length === 1) return fn[0]
return fn.reduce((pre, cur) => {
// 相当于一个个入栈,再出栈,从右往左执行
return (...args) => {
return pre(cur(...args))
}
})
}
function fn1(x) {
return x + 1;
}
function fn2(x) {
return x + 2;
}
function fn3(x) {
return x + 3;
}
function fn4(x) {
return x + 4;
}
const a_test = compose(fn1, fn2, fn3, fn4);
console.log(a_test(1)); // 1+4+3+2+1=11
版本号排序
function sortVersion () {
var list = ['0.1.1', '2.3.3', '0.302.1', '4.2', '4.3.5', '4.3.4.5']
list.sort((a, b) => {
let i = 0
let arr1 = a.split('.')
let arr2 = b.split('.')
while (true) {
const s1 = arr1[i]
const s2 = arr2[i]
i++
if (s1 === undefined || s2 === undefined) {
return arr2.length - arr1.length
}
if (s1 === s2) continue
return s2 - s1
}
})
console.log(list)
}
订阅发布模式
class EventEmitter {
constructor () {
this.events = {}
}
on (type, callback) {
if (!this.events[type]) {
this.events[type] = [callback]
} else {
this.events[type].push(callback)
}
}
off (type, callback) {
if (!this.events[type]) {
return
}
this.events[type] = this.events[type].filter(item => {
return item !== callback
})
console.log('off', this.events)
}
once (type, callback) {
// callback需要使用具名函数,绑定一次,off一次
function fn () {
callback()
this.off(type, fn)
}
this.on(type, fn)
}
emit (type, ...rest) {
console.log('emit', this.events)
this.events[type] && this.events[type].forEach(fn => {
fn.apply(this, rest)
})
}
}
const event = new EventEmitter()
更详细的设计模式请参考 设计模式
实现Promise及其方法
const PEDDING = 'pedding'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
function MyPromise (exector) {
this.status = PEDDING
this.value = undefined
this.reason = undefined
this.onFulfilledCb = []
this.onRejectedCb = []
function resolve (value) {
if (this.status === PEDDING) {
this.status = FULFILLED
this.value = value
// 调用then函数的回调函数,因为有可能.then().then()所以回调用数组保存
this.onFulfilledCb.forEach(fn => fn())
}
}
function reject (reason) {
if (this.status === PEDDING) {
this.status = REJECTED
this.reason = reason
this.onRejectedCb.forEach(fn => fn())
}
}
try {
exector(resolve, reject)
} catch (error) {
reject(error)
}
}
MyPromise.prototype.then = function (onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err }
const promise2 = new MyPromise((resolve, reject) => {
if (this.status === FULFILLED) {
// A+规范onFulfilled函数必须被异步调用
setTimeout(() => {
let x = onFulfilled(this.value)
// 1、首先,要看x是不是promise。
// 2、如果是promise,则取它的结果,作为新的promise2成功的结果
// 3、如果是普通值,直接作为promise2成功的结果
resolvePromise(promise2, x, resolve, reject)
}, 0)
}
if (this.status === REJECTED) {
setTimeout(() => {
let x = onRejected(this.reason)
resolvePromise(promise2, x, resolve, reject)
}, 0)
}
if (this.status === PEDDING) {
// 此时new Promise的异步回调还未结束,所以需要暂时保存then的回调
this.onFulfilledCb.push(()=> {
setTimeout(() => {
try {
let x = onFulfilled(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch (error) {
reject(error)
}
}, 0)
})
this.onRejectedCb.push(() => {
try {
let x = onRejected(this.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (error) {
reject(error)
}
})
}
})
return promise2
}
MyPromise.prototype.catch = function (onRejected) {
return this.then(null, onRejected)
}
MyPromise.prototype.resolve = function (value) {
if (value instanceof MyPromise) {
return value
}
if (typeof value === 'object' || typeof value === 'function') {
try {
const then = value.then
if (typeof then === 'function') {
// 如果参数是一个promise,那么应该做的是把参数promise的结果(通过promise.then(resolve,reject)获取结果)传递给返回的promise的then
// return new Promise(then.bind(value)) // 跟下面的写法一个意思
return new Promise((resolve, reject) => {
// 这样,参数value在status发生变化的时候,返回的promise实例status也会同时发生变化,并且会将参数的this.value传递下去
then.call(value, resolve, reject)
})
}
} catch (error) {
return new MyPromise((resolve, reject) => {
reject(error)
})
}
} else {
return new MyPromise((resolve, reject) => {
resolve(value)
})
}
}
MyPromise.prototype.reject = function (error) {
return new MyPromise((resolve, reject) => {
reject(error)
})
}
MyPromise.prototype.all = function (promises) {
return new Promise ((resolve, reject) => {
let i = 0
let res = []
if (promises.length === 0) {
return resolve(res)
}
promises.forEach((promise, index) => {
promise.then(data => {
res[index] = data
i++
if (i === promises.length) {
resolve(res)
}
}, reject)
})
})
}
MyPromise.prototype.race = function (promises) {
return new MyPromise((resolve, reject) => {
if (promises.length === 0) {
return resolve()
}
promises.forEach((promise) => promise.then(resolve, reject))
})
}
MyPromise.prototype.finally = function (fn) {
// 与catch类似,都是this.then的变式, finally要求返回一个promise,并且调用方的value传递下去
// 即 A.finally().then(val => val) 需要将A.value传递下去
return this.then( value => {
// Promise.resolve(fn())可以等待fn执行完成之后再返回结果
return MyPromise.resolve(fn()).then(() => {
// value就是 new Promise(...).finally() 过程中,finally方法的调用方的值
return value
})
}, error => {
return MyPromise.resolve(fn()).then(() => {
throw error
})
})
}
function resolvePromise (promise, x, resolve, reject) {
if (promise === x) {
reject(new TypeError('Chaining cycle detected for promise'))
}
let called
if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
try {
let then = x.then
if (then && typeof then === 'function') {
then.call(x, y => {
if (called) return
called = true
resolvePromise(promise, y, resolve, reject)
}, err => {
if (called) return
called = true
reject(err)
})
} else {
resolve(x)
}
} catch (error) {
if (called) return
called = true
reject(error)
}
} else {
resolve(x)
}
}
并发问题1
最大同时发送n个,有一个返回才能继续发送下一个
const promises = Array(21).fill(0).map((v, i) => {
return () => {
return new Promise((resolve) => {
setTimeout(() => {
console.log(`完成第 ${i} 个请求`)
resolve(i)
}, 2000)
})
}
})
function conurlRequest (urls, maxNum) {
return new Promise((resolve, reject) => {
if (urls.length === 0) {
resolve([])
return
}
const result = []
const len = urls.length
let index = 0
let completedCount = 0
async function request () {
// 存储这个请求在原数组中的下标
const i = index
const url = urls[index]
index++
try {
// const resp = await fetch(url)
console.log(`执行第 ${i} 个请求`)
const resp = await url()
result[i] = resp
} catch (error) {
result[i] = error
} finally {
completedCount++
if (completedCount === len) {
resolve(result)
}
if (index < len) {
request()
}
}
}
// maxNum可能大于len,所以不能直接用len
for (let i = 0; i < Math.min(len, maxNum); i++) {
request()
}
})
}
conurlRequest(promises, 3)
并发问题2
let urls = ['http://dcdapp.com'];
/*
*实现一个方法,比如每次并发的执行三个请求,如果超时(timeout)就输入null,直到全部请求完
*batchGet(urls, batchnum=3, timeout=3000);
*urls是一个请求的数组,每一项是一个url
*最后按照输入的顺序返回结果数组[]
*/
function batchGet (urls, batchnum, timeout = 3000) {
var ret = []
while (urls.length > 0) {
var pre = urls.splice(0, batchnum)
var requestList = pre.map(url => request(url, timeout))
var result = await Promise.allSettled(requestList)
ret.concat(result.map(item => {
if (item.status === 'rejected') {
return null
} else {
return item.value
}
}))
}
return ret
}
function request (url ,timeout) {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject()
}, timeout)
fetch(url).then(res => {
resolve(res)
})
})
}
算法(常见问题)
排序篇
常见排序算法可以看 这篇文章
链表篇(链表问题最好还是去Leecode上刷)
反转单链表
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} head
* @return {ListNode}
*/
// 递归法
var reverseList2 = function(head) {
if (head === null) return null
if (head.next === null) { // head.next === null时,说明是翻转前的最后一个节点,也就是反转后的头节点,所以直接return head
return head
}
var last = reverseList(head.next)
head.next.next = head
head.next = null // 反转完之后,之前的头节点就是最后一个节点,所以指向null
return last
};
// 迭代法
var reverseList = function (head) {
if (head === null) return null
var pre = null, cur = head, nxt = head
while (cur !== null) {
nxt = cur.next
cur.next = pre
pre = cur
cur = nxt
}
return pre
}
合并两个有序链表
/**
* @param {ListNode} l1
* @param {ListNode} l2
* @return {ListNode}
*/
var mergeTwoLists = function(l1, l2) {
// 这道题可以使用递归实现,新链表也不需要构造新节点,我们下面列举递归三个要素
// 终止条件:两条链表分别名为 l1 和 l2,当 l1 为空或 l2 为空时结束
// 返回值:每一层调用都返回排序好的链表头
// 本级递归内容:如果 l1 的 val 值更小,则将 l1.next 与排序好的链表头相接,l2 同理
// O(m+n)O(m+n),mm 为 l1的长度,nn 为 l2 的长度
if (l1 === null) return l2
if (l2 === null) return l1
if (l1.val < l2.val) {
l1.next = mergeTwoLists(l1.next, l2)
return l1
} else {
l2.next = mergeTwoLists(l2.next, l1)
return l2
}
};
var mergeTwoLists2 = function(l1, l2) {
// 迭代方式
const prehead = new ListNode(-1);
let prev = prehead;
while (l1 != null && l2 != null) {
if (l1.val <= l2.val) {
prev.next = l1;
l1 = l1.next;
} else {
prev.next = l2;
l2 = l2.next;
}
prev = prev.next;
}
// 合并后 l1 和 l2 最多只有一个还未被合并完,我们直接将链表末尾指向未合并完的链表即可
prev.next = l1 === null ? l2 : l1;
return prehead.next;
};
两个链表相加
/**
* @param {ListNode} l1
* @param {ListNode} l2
* @return {ListNode}
*/
var addTwoNumbers = function(l1, l2) {
var head = null, tail = null, carry = 0 // tail存放head的下一个节点
while (l1 || l2) {
var v1 = l1 ? l1.val : 0
var v2 = l2 ? l2.val : 0
var val = v1 + v2 + carry
if (!head) { // 没有头节点时
head = tail = new ListNode(val % 10)
} else {
tail.next = new ListNode(val % 10)
tail = tail.next
}
carry = Math.floor(val / 10)
if (l1) {
l1 = l1.next
}
if (l2) {
l2 = l2.next
}
}
if (carry) {
tail.next = new ListNode(carry)
}
return head
};
leecode地址 两数相加
相交链表
/**
* @param {ListNode} headA
* @param {ListNode} headB
* @return {ListNode}
*/
var getIntersectionNode = function(headA, headB) {
if (headA === null || headB === null) {
return null
}
var p1 = headA, p2 = headB
// 两个链表相交后,后续的长度是一样的。假如链表长度分别为m,n 并且 m > n, 那么在相交点之前,一定是有m - n个节点。
// 当n走到尽头时,m还有 m - n个节点没走。此时n从headA开始走,当m走到终点是,n也恰好继续再走m - n步,即为相交点。
while (p1 !== p2) {
p1 = p1 === null ? headB : p1.next
p2 = p2 === null ? headA : p2.next
}
return p1
};
判断链表中有环
/**
* @param {ListNode} head
* @return {boolean}
*/
var hasCycle = function(head) {
if (!head || head.next === null) {
return false
}
var slow = head, fast = head
while (fast !== null && fast.next !== null) {
slow = slow.next
fast = fast.next.next
if (slow === fast) {
return true
}
}
return false
};
删除链表的倒数第 N 个结点
/**
* @param {ListNode} head
* @param {number} n
* @return {ListNode}
*/
var removeNthFromEnd = function(head, n) {
if (head === null) { return null }
var slow = head, fast = head
while (n-- > 0) {
fast = fast.next
}
if (fast === null) {
// 走了n步走到头了,说明倒数第n个就是第一个
return head.next
}
while (fast !== null && fast.next !== null) {
slow = slow.next
fast = fast.next
}
// fast始终快slow n步, n步即可以向前走n个节点,当fast.next === null时,说明fast就是最后一个,此时slow后面还有n个节点。那倒数第n个就是slow.next
slow.next = slow.next.next
return head
};
树
根据数组构建树,并输出广度遍历结果
var arr = [
{ node: 2, children: [3, 9, 4] },
{ node: 7, children: [2] },
{ node: 3, children: [6] },
{ node: 4, children: [5] },
{ node: 5, children: [8] },
{ node: 10, children: [11] },
];
// 7 10
// | |
// 2 11
// / | \
// 3 9 4
// | |
// 6 5
// |
// 8
//[7, 10, 2, 11, 3, 9, 4, 6, 5, 8]
class Node {
constructor(key, children) {
this.key = key;
this.children = children || [];
}
}
const nodeMap = {};
const childrenMap = {};
arr.forEach((item) => {
let { node: key, children } = item;
children = children.map((key) => {
childrenMap[key] = 1;
if (nodeMap[key]) {
return nodeMap[key];
}
const child = new Node(key);
nodeMap[key] = child;
return child;
});
if (nodeMap[key]) {
nodeMap[key].children = children;
} else {
const node = new Node(key, children);
nodeMap[key] = node;
}
});
console.log(nodeMap);
let roots = [];
for (const key in nodeMap) {
if (!childrenMap[key]) {
roots.push(nodeMap[key]);
}
}
console.log(roots);
const res = []
while (roots.length) {
var len = roots.length
while (len--) {
const node = roots.shift()
res.push(node.key)
if (node.children.length) {
roots = roots.concat(node.children)
console.log(roots)
}
}
}
console.log(res)
其余算法
出现次数最少的字符
const deleteMinNumStr = (str) => {
const strNumHash = new Map()
let minNum = str.length
let minNumKey = []
let newStr = ''
for (let i = 0; i < str.length; i++) {
strNumHash.set(str[i], strNumHash.get(str[i]) + 1 || 1)
}
for (let [key, value] of strNumHash) {
if (value < minNum) {
minNum = value
minNumKey = [key]
} else if (value === minNum) {
minNumKey.push(key)
}
}
let fast = 0
// 这里可以用replace替换
while (fast < str.length) {
while (minNumKey.includes(str[fast])) {
fast++
}
newStr += str[fast]
fast++
}
return newStr
}
deleteMinNumStr('aafbbbbbaffcccc')
两数之和
var twoSum = function(nums, target) {
var map = {}
for (var i = 0; i < nums.length; i++) {
if (map[target - nums[i]] !== undefined) {
return [map[target - nums[i]], i]
}
map[nums[i]] = i
}
return []
}
最大子序和
输入: nums = [-2,1,-3,4,-1,2,1,-5,4]
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6 。
var maxSubArray = function(nums) {
var max_end_here = nums[0] // 该字段表示,以 i 节点为结束节点的所有子序列的最大和
var max_so_far = nums[0] // 该字段表示目前所有子序列的最大和
for (var i = 1; i < nums.length; i++) {
max_end_here = Math.max(max_end_here + nums[i], nums[i])
max_so_far = Math.max(max_end_here, max_so_far)
}
return max_so_far
};
无重复字符的最长字串
/** 字串问题就使用滑动窗口
* @param {string} s
* @return {number}
*/
var lengthOfLongestSubstring = function(s) {
var window = new Map()
// 构建窗口和字典
// needs = { a: 1, b: 2, c: 3}, 存储有哪些字符和他们出现的次数
for (var i = 0; i < s.length; i++) {
var item = s[i]
if (!window.get(item)) {
window.set(item, 0)
}
}
var len = 0
var left = 0, right = 0, valid = 0
while (right < s.length) {
var c = s[right]
right++
window.set(c, window.get(c) + 1)
while (window.get(c) > 1) {
var d = s[left]
left++
window.set(d, window.get(d) - 1)
}
// 每次都计算最大不重复的长度,当出现重复时,之前不重复的长度已经被记录下来了
len = Math.max(len, right - left)
}
return len
};
最小覆盖字串(还是子串问题,解决方式还是滑动窗口)
/**
* @param {string} s
* @param {string} t
* @return {string}
*/
var minWindow = function(s, t) {
var window = new Map(), needs = new Map()
// 构建窗口和字典
// needs = { a: 1, b: 2, c: 3}, 存储有哪些字符和他们出现的次数
for (var i = 0; i < t.length; i++) {
var item = t[i]
if (!needs.get(item)) {
needs.set(item, 1)
} else {
needs.set(item, needs.get(item) + 1)
}
if (!window.get(item)) {
window.set(item, 0)
}
}
var valid = 0
var len = Number.MAX_VALUE, start = 0 // start和len用于最后拿字串
var left = right = 0
while (right < s.length) {
// c是移入窗口的字符
var c = s[right]
right++
// 进行窗口内数据的更新操作
if (needs.get(c)) {
// 每出现一次t中包含的字符,就将窗口内该字符的出现次数+1
window.set(c, window.get(c) + 1)
if (window.get(c) === needs.get(c)) { // 如果窗口内该字符出现的次数和t中该字符的次数相同,则已经找到的字符数+1
valid++
}
}
while (valid === needs.size) { // 如果已经找到的字符和needs字典中的字符数相等(即needs中包含的字符种类),那么就算完成匹配
if (right - left < len) {
// 存储匹配成功的结果,然后与前一次对比,如果是更短的串则进行替换
start = left
len = right - left
}
var d = s[left]
left++
if (needs.get(d)) {
if (window.get(d) === needs.get(d)) {
valid--
}
window.set(d, window.get(d) - 1)
}
}
}
return len === Number.MAX_VALUE ? '' : s.substr(start, len)
};
斐波那契数列
// 1、迭代方式
function fib (n) {
if (n < 0) throw new Error('输入的数字不能小于0')
if (n < 2) {
return n
}
var list = []
list[0] = 0
list[1] = 1
for (var i = 0; i < n; i++) {
list[i + 1] = list[i] + list[i - 1]
}
return list[i]
}
// 2、记忆化递归
function fibMem (n, mem) {
if (n < 2) {
return n
}
if (mem[n]) return mem[n]
mem[n] = fibMem(n - 1, mem) + fibMem(n - 2, mem)
return mem[n]
}
// 3、求斐波那契前n项和
function Fibonacci (n, acc1, acc2, sum) {
if (n === 0) return sum
return Fibonacci(n - 1, acc2, acc1 + acc2, acc2 + sum)
}
实现一个数组方法,获取数组的最大层级
Array.prototype.getLevel = function () {
let level = 0
let res = 1
const dfs = (arr, level) => {
if (Array.isArray(arr)) {
level++
arr.forEach(item => {
dfs(item, level)
res = Math.max(res, level)
})
} else {
return
}
}
dfs(this, level)
return res
}
var arr = [[1, [2]], [3], [[[5, [6]]]]]
arr.getLevel()
获取父节点id
// 给定一个节点列表,实现一个方法,输入一个childId, 返回它的所有父节点的id
var arr = [
{
id: 1,
child: [{
id: 2,
child: [{
id: 3,
child: null
}]
}]
},
{
id: 5,
child: [{
id: 6,
child: null
}]
}
]
function getParent (arr, childId) {
for (let i = 0; i < arr.length; i++) {
var res = []
const flag = dfs(arr[i], childId, res)
if (flag) {
return res
}
}
return res
}
function dfs (node, childId, res) {
if (node.id === childId) {
return true
}
if (node.child) {
res.push(node.id)
for (let j = 0; j < node.child.length; j++) {
const child = node.child[j]
dfs(child, childId, res)
}
} else {
res.pop()
return false
}
}
console.log('getParent', getParent(arr, 3))
浏览器
理解前端缓存
前端安全问题(xss, csrf)
输入url之后到看到页面发生的事情
浏览器渲染机制
js垃圾回收机制
http/https
https加密过程
http2.0
http的知识也属于常见问题,大厂很喜欢问