201909面试未答题(不间断更新)

246 阅读12分钟

20190827 涂鸦智能一面(仅部分回忆)

  1. 谈谈js内的作用域?
  • 全局作用域:生命周期在整个程序内,能被程序中的任何函数或者方法访问,默认可以修改
  • 函数作用域:外部作用域无法访问函数内部作用域
  • 块作用域:{}let const catch声明时存在块级作用域,同时其声明的变量没有提升,变量在执行时初始化
  • let: 块作用域,无声明提前,var定义的变量为函数作用域,tdz(暂时性死区包reference错误) typeof,重复定义变量报错,for循环形成块作用域
function go(n) {
  // n here is defined!
  console.log(n); // Object {a: [1,2,3]}

  for (let n of n.a) { // ReferenceError 块作用并未初始化n值,故无n.a
    console.log(n);
  }
}

go({a: [1, 2, 3]});
//块作用域与函数作用域
var a = 1;
var b = 2;

if (a === 1) {
  var a = 11; // the scope is global
  let b = 22; // the scope is inside the if-block

  console.log(a);  // 11
  console.log(b);  // 22
} 

console.log(a); // 11
console.log(b); // 2
  • 词法作用域:基于作用域嵌套的思想,在写下代码的时候就已定义,寻找变量一层一层的向外查找
  • 动态作用域:函数执行的时候生成,基于调用栈的思想,动态作用域常用于分析this引用。
  1. 关于>=的隐式类型转换?
  • 可转化为!<来比较,也可以直接比较;
  • 不论> / < / == 都有一条定律则是转化为基本类型值后若至少有一个数不为字符串,则转化为整形比较。特殊 new Date() 在 == 中只能通过toString()转化为字符串,在> < 中和通常的抽象操作toPrimitive一样, valueOf()出来是整形值相应秒数,toString()日期字符串。< / > 能够进行类型转化进行比较,且转化最后都是字符串或者数字进行比较,字符串根据ASCII码进行比较。null 和 undefined需要考虑。

20190827 新意互动

  1. 谈谈闭包?
  • 在非当前词法作用域调用它,回调函数基于这一思想。具体就是一个函数里有一个函数,返回这个函数,然后执行可以访问其词法作用域的一些变量,案例如下;
//闭包词法作用域 定义的时候则已决定
function func(){
    var funcs = [];
    for(var i = 0; i < 10; i++){
            funcs[i] = function(){
                return i;
        }
    }
    return funcs;
}
var funcArr = func();
console.log(funcArr[1]());
//id自增 IIFE闭包  匿名函数样式闭包
var increate = (function(){
    let count = 0;
    return function(){
        count++;
        return count;
    }
})();
console.log(increate());
console.log(increate());
  • 闭包优点:实现函数嵌套,对数据/方法进行封装,访问词法作用域的变量,避免垃圾回收
  • 闭包缺点:内存泄漏,保存环境变量,造成内存消耗大

20190828 美团打车一面

  1. http协商缓存
  • 命中强缓存失败后,发送http请求带有相关http头,if-none-math if-modified-since进行询问,服务受到后根据e-tag 值 或者last-modified判断是否命中协商缓存,命中了返回请求不带有资源,状态码为304,没有命中则返回带有资源的响应报文,返回状态码为200,E-tag的优先级高于last-modified。
  1. fetch
  2. jquery 和react的区别
  • react特点:ui框架,操作的是vdom而非真实dom,实现了组件化,界面复用,搭建界面通过堆组件的方式完成,侧重于js的应用,都是在jsx内写代码,实现了ui和业务逻辑的分离,状态改变导致界面改变,代码可读性性高,便于维护。
  • juqery则是一个工具库,直接操作真实dom,是更好的找到dom元素,简化操作dom元素的方法,链式调用,隐式迭代等方法,ui和逻辑混合在一起,维护困难,可读性也较差。

20190902 美团打车二面

  1. js实现sleep函数
  • 计时实现
sleep = (duration) => {
    return new Promise((resolve,reject) => {
        var nowTime = Date.now();
        while(true){
            if(Date.now() - nowTime > duration){
                resolve();
                break;
            }
        }
    })
}
func = async() => {
    await sleep(3000);
    console.log('3s later');
}
func()
  • setTimeout实现
sleep = (duration) => {
    return new Promise((resolve,reject) => {
        setTimeout(resolve,duration);
    })
}
func = async() => {
    await sleep(3000);
    console.log('3s later');
}
func()
  1. js实现map函数
/*
map函数总结 
不会遍历没有初始化下标的数组(Aarry(10)),若调用将会返回 [ <10 empty items > ] 
map一开始遍历就已确定遍历范围,不会改变数组长度,不会改变原数组,返回一个遍历执行回调函数将结果放入数组的新数组
箭头函数勿乱使用,当需要函数需要绑定this的值,不要写回调函数箭头函数中的this无法修改
*/
Array.prototype.myMap = function(callback, context) {
    //判断this不能为空
    if(this === null || this === undefined){
        throw new TypeError('cannot read property map of null or undefined');
    }
    //判断callback必须是一个函数
    if (Object.prototype.toString.call(callback) !== '[object Function]') {
        throw new TypeError(callback + 'is not a function');
    }
    const arr = Object(this);
    const len = this.length >>> 0;
    //判读数组仅初始化长度为初始化值时的返回情况 判断数组是否初始化了下标
    let k = 0;
    while(k < len && !( k in arr)){
        k++;
    }
    if(k >= len){
        return `[ < ${len} empty items > ]`;
    }
    var newArr = [];
    for (var i = 0; i < len; i++) {
        newArr.push(callback.call(context, arr[i], i, arr));
    }
    return newArr;
};
  • filter函数
Array.prototype.achieveFilter = function (callback,context){
    if(this === null && this === undefined){
        throw new TypeError('cannot read property filter of null or undefined');
    }
    if(typeof callback !== 'function'){
        throw new TypeError(callback + 'is not a function');
    }
    const arr = Object(this);
    const len = arr.length >>> 0;
    var resArr = [];
    for(let i = 0; i < len; i++){
        if(callback.call(context,arr[i],i,arr)){
            resArr.push(arr[i])
        }
    }
    return resArr;
}
  • reduce
Array.prototype.achieveReduce = function (callback,initialValue){
    //== 宽松相等 null只会和undefined和null相等 先比较类型在比较值 string number string => number number boolean boolean => number进行比较 重点会进行类型转换拿 和对象比较则会调用toPrimitive抽象方法通过valueOf() 或者toString()方法得到值后进行比较
    if(this === null && this === undefined){
        throw new TypeError('cannot read property of null or undefined');
    }
    if(typeof callback != 'function'){
        throw new TypeError(`${callback} is not a function`);
    }
    const arr = Object(this);
    //初始值存在时下标为0否则为1
    let acc = initialValue || arr[0];
    let startIndex = initialValue ? 0 : 1;
    let k = 0;
    //判断是否为空数组 针对Array(10)判断数组是否为空
    while(k < arr.length && !(k in arr)){
        k++;
    }
    //判断空数组时候的情况
    if(k >= arr.length){
        return initialValue ? initialValue : new TypeError('Reduce of empty array with no initial value');
    }
    for(var i = startIndex; i < arr.length; i++){
        acc = callback(acc, arr[i], i, arr);
    }
    return acc;
}
  • reduce 递归实现
const reduceHelper = (callback, acc, idx, arr) => {
    if (idx === arr.length) return acc;
    return reduceHelper(callback, callback(acc, arr[idx], idx, array), idx++, arr);
};
Array.prototype.achieveReduce = function(callback, initialValue) {
    if (this == null) {
        throw new TypeError('this is not null or undefined');
    }
    if (typeof callback != 'function') {
        throw new TypeError(`${callback} is not function`);
    }
    const arr = this;
    let k = 0;
    let acc = initialValue || arr[0];
    let startIndex = initialValue ? 0 : 1;
    while (k < arr.length && !(k in arr)) {
        k++;
    }
    if (k >= arr.length) {
        return initialValue ? initialValue : new TypeError('reduce of arr is not empty and initialValue is undefined');
    }
    return reduceHelper(callback, acc, startIndex, arr);
};
  • splice函数
Array.prototype.spliceMy = function(startIndex, deleteCount, ...addValue) {
    if (this === null || this === undefined) {
        throw new TypeError(`cannot read property 'splice' of null or undefined`);
    }
    let len,
        arr,
        deleteArr = [],
        addLen = addValue.length,
        argsLen;
    arr = Object(this);
    len = arr.length >>> 0; //取整效果
    argsLen = arguments.length;
    startIndex = calcStartIndex(startIndex, len); // 计算开始下标合法性 为负为正超出数组长度
    deleteCount = calcDeleteCount(deleteCount, startIndex, len, argsLen); //计算删除元素合法性 为负 或者超出数组元素
    //计算删除元素数组, 结果返回删除的数组
    calcDeleteArr(arr, startIndex, deleteCount, len, deleteArr);
    //根据删除和添加的元素移动数组
    calcMoveArr(arr, startIndex, deleteCount, len, addLen);
    //已将插入数据的位置移动出来,开始插入数据
    for (let i = 0; i < addLen; i++) {
        arr[i + startIndex] = addValue[i];
    }

    //密封 configurable:false 不可以添加属性 冰冻 frozen  configurable:false writable:false 不可添加属性和修改属性值
    if (Object.isSealed(arr) && deleteCount !== addLen) {
        throw new TypeError('the arr is sealed');
    }
    if (Object.isFrozen(arr) && (deleteCount > 0 || addLen > 0)) {
        throw new TypeError('the arr is frozen');
    }
    arr.length = len + addLen - deleteCount;
    return deleteArr;
};
//判断起始位置是否合法 如果起始位置为负 则为 startIndex + len ,若负的超过数组长度起始位置为0
function calcStartIndex(startIndex, len) {
    if (startIndex < 0) {
        return startIndex + len >= 0 ? startIndex + len : 0;
    }
    return startIndex >= len ? len - 1 : startIndex;
}
//删除长度为负则删除个数为0 删除元素超出数组长度 则删除数组剩下的元素 若果没有传值 也删除剩下的
function calcDeleteCount(deleteCount, startIndex, len, argsLen) {
    // if (deleteCount === undefined) return len - startIndex;
    if (argsLen === 1) return len - startIndex;
    if (deleteCount < 0) {
        return 0;
    }
    return deleteCount > len ? len - startIndex : deleteCount;
}
function calcDeleteArr(arr, startIndex, deleteCount, len, deleteArr) {
    for (let i = 0; i < deleteCount; i++) {
        deleteArr[i] = arr[i + startIndex]; //删除数组
    }
}
function calcMoveArr(arr, startIndex, deleteCount, len, addLen) {
    if (deleteCount === addLen) return; // 如果删除数据和添加数据长度一致,则不用移动数组,直接修改相应位置上的值
    if (deleteCount > addLen) {
        //数据前移deleteCount - len这么个长度
        for (let i = startIndex + deleteCount; i < len; i++) {
            let fromIndex = i; //需要移动的元素下标
            let toIndex = i - (deleteCount - addLen); //计算移动后的下标
            arr[toIndex] = arr[fromIndex]; //进行移动
        }
        //删除多余元素
        for (let i = len - 1; i >= len + addLen - deleteCount; i--) {
            delete arr[i];
        }
    } else if (deleteCount < addLen) {
        //后移不用删除元素
        for (let i = len - 1; i >= startIndex + deleteCount; i--) {
            let fromIndex = i;
            let toIndex = i + addLen - deleteCount;
            arr[toIndex] = arr[fromIndex];
        }
    }
}
let arr = [1, 2, 3];
console.log(arr.spliceMy(0, 4));
console.log(arr);
  • push方法/pop方法
//delete 删除数组时 会删除相应属性,但不会修改数组长度
Array.prototype.pushI = function(...addValues) {
    //返回数组长度
    let arr = Object(this);
    let len = arr.length >>> 0;
    for (let i = 0; i < addValues.length; i++) {
        arr[i + len] = addValues[i];
    }
    return len + addValues.length;
};
Array.prototype.popI = function() {
    //返回弹出的元素值
    let arr = Object(this);
    let len = arr.length >>> 0;
    if (len === 0) {
        return undefined;
    }
    len--;
    let value = arr[len];
    delete arr[len]; //delete会删除属性 但不会修改length值
    arr.length = len;
    return value;
};
  • sort方法
// sort是结合直接插入排序和快速排序的一种思路
/**
 * 1. n < 10 使用直接插入排序
 * 2. n < 1000 使用快速排序
 * 3. n > 1000 隔200 - 215抽出一个数组成数组,排序后去中位数作为哨兵
 */
  • every方法
Array.prototype.every = function(callback, context){
    let len , arr;
    if(this == undefined){
        throw new TypeError('cannot read property of null or undefined');
    }
    if(typeof callback != 'function'){
        throw new TypeError(`${callback} is not a function`);
    }

    arr = Object(this);
    let = arr.length >>> 0;
    //没有初始化的下标不会执行callback函数
    for(let i = 0 ; i < len; i++){
        if(i in arr && !callback.call(context, arr[i] , i, arr)){
            return false;
        }
    }
    return true;
}
  • some方法
Array.prototype.some = function(callback, context){
    let len , arr;
    if(this == undefined){
        throw new TypeError('cannot read property of null or undefined');
    }
    if(typeof callback != 'function'){
        throw new TypeError(`${callback} is not a function`);
    }

    arr = Object(this);
    let = arr.length >>> 0;
    //没有初始化的下标不会执行callback函数
    for(let i = 0 ; i < len; i++){
        if(i in arr && callback.call(context, arr[i] , i, arr)){
            return true;
        }
    }
    return false;
}
  • 如何将类数组转化为数组
1. Array.prototype.slice.call(arguments)
2. Array.from(arguments)
3. [...arguments] //展开成为数组 ...只能展开含Symbol.iterator属性的类型,argumentes类数组实现了Symbol.iterator,展开对象时只能用{}接收才不回报错,是个例外。
4. Array.prototype.concat.apply([], arguments); 类数组进行展开 
  • 如何查找到一个数
arr.indexOf(value)
arr.includes(value, [startIndex])
arr.find(callback, context)
arr.findIndex(callback, context)
  • forEach return有无效果
回调函数return都无效果,数组会继续下一次循环
解决办法: 使用some every满足某一条件时会结束遍历数组返回结果。
  1. 如何实现同时发出多个请求
Promise.all([promise1,promise1,promise2]).then(function(values){});
  1. react-redux Provider context如何实现
  2. redux代码写的好的地方
  3. vdom为什么好?
  • js 和 操作真实dom的一个缓冲,先操作vdom通过deff算法找出差异,再由react作用到真实的dom上。如增加1000个元素,可能要操作1000次dom,触发1000次浏览器渲染过程 解析dom 渲染dom layout dom panit dom,性能变差。而使用了vdom先将这1000次增加的元素作用到vdom上,最后在作用到真实dom只用触发一次浏览器渲染过程,可使性能优化。 当然如果将这1000次添加元素变成触发一次浏览渲染过程,其性能是要比react的性能要高的。
  • 误解: react性能比操作真实dom要高,任何操作dom的性能都是最高的,因为没有任何封装。react只是做了相应的自动优化,保证其有一定性能可言。
  1. 浏览器关闭后如何删除localStorage
window.onunload = function(){
    localStorage.clear();
}
localStorage.setItem(key,value);
localStroage.getItem(key);
localStroage.removeItem(key);
  1. split有几个参数
有2个参数 分割符separator(可能为字符串,也可以为正则表达式) limit 限制分割的数量,当按照分割符分割超过该数量时不再分割;
split(separator, [limit])
如果分割符中的正则表达式含括号,会把分割符也分割进数组。

20190904 美团打车三面

  • 括号匹配 {{arr.length}} true {{}{false
//多种括号匹配配
function validBraces(str) {
    let reg = /[\(\{\[]/ ;// 正则表达式选中一个
    let arr = [], strArr = str.split('');
    for (let value of strArr) {
        if (reg.test(value)) { 
            arr.push(value);
        } else {
            switch (value) {
                case ')':
                    var temp = arr.pop();
                    if (temp !== '(') {
                        return false;
                    }
                    break;
                case '}':
                    var temp = arr.pop();
                    if (temp !== '{') {
                        return false;
                    }
                    break;
                case ']':
                    var temp = arr.pop();
                    if (temp !== '[') {
                        return false;
                    }
                    break;
            }
        }
    }
    return !arr.length;
}
console.log(validBraces('))))'));
var arr = [];
  • 栈的insert 和 remove方法
function Stack(){
    this.top = null; //栈顶
    this.size = 0; //长度
}
function Node(data){
    this.data = data;
    this.next = null;
}
//头插法 stack栈反转链表都是头插法的应用
Stack.prototype.push = function(v){
    let node = new Node(v);
    node.next = this.top;
    this.top = node;
    return ++this.size;
}
Stack.prototype.pop = function(){
    //寻找删除的前一项
    if(this.top){
        let data = this.top.data;
        this.top = this.top.next;
        this.size--;
        return data;
    }
}
var stack = new Stack();

stack.push(1);
stack.push(2);
stack.push(3);

console.log(stack.pop()); 
console.log(stack.pop()); 
console.log(stack.pop()); 
console.log(stack.pop()); 
  • mysql查询语句:思路和手写

20190904 跟谁学一面

  • 实现bind方法 参数传递 返回新函数 原型链委托
//call方法
Function.prototype.callFunc = function(context, ...args) {
    //context为undefined null 非严格模式绑定到window严格模式绑定到undefined context为非对象时封装成相应的对象
    if(context === undefined || context === null){
        context = window;
    }else{
        context = Object(context);
    }
    // let fn = Symbol('call'); context[fn] 可计算属性名 
    context.fn = this;
    let result = context.fn(...args);
    delete context.fn;
    return result;
};
//apply方法
Function.prototype.applyFunc = function(context, objlike) {
    if(context === null || context === undefined){
        context = window;
    }else{
        context = Object(context);
    }
    context.fn = this;
    //判断是否为类数组
    function isObjectLike(obj){
        if(obj && typeof obj === 'object' && obj.length >= 0 && isFinite(obj.length) && obj.length < Math.pow(2,32)){
            return true;
        }else{
            return false;
        }
    }
    if(objlike){
            if(!Array.isArray(objlike) && !isObjectLike(objlike)){
                throw new TypeError('the sceond params is a object like');
            }else{
                let args = [].slice.call(objlike);
                let result = context.fn(...args);
                delete context.fn;
                return result;
            }
    }else{
        let result = context.fn();
        delete context.fn;
        return result;
    }    
};
Function.prototype.bindFunc = function(context,...args) {
    if (typeof this != 'function') {
        throw new TypeError(`${this} not a function`);
    }
    const func = this;
    const bindThis = context;
    //数组的api需要上下文 单独执行行时需用call / applay方法进行调用
    //如果是new 绑定则忽略硬绑定的this
    let bindFunc = function(...args2) {
        return func.apply(this instanceof bindFunc ? this : bindThis, args.concat(args2));
    };
    //原型链委托修改 有点想继承的概念
    if(func.prototype){
        bindFunc.prototype = Object.create(func.prototype);//类似继承 new时 this可以防func上的属性或者方法
    }
    return bindFunc;
};
let obj = { a: 1, b: 2 };
function add(c, d) {
    return this.a + this.b + c + d;
}
console.log(add.callFunc(obj, 3, 4));//10
console.log(add.applyFunc(obj, [3, 4]));//10
// console.log(add.applyFunc(obj, 1));
console.log(add.applyFunc(obj, {length: 2, 0: 1, 1: 2})); //6
var newFunc = add.bindFunc(obj, 2,2);
console.log(newFunc());
// console.log(newFunc.__proto__, add.prototype);
console.log(newFunc instanceof add); //false
let newObj = new newFunc();
// console.log(newObj.__proto__);
console.log(newObj instanceof add); //true
  • 实现仅有数字的排序方法[0,1,2, 0] 时间复杂度为O(n) 大量数字 范围固定 计数排序
var sortArr = (arr) => {
    let countArr = [], min = arr[0],resArr = [];
    //获取计数数组
    for(let i = 1; i < arr.length; i++){
        if(arr[i] < min){
            min = arr[i];
        }        
    }
    //计数
    for(let j = 0; j < arr.length; j++){
        if(!countArr[arr[j] - min]){
            countArr[arr[j] - min] = 1;
        }else{
            countArr[arr[j] - min]++;
        }
    }
    //展开值和下标
    let m = 0;
    for(let k = 0; k < countArr.length; k++){
        while(countArr[k]){
            resArr[m++] = k + min;
            countArr[k]--;
        }
    }
    /*
    //下标为数组值 ,值为相应数组值的结果数组中的下标
    for(let j = 1; j < max - min + 1; j++){
        countArr[j] = countArr[j] + countArr[j - 1]; 
    }
    //返回结果
    for(let j = 0; j < arr.length; j++){
        resArr[countArr[arr[j] - min] - 1] = arr[j];
        countArr[arr[j] - min]--;
    }*/
    return resArr;
};
var arr = [3, 5, 5, 1, 16,1, 66];
console.log(sortArr(arr));
  • 实现布局如下图
float;
flex;
grid;
  • 实现三行三列布局(9九空格)
float;
flex;
grid;
详见vscode面试编程题


  • node事件驱动
const fs = require('fs');
const path = require('path');
const events = require('events');

//继承events.EventEmitter实例
class MyEeventEmitter extends events.EventEmitter {}
let eventEmitterIns = new MyEeventEmitter();
//注册事件
eventEmitterIns.addListener('read', function(data) {
    console.log(data);
});
eventEmitterIns.on('read', function(data) {
    console.log('这是观察者模式');
});

//触发事件 error first的方式
fs.readFile(path.resolve(__dirname, 'date.html'), { encoding: 'utf-8' }, function(err, data) {
    eventEmitterIns.emit('read', data); //触发事件
});
/**
 * 相关api:
 * on(type, callback) addListener(type, callback) emit(type, data) removeListener(type, callback) removeAllListeners() setMaxListenter(number)
 * once(type,callback) 注册的回调值执行一次,执行完后则删除
 * listenerCount() //注册监听数量
 */
//自己实现一个EventEmitter()

class EventEmitter {
    constructor() {
        //对象属性
        this.events = {};
        this.maxListeners = 10;
    }
    //原型方法
    //注册回调 同一事件可以有多个回调
    on(type, callback) {
        if (this.events[type]) {
            if (this.events[type] > this.maxListeners) {
                throw new TypeError('the listeners is full');
            }
            this.events[type].push(callback);
        } else {
            this.events[type] = [callback];
        }
    }
    //触发事件
    emit(type, data) {
        this.events[type] && this.events[type].forEach(cb => cb.call(this, data));
        // for(let cb of this.events[type]){
        //     cb.call(this, data);
        // }
    }
    //只调用一次的回调函数 调用后则删除注册的事件
    once(type, callback) {
        let wrapper = data => {
            callback.call(this, data);
            this.removeListener(type, wrapper); //执行后则删除该注册事件
        };
        this.on(type, wrapper);
    }
    //删除事件
    removeListener(type, callback) {
        this.events[type] && (this.events[type] = this.events[type].filter(cb => callback !== cb));
    }
    //删除所有注册事件
    removeAllListeners(type) {
        if (type) {
            delete this.events[type];
        } else {
            this.events = {};
        }
    }
    setMaxListeners(number) {
        this.maxListeners = number;
    }
    getMaxListeners() {
        return this.maxListeners;
    }
    listeners(type) {
        return this.events[type];
    }
}
  • express路由嵌套
  • class继承方法
  • jsonp实现源码
//将对象转化为&连接后的字符串
function dataToUrl(data){
    let res = [];
    for(let key in data){
        res.push(encodeURIComponent(key) + '=' + encodeURIComponent(data[key]));
    }
    return res.join('&');
}
function jsonp(url, data, success, errorCb, time){
    //callback参数
    let cb = 'cb' + Math.floor(Math.random() * 10);
    //设置callback = cb
    data.callback = cb;
    let flag = url.indexOf('?') > -1 ? '&' : '?';
    let script = document.createElement('script');
    script.setAttribute('src') = url + flag + data;
    document.getElementsByTagName('head')[0].appendChild(script);

    window[cb] = function(data){
        clearTimeout(script.timer);
        window[cb] = null;
        document.getElementsByTagName('head')[0].removeChild(script);
        success && success(data);
    }    
    if(time){
        script.timer = setTimeOut(function(){
            clearTimeout(script.timer);
            window[cb] = null;
            document.getElementsByTagName('head')[0].removeChild(script);
            errorCb && errorCb('超时访问');
        },time);
    }
}
  • Promise.all()源码
Promise.allFunc = function(arr) {
    return new Promise(function(resolve, reject) {
        //参数为类数组
        function isObjectLike(val) {
            if (
                val &&
                typeof val === 'object' &&
                val.length &&
                val.length < Math.pow(2, 32) &&
                val.length >= 0 &&
                isFinite(val.length)
            ) {
                return true;
            } else {
                return false;
            }
        }
        try {
            if (!isObjectLike(arr)) {
                throw new Error('the arguments must be the array type');
            }
            //数组为空时立即决议 []
            newArr = Array.from(arr);
            if (newArr.length === 0) resolve([]);
            let result = [],
                count = arr.length;
            function res(index, value) {
                //利用thenable方式检测是否为promise对象 是promise对象的 thenable new Promise then方法
                if (
                    value &&
                    (typeof value === 'object' || typeof value === 'function') &&
                    value.then &&
                    typeof value.then === 'function'
                ) {
                    Promise.resolve(value).then(function(data) {
                        res(index, data);
                    }, reject);
                    return;
                }
                count--;
                result[index] = value;
                if (count === 0) {
                    resolve(result);
                }
            }
            for (let i = 0; i < arr.length; i++) {
                res(i, arr[i]);
            }
        } catch (err) {
            reject(err);
        }
    });
};
Promise.raceFunc = function(arr) {
    return new Promise(function(resolve, reject) {
        function isArrayLike(val) {
            if (
                val &&
                val.length &&
                typeof val === 'object' &&
                val.length >= 0 &&
                val.length < Math.pow(2, 32) &&
                isFinite(val.length)
            ) {
                return true;
            } else {
                return false;
            }
        }
        try {
            if (!isArrayLike(arr)) {
                throw new Error('the arguments must be the iterable');
            }
            let newArr = Array.from(arr);
            for (let pro of newArr) {
                pro.then(function(data) {
                    resolve(data);
                }, reject);
            }
        } catch (err) {
            reject(err);
        }
    });
};
  • bfc(block formatting context) 块格式化上下文
  1. 什么能形成bfc:所有脱离文档流的css样式:1.float除none2.position:fixed,absolute 3.display为inline-block4.overflow不为visible
  2. bfc特性:元素从左到右排列,同一bfc内出现margin上下放下重叠,bfc区域不用float区域重叠,计算元素高度时浮动元素也加入计算
  3. bfc应用:两栏自适应布局(float后的元素设置为bfc),清楚浮动(父元素设置为bfc),解决margin重叠(生成2个bfc);、
 <style>
        #left{
            float: left;
            width: 200px;
            background-color: #000;
            height: 200px;
        }
        #right{
            background-color: #f0f;
            height: 300px;
            overflow: hidden;
        }
    </style>
</head>
<body>
    <div id="main">
        <div id="left"></div>
        <div id="right">dfsdfa</div>
    </div>
</body>

20190916 猫眼二面(仅部分回忆)

  • react key加index和加唯一key以及不加Key的区别,不加key什么时候会报错
  1. 结论:React加key在diff算法时更快的寻找组件(map对象方式寻找value),提升diff算法的速度。对整体性能要结合具体场景考虑;
  2. 不加key 则根据diff算法顺序遍历组件,进行比较找出差异(diff);有点类似加index方式的Key但又不同,此时为顺序遍历,并未通过Key的方式标识组件
  3. key 为index : 适用于静态数组,数组值不改变,根据Key寻找组件,将相同Key间组件的差异进行更新,即组件内容更新,可能导致非受控组件如表单内容出现bug;
  4. key 为唯一值:使用于key稳定的情况,相同的key组件内容相同,只需要组件重排即可。key不同则会不断销毁和创建组件,对性能有影响。
  5. key应为唯一值(同一层级下),稳定值,不为随机值,非受控组件不加key会出bug
  • 受控组件和非受控组件 本质为表单受控组件和表单非受控组件
  1. 受控组件: 类似双向绑定: 表单值体现到状态值(state)通过绑定onChange事件实现表单值和state值相同
  2. 非受控组件 表单值不体现在state上 通过ref或者React.createRef()直接获取dom节点获取表单值 this.refs.key || this.createRef.current 获取dom节点或者有状态节点的实例(此方法可以父组件获取子组件的属性方法)
  3. 优选受控组件,尽管繁杂,符合React层级控制和状态管理思想,实现表单提示功能。
  • async await函数如何执行无尽循环
  1. async 异步函数返回Pormise对象,故不通过await获取值时,通过Promise.then方法获取返回值
  2. await 等待异步函数其实就是Promise对象或者立即值则立即决议,等待resolve值
  3. 同步方式写异步代码,async非阻塞执行,await异步执行时则阻塞
  4. Promise方法传递参数 async传递参数对比
async function add(a) {
    let b = await sub(a);
    let c = await sub(a, b);
    let d = await sub(a, b, c);
    console.log(d);
}
//多个参数传递需手动传递
function add(a) {
    sub(a)
        .then(
            b => sub(a, b).then(c => [a, b, c])
        )
        .then(d => {
            let [a, b, c] = d;
            return sub(a, b, c);
        })
        .then(d => console.log(d));
}
  • animation和transition的区别
  1. transition: transition-property transition-duration transition-delay transition-time-function
  2. animation: animation-name animation-duration animation-delay animation-iterable-count:n,infinite animation-direation:nomarl,alternative,animation-time-function
  3. 区别:1.触发方式 通过事件触发,如hover,js操作dom元素使css属性改变,animation自动触发2. 功能全面:animation功能更多,设置循环次数,动画方向等,可设置不同帧过渡效果,transition只有开始和结束两种功能(注意:display都无动画效果,只有visiblity有动画效果)
  • 防抖节流
//防抖:debounce 任务频繁时任务间隔只有超过指定间隔后才能执行 间隔可以是事件 可以是输入文字长度
function debounce(fn, interval = 3) {
    let timer = null;
    return function() {
        clearTimeout(timer);
        timer = setTimeout(() => {
            fn.apply(this, arguments);
        }, interval);
    };
}
$(function() {
    var minlength = 3;
    $('#sample_search').keyup(function() {
        var that = this,
            value = $(this).val();

        if (value.length >= minlength) {
            if (searchRequest != null) searchRequest.abort();
            searchRequest = $.ajax({
                type: 'GET',
                url: 'sample.php',
                data: {
                    search_keyword: value
                },
                dataType: 'text',
                success: function(msg) {
                    //we need to check if the value is the same
                    if (value == $(that).val()) {
                        //Receiving the result of search here
                    }
                }
            });
        }
    });
});

let req = null;
$('.keyword').on('keyup', function() {
    let timer = null;
    clearTimeout(timer);
    timer = setTimeout(function() {
        if (req) $(req).abort();
        req = $.ajax({
            url: '/suggest',
            data: { keyword: this.value },
            success: function(json) {
                $('.suggest').html(json.html);
            }
        });
    }, 500);
});
//节流 throttle 每隔一段时间执行一次 如滚动
function throttle(fn, interval = 2) {
    let execute = false;
    return function() {
        if (execute) return;
        execute = true;
        setTimeout(() => {
            fn.apply(this, arguments);
            execute = false;
        }, interval);
    };
}

  • 观察者模式
on('foo', function() {
    console.log('foo fired');
});
on('foo', function(a) {
    console.log(a); // 1
});
on('bar', function(a, b) {
    console.log(a + ' ' + b); // 2 3
});

trigger('foo', 1);
trigger('bar', 2, 3);
//利用key键值进行计算 value为key对应的回调函数数组
function on(type, fn) {
    if (callback[type]) {
        callback[type] = [];
    }
    callback[type].push(fn);
}
function trigger(type, ...args) {
    for (let cb of callback[type]) {
        cb(...args);
    }
}

  • 闭包题
 var config = [{ elem: 'foo' }, { elem: 'bar' }, { elem: 'hello' }];

            function Item() {
                var self = this; //self为item对象 不会丢失
                self.click = function() {
                    alert(self.elem + ':' + self.index); // .hello:2
                };
            }
            var item = new Item();
            for (var i = 0; i < config.length; i++) {
                //i item为函数作用域 var 声明访问时访问都为同一变量
                // var item = new Item();
                item.elem = config[i].elem;
                item.index = i;
                document.getElementById(item.elem).onclick = item.click; //闭包
            }
  • fib数列
//递归法
function fib(n) {
    if (n === 1) return 0;
    if (n === 2) return 1;
    return fib(n - 1) + fib(n - 2);
}
console.log(fib(10));
//迭代法 设置初始条件 fn = fib( n - 1) + fib(n - 2);
function fibnacci(n) {
    let first = 0, second = 1, count = 2, res;
    if (n === 1) return first;
    if (n === 2) return second;
    while(count < n){
        res = first + second;
        first = second;
        second = res;
        count++;
    }
    return res;
}
console.log(fibnacci(10));
  • 并行异步回调
parallel(
    [
        function(done) {
            setTimeout(function() {
                done('one');
            }, 200);
        },
        function(done) {
            setTimeout(function() {
                done('two');
            }, 100);
        }
    ],
    function(res1, res2) {
        console.log(res1, res2); // one, two
    }
);
//实现并行异步回调
function parallel(tasks, callback) {
    //并行异步回调
    /*Promise.all(tasks).then(function(...args){
        console.log(args);
    })*/
    let res = [],
        count = tasks.length;
    // function done(k, data) {
    //     res[k] = data;
    //     count--;
    //     if (!count) {
    //         callback(...res);
    //     }
    // }
    for (let i = 0; i < tasks.length; i++) {
        done = function(data) {
            res[i] = data;
            count--;
            if (!count) {
                callback(...res);
            }
        };
        tasks[i](done);
    }
}
  • juqery插件机制
//$('.foo').datePicker().find('.tips').html('bar'); 挂载到构造函数原型上可以返回当前对象继续调用相应方法
window.$ = function(name) {
    return new _$(name);
};
function _$(name) {
    this.elementArrays = [];
    if (typeof name !== 'string' && typeof name !== 'object') {
        throw new Error('the naem must be the string or object');
    } else {
        if (/\./.test(name)) {
            let elemArr = document.getElementsByClassName(name.substr(1));
            for (let i = 0, len = elemArr.length; i < len; i++) {
                this.elementArrays.push(elemArr[i]);
            }
        } else if (/#/.test(name)) {
            this.elementArrays.push(document.getElementById(name));
        } else {
            this.elementArrays.push(name);
        }
    }
}
_$.prototype = {
    constructor: _$,
    each: function(fn) {
        for (let i = 0, len = this.elementArrays.length; i < len; i++) {
            fn.call(this, this.elementArrays[i]);
        }
        return this;
    },
    addClass: function(name) {
        this.each(function(ele) {
            ele.className = name;
        });
        return this;
    },
    dataPicker: function() {
        return this;
    }
};