Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

常见前端面试题 #6

Open
Jogiter opened this issue Jun 8, 2018 · 21 comments
Open

常见前端面试题 #6

Jogiter opened this issue Jun 8, 2018 · 21 comments
Labels
question Further information is requested

Comments

@Jogiter Jogiter closed this as completed Jun 8, 2018
@Jogiter Jogiter added the question Further information is requested label Jun 8, 2018
@Jogiter Jogiter reopened this Jun 8, 2018
@Jogiter
Copy link
Owner Author

Jogiter commented Mar 26, 2019

前端内容

  • 基础
    • js 基本类型:Undefined 、 Null 、 Boolean 、 Number 、 String 、 Symbol
      • 对象的继承
      • exports 和 module.exports 的区别?
    • 原型链:原型链是由原型对象组成,每个对象都有 __proto__ 属性,指向了创建该对象的构造函数的原型,__proto__ 将对象连接起来组成了原型链。是一个用来实现继承和共享属性的有限的对象链。
      • 属性查找机制: 当查找对象的属性时,如果实例对象自身不存在该属性,则沿着原型链往上一级查找,找到时则输出,不存在时,则继续沿着原型链往上一级查找,直至最顶级的原型对象Object.prototype,如还是没找到,则输出undefined;
      • 属性修改机制: 只会修改实例对象本身的属性,如果不存在,则进行添加该属性,如果需要修改原型的属性时,则可以用: b.prototype.x = 2;但是这样会造成所有继承于该对象的实例的属性发生改变。
    • 继承的实现
    • 闭包
    • 垃圾回收机制
  • 异步
  • http
  • Nodejs
    • stream
    • exports 和 module.exports 的区别
    • nextTick
    • process
    • 线程和进程的区别
  • 前端性能优化
    • 缓存
    • 打包
      • sourcemap 实现原理
      • AST 树
      • tree-shaking
    • 延迟加载
    • svg、webp 等
  • 编程方式
    • oop
    • functional
    • 设计模式
      • 单例模式
      • 原型模式
      • 工厂模式
      • 观察者模式:观察者是知道Subject的,Subject一直保持对观察者进行记录。大多数时候是同步的。
      • 发布订阅模式:发布者和订阅者不知道对方的存在。它们只有通过消息代理进行通信。大多数时候是异步的。
      • 策略模式
      • 代理模式
  • 框架相关知识
    • vue
      • 双向绑定的实现
      • 生命周期
      • 组件通信
      • mixins、extends 混合策略、区别
      • keep-alive
        • 将不活动的组件实例保存在内存中,而不是直接将其销毁,它是一个抽象组件,不会被渲染到真实DOM中,也不会出现在父组件链中。
        • 生命周期;activated 与 deactivated
        • created 钩子会创建一个 cache 对象,用来作为缓存容器,保存vnode节点。destroyed 钩子则在组件被销毁的时候清除 cache 缓存中的所有组件实例
      • vdom
      • diff 算法
      • ssr 原理:将同一个组件渲染为服务器端的 HTML 字符串,将它们直接发送到浏览器,最后将这些静态标记"激活"为客户端上完全可交互的应用程序
      • vue 性能优化
        • style-guide
        • 按需加载:i18n、store、svg、使用动态引入来分包
      • 3.0 升级点
      • 20+Vue面试题整理
  • 算法相关知识
  • nodejs 相关知识
    • stream
  • 跨端
    • JSBridge
    • nm
    • electron
    • flutter
  • 工程化
    • vscode + plugin
    • git + git-flow + pr + codeReview
    • webpack
      • loaders
      • plugins
      • 优化打包速度
        • webpack.DllReferencePlugin:动态链接库(dll),这在 windows 系统下面是一种很常见的思想。一个 dll 包,就是一个很纯净的依赖库,它本身不能运行,是用来给你的 app 或者业务代码引用的。
        • happypack/thread-loader:多线程打包,加快打包速度
        • cache
      • tree-shaking;uglify完成了javascript的DCE(dead code elimination)。依赖于ES6的模块特性。
    • devops
      • 日志 & 监控
      • 自动化测试
      • 持续集成
      • 持续部署
  • 组件化
    • css 基础
    • 预编译 sass、less、stylus 等
    • tialwind.css 组件库等
    • 组件开发

@Jogiter
Copy link
Owner Author

Jogiter commented Apr 8, 2019

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
Copy link
Owner Author

Jogiter commented Apr 10, 2019

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
Copy link
Owner Author

Jogiter commented Apr 10, 2019

  1. oauth2
  2. 二维码实现原理

@Jogiter
Copy link
Owner Author

Jogiter commented Apr 11, 2019

改造下面的代码,使之输出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
Copy link
Owner Author

Jogiter commented Apr 11, 2019

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

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

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

@Jogiter
Copy link
Owner Author

Jogiter commented Apr 11, 2019

下面代码中 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
Copy link
Owner Author

Jogiter commented Apr 11, 2019

博客: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
Copy link
Owner Author

Jogiter commented Apr 11, 2019

实现 (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
Copy link
Owner Author

Jogiter commented Apr 11, 2019

阅读链接:

缓存

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

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

强缓存

实现强缓存可以通过两种响应头实现: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
Copy link
Owner Author

Jogiter commented Apr 14, 2019

算法

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
Copy link
Owner Author

Jogiter commented Apr 15, 2019

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
Copy link
Owner Author

Jogiter commented Apr 15, 2019

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
Copy link
Owner Author

Jogiter commented Apr 15, 2019

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
Copy link
Owner Author

Jogiter commented May 5, 2020

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
Copy link
Owner Author

Jogiter commented May 28, 2020

Vue 相关

  • vue-router 源码阅读。通过 mixins 的方式,在 beforeCreate 生命周期里,给 vm 添加了 _route_router 属性;然后在 vue.prototype 上添加了 $router$route 属性
  • vuex 源码阅读。通过 mixins 的方式,在 beforeCreate 生命周期里,给 vm 添加了 $store 属性。
  • mixins :按照官网文档中给出的合并策略如下
    • data 数据对象在内部会进行递归合并,并在发生冲突时以组件数据优先
    • 同名钩子函数将合并为一个数组,因此都将被调用mixins 中的钩子将优先调用
    • 值为对象的选项,例如 methodscomponentsdirectives,将被合并为同一个对象。两个对象键名冲突时,取组件对象的键值对
    • Vue.extend() 也使用同样的策略进行合并。
  • vue 组件通信方式
    • props/$emit
    • $emit/$on eventbus
    • vuex
    • $attrs/$listeners
    • provide/inject
    • $parent/$children 与 ref
    • Vue.observable (2.6+)
  • vue 组件内的 data 为什么要是函数?
    • 如果 data 仍然是一个纯粹的对象,则所有的实例将共享引用同一个数据对象!通过提供 data 函数,每次创建一个新实例后,我们能够调用 data 函数,从而返回初始数据的一个全新副本数据对象。
  • vue 组件递归
  • vue slot/scoped-slot
  • vue v-model 实现

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

@Jogiter
Copy link
Owner Author

Jogiter commented Dec 16, 2021

基础问题:

  • 原型链
    • new 和 Object.create 的区别

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

  • Object.create(null) 和 Object.create({}) 的区别

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

@Jogiter
Copy link
Owner Author

Jogiter commented Dec 16, 2021

es6 新增语法有哪些

@Jogiter
Copy link
Owner Author

Jogiter commented Dec 16, 2021

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

@Jogiter
Copy link
Owner Author

Jogiter commented Dec 16, 2021

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

@Jogiter
Copy link
Owner Author

Jogiter commented Nov 16, 2022

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));
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

1 participant