实现下面这道题中的machine 函数功能
function machine(){}
machine('ygy').execute() // 输出 start ygy
machine('ygy').do('eat').execute // 输出 start ygy ygy eat
machine('ygy').wait(5).do('eat').execute() // start ygy (wait 5s等了5s) ygy eat
machine('ygy').waitFirst(5).do('eat').execute() // wait 5s start ygy ygy eat
答案
function machine(name){
return new Action(name)
}
const defer = (time, callback) => {
return new Promise((resolve)=>{
setTimeout(()=>{
resolve(callback())
}, time * 1000)
})
}
class QueueItem {
constructor(defer, callback){
this.defer = defer
this.callback = callback
}
}
class Action{
queue = []
constructor(name){
this.name = name
this.queue.push(new QueueItem(0, ()=> console.log(`start ${this.name}`)))
}
do(eat){
this.queue.push(new QueueItem(0, () => console.log(`${this.name} ${eat}`) ))
return this
}
wait(time){
this.queue.push(new QueueItem( time, ()=> console.log(`wait ${time}s`) ))
return this
}
waitFirst(time){
this.queue.unshift(new QueueItem( time, () => console.log( `wait ${time}s` )))
return this
}
async execute(){
while(this.queue.length > 0){
const curItem = this.queue.shift()
if(!curItem.defer){
curItem.callback()
continue
}
await defer(curItem.defer, curItem.callback)
}
}
}
function machine (name) {
class _machine {
constructor () {
this.name = name
this.eventArr = [
[this.__machine]
]
this.priorityEventArr = []
}
__machine () {
console.log(`start ${this.name}`)
}
_do (what) {
console.log(`${this.name} ${what}`)
}
do (what) {
this.eventArr.push([this._do, what])
return this
}
_wait (num) {
console.log(`wait ${num}s`)
return new Promise(resolve => {
setTimeout(() => resolve(true), num * 1000)
})
}
wait (num) {
this.eventArr.push([this._wait, num])
return this
}
waitFirst (num) {
this.priorityEventArr.push([this._wait, num])
return this
}
async execute () {
for (let item of this.priorityEventArr) {
await item[0].apply(this, [item[1]])
}
for (let item of this.eventArr) {
await item[0].apply(this, [item[1]])
}
}
}
return new _machine()
}
machine('richole').do('get up').wait(3).waitFirst(2).do('eat').do('program').execute()
二道机试题
1、提取url中search部分参数,www.taobao.com?a=1&b=2
2、2个正整数字符串的相加,即'1'+'19'——>’20’(考虑超长字符串相加)
3、
- 写一个类Person,拥有属性age和name,拥有方法say(something)
- 再写一个类Superman,继承Person,拥有自己的属性power,拥有自己的方法fly(height)
全url 解析 含decodeURIComponent & 多个数据转为数组 & 不带值的情况
let url = 'http://www.baidu.com/?user=XXX&id=123&id=456&city=%E5%8C%97%E4%BA%AC&enabled'
function parseQuery(url){
let o = {}
let queryString = url.split('?')[1]
if(queryString){
queryString.split('&').forEach((item)=>{
let [key, val] = item.split('=')
val = val ? decodeURIComponent(val) : undefined
if(o.hasOwnProperty(key)){
o[key] = [].concat(o[key], val)
}else{
o[key] = val
}
})
}
return o
}
2、 js 中采用64位浮点数格式表示,Number范围为正负2的53次方,即从最小值-9007199254740992到最大值+9007199254740992之间的范围。
- 第0位:符号位, s 表示 ,0表示正数,1表示负数;
- 第1位到第11位:储存指数部分, e 表示 ;
- 第12位到第63位:储存小数部分(即有效数字),f 表示,
浮点预算精度问题,最便捷的方式就是用parseFloat((num).toFixed(10)) // 10 长度依照情况处理。
工作中对超大数组使用 big-integer 进行处理。
面试(字符串数字相加)思路:
- 对num1与num2 进行切割成数组
- 对数组取长度长的那个进行循环相加得到第三个数组,即可。
let a = '98523134141232331231321323232131457896449000992874903879852313414123233123132132323213145789644'
let b = '80009928749038798523134141232331231321323232131457896449000992874903879852313414123233123132132323213145789644'
function str2Arr(val) {
if (val) {
val = val.toString()
return val
.split('')
.map(item => {
return parseInt(item)
})
.reverse()
}
return val
}
function strPlus(num1, num2) {
let arrA = str2Arr(num1)
let arrB = str2Arr(num2)
let maxLen = Math.max(arrA.length, arrB.length)
let arrC = []
for (let i = 0; i < maxLen; i++) {
let before = arrC[i + 1] ? arrC[i + 1] : 0
arrA[i] = arrA[i] ? arrA[i] : 0
arrB[i] = arrB[i] ? arrB[i] : 0
let currVal = arrA[i] + arrB[i] + before
arrC[i] = currVal % 10
arrC[i + 1] = currVal >= 10 ? parseInt(arrA[i] + arrB[i] + before / 10) : 0
}
return arrC.reverse().join('')
}
数组全排列
数状递归
function arrList(arr){
let result = []
let used = {}
function dfs(path){
if(path.length === arr.length){
result.push(path.slice())
return
}
for(let num in arr){
if(used[num]) continue
path.push(num)
used[num]= true
dfs(path)
path.pop()
used[num]=false
}
}
dfs([])
return result
}
利用reduce 实现累加器。
reduce方法接收两个参数,回调函数reducer,和初始值。reducer函数接收四个参数: Accumulator:MDN上解释为累计器,但我觉得不恰当,按我的理解它应该是截至当前元素,之前所有的数组元素被reducer函数处理累计的结果 Current:当前被执行的数组元素 CurrentIndex: 当前被执行的数组元素索引 SourceArray:原数组,也就是调用reduce方法的数组 如果传入第二个参数,reduce方法会在这个参数的基础上开始累计执行。
const str = 'adefrfdnnfhdueassjfkdiskcddfjds'
const arr = str.split('')
const strObj = arr.reduce((all, current) => {
if (current in all) {
all[current]++
} else {
all[current] = 1
}
return all
}, {})
通过上面的用法,可以总结出来reduce的特点: 接收两个参数: 第一个为函数,函数内会接收四个参数:Accumulator Current CurrentIndex SourceArray。 第二个参数为初始值。 返回值为一个所有Accumulator累计执行的结果
Array.prototype.myReduce = function(fn, base) {
if (this.length === 0 && !base) {
throw new Error('Reduce of empty array with no initial value')
}
for (let i = 0; i < this.length; i++) {
if (!base) {
base = fn(this[i], this[i + 1], i, this)
i++
} else {
base = fn(base, this[i], i, this)
}
}
return base
}
js replace 变量替换
var str = '哈哈哈:${name} ,将该字符串name替换';
let _reg = /\$\{(.*?)\}/g;
str.replace(_reg, function(){
let _argument = arguments; //
if (_argument && _argument[1]) {
return _this.showOneProtol[_argument[1]]; // 替换的值
}
})
获得函数中的参数
// 获取argument对象 类数组对象 不能调用数组方法
function test1() {
console.log('获取argument对象 类数组对象 不能调用数组方法', arguments);
}
// 获取参数数组 可以调用数组方法
function test2(...args) {
console.log('获取参数数组 可以调用数组方法', args);
}
// 获取除第一个参数的剩余参数数组
function test3(first, ...args) {
console.log('获取argument对象 类数组对象 不能调用数组方法', args);
}
// 透传参数
function test4(first, ...args) {
fn(...args); //
fn(...arguments);
}
function fn() {
console.log('透传', ...arguments);
}
test1(1, 2, 3);
test2(1, 2, 3);
test3(1, 2, 3);
test4(1, 2, 3);
结果:
获取argument对象 类数组对象 不能调用数组方法 Arguments(3) [1, 2, 3, callee: ƒ, Symbol(Symbol.iterator): ƒ]
VM178:7 获取参数数组 可以调用数组方法 (3) [1, 2, 3]
VM178:11 获取argument对象 类数组对象 不能调用数组方法 (2) [2, 3]
VM178:19 透传 2 3
VM178:19 透传 1 2 3
其他类型
- 手动实现call、apply、bind
- EventEmitter
- 防抖
// flag 参数设定为了;有时候我们需要让函数立即执行一次,再等后面事件触发后等待n秒执行
function debounce(event, time, flag) {
let timer = null;
return function (...args) {
clearTimeout(timer);
if (flag && !timer) {
event.apply(this, args);
}
timer = setTimeout(() => {
event.apply(this, args);
}, time);
};
}
- 节流
防抖、节流效果图
- 浅拷贝和深拷贝
- 数组去重、扁平、最值
- 数组乱序-洗牌算法 从数组中随机选出一个位置,交换,直到最后一个元素
function shuffle (arr) {
var len = arr.length
for (var i = 0;i < len - 1;i++) {
var idx = Math.floor(Math.random() * (len - i))
var temp = arr[idx]
arr[idx] = arr[len - i - 1]
arr[len - i - 1] = temp
}
return arr
}
- 函数柯里化 用闭包把参数保存起来,当参数的数量足够执行函数了,就开始执行函数
function progressCurrying(fn, args) {
var _this = this
var len = fn.length;
var args = args || [];
return function() {
var _args = Array.prototype.slice.call(arguments);
Array.prototype.push.apply(args, _args);
// 如果参数个数小于最初的fn.length,则递归调用,继续收集参数
if (_args.length < len) {
return progressCurrying.call(_this, fn, _args);
}
// 参数收集完毕,则执行fn
return fn.apply(this, _args);
}
}
- 手动实现JSONP
(function (window,document) {
"use strict";
var jsonp = function (url,data,callback) {
// 1.将传入的data数据转化为url字符串形式
// {id:1,name:'jack'} => id=1&name=jack
var dataString = url.indexof('?') == -1? '?': '&';
for(var key in data){
dataString += key + '=' + data[key] + '&';
};
// 2 处理url中的回调函数
// cbFuncName回调函数的名字 :my_json_cb_名字的前缀 + 随机数(把小数点去掉)
var cbFuncName = 'my_json_cb_' + Math.random().toString().replace('.','');
dataString += 'callback=' + cbFuncName;
// 3.创建一个script标签并插入到页面中
var scriptEle = document.createElement('script');
scriptEle.src = url + dataString;
// 4.挂载回调函数
window[cbFuncName] = function (data) {
callback(data);
// 处理完回调函数的数据之后,删除jsonp的script标签
document.body.removeChild(scriptEle);
}
document.body.appendChild(scriptEle);
}
window.$jsonp = jsonp;
})(window,document)
-
模拟实现promise promise
-
手动实现ES5继承
-
手动实现instanceof
-
单例模式 基于闭包形式的封装
Singleton.getInstance = (function(name) {
var instance;
return function(name){
if (!instance) {
instance = new Singleton(name);
}
return instance;
}
})();
JS 实现对象的扁平化
传入对象的 key 值和 value,对 value 再进行递归遍历
let flatten = (obj) => {
let result = {};
let process = (key, value) => {
// 首先判断是基础数据类型还是引用数据类型
// Object.prototype.toString.call() "[object Object]"
if (Object(value) !== value) {
// 基础数据类型
if (key) {
result[key] = value;
}
} else if(Array.isArray(value)){
for (let i = 0; i< value.length; i++) {
process(`${key}[${i}]`, value[i])
}
if (value.length === 0) {
result[key] = [];
}
} else {
let objArr = Object.keys(value);
objArr.forEach(item => {
process(key?`${key}.${item}`:`${item}`, value[item])
});
if (objArr.length === 0 && key) {
result[key] = {};
}
}
}
process('', obj)
return result;
}
// 简洁2
function flat(obj, key="", res= {}, isArray = false){
for(let [k, v] of Object.entries(obj)){
if(Array.isArray(v)){
let tmp = isArray ? key + `[${k}]`: key + k
flat(v, tmp, res, true)
}else if(typeof v === 'object'){
let tmp = isArray ? `${key}[${k}].`: key +k + '.'
flat(v, tmp, res)
} else {
let tmp = isArray ? `${key}[${k}]`: key +k
res[tmp] = v
}
}
return res
}
数组嵌套扁平化
let arr = [1,2,3,[4,5,[6]]]
function flatten(arr){
return arr.reduce((pre, current)=>{
return pre.concat(Array.isArray(current) ? flatten(current): current)
}, [])
}
原型相关
function Page() {
return this.hosts;
}
Page.hosts = ['h1'];
Page.prototype.hosts = ['h2'];
const p1 = new Page();
const p2 = Page();
console.log(p1.hosts); // undefined
console.log(p2.hosts); // connot read property 'hosts' of undefined
new 的时候如果 return 了对象,会直接拿这个对象作为 new 的结果,因此,p1 应该是 this.hosts 的结果,而在 new Page() 的时候,this 是一个以 Page.prototype 为原型的 target 对象,所以这里 this.hosts 可以访问到 Page.prototype.hosts 也就是 ['h2']。这样 p1 就是等于 ['h2'],['h2'] 没有 hosts 属性所以返回 undefined。 为什么 console.log(p2.hosts) 会报错呢,p2 是直接调用 Page 构造函数的结果,直接调用 page 函数,这个时候 this 指向全局对象,全局对象并没 hosts 属性,因此返回 undefined,往 undefined 上访问 hosts 当然报错
分隔符转驼峰
function cssStyle2DomStyle(sName) {
//1. 将字符串切割为数组
let arr = sName.split("")
//2. 删除数组中的“-”,并将“-”后面紧挨的元素转成大写字母
while(arr.indexOf('-') !== -1){
const index = arr.indexOf('-')
arr.splice(index,1)
arr[index] = arr[index].toUpperCase()
}
//3. 将数组的第一个元素改为小写字母
arr[0] = arr[0].toLowerCase()
return arr.join("")
}
是否是重复子串
var repeatedSubstringPattern = function(s) {
let len = s.length;
// len可奇数可偶数,i是它一半最大的数
for(let i = 1; i * 2 <= len; i++) {
// 子串长度为i,如果可重复构成,则余数为0
if(len % i === 0) {
// 找到重复子串
let str = s.slice(0, i);
let result = '';
// 计算重复次数
let j = len / i;
while(j !== 0) {
--j;
// 重复j次后,构成字符串后面做比较
result = result + str;
}
if(result === s) {
return true;
}
}
}
return false;
};
最长字符串 ,滑动窗口法
compareVersion(v1, v2) 比较版本号
function compareVersion(v1, v2){
v1 = v1.split('.')
v2 = v2.split('.')
const len = Math.max(v1.length, v2.length)
while(v1.length < len){
v1.push('0')
}
while(v2.length < len){
v2.push('0')
}
for(let i = 0; i< len; i++){
const num1 = parseInt(v1[i])
const num2 = parseInt(v2[i])
if(num1 > num2){
return 1
} else if(num1 < num2){
return -1
}
}
return 0
}
compareVersion('1.11.0', '2.6.6')