题目记录

381 阅读2分钟

实现下面这道题中的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')