面经 美团前端实习一面

297 阅读6分钟

fetch和XMLHttpRequest区别

fetch使用了Promise,这让它使用起来更加简洁,从而避免陷入”回调地狱”。

在将响应的文本信息转换成JSON格式前,需要先确保响应的状态码为200。 fetch()请求后返回的响应是一个stream对象,这就意味着我们在调用json()方法后会返回一个Promise,因为读取stream的过程是异步操作的。

fetch('./api/some.json')
    .then(function(res) {
        if (res.status !== 200) {
            console.log('Looks like there was a problem. Status Code: ' + res.status);
            return;
        }
​
        // 处理响应中的文本信息 
        res.json().then(function(data) {
            console.log(data);
        });
    })
    .catch(function(err) {
        console.log('Fetch Error : %S', err);
    })

宏任务微任务——promise()

宏任务中的方法: 1. script (可以理解为外层同步代码,作为入口 ) 2. setTimeout/setInterval

微任务中的方法: 1.Promise 2. nextTick

而他们的执行顺序 是 微任务 先输出 在输出 宏任务

img

setTimeout(() => {
  console.log('定时器');
}, 0)
new Promise((resolve) => {
  console.log('同步代码')  
  resolve('异步代码')
}).then((res) => {
  console.log(res);   
})
console.log('奥特曼');
// 宏任务和微任务
console.log('start')
​
setTimeout(()=>{
    console.log('setTimeOut')
},0)
​
Promise.resolve().then(() => {
    console.log('p1');
    return Promise.resolve()
}).then(()=>{
    console.log('p2');
})
​
Promise.resolve().then(() => {
    console.log('p3');
})
​
console.log('end');
​
start
end
p1
p3
p2
setTimeOut

Promise()原理

JavaScript 中的 Promise 是一种用于处理异步操作的对象。它代表了一个异步操作的最终完成(或失败)及其结果值。以下是 Promise 的实现原理和工作机制的详细解释。

Promise 的基本概念

  • 状态

    :一个 Promise 对象有以下几种状态:

    • pending:初始状态,既不是成功,也不是失败状态。
    • fulfilled:意味着操作成功完成。
    • rejected:意味着操作失败。
  • 结果:Promise 的结果(一个值),在 fulfilled 状态下是成功的值,在 rejected 状态下是拒绝的原因。

Promise 的实现原理

以下是一个简化版的 Promise 实现,用于演示其基本原理:

class MyPromise {
    constructor(executor) {
        this.state = 'pending';
        this.value = undefined;
        this.reason = undefined;
        this.onFulfilledCallbacks = [];
        this.onRejectedCallbacks = [];
​
        const resolve = value => {
            if (this.state === 'pending') {
                this.state = 'fulfilled';
                this.value = value;
                this.onFulfilledCallbacks.forEach(fn => fn());
            }
        };
​
        const reject = reason => {
            if (this.state === 'pending') {
                this.state = 'rejected';
                this.reason = reason;
                this.onRejectedCallbacks.forEach(fn => fn());
            }
        };
​
        try {
            executor(resolve, reject);
        } catch (err) {
            reject(err);
        }
    }
​
    then(onFulfilled, onRejected) {
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
        onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason };
​
        let promise2 = new MyPromise((resolve, reject) => {
            if (this.state === 'fulfilled') {
                setTimeout(() => {
                    try {
                        let x = onFulfilled(this.value);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (e) {
                        reject(e);
                    }
                }, 0);
            }
​
            if (this.state === 'rejected') {
                setTimeout(() => {
                    try {
                        let x = onRejected(this.reason);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (e) {
                        reject(e);
                    }
                }, 0);
            }
​
            if (this.state === 'pending') {
                this.onFulfilledCallbacks.push(() => {
                    setTimeout(() => {
                        try {
                            let x = onFulfilled(this.value);
                            resolvePromise(promise2, x, resolve, reject);
                        } catch (e) {
                            reject(e);
                        }
                    }, 0);
                });
​
                this.onRejectedCallbacks.push(() => {
                    setTimeout(() => {
                        try {
                            let x = onRejected(this.reason);
                            resolvePromise(promise2, x, resolve, reject);
                        } catch (e) {
                            reject(e);
                        }
                    }, 0);
                });
            }
        });
​
        return promise2;
    }
}
​
function resolvePromise(promise2, x, resolve, reject) {
    if (promise2 === x) {
        return reject(new TypeError('Chaining cycle detected for promise'));
    }
    let called;
    if (x != null && (typeof x === 'object' || typeof x === 'function')) {
        try {
            let then = x.then;
            if (typeof then === 'function') {
                then.call(x, y => {
                    if (called) return;
                    called = true;
                    resolvePromise(promise2, y, resolve, reject);
                }, r => {
                    if (called) return;
                    called = true;
                    reject(r);
                });
            } else {
                resolve(x);
            }
        } catch (e) {
            if (called) return;
            called = true;
            reject(e);
        }
    } else {
        resolve(x);
    }
}

解释

  1. 状态管理:Promise 初始状态为 pending。通过 resolvereject 函数可以将状态变为 fulfilledrejected。一旦状态改变,就不能再修改。
  2. then 方法then 方法接受两个回调函数,分别对应 Promise 解决和拒绝时的处理。它返回一个新的 Promise,以实现链式调用。若 then 方法中回调函数抛出错误,则返回的 Promise 进入 rejected 状态。
  3. 回调队列:在 pending 状态时,then 方法中的回调函数会被添加到相应的回调队列中(onFulfilledCallbacksonRejectedCallbacks)。在状态变为 fulfilledrejected 时,回调队列中的函数会被依次执行。
  4. 异步执行:为确保 Promise 的回调函数是异步执行的,我们使用 setTimeout 将回调函数的执行推迟到 JavaScript 事件循环的下一个周期。
  5. resolvePromise 函数:这是一个辅助函数,用于处理 then 方法中返回的值。如果返回值是一个 Promise,则需要等待其完成;如果是一个普通值,则直接 resolve

这只是一个简化版的实现,用于展示 Promise 的核心原理。在实际应用中,Promise 的实现会更加复杂,需要考虑更多的边界情况和兼容性问题。

vuex实现原理

uex 是 Vue.js 应用的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。下面是 Vuex 实现原理的关键点:

1. 核心概念

  • State(状态) : Vuex 使用单一状态树,即一个对象就包含了全部应用层级状态。这样可以直接查找特定的状态,方便调试。
  • Getter: 类似于 Vue 的计算属性,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生改变时才会被重新计算。
  • Mutation(变更) : 更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。Vuex 中的 mutation 非常类似于事件,每个 mutation 都有一个字符串的事件类型(type)和一个回调函数(handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数。
  • Action: 类似于 mutation,不同在于:Action 提交的是 mutation,而不是直接变更状态。Action 可以包含任意异步操作。
  • Module: 由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。为了解决这个问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter,甚至是嵌套子模块——从上至下进行同样方式的分割。

2. 实现原理

2.1. State

Vuex 的 state 存储在一个单一对象中,通过 new Vuex.Store 创建。

const store = new Vuex.Store({
  state: {
    count: 0
  }
})
2.2. Getter

通过 getter 来获取 state 的某些状态。

const store = new Vuex.Store({
  state: {
    count: 0
  },
  getters: {
    getCount: state => state.count
  }
})
2.3. Mutation

Mutation 是 Vuex 中改变状态的唯一方法,它必须是同步函数。

const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state) {
      state.count++
    }
  }
})

通过 store.commit 触发 mutation。

store.commit('increment')
2.4. Action

Action 类似于 mutation,不同的是:

  1. Action 提交的是 mutation,而不是直接变更状态。
  2. Action 可以包含任意异步操作。
const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state) {
      state.count++
    }
  },
  actions: {
    incrementAsync ({ commit }) {
      setTimeout(() => {
        commit('increment')
      }, 1000)
    }
  }
})

通过 store.dispatch 分发 action。

store.dispatch('incrementAsync')
2.5. Module

Vuex 允许我们将 store 分割成模块(module),每个模块拥有自己的 state、mutation、action、getter,甚至是嵌套子模块。

const moduleA = {
  state: { count: 0 },
  mutations: { increment (state) { state.count++ } },
  actions: { incrementIfOddOnRootSum ({ state, commit, rootState }) { if ((state.count + rootState.count) % 2 === 1) { commit('increment') } } },
  getters: { doubleCount (state) { return state.count * 2 } }
}
​
const moduleB = {
  state: { count: 0 },
  mutations: { increment (state) { state.count++ } },
  actions: { increment ({ commit }) { commit('increment') } },
  getters: { doubleCount (state) { return state.count * 2 } }
}
​
const store = new Vuex.Store({
  modules: {
    a: moduleA,
    b: moduleB
  }
})
3. 数据流

Vuex 中的数据流如下:

  1. 组件调用:组件调用 dispatch 去触发一个 action 或调用 commit 去触发一个 mutation。
  2. Action 调用:Action 可以包含异步操作,在其内部可以触发多个 mutation。
  3. Mutation 调用:Mutation 必须是同步函数,在它的回调函数里改变 state。
  4. State 变更:State 被响应式地更新。

这种单向数据流确保了数据的可追踪性,使应用的状态管理更加可预测和可维护。

img

公共最长前缀

// 最长公共前缀
// 先判断数组长度,若为0则返回空字符串
// 记录第一个字符串,循环对比,prefix逐渐缩短,判断是否存在公共前缀
function longestCommonPrefix(strs) {
    if(!strs.length){
        return ''
    }
​
    let prefix = strs[0]
    for(let i = 0;i<strs.length;i++){
        while(strs[i].indexOf(prefix) !== 0){
            prefix = prefix.substring(0,prefix.length-1)
        }
    }
    return prefix
}
console.log(longestCommonPrefix(["flower","flow","flight"]));