1. 模版渲染
实现一个 render(template, context)
方法,将 template
中的占位符用 context
填充。
示例:
var template = "{{name}}很厉害,才{{age}}岁"
var context = {name:"bottle",age:"15"}
输入:template context
输出:bottle很厉害,才15岁
要求:
- 级联的变量也可以展开
- 分隔符与变量之间允许有空白字符
1.1 解答:使用正则 + trim
- 利用非贪婪匹配
/{{(.*?)}}/g
匹配到到所有的{{name}}
,{{age}}
- 利用
str.replace(regexp|substr, newSubStr|function)
,其中第二个参数可以是fucntion (replacement)
,该函数的返回值将替换掉第一个参数匹配到的结果,将所有匹配到的字符替换成指定的字符 - 最后,
String.prototype.trim()
去除分隔符与变量之间空白字符
var template = "{{name}}很厉害,才{{age}}岁"
var context = {name:"bottle",age:"15"}
function render(template, context) {
return template.replace(/{{(.*?)}}/g, (match, key) => context[key.trim()])
}
render(template, context)
// "bottle很厉害,才15岁"
2. Object 的深拷贝
2.1 递归
遍历对象、数组直到里边都是基本数据类型,然后再去复制,就是深拷贝
// 递归判断是否对象和数组
function deepClone(obj, target) {
if(!obj) return
for(let key in obj) {
if(obj.hasOwnProperty(key)) {
if(Array.isArray(key) || (typeof obj[key] === 'object' && obj[key] !== null)) {
target[key] = []
deepClone(obj[key], target[key])
} else {
target[key] = obj[key]
}
}
}
return target
}
3. 手写 async/await 的实现
await 内部实现了 generator,其实 await 就是 generator 加上 Promise的语法糖,且内部实现了自动执行 generator。
/**
* async/await 实现
* @param {*} generatorFunc
*/
function asyncToGenerator(generatorFunc) {
// 返回的是一个新的函数
return function(...args) {
// 先调用generator函数 生成迭代器
// 对应 var gen = testG()
const gen = generatorFunc.apply(this, args)
// 返回一个promise 因为外部是用.then的方式 或者await的方式去使用这个函数的返回值的
// var test = asyncToGenerator(testG)
// test().then(res => console.log(res))
return new Promise((resolve, reject) => {
// 内部定义一个step函数 用来一步一步的跨过yield的阻碍
// key有next和throw两种取值,分别对应了gen的next和throw方法
// arg参数则是用来把promise resolve出来的值交给下一个yield
function step(key, arg) {
let genResult
// 这个方法需要包裹在try catch中
// 如果报错了 就把promise给reject掉 外部通过.catch可以获取到错误
try {
genResult = gen[key](arg)
} catch (error) {
return reject(error)
}
// gen.next() 得到的结果是一个 { value, done } 的结构
const { value, done } = genResult
if (done) {
// 如果已经完成了 就直接resolve这个promise
// 这个done是在最后一次调用next后才会为true
// 以本文的例子来说 此时的结果是 { done: true, value: 'success' }
// 这个value也就是generator函数最后的返回值
return resolve(value)
} else {
// 除了最后结束的时候外,每次调用gen.next()
// 其实是返回 { value: Promise, done: false } 的结构,
// 这里要注意的是Promise.resolve可以接受一个promise为参数
// 并且这个promise参数被resolve的时候,这个then才会被调用
return Promise.resolve(
// 这个value对应的是yield后面的promise
value
).then(
// value这个promise被resove的时候,就会执行next
// 并且只要done不是true的时候 就会递归的往下解开promise
// 对应gen.next().value.then(value => {
// gen.next(value).value.then(value2 => {
// gen.next()
//
// // 此时done为true了 整个promise被resolve了
// // 最外部的test().then(res => console.log(res))的then就开始执行了
// })
// })
function onResolve(val) {
step("next", val)
},
// 如果promise被reject了 就再次进入step函数
// 不同的是,这次的try catch中调用的是gen.throw(err)
// 那么自然就被catch到 然后把promise给reject掉啦
function onReject(err) {
step("throw", err)
},
)
}
}
step("next")
})
}
}
var getData = () => new Promise(resolve => setTimeout(() => resolve('data'), 1000));
function* testG() {
const data = yield getData();
console.log('data: ', data);
const data2 = yield getData();
console.log('data2: ', data2);
return 'success';
}
var gen = asyncToGenerator(testG);
gen().then(res => console.log(res));
4. 用最简洁代码实现 indexOf 方法
4.1 indexOf
有两种:
String.prototype.indexOf()
返回从 fromIndex
处开始搜索第一次出现的指定值的索引,如果未找到,返回 -1
str.indexOf(searchValue [, fromIndex])
// fromIndex 默认值为 0
Array.prototype.indexOf()
返回在数组中可以找到一个给定元素的第一个索引,如果不存在,则返回 -1
arr.indexOf(searchElement[, fromIndex])
4.2 String.prototype.indexOf()
解题思路:正则,字符串匹配
function sIndexOf(str, searchStr, fromIndex = 0){
var regex = new RegExp(`${searchStr}`, 'ig')
regex.lastIndex = fromIndex
var result = regex.exec(str)
return result ? result.index : -1
}
// 测试
var paragraph = 'The quick brown fox jumps over the lazy dog. If the dog barked, was it really lazy?'
var searchTerm = 'dog'
// 测试一:不设置 fromIndex
console.log(sIndexOf(paragraph, searchTerm))
// 40
console.log(paragraph.indexOf(searchTerm));
// 40
// 测试二:设置 fromIndex
console.log(sIndexOf(paragraph, searchTerm, 41))
// 52
console.log(paragraph.indexOf(searchTerm, 41));
// 52
4.3 Array.prototype.indexOf()
解题思路:遍历匹配
function aIndexOf(arr, elem, fromIndex = 0){
if(!elem) return -1
for(let i = fromIndex; i < arr.length; i++) {
if(arr[i] === elem) return i
}
return -1
}
// 测试
var beasts = ['ant', 'bison', 'camel', 'duck', 'bison']
// 测试一:不设置 fromIndex
console.log(aIndexOf(beasts, 'bison'))
// 1
console.log(beasts.indexOf('bison'))
// 1
// 测试二:设置 fromIndex
console.log(aIndexOf(beasts, 'bison', 2))
// 4
console.log(beasts.indexOf('bison', 2))
4.4 总结indexOf
:
function indexOf(items, item, fromIndex = 0) {
let isArray = Array.isArray(items);
let isString = Object.prototype.toString.call(items) == '[object String]';
if (!isArray && !isString) throw new SyntaxError();
if(isArray) return sIndexOf(items, item, fromIndex)
else return aIndexOf(items, item, fromIndex)
}
5. 实现一个方法,拆解URL参数中queryString
入参格式参考:
const url = 'http://sample.com/?a=1&b=2&c=xx&d=#hash';
出参格式参考:
const result = { a: '1', b: '2', c: 'xx', d: '' };
5.1 正则匹配:
const url = 'http://sample.com/?a=1&b=2&c=xx&d=2#hash';
const queryString = (str)=>{
const obj = {}
str.replace(/([^?&]+)=([^&#]*)/g, (_, k, v)=>{
obj[k] = v
})
return obj
}
queryString(url)
//{a: '1', b: '2', c: 'xx', d: '2'}
5.2
function getParams(u: URL) {
const s = new URLSearchParams(u.search)
const obj = {}
s.forEach((v, k) => (obj[k] = v))
return obj
}
const url = 'http://sample.com/?a=1&b=2&c=xx&d=2#hash';
getParams(new URL(url))
//{a: '1', b: '2', c: 'xx', d: '2'}
5.3 万能reduce
const getQuery = (queryStr) => {
//const [, query] = queryStr.split('?')
// 去除hash标识
const [query,] = queryStr.split('?')[1].split('#')
if (query) {
return query.split('&').reduce((pre, cur) => {
const [key, val] = cur.split('=')
pre[key] = decodeURIComponent(val)
return pre
}, {})
}
return {}
}
const url = 'http://sample.com/?a=1&b=2&c=xx&d=2#hash';
getQuery(url)
//{a: '1', b: '2', c: 'xx', d: '#hash'}
// 去除hash标识结果
//{a: '1', b: '2', c: 'xx', d: '2'}
6. 按照以下要求,实现 createFlow 函数
6.1
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const subFlow = createFlow([() => delay(1000).then(() => console.log("c"))]);
createFlow([
() => console.log("a"),
() => console.log("b"),
subFlow,
[() => delay(1000).then(() => console.log("d")), () => console.log("e")],
]).run(() => {
console.log("done");
});
// 需要按照 a,b,延迟1秒,c,延迟1秒,d,e, done 的顺序打印
按照上面的测试用例,实现 createFlow
:
flow
是指一系列effects
组成的逻辑片段。flow
支持嵌套。effects
的执行只需要支持串行。
6.2 解题步骤:
createFlow
以一个数组作为参数,数组参数可有以下几种类型:
- 普通函数
() => console.log("a")
- 异步函数
() => delay(1000).then(() => console.log("c"))
- 嵌套
createFlow
const subFlow = createFlow([() => delay(1000).then(() => console.log("c"))])
- 数组
[() => delay(1000).then(() => console.log("d")), () => console.log("e")]
步骤一:扁平化
因为 effects
的执行只需要支持串行,所以我们可以把数组扁平化一下
function createFlow(effects = []) {
// 浅拷贝一下,今年不影响传入的参数
const queue = [...effects.flat()]
}
步骤二:run执行
createFlow
并不是直接执行,而是 .run()
之后才会开始执行
function createFlow(effects = []) {
const queue = [...effects.flat()]
const run = async function(cb) {
for(let task of queue) {
}
if(cb) cb()
}
}
步骤三:异步函数
因为参数中有异步函数,这里使用 await
执行
function createFlow(effects = []) {
const queue = [...effects.flat()]
const run = async function(cb) {
for(let task of queue) {
await task()
}
if(cb) cb()
}
}
步骤四:支持嵌套
使用 isFlow
来判断当前是否是嵌套执行
function createFlow(effects = []) {
const queue = [...effects.flat()]
const run = async function(cb) {
for(let task of queue) {
if(task.isFlow) { // 如果是嵌套,执行嵌套函数
await task.run()
} else {
await task()
}
}
if(cb) cb()
}
return {
run,
isFlow: true
}
}
6.3 完整代码:两种
- function
function createFlow(effects = []) {
const queue = [...effects.flat()]
const run = async function(cb) {
for(let task of queue) {
if(task.isFlow) {
await task.run()
} else {
await task()
}
}
if(cb) cb()
}
return {
run,
isFlow: true
}
}
// 测试
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const subFlow = createFlow([() => delay(1000).then(() => console.log("c"))]);
createFlow([
() => console.log("a"),
() => console.log("b"),
subFlow,
[() => delay(1000).then(() => console.log("d")), () => console.log("e")],
]).run(() => {
console.log("done");
});
// a,b,延迟1秒,c,延迟1秒,d,e, done 的顺序打印
- class
class Flow {
constructor(effects) {
this.queue = [...effects.flat()]
}
async run(cb) {
for(let task of this.queue) {
if(task instanceof Flow) {
await task.run()
} else {
await task()
}
}
if(cb) cb()
}
};
function createFlow(effects = []) {
return new Flow(effects)
};
// 测试
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const subFlow = createFlow([() => delay(1000).then(() => console.log("c"))]);
createFlow([
() => console.log("a"),
() => console.log("b"),
subFlow,
[() => delay(1000).then(() => console.log("d")), () => console.log("e")],
]).run(() => {
console.log("done");
});
// a,b,延迟1秒,c,延迟1秒,d,e, done 的顺序打印
7. 如何判断两个变量相等
7.1 使用 API: Object.is()
方法判断两个值是否为同一个值
Object.is(x, y)
Polyfill:
if (!Object.is) {
Object.is = function(x, y) {
// SameValue algorithm
if (x === y) { // Steps 1-5, 7-10
// Steps 6.b-6.e: +0 != -0
return x !== 0 || 1 / x === 1 / y;
} else {
// Step 6.a: NaN == NaN
return x !== x && y !== y;
}
};
}
7.2 扩展:
JavaScript提供三种不同的值比较操作:
其中:
- ===:进行相同的比较,不进行类型转换 (如果类型不同, 只是总会返回 false )
- ==:执行类型转换,比较两个值是否相等
Object.is
:与 === 相同,但是对于NaN
和-0
和+0
进行特殊处理,Object.is(NaN, NaN)
为true
,Object.is(+0, -0)
为false
8. 找出字符串中连续出现最多的字符和个数
8.1 new Map
求解:
const mostChars = function(str) {
if (!str) return '';
let stack = [];
let map = new Map();
str.split('').forEach(c => {
let last = stack.pop();
if(last === c) {
map.set(c, map.get(c) + 1);
stack.push(c);
} else {
stack.push(c);
map.set(c, 1);
}
})
let max = 0;
[...map.values()].forEach(n => {
if (max < n) max = n;
})
for (let key of map.keys()) {
if (map.get(key) < max) {
//删除非连续出现次数最多的字符
map.delete(key);
}
}
// console.log(Object.fromEntries(map));
return Object.fromEntries(map);
}
8.2 普通求解:
function fn(str) {
if (!str) return {}
//vals存储当前满足的字符,max是数量,prev是上个字符,prevNum是上个字符的数量
let vals = [], max = 1
let prev = '', prevNum = 1
for (const le of str) {
if (le === prev) {
prevNum++
if (prevNum > max) {
vals = [prev]
max = prevNum
} else if(prevNum === max){
vals.push(prev)
}
} else {
prev = le
prevNum = 1
}
}
const res = vals.map(val => ({ [val]: max }))
return res
// assign数组转对象
// return Object.assign({},...res)
}
9. 找出一个字符串中的不匹配括号的位置,以json形式输出,位置index从0开始
9.1 解答:利用栈结构
解题思路: 将字符串中的字符依次入栈,遍历字符依次判断:
-
首先判断该元素是否括号,不是则遍历下一个字符
-
是
{
、(
、[
,直接入栈 -
否则该字符为
}
、)
、]
中的一种,- 如果栈为空,则当前右括号无匹配左括号,直接写进结果数组,并遍历下一个字符
- 栈顶元素出栈,判断当前元素是否与出栈元素匹配,例如栈中元素有
({
, 如果继续遍历到的元素为)
, 那么当前元素序列为({)
是不可能有效的,所以此时与出栈元素匹配失败,将出栈元素写进结果数组,并继续匹配当前元素
当遍历完成时,所有已匹配的字符都已匹配出栈,如果栈不为空,说明字符串中还有未匹配的左括号字符,则将栈元素直接写进结果数组
代码实现:
let getUnmatchJson = function(s) {
let map = {
'{': '}',
'(': ')',
'[': ']'
}
let stack = [],
brackets = '{[()]}',
result = {}
for(let i = 0; i < s.length ; i++) {
// 如果不是括号,跳过
if(brackets.indexOf(s[i]) === -1) continue
// 如果是左括号,则进栈
if(map[s[i]]) {
stack.push({
char: s[i],
index: i
})
} else {
// 如果是右括号,则出栈匹配
if(!stack.length) {
//如果栈为 null ,则表示没有匹配的左括号,则当前有括号直接进结果数组
result[i] = s[i]
continue
}
// 出栈
let temp = stack.pop()
// 括号不匹配
if (s[i] !== map[temp.char]) {
// 不匹配左括号进结果数组,并i--,继续匹配当前字符
result[temp.index] = temp.char
i --
}
}
}
// 如果匹配结束,依然有剩余的左括号,则直接进结果数组
while(stack.length) {
let temp = stack.pop()
result[temp.index] = temp.char
}
return result
};
let s1 = '${{(3+5)*2+(5/(24)}'
let s2 = '[a+b]/${x}'
let s3 = '${(3+5)*2+(5/(24)}(}'
console.log(getUnmatchJson(s1)) // {1: "{", 11: "("}
console.log(getUnmatchJson(s2)) // {}
console.log(getUnmatchJson(s3)) // {10: "(", 18: "(", 19: "}"}
时间复杂度:O(n)
空间复杂度:O(n)
9.2 解答:正则
/**
* 力扣20 是字符串匹配判断
* 输入:${{(3+5)*2+(5/(24)}
* 输出:
* {
* 1: '{',
* 11: '(',
* }
*/
let str = '${{(3+5)*2+(5/(24)}'
const getUnmatchJson = function (s) {
let stack = []
const res = {}
let map = {
'{': '}',
'(': ')',
'[': ']'
}
let reg = /[^{[()]}]/
for (let i = 0, len = s.length; i < len; i++) {
if (reg.test(s[i])) continue
if(map[s[i]]) {
stack.push({
char:s[i],
index:i
})
} else {
//右括号
if(stack.length===0) {
//没有匹配的左括号
res[i] = s[i]
continue
}
let temp = stack.pop()
if(s[i]!==map[temp.char]) {
res[temp.index] = temp.char
i--
}
}
}
//字符串匹配结束
while(stack.length) {
let temp = stack.pop()
res[temp.index] = temp.char
}
return res
}
let str2 = '[a+b]/${dg'
console.log(getUnmatchJson(str))
console.log(getUnmatchJson(str2))
console.log(/[^{[()]}]/.test(str[1]))
10. 字符串相加
10.1
function add(str1, str2) {
let result = ''
let tempVal = 0
let arr1 = str1.split('')
let arr2 = str2.split('')
while (arr1.length || arr2.length || tempVal) {
tempVal += ~~arr1.pop() + ~~arr2.pop()
result = tempVal % 10 + result
tempVal = ~~(tempVal / 10)
}
return result.replace(/^0+/, '')
}
10.2
1、新建立双指针 i 、j,倒序循环两个数组
2、判断两数相加是否大于10,大于则进1,缓存 (result % 10)
3、循环结束判断move是否有值,有则加上,没有直接return
空间复杂度: 循环num1, num2,则空间复杂度O(n)
空间复杂度: result缓存结果,常数级复杂度O(1)
var addStrings = function(num1, num2) {
var i = num1.length - 1,
j = num2.length - 1,
move = 0,
result = '';
while (i >= 0 || j >= 0) {
n1 = i >= 0 ? num1[i] : 0;
n2 = j >= 0 ? num2[j] : 0;
var tmp = +n1 + +n2 + move;
move = tmp >= 10 ? 1 : 0;
result = (tmp % 10) + result;
i--;
j--
}
return move ? move + result : result;
};