跳至主要內容

面试题

Harry Xiong大约 8 分钟Web 前端interview

面试题

网络相关

http&httpsopen in new window

Http2和Http1.X的区别open in new window

强制缓存和协商缓存open in new window

cookie有哪些字段open in new window

options请求的理解open in new window

HTTP长连接和短连接open in new window

TCP三次握手四次挥手open in new window

计算机网络协议层次open in new window

TCP与UDP的区别open in new window

TCP流量控制和拥塞控制open in new window

DNS域名解析open in new window

CDNopen in new window

安全相关

前端安全open in new window

跨域问题open in new window

JSONPopen in new window

前端监控open in new window

XSS和XSRFopen in new window

技术相关

事件冒泡与捕获open in new window

事件冒泡与捕获2open in new window

负载均衡的实现方式open in new window

事件代理与应用场景open in new window

前端性能优化open in new window

浏览器渲染原理open in new window

Node.js 中的内存泄漏open in new window

SSR优缺点open in new window

前端常见的9种设计模式open in new window

手写迭代器open in new window

token 加密原理open in new window

Webpack 相关open in new window

JS Bridgeopen in new window

标签语义化的理解open in new window

协程的理解open in new window

JavaScript中的协程open in new window

浏览器的帧、requestIdleCallback和requestAnimationFrame详解open in new window

Bom和Dom的区别总结open in new window

给js对象添加迭代器open in new window

js闭包的理解open in new window

Vue 与 react

React、Vue2、Vue3 的 diff 算法open in new window

Vue 响应式原理open in new window

React 与 Vue 框架的设计思路open in new window

vue-router的两种模式的区别open in new window

为什么Vue3不使用时间切片open in new window

React异步渲染(时间切片&渲染挂起)open in new window

Vue3新特性open in new window

前端场景相关

微信小程序登录鉴权流程图open in new window

从URL输入到页面展现过程open in new window

浏览器的两个页面之间通信的问题open in new window

网页白屏问题分析open in new window

排查网页打开慢的方法open in new window

前端下载文件的5种方法open in new window

实现一个大文件上传和断点续传open in new window

微信小程序登录鉴权流程图open in new window

计算机基础相关

计算机启动过程open in new window

进程间8种通信方式open in new window

进程间通信和线程间通信的几种方式open in new window

数据库三大范式理解open in new window

代码题

CSS相关

css将div画成三角形open in new window

css画扇形open in new window

CSS两栏布局、三栏布局open in new window

css实现水平垂直居中open in new window

CSS动画和JS动画的区别open in new window

高度塌陷&BFCopen in new window

盒子定位open in new window

flex布局open in new window

JS相关

JS常见手写笔试open in new window

vue-简易版message组件open in new window

解析 URL 提取 params 参数open in new window

斐波那契数列的js实现open in new window

求解topK--快排、大小堆顶open in new window

手写Promise.all和Promise.raceopen in new window

算法相关

排序、查找JS实现open in new window

常见的排序和查找算法open in new window

平衡二叉树open in new window

大顶堆构造过程open in new window

hash冲突解决open in new window

杂记

有效的括号

var isValid = function(s) {
    const n = s.length;
    if (n % 2 === 1) {
        return false;
    }
    const pairs = new Map([
        [')', '('],
        [']', '['],
        ['}', '{']
    ]);
    const stk = [];
    for (let ch of s){
        if (pairs.has(ch)) {
            if (!stk.length || stk[stk.length - 1] !== pairs.get(ch)) {
                return false;
            }
            stk.pop();
        } 
        else {
            stk.push(ch);
        }
    };
    return !stk.length;
};

判断this

var length = 10;
function fn() {
 return this.length + 1;
}
var obj1 = {
  length: 5,
  test1: function() {
    return fn()
 }
}
obj1.test2 = fn;
let res1 = obj1.test1.call()
let res2 = obj1.test1()
let res3 = obj1.test2.call()
let res4 = obj1.test2()

console.log(res1, res2, res3, res4); // 11 11 11 6

原型链判断

Object.prototype.__proto__; //null
Function.prototype.__proto__; //Object.prototype
Object.__proto__; //Function.prototype
Object instanceof Function; //true
Function instanceof Object; //true
Function.prototype === Function.__proto__; //true

比较版本号

function compare(version1, version2) {
    let arr1 = version1.split('.'), arr2 = version2.split('.')
    const len1 = arr1.length, len2 = arr2.length;
    const len = Math.max(len1, len2);
    while(arr1.length < len) {
        arr1.push(0);
    }
    while(arr2.length < len) {
        arr2.push(0);
    }
    for (let i = 0; i < len; i++) {
        let num1 = parseInt(arr1[i]), num2 = parseInt(arr2[i]);
        if (num1 < num2) {
            return -1;
        } else if (num1 > num2) {
            return 1;
        }
    }
    return 0;
}
 
console.log(compare("0.1", "1.1"));
console.log(compare("1.0.1", "1"));
console.log(compare("7.5.2.4", "7.5.3"));
console.log(compare("1.01", "1.001"));
console.log(compare("1.0", "1.0.0"));

说输出

// ./a.js
let count = 1;

setCount = () => {
count++;
}

setTimeout(() => {
console.log('a', count)
}, 1000);

module.exports = {
count,
setCount
}

//b.js
const obj = require('./a.js');

obj.setCount();

console.log('b', obj.count)

setTimeout(() => {
console.log('b next', obj.count);
}, 2000);

// b 1
// a 2
// b next 1

说输出2

function test(a,b) {
  console.log(b)
  return {
    test:function(c){
      return test(c,a);
    }
  };
}
var retA = test(0);  
retA.test(2);  
retA.test(4);  
retA.test(8);
var retB = test(0).test(2).test(4).test(8);
var retC = test('good').test('bad');  
retC.test('good');  
retC.test('bad');

// undefined
// 0        
// 0        
// 0        
// undefined
// 0        
// 2
// 4
// undefined
// good
// bad
// bad

mul函数

写一个mul函数,使用方法如下:

console.log(mul(2)(3)(4)); // output : 24 
console.log(mul(4)(3)(4)); // output : 48

答案直接给出:

function mul (x) {
    return function (y) { // anonymous function 
        return function (z) { // anonymous function 
            return x * y * z; 
        };
    };
}

mul 返回一个匿名函数,运行这个匿名函数又返回一个匿名函数,最里面的匿名函数可以访问 x,y,z 进而算出乘积返回即可。

对于JavaScript中的函数一般可以考察如下知识点:

  1. 函数是一等公民
  2. 函数可以有属性,并且能连接到它的构造方法
  3. 函数可以像一个变量一样存在内存中
  4. 函数可以当做参数传给其他函数
  5. 函数可以返回其他函数

二分查找

// 一般二分
export function search(nums: number[], target: number): number {
  let left: number = 0;
  let right: number = nums.length - 1;
  while (left <= right) {
    let middle: number = Math.floor((left + right) / 2);
    if (nums[middle] === target) {
      return middle;
    } else if (nums[middle] < target) {
      left = middle + 1;
    } else if (nums[middle] > target) {
      right = middle - 1;
    }
  }
  return -1
};

// 左右二分
export function searchRange(nums: number[], target: number): number[] {
  let left: number = searchBound(nums, target, true);
  let right: number = searchBound(nums, target, false);
  return [left, right];
};

export function searchBound(nums: number[], target: number, isLeft: boolean): number {
  let left: number = 0;
  let right: number = nums.length - 1;
  let result = -1;
  while (left <= right) {
    let mid = Math.floor((left + right) / 2);
    if (nums[mid] === target) {
      result = mid;
      if (isLeft) {
        right = mid - 1;
      } else {
        left = mid + 1;
      }
    } else if (nums[mid] > target) {
      right = mid - 1;
    } else if (nums[mid] < target) {
      left = mid + 1;
    }
  }
  return result;
}

// test
let res = searchRange([5, 7, 7, 8, 8, 10], 8);
console.log(res);

寻找单链表的倒数第 k 个元素

class ListNode {
  val: number;
  next: ListNode | null;
  constructor(val?: number, next?: ListNode | null) {
    this.val = (val === undefined ? 0 : val);
    this.next = (next === undefined ? null : next);
  }
}

// 画图一目了然整个过程
export function findIndex(head: ListNode | null, k: number): ListNode | null {
  let fast: ListNode | null;
  let slow: ListNode | null;
  fast = slow = head;
  while ( k-- > 0) {
    fast = fast!.next;
  }
  while (fast !== null) {
    fast = fast.next
    slow = slow!.next;
  }
  return slow;
}

寻找无环单链表的中点

class ListNode {
  val: number;
  next: ListNode | null;
  constructor(val?: number, next?: ListNode | null) {
    this.val = (val === undefined ? 0 : val);
    this.next = (next === undefined ? null : next);
  }
}

export function isMiddle(head: ListNode | null): ListNode | null {
  let fast: ListNode | null;
  let slow: ListNode | null;
  fast = slow = head;
  // 因为 fast 始终是 slow 的两倍,所以当 fast 走完的时候,slow 刚好是 fast 的一半,即链表中点
  while (fast !== null && fast.next !== null) {
    fast = fast.next.next;
    slow = slow!.next;
  }
  return slow;
}

有效三角形的个数

let triangleNumber = function(nums) {
    if(!nums || nums.length < 3) return 0
    let count = 0
    // 排序
    nums.sort((a, b) => a - b) 
    for(let k = nums.length - 1; k > 1; k--){
        let i = 0, j = k - 1
        while(i < j){ 
            if(nums[i] + nums[j] > nums[k]){
                count += j - i
                j--
            } else {
                i++
            }
        }
    }       
    return count
}

浮点数相乘

function mut(num1, num2) {
 
  const numStr1 = num1.toString(), numStr2 = num2.toString();
  if (numStr1.indexOf(".") == -1 && numStr2.indexOf(".") == -1)
    return num1 * num2;
 
  const [a1, a2 = 1] = numStr1.split(".").map((item) => parseInt(item));
  const [b1, b2 = 1] = numStr2.split(".").map((item) => parseInt(item));
  let res1 = a1 * b1;
  let res2 = a2 * b2;
  let res = res1 + "." + res2;
  return parseFloat(res);
}

防抖和节流

// 防抖函数
const debounce = (fn, delay) => {
  let timer = null;
  return (...args) => {
    clearTimeout(timer);
    timer = setTimeout(() => {
      fn.apply(this, args);
    }, delay);
  };
};
// 节流函数
const throttle = (fn, delay = 500) => {
  let flag = true;
  return (...args) => {
    if (!flag) return;
    flag = false;
    setTimeout(() => {
      fn.apply(this, args);
      flag = true;
    }, delay);
  };
};

转化为驼峰命名

var s1 = "get-element-by-id"

// 转化为 getElementById
复制代码var f = function(s) {
    return s.replace(/-\w/g, function(x) {
        return x.slice(1).toUpperCase();
    })
}

模拟new

new操作符做了这些事:

  1. 它创建了一个全新的对象
  2. 它会被执行[[Prototype]](也就是__proto__)链接
  3. 它使this指向新创建的对象
  4. 通过new创建的每个对象将最终被[[Prototype]]链接到这个函数的prototype对象上
  5. 如果函数没有返回对象类型Object(包含Functoin, Array, Date, RegExg, Error),那么new表达式中的函数调用将返回该对象引用
// objectFactory(name, 'cxk', '18')
function objectFactory() {
  const obj = new Object();
  const Constructor = [].shift.call(arguments);

  obj.__proto__ = Constructor.prototype;

  const ret = Constructor.apply(obj, arguments);

  return typeof ret === "object" ? ret : obj;
}

顺时针打印矩阵

输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。

可以模拟打印矩阵的路径。初始位置是矩阵的左上角,初始方向是向右,当路径超出界限或者进入之前访问过的位置时,顺时针旋转,进入下一个方向。

判断路径是否进入之前访问过的位置需要使用一个与输入矩阵大小相同的辅助矩阵 \textit{visited}visited,其中的每个元素表示该位置是否被访问过。当一个元素被访问时,将 \textit{visited}visited 中的对应位置的元素设为已访问。

如何判断路径是否结束?由于矩阵中的每个元素都被访问一次,因此路径的长度即为矩阵中的元素数量,当路径的长度达到矩阵中的元素数量时即为完整路径,将该路径返回。

var spiralOrder = function(matrix) {
    if (!matrix.length || !matrix[0].length) {
        return [];
    }
    const rows = matrix.length, columns = matrix[0].length;
    const visited = new Array(rows).fill(0).map(() => new Array(columns).fill(false));
    const total = rows * columns;
    const order = new Array(total).fill(0);

    let directionIndex = 0, row = 0, column = 0;
    const directions = [[0, 1], [1, 0], [0, -1], [-1, 0]];
    for (let i = 0; i < total; i++) { 
        order[i] = matrix[row][column];
        visited[row][column] = true;
        const nextRow = row + directions[directionIndex][0], nextColumn = column + directions[directionIndex][1];
        if (!(0 <= nextRow && nextRow < rows && 0 <= nextColumn && nextColumn < columns && !(visited[nextRow][nextColumn]))) {
            directionIndex = (directionIndex + 1) % 4;
        }
        row += directions[directionIndex][0];
        column += directions[directionIndex][1];
    }
    return order;
};