Skip to content

面试记录

美团

1. 一面实现一个string的indexOf方法

javascript
// 自己思路的实现
function indexOf(a, b){
	let result = -1, flag = false, count = 0
	for (let i = 0; i < a.length; i++) {
		if (flag) {
			if (count === b.length) return i - count
			if (a[i] === b[count]) {
				count++
			} else {
				flag = false
				i = i - count + 1
				count = 0
			}
		} else {
			if (a[i] === b[count]) {
				flag = true
				count++
			} else {
				flag = false
				count = 0
			}
		}
	}
	return result
}

console.log(indexOf('0121abv1', 'abv') === '0121abv1'.indexOf('abv'))

String.prototype.myIndexOf = function(str){
	var sourceArr = this.split('')
	var num = -1
	for (var i in sourceArr) {
		if (sourceArr[i] === str.slice(0, 1)) {
			if (str === this.slice(i, Number(i) + str.length)) {
				num = i
			}
		}
	}
	return num
}

promise的发布订阅

题目

js
class PubSub {
  constructor() {
    // TODO
  }
  on (event, fn)  {
  }

  onOnce (event)  {
  }
  emit (event, data) {
  }
}

const pubsub = new PubSub()
// 订阅
pubsub.on('event', (data) => {
  console.log('event:' + JSON.stringify(data));
})
// 发布
pubsub.emit('event', { a: 1 });

// 异步订阅
const result = await pubsub.onOnce('event');

最重要的卡点就是onOnce是一个异步订阅,订阅之后要等该事件都执行完成才继续执行

js
class PubSub {
  constructor() {
    this.cache = {}
  }
  on (event, fn)  {
    if (!this.cache[event]) {
      this.cache[event] = []
    }
    this.cache[event].push(fn);
  }

  onOnce (event)  {
    return new Promise((resolve,reject) => {
      this.on(event, () => resolve())
    })
  }
  emit (event, data) {
    const queue = this.cache[event];
    if (Array.isArray(queue)) {
      queue.forEach((task) => task(data))
    }
  }
}

const pubsub = new PubSub()
// 订阅
pubsub.on('event', (data) => {
  console.log('event:' + JSON.stringify(data));
})
// 发布
pubsub.emit('event', { a: 1 });

// 异步订阅
const result = await pubsub.onOnce('event');

熙牛医疗笔试题

1、树形结构数据打印

我的思路:利于对象引用关系

javascript
const data = [
	{ id: 1001, parentId: 0, name: 'AA' },
	{ id: 1002, parentId: 1001, name: 'BB' },
	{ id: 1003, parentId: 1001, name: 'CC' },
	{ id: 1004, parentId: 1003, name: 'DD' },
	{ id: 1005, parentId: 1003, name: 'EE' },
	{ id: 1006, parentId: 1002, name: 'FF' },
	{ id: 1007, parentId: 1002, name: 'GG' },
	{ id: 1008, parentId: 1004, name: 'HH' },
	{ id: 1009, parentId: 1005, name: 'II' },
	{ id: 1011, parentId: 101, name: '444' },
	{ id: 10108, parentId: 10109, name: 'tt' },
	{ id: 10109, parentId: 10015, name: 'JJ' },
	{ id: 101, parentId: 10108, name: '333' },
];

function buildTree (data) {
	const result = []
	const dataMap = new Map()
	data.forEach(item => dataMap.set(item.id, item))
	data.forEach(item => {
		const { parentId } = item
		const parentNode = dataMap.get(parentId)
		if (parentNode) {
			parentNode.Children = parentNode.Children || []
			parentNode.Children.push(item)
		} else {
			result.push(item)
		}
	})
	return result
}

const print = (arr,space = 0) =>  {
	arr.forEach(node => {
		console.log('  '.repeat(space) + node.name + ' \n ')
		if (node.Children) {
			print(node.Children, space + 2)
		}
	})
}
print(buildTree(data), 0)

2、React Input 表单组件

类似于element的input组件,props可设置默认值,maxLength, 有show-word-limit的效果,组件支持受控和非受控模式

typescript
import React, {useState, useEffect} from 'react'

interface Props {
	value?: string,
	maxLength?: number
}

export default function App({value, maxLength}: Props) {
	const [state, setState] = useState('')
	useEffect(() => {
		value && setState(value)
	}, [value])
	function updateValue(inputValue: string) {
		if (maxLength !== undefined && inputValue.length > maxLength) return
		else setState(inputValue)
	}
	return (
		<div className='input-item'>
			<input value={state} onInput={(e => updateValue(e.currentTarget.value))} style={{ paddingRight: maxLength !== undefined ? '36px' : '10px'}}/>
			{ maxLength !== undefined &&
				<span>{ state.length + '/' + maxLength}</span>
			}
		</div>
	)
}
css
.input-item {
  position: relative;
  display: inline-block;
}
.input-item input {
  color: rgba(0,0,0,.85);
  font-size: 14px;
  padding: 4px 10px;
  border: 1px solid #d9d9d9;
  border-radius: 3px;
}
.input-item input:hover {
  border-color: #40a9ff;
}
.input-item input:focus {
  border-color: #40a9ff;
  outline: none;
  box-shadow: 0 0 0 2px rgba(24,144,255,0.2);
}
.input-item span {
  font-size: 12px;
  color: rgba(0,0,0,.45);
  position: absolute;
  right: 5px;
  top: 50%;
  transform: translateY(-50%);
 }
jsx
import React, {
    Component
} from 'react';


class App extends Component {
    static propTypes = {
        value: React.PropTypes.any
    }

    constructor(props, context) {
        super(props, context);

        this.state = {
            value: this.props.defaultValue || ''
        };

        this.changeHandler = this.changeHandler.bind(this);
    }

    get displayValue() {
        const key = 'value';
        const internalKey = 'value';

        return this.props[key] !== undefined ? this.props[key] : this.state[internalKey];
    }

    handleChange(newVal) {
        if (newVal === this.state.value) {
            return;
        }

        this.setState({
            value: newVal
        }, () => {
            this.props.onChange && this.props.onChange(newVal);
        });
    }

    componentWillReceiveProps(nextProps) {
        const controlledValue = nextProps.defaultValue;

        if (controlledValue !== undefined && controlledValue !== this.state.value) {
            this.setState({
                value: controlledValue
            }, () => {
                this.props.onChange && this.props.onChange(controlledValue);
            });
        }
    }

    shouldComponentUpdate(nextProps, nextState) {
        if (nextProps.value !== undefined) {
            // controlled, use `props.value`
            return nextProps.value !== this.props.value;
        }

        // uncontrolled, use `state.value`
        return nextState.value !== this.state.value;
    }

    mapPropToState(controlledValue) {
        // your can do some transformations from `props.value` to `state.value`
    }

    changeHandler(e) {
        const val = e.target.value;

        this.handleChange(val);
    }

    render() {
        return ( 
            <input 
                value = {this.displayValue} 
                onChange={this.changeHandler}
            />
        );
    }
}

export default App;

3、JSON Key 驼峰转换

typescript
function convert(jsonObj: object | object[]) {
	// 请实现
	if (Array.isArray(jsonObj)) {
		jsonObj.forEach(item => {
			convert(item)
		})
	} else {
		const keys = Object.keys(jsonObj)
		keys.forEach(key => {
			const newKey = convertKey(key)
			jsonObj[newKey] = jsonObj[key]
			delete jsonObj[key]
			if (jsonObj[newKey] && typeof jsonObj[newKey] === 'object') {
				convert(jsonObj[newKey])
			}
		})
	}
	return jsonObj
}

function convertKey(str: string): string {
	let result: string = ''
	for (let i = 0; i < str.length; i++) {
		if (str[i] === '_' && str[i+1]) {
			result = result + str[++i].toUpperCase()
		} else {
			result = result + str[i]
		}
	}
	return result
}


// ------ 测试 ---------
console.log(convert({ 'a_bc_def': 1 }));
console.log(convert({ 'a_bc_def': { 'foo_bar': 2 } }));
console.log(convert({ 'a_bc_def': [{ 'foo_bar': 2 }, { 'goo_xyz': 3 }] }));

因数分解(神策数据)

例子:24分解质因数为2*2*2*3,简写成(2^3) * (3^1)。

javascript
// 判断是否为质数的方法(质数是一个大于1的自然数,除了1和它自身外,不能被其他自然数整除的数叫做质数。)
function isPrime(n) {
	for (let i = 2; i <= Math.sqrt(n); i++) {
		if (n % i === 0) {
			return false
		}
	}
	return true
}

function PrimeFactorizer(n) {
	//用来存储结果
	const result = []
	while (n > 1) {
		//从最小的质数开始除
		for (let i = 2; i <= n; i++) {
			if (isPrime(i) && n % i === 0) {
				result.push(i)
				//除掉这个最小的质数因子
				n /= i
			}
			
		}
	}
	//给实例上加个factor属性
	return result
	
}

console.log(PrimeFactorizer(24))

promise并发控制

实现一个批量请求函数 multiRequest(urls, maxNum),要求如下:

  • 要求最大并发数 maxNum
  • 每当有一个请求返回,就留下一个空位,可以增加新的请求
  • 所有请求完成后,结果按照 urls 里面的顺序依次打出
javascript
function multiRequest(urls = [], maxNum){
	// 请求总数量
	const len = urls.length
	// 根据请求数量创建一个数组来保存请求的结果
	const result = new Array(len).fill(false)
	// 当前完成的数量
	let count = 0

	return new Promise((resolve, reject) => {
		// 请求maxNum个
		while (count < maxNum) {
			next()
		}

		function next(){
			let current = count++
			// 处理边界条件
			if (current >= len) {
				// 请求全部完成就将promise置为成功状态, 然后将result作为promise值返回
				!result.includes(false) && resolve(result)
				return
			}
			const url = urls[current]
			console.log(`开始 ${current}`, new Date().toLocaleString())
			fetch(url)
				.then((res) => {
					// 保存请求结果
					result[current] = res
					console.log(`完成 ${current}`, new Date().toLocaleString())
					// 请求没有全部完成, 就递归
					if (current < len) {
						next()
					}
				})
				.catch((err) => {
					console.log(`结束 ${current}`, new Date().toLocaleString())
					result[current] = err
					// 请求没有全部完成, 就递归
					if (current < len) {
						next()
					}
				})
		}
	})
}

实现一个带并发限制的异步调度器Scheduler,保证同时运行的任务最多 n 个。

js
class Scheduler {
    constructor(count) {
        this.count = count
    }
    add(task) {}
    schedule() {}
}

// Usage
const timeout = (time) => new Promise(resolve => {
  setTimeout(resolve, time)
})
const scheduler = new Scheduler(2)
const addTask = (time, order) => {
  scheduler.add(() => timeout(time))
    .then(() => console.log(order))
}

addTask(1000, '1')
addTask(500, '2')
addTask(300, '3')
addTask(400, '4')
// output: 2 3 1 4
// 500ms时,2完成,输出2,任务3进队
// 800ms时,3完成,输出3,任务4进队
// 1000ms时,1完成,输出1
// 1200ms时,4完成,输出4
javascript
class Scheduler {
	constructor(count) {
		this.count = count
		this.runningCount = 0
		this.pendingTask= []
	}
	add(task) {
		return new Promise((resolve, reject) => {
			if (this.runningCount < this.count) {
				this.runningCount++
				task()
					.then(res => {
						resolve(res)
						this.runningCount--
						this.schedule()
					})
			} else {
				this.pendingTask.push({
					task,
					resolve,
					reject
				})
			}
		})
	}
	schedule() {
		if(this.pendingTask.length === 0) return
		const obj = this.pendingTask.shift()
		obj.task()
			.then(res => {
				obj.resolve(res)
				this.schedule()
			})
	}
}

// Usage
const timeout = (time) => new Promise(resolve => {
	setTimeout(resolve, time)
})
const scheduler = new Scheduler(2)
const addTask = (time, order) => {
	scheduler.add(() => timeout(time))
		.then(() => console.log(order))
}

addTask(1000, '1')
addTask(500, '2')
addTask(300, '3')
addTask(400, '4')

promise串行控制

页面上有三个按钮,分别为 A、B、C,点击各个按钮都会发送异步请求且互不影响,每次请求回来的数据都为按钮的名字。 请实现当用户依次点击 A、B、C、A、C、B 的时候,最终获取的数据为 ABCACB。

这道题目主要两个考点:

  1. 请求不能阻塞,但是输出可以阻塞。比如说 B 请求需要耗时 3 秒,其他请求耗时 1 秒,那么当用户点击 BAC 时,三个请求都应该发起,但是因为 B 请求回来的慢,所以得等着输出结果。
  2. 如何实现一个队列? 我的实现
javascript
const delay = (params) => {
	const time = Math.floor(Math.random() * 5)
	
	return new Promise((resolve => {
		setTimeout(() => {
			resolve(params)
		}, time * 500)
	}))
}

class Queue {
	constructor() {
		this.stack = []
	}
	execute (p) {
		return new Promise((resolve => {
			this.stack.push({
				resolve
			})
			p.then(res => {
				const index = this.stack.findIndex(v => v.resolve === resolve)
				this.stack[index].res = res
				for(let i = 0; i < this.stack.length; i++) {
					if(this.stack[i].res) {
						this.stack[i].resolve(this.stack[i].res)
					} else {
						break
					}
				}
			})
		}))
	}
}

const queue = new Queue()

const handleClick = async (name) => {
	const res = await queue.execute(delay(name))
	console.log(res)
}

handleClick('A');
handleClick('B');
handleClick('C');
handleClick('A');
handleClick('C');
handleClick('B');

其实队列可以直接借助promise.then实现

javascript
class Queue {
	
	constructor() {
		this.promise = Promise.resolve();
	}
	execute(promise) {
		this.promise = this.promise.then(() => promise);
		return this.promise;
	}
}

红绿灯问题

题目:红灯三秒亮一次,绿灯一秒亮一次,黄灯2秒亮一次;如何让三个灯不断交替重复亮灯?(用 Promise 实现)

三个亮灯函数已经存在:

javascript
function red(){
    console.log('red');
}
function green(){
    console.log('green');
}
function yellow(){
    console.log('yellow');
}
点击查看代码
javascript
function createTask(cb, time) {
    return new Promise((resolve) => {
        setTimeout(() => {
            cb()
            resolve()
        }, time)
    })
}

function loopTask() {
    return createTask(red, 3000)
        .then(() => createTask(green, 1000))
        .then(() => createTask(yellow, 2000))
        .then(() => loopTask())
}

In case I don't see you. Good afternoon, good evening, and good night.