Jogiter / jogiter.github.com

Jogiter`s Blog
https://blog.jogiter.cn/
3 stars 0 forks source link

常见前端面试题 #6

Open Jogiter opened 6 years ago

Jogiter commented 6 years ago

面试过程中,需要回答和提问的问题(参考 tech-interview-handbook


面试题:

Jogiter commented 5 years ago

前端内容

Jogiter commented 5 years ago

eventloop 的理解, 我的博客

Q1:

setTimeout(function() {
    setTimeout(function() { console.log(1) }, 100)
    console.log(2)
    setTimeout(function() { console.log(3) }, 0)
}, 0)

setTimeout(function () {
    console.log(4)
}, 100) 

console.log(5)
// 输出顺序: 5 2 3 4 1

Q2:

console.log('script start')
let promise1 = new Promise(function (resolve) {
    console.log('promise1')
    resolve()
    console.log('promise1 end')
}).then(function () {
    console.log('promise2')
})
setTimeout(function(){
    console.log('settimeout')
})
console.log('script end')
// 输出顺序: script start->promise1->promise1 end->script end->promise2->settimeout

Q3:

async function async1(){
   console.log('async1 start');
    await async2();
    console.log('async1 end')
}
async function async2(){
    console.log('async2')
}

console.log('script start');
async1();
console.log('script end')

// 输出顺序:script start->async1 start->async2->script end->async1 end

Q4:

function* generator(i) {
  console.log('inside before')
  yield i;
  yield i + 10;
  console.log('inside after')
}

var gen = generator(10);
console.log('outside before')
console.log(gen.next().value);
console.log(gen.next().value);
console.log('outside after')
gen.next();

/**
 * outside before
 * inside before
 * 10
 * 20
 * outside after
 * inside after // 如果不加最后一个gen.next(); 就不会有这一行
 */
Jogiter commented 5 years ago

Q: 完成一个 sum 函数,使调用后输出 6

考点:高阶函数,高级柯里化,参见 博客:higher-order-function

sum(1)(2)(3).valueOf()
function curryingHelper(fn, ...args) {
  return (...newArgs) => {
    return fn.apply(this, args.concat(newArgs));
  }
}

// fn.length:获取 fn 的参数个数
function betterCurryingHelper(fn, length = fn.length) {
  return (...args) => {
    let allArgsFulfilled = args.length >= length

    // 如果参数全部满足,就可以终止递归调用
    if (allArgsFulfilled) {
      return fn.apply(this, args);
    } else {
      let argsNeedFulfilled = [fn].concat(args);
      return betterCurryingHelper(curryingHelper.apply(this, argsNeedFulfilled), length - args.length)
    }
  }
}

function add(a, b, c) {
  return {
    valueOf() {
      return a + b + c
    }
  }
}

var sum = betterCurryingHelper(add)

sum(1)(2)(3).valueOf()
Jogiter commented 5 years ago
  1. oauth2
  2. 二维码实现原理
Jogiter commented 5 years ago

改造下面的代码,使之输出0 - 9,写出你能想到的所有解法。

for (var i = 0; i< 10; i++){
    setTimeout(() => {
        console.log(i);
    }, 1000)
}

解:

// 解一
for (var i = 0; i< 10; i++){
   setTimeout((i) => {
   console.log(i);
   }, 1000, i)
}

// 解二
for (let i = 0; i< 10; i++){
  setTimeout(() => {
    console.log(i);
  }, 1000)
}

// 解三
for (var i = 0; i< 10; i++){
   setTimeout((function(i) {
    console.log(i);
    })(i), 1000)
}
Jogiter commented 5 years ago

下面的代码打印什么内容,为什么?

非匿名自执行函数,函数名只读。

var b = 10;
(function b(){
    b = 20;
    console.log(b); 
})();
/**
ƒ b() {
  b = 20;
  console.log(b)
}
*/
  1. 函数表达式与函数声明不同,函数名只在该函数内部有效,并且此绑定是常量绑定。
  2. 对于一个常量进行赋值,在 strict 模式下会报错,非 strict 模式下静默失败。
  3. IIFE中的函数是函数表达式,而不是函数声明。
Jogiter commented 5 years ago

下面代码中 a 在什么情况下会打印 1?

var a = ?;
if(a == 1 && a == 2 && a == 3){
    conso.log(1);
}

答案解析 因为==会进行隐式类型转换 所以我们重写toString方法就可以了

var a = {
  i: 1,
  toString() {
    return a.i++;
  }
}

if( a == 1 && a == 2 && a == 3 ) {
  console.log(1);
}
var a = {
  i: 1,
  valueOf() {
    return a.i++;
  }
}

if( a == 1 && a == 2 && a == 3 ) {
  console.log(1);
}
Jogiter commented 5 years ago

博客:js 字符串比较大小

[3, 15, 8, 29, 102, 22].sort() 
// [102, 15, 22, 29, 3, 8]

[3, 15, 8, 29, 102, 22].sort((a, b) => a - b) 
// [3, 8, 15, 22, 29, 102]
Jogiter commented 5 years ago

实现 (5).add(3).minus(2) 功能,参考 Daily-Interview-Question#88

简易版

Number.prototype.add = function(n) {
  return this.valueOf() + n;
};
Number.prototype.minus = function(n) {
  return this.valueOf() - n;
};

完整版

Number.MAX_SAFE_DIGITS = Number.MAX_SAFE_INTEGER.toString().length-2
Number.prototype.digits = function(){
    let result = (this.valueOf().toString().split('.')[1] || '').length
    return result > Number.MAX_SAFE_DIGITS ? Number.MAX_SAFE_DIGITS : result
}
Number.prototype.add = function(i=0){
    if (typeof i !== 'number') {
            throw new Error('请输入正确的数字');
        }
    const v = this.valueOf();
    const thisDigits = this.digits();
    const iDigits = i.digits();
    const baseNum = Math.pow(10, Math.max(thisDigits, iDigits));
    const result = (v * baseNum + i * baseNum) / baseNum;
    if(result>0){ return result > Number.MAX_SAFE_INTEGER ? Number.MAX_SAFE_INTEGER : result }
    else{ return result < Number.MIN_SAFE_INTEGER ? Number.MIN_SAFE_INTEGER : result }
}
Number.prototype.minus = function(i=0){
    if (typeof i !== 'number') {
            throw new Error('请输入正确的数字');
        }
    const v = this.valueOf();
    const thisDigits = this.digits();
    const iDigits = i.digits();
    const baseNum = Math.pow(10, Math.max(thisDigits, iDigits));
    const result = (v * baseNum - i * baseNum) / baseNum;
    if(result>0){ return result > Number.MAX_SAFE_INTEGER ? Number.MAX_SAFE_INTEGER : result }
    else{ return result < Number.MIN_SAFE_INTEGER ? Number.MIN_SAFE_INTEGER : result }
}
Jogiter commented 5 years ago

阅读链接:

缓存

缓存对于前端性能优化来说是个很重要的点,良好的缓存策略可以降低资源的重复加载提高网页的整体加载速度。

通常浏览器缓存策略分为两种:强缓存和协商缓存。

强缓存

实现强缓存可以通过两种响应头实现:ExpiresCache-Control 。强缓存表示在缓存期间不需要请求,state code 为 200

Expires: Wed, 22 Oct 2018 08:41:00 GMT

ExpiresHTTP / 1.0 的产物,表示资源会在 Wed, 22 Oct 2018 08:41:00 GMT 后过期,需要再次请求。并且 Expires 受限于本地时间,如果修改了本地时间,可能会造成缓存失效。

Cache-control: max-age=30

Cache-Control 出现于 HTTP / 1.1,优先级高于 Expires 。该属性表示资源会在 30 秒后过期,需要再次请求。

协商缓存

如果缓存过期了,我们就可以使用协商缓存来解决问题。协商缓存需要请求,如果缓存有效会返回 304。 协商缓存需要客户端和服务端共同实现。

Jogiter commented 5 years ago

算法

1.快排

function quickSort(arr){
    //如果数组<=1,则直接返回
    if(arr.length<=1){return arr;}
    var pivotIndex=Math.floor(arr.length/2);
    //找基准,并把基准从原数组删除
    var pivot=arr.splice(pivotIndex,1)[0];
    //定义左右数组
    var left=[];
    var right=[];

    //比基准小的放在left,比基准大的放在right
    for(var i=0;i<arr.length;i++){
        if(arr[i]<=pivot){
            left.push(arr[i]);
        }
        else{
            right.push(arr[i]);
        }
    }
    //递归
    return quickSort(left).concat([pivot],quickSort(right));
}

2.给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序

示例:

输入: [0,1,0,3,12]
输出: [1,3,12,0,0]

说明:

Answer:

思路:双指针

设定一个慢指针一个快指针,快指针每次+1, 当慢指针的值不等于0的时候也往后移动,当慢指针等于0并且快指针不等于0的时候,交换快慢指针的值,慢指针再+1

function moveZero(arr) {
  let i = 0
  let j = 0
  while (j < arr.length) {
    if (arr[i] !== 0) {
      i++
    } else if (arr[j] !== 0) {
      ;[arr[i], arr[j]] = [arr[j], arr[i]]
      i++
    }
    j++
  }
}

时间复杂度O(n),n是数组长度,空间复杂度O(1)

Jogiter commented 5 years ago

array.flat(depth)

var arr1 = [1, 2, [3, 4]];
arr1.flat();

// 反嵌套一层数组
arr1.reduce((acc, val) => acc.concat(val), []);// [1, 2, 3, 4]

// 或使用 ...
const flatSingle = arr => [].concat(...arr);
// 使用 reduce、concat 和递归无限反嵌套多层嵌套的数组
var arr1 = [1,2,3,[1,2,3,4, [2,3,4]]];

function flattenDeep(arr1) {
   return arr1.reduce((acc, val) => Array.isArray(val) ? acc.concat(flattenDeep(val)) : acc.concat(val), []);
}
flattenDeep(arr1);
// [1, 2, 3, 1, 2, 3, 4, 2, 3, 4]

参考 MDN:Array 的一些用法:

修改 array.length

var fruits = [];
fruits.push('banana', 'apple', 'peach');
fruits[5] = 'mango'; // 给超出当前数组大小的下标赋值

console.log(Object.keys(fruits));  // ['0', '1', '2', '5']
console.log(fruits.length); // 6
console.log(fruits); // ["banana", "apple", "peach", empty × 2, "mango"]

fruits.length = 2; // 为 length 赋一个更小的值则会删掉一部分元素
console.log(Object.keys(fruits)); // ['0', '1']
console.log(fruits.length); // 2

Array.from(arrayLike[, mapFn[, thisArg]])

Array.from('foo') // ["f", "o", "o"]

function f() {
  return Array.from(arguments);
}
f(1, 2, 3); // [1, 2, 3]

Array.from({length: 5}, (v, i) => i); // [0, 1, 2, 3, 4]
Array.from({length: 5}, () => 6); // [6, 6, 6, 6, 6]
Array(5).fill(6); // [6, 6, 6, 6, 6]
Array(5).join('6').split(''); // ["6", "6", "6", "6"]

// !Objects by reference.
var arr = Array.from({length: 3}).fill({}) // [{}, {}, {}];
arr[0].hi = "hi"; // [{ hi: "hi" }, { hi: "hi" }, { hi: "hi" }]

// !Objects by reference.
var arr = Array(3).fill([]) // [[], [], []];
arr[0].push("hi"); // [["hi"], ["hi"], ["hi"]]

// es6
Array.from({length: 3}, () => {}) // [undefined, undefined, undefined]
Array.from({length: 3}, () => ({})) // [{}, {}, {}]
Array.from({length: 3}, () => [])) // [[], [], []];

Q: 求两个数组的交集并集?

测试用例:

var nums1 = [1], nums2 = [1,1]; 
var nums1 = [1, 2, 2, 1], nums2 = [2, 2];
var nums1 = [1, 2, 2, 1], nums2 = [2, 2, 1];
// 交集
const intersect = (nums1, nums2) => {
  const map = {}
  const res = []
  for (let n of nums1) {
    if (map[n]) {
      map[n]++
    } else {
      map[n] = 1
    }
  }
  for (let n of nums2) {
    if (map[n] > 0) {
      res.push(n)
      map[n]--
    }
  }
  return res
}
// 并集
const union = (nums1, nums2) => Array.from(new Set(nums1.concat(nums2)))
const union = (nums1, nums2) =>nums1.concat(nums2).reduce((acc, val, index) => acc.includes(val) ? acc : acc.concat(val), [])
Jogiter commented 5 years ago

javascript 判断一个数字是否为质数

参考 MDN

function isPrime(element) {
  var start = 2;
  while (start <= Math.sqrt(element)) {
    if (element % start++ < 1) {
      return false;
    }
  }
  return element > 1;
}

正则表达式(待验证)

function isPrimeNum2(num){
    return !/^.?$|^(..+?)\1+$/.test(Array(num + 1).join('1'))
}
Jogiter commented 5 years ago

100 元的红包分给 10 个人抢,每个人最少 6 元,最多 12 元,求怎么分配?

function sharing(total, count, max, min = 0) {
  var array = Array(count).fill(min)
  var left = total - count * min
  var once
  var index

  while (left > 0) {
    once = Math.floor(Math.random() * (max - min))
    index = Math.floor(Math.random() * (count - 1))

    if (array[index] + once <= max) {
      array[index] += once
      left -= once
    }
  }

  return array
}

var x = sharing(100, 10, 12, 6)
console.log(x) // [12, 10, 10, 11, 11, 9, 10, 12, 9, 6]
Jogiter commented 4 years ago

Fibonacci 实现方式

递归非常耗费内存,因为需要同时保存成千上百个调用帧,很容易发生“栈溢出”错误(stack overflow)。

function Fibonacci (n) {
  if ( n <= 1 ) {return 1};

  return Fibonacci(n - 1) + Fibonacci(n - 2);
}

但对于尾递归来说,由于只存在一个调用帧,所以永远不会发生“栈溢出”错误。

function Fibonacci2 (n , ac1 = 1 , ac2 = 1) {
  if( n <= 1 ) {return ac2};

  return Fibonacci2 (n - 1, ac2, ac1 + ac2);
}

迭代版本:(n = 10e6, time: 66.8ms)

function iterFib(n) {
    let last = 1;
    let nextLast = 1;
    let result = 1;
    for (let i = 2; i < n; ++i) {
        result = last + nextLast;
        nextLast = last;
        last = result;
    }
    return result;
}

动态规划:(n = 10e6, time: 410.4ms)

function dynFib(n) {
    let val = [];
    for (let i = 0; i <= n; ++i) {
        val[i] = 0;
    }
    if (n === 1 || n === 2) {
        return 1;
    } else {
        val[1] = 1;
        val[2] = 2;
        for (let i = 3; i <= n; ++i) {
            val[i] = val[i - 1] + val[i - 2];
        }
    }
    return val[n - 1]
}

perf

function main(fn, x){
  console.time('t')
  fn(x)
  console.timeEnd('t')
}
main(Fibonacci2, 400) // t: 0.02099609375 ms
main(iterFib, 400)  // t: 0.010009765625 ms
main(dynFib, 400) // t: 0.097900390625 ms
Jogiter commented 4 years ago

Vue 相关

proxy reactive 实现 diff 算法 vdom 算法优化

Jogiter commented 2 years ago

基础问题:

new 适用于使用构造函数创建对象并继承原型链的场景,而 Object.create 适用于直接创建对象并继承指定对象的属性和方法的场景。根据具体的需求和场景,选择合适的创建方式。

Object.create(null) 创建的对象没有原型链,不继承任何属性和方法。 Object.create({}) 创建的对象具有原型链,继承传入对象的属性和方法。

Jogiter commented 2 years ago

es6 新增语法有哪些

Jogiter commented 2 years ago

手写 promise (async、await、generator、Iterator)

Jogiter commented 2 years ago

事件循环:async/await、setTimeout、nextTick、promise 的执行顺序

Jogiter commented 1 year ago

sleep 函数:

function sleepSync(delay) {
  let endTime = new Date().getTime() + parseInt(delay);
  while (new Date().getTime() < endTime);
}

function sleepAsync(delay) {
  return new Promise((resolve) => setTimeout(resolve, delay));
}