写点什么

前端经典面试题(有答案)

作者:loveX001
  • 2023-01-06
    浙江
  • 本文字数:6844 字

    阅读完需:约 22 分钟

Promise.resolve

Promise.resolve = function(value) {    // 1.如果 value 参数是一个 Promise 对象,则原封不动返回该对象    if(value instanceof Promise) return value;    // 2.如果 value 参数是一个具有 then 方法的对象,则将这个对象转为 Promise 对象,并立即执行它的then方法    if(typeof value === "object" && 'then' in value) {        return new Promise((resolve, reject) => {           value.then(resolve, reject);        });    }    // 3.否则返回一个新的 Promise 对象,状态为 fulfilled    return new Promise(resolve => resolve(value));}
复制代码

display 的属性值及其作用

代码输出结果

function Dog() {  this.name = 'puppy'}Dog.prototype.bark = () => {  console.log('woof!woof!')}const dog = new Dog()console.log(Dog.prototype.constructor === Dog && dog.constructor === Dog && dog instanceof Dog)
复制代码


输出结果:true


解析: 因为 constructor 是 prototype 上的属性,所以 dog.constructor 实际上就是指向 Dog.prototype.constructor;constructor 属性指向构造函数。instanceof 而实际检测的是类型是否在实例的原型链上。


constructor 是 prototype 上的属性,这一点很容易被忽略掉。constructor 和 instanceof 的作用是不同的,感性地来说,constructor 的限制比较严格,它只能严格对比对象的构造函数是不是指定的值;而 instanceof 比较松散,只要检测的类型在原型链上,就会返回 true。

代码输出结果

function Foo(){    Foo.a = function(){        console.log(1);    }    this.a = function(){        console.log(2)    }}
Foo.prototype.a = function(){ console.log(3);}
Foo.a = function(){ console.log(4);}
Foo.a();let obj = new Foo();obj.a();Foo.a();
复制代码


输出结果:4 2 1


解析:


  1. Foo.a() 这个是调用 Foo 函数的静态方法 a,虽然 Foo 中有优先级更高的属性方法 a,但 Foo 此时没有被调用,所以此时输出 Foo 的静态方法 a 的结果:4

  2. let obj = new Foo(); 使用了 new 方法调用了函数,返回了函数实例对象,此时 Foo 函数内部的属性方法初始化,原型链建立。

  3. obj.a() ; 调用 obj 实例上的方法 a,该实例上目前有两个 a 方法:一个是内部属性方法,另一个是原型上的方法。当这两者都存在时,首先查找 ownProperty ,如果没有才去原型链上找,所以调用实例上的 a 输出:2

  4. Foo.a() ; 根据第 2 步可知 Foo 函数内部的属性方法已初始化,覆盖了同名的静态方法,所以输出:1

如何减少 Webpack 打包体积

(1)按需加载

在开发 SPA 项目的时候,项目中都会存在很多路由页面。如果将这些页面全部打包进一个 JS 文件的话,虽然将多个请求合并了,但是同样也加载了很多并不需要的代码,耗费了更长的时间。那么为了首页能更快地呈现给用户,希望首页能加载的文件体积越小越好,这时候就可以使用按需加载,将每个路由页面单独打包为一个文件。当然不仅仅路由可以按需加载,对于 loadash 这种大型类库同样可以使用这个功能。


按需加载的代码实现这里就不详细展开了,因为鉴于用的框架不同,实现起来都是不一样的。当然了,虽然他们的用法可能不同,但是底层的机制都是一样的。都是当使用的时候再去下载对应文件,返回一个 Promise,当 Promise 成功以后去执行回调。

(2)Scope Hoisting

Scope Hoisting 会分析出模块之间的依赖关系,尽可能的把打包出来的模块合并到一个函数中去。


比如希望打包两个文件:


// test.jsexport const a = 1// index.jsimport { a } from './test.js'
复制代码


对于这种情况,打包出来的代码会类似这样:


[  /* 0 */  function (module, exports, require) {    //...  },  /* 1 */  function (module, exports, require) {    //...  }]
复制代码


但是如果使用 Scope Hoisting ,代码就会尽可能的合并到一个函数中去,也就变成了这样的类似代码:


[  /* 0 */  function (module, exports, require) {    //...  }]
复制代码


这样的打包方式生成的代码明显比之前的少多了。如果在 Webpack4 中你希望开启这个功能,只需要启用 optimization.concatenateModules 就可以了:


module.exports = {  optimization: {    concatenateModules: true  }}
复制代码

(3)Tree Shaking

Tree Shaking 可以实现删除项目中未被引用的代码,比如:


// test.jsexport const a = 1export const b = 2// index.jsimport { a } from './test.js'
复制代码


对于以上情况,test 文件中的变量 b 如果没有在项目中使用到的话,就不会被打包到文件中。


如果使用 Webpack 4 的话,开启生产环境就会自动启动这个优化功能。

怎么加事件监听,两种

onclick 和 addEventListener


参考 前端进阶面试题详细解答

基于 Localstorage 设计一个 1M 的缓存系统,需要实现缓存淘汰机制

设计思路如下:


  • 存储的每个对象需要添加两个属性:分别是过期时间和存储时间。

  • 利用一个属性保存系统中目前所占空间大小,每次存储都增加该属性。当该属性值大于 1M 时,需要按照时间排序系统中的数据,删除一定量的数据保证能够存储下目前需要存储的数据。

  • 每次取数据时,需要判断该缓存数据是否过期,如果过期就删除。


以下是代码实现,实现了思路,但是可能会存在 Bug,但是这种设计题一般是给出设计思路和部分代码,不会需要写出一个无问题的代码


class Store {  constructor() {    let store = localStorage.getItem('cache')    if (!store) {      store = {        maxSize: 1024 * 1024,        size: 0      }      this.store = store    } else {      this.store = JSON.parse(store)    }  }  set(key, value, expire) {    this.store[key] = {      date: Date.now(),      expire,      value    }    let size = this.sizeOf(JSON.stringify(this.store[key]))    if (this.store.maxSize < size + this.store.size) {      console.log('超了-----------');      var keys = Object.keys(this.store);      // 时间排序      keys = keys.sort((a, b) => {        let item1 = this.store[a], item2 = this.store[b];        return item2.date - item1.date;      });      while (size + this.store.size > this.store.maxSize) {        let index = keys[keys.length - 1]        this.store.size -= this.sizeOf(JSON.stringify(this.store[index]))        delete this.store[index]      }    }    this.store.size += size
localStorage.setItem('cache', JSON.stringify(this.store)) } get(key) { let d = this.store[key] if (!d) { console.log('找不到该属性'); return } if (d.expire > Date.now) { console.log('过期删除'); delete this.store[key] localStorage.setItem('cache', JSON.stringify(this.store)) } else { return d.value } } sizeOf(str, charset) { var total = 0, charCode, i, len; charset = charset ? charset.toLowerCase() : ''; if (charset === 'utf-16' || charset === 'utf16') { for (i = 0, len = str.length; i < len; i++) { charCode = str.charCodeAt(i); if (charCode <= 0xffff) { total += 2; } else { total += 4; } } } else { for (i = 0, len = str.length; i < len; i++) { charCode = str.charCodeAt(i); if (charCode <= 0x007f) { total += 1; } else if (charCode <= 0x07ff) { total += 2; } else if (charCode <= 0xffff) { total += 3; } else { total += 4; } } } return total; }}
复制代码

事件总线(发布订阅模式)

class EventEmitter {    constructor() {        this.cache = {}    }    on(name, fn) {        if (this.cache[name]) {            this.cache[name].push(fn)        } else {            this.cache[name] = [fn]        }    }    off(name, fn) {        let tasks = this.cache[name]        if (tasks) {            const index = tasks.findIndex(f => f === fn || f.callback === fn)            if (index >= 0) {                tasks.splice(index, 1)            }        }    }    emit(name, once = false, ...args) {        if (this.cache[name]) {            // 创建副本,如果回调函数内继续注册相同事件,会造成死循环            let tasks = this.cache[name].slice()            for (let fn of tasks) {                fn(...args)            }            if (once) {                delete this.cache[name]            }        }    }}
// 测试let eventBus = new EventEmitter()let fn1 = function(name, age) { console.log(`${name} ${age}`)}let fn2 = function(name, age) { console.log(`hello, ${name} ${age}`)}eventBus.on('aaa', fn1)eventBus.on('aaa', fn2)eventBus.emit('aaa', false, '布兰', 12)// '布兰 12'// 'hello, 布兰 12'
复制代码

常见浏览器所用内核

(1) IE 浏览器内核:Trident 内核,也是俗称的 IE 内核;


(2) Chrome 浏览器内核:统称为 Chromium 内核或 Chrome 内核,以前是 Webkit 内核,现在是 Blink 内核;


(3) Firefox 浏览器内核:Gecko 内核,俗称 Firefox 内核;


(4) Safari 浏览器内核:Webkit 内核;


(5) Opera 浏览器内核:最初是自己的 Presto 内核,后来加入谷歌大军,从 Webkit 又到了 Blink 内核;


(6) 360 浏览器、猎豹浏览器内核:IE + Chrome 双内核;


(7) 搜狗、遨游、QQ 浏览器内核:Trident(兼容模式)+ Webkit(高速模式);


(8) 百度浏览器、世界之窗内核:IE 内核;


(9) 2345 浏览器内核:好像以前是 IE 内核,现在也是 IE + Chrome 双内核了;


(10)UC 浏览器内核:这个众口不一,UC 说是他们自己研发的 U3 内核,但好像还是基于 Webkit 和 Trident ,还有说是基于火狐内核。

new 一个构造函数,如果函数返回 return {}return nullreturn 1return true 会发生什么情况?

如果函数返回一个对象,那么 new 这个函数调用返回这个函数的返回对象,否则返回 new 创建的新对象

vuex

vuex是一个专为vue.js应用程序开发的状态管理器,它采用集中式存储管理应用的所有组件的状态,并且以相应的规则保证状态以一种可以预测的方式发生变化。
state: vuex使用单一状态树,用一个对象就包含来全部的应用层级状态
mutation: 更改vuex中state的状态的唯一方法就是提交mutation
action: action提交的是mutation,而不是直接变更状态,action可以包含任意异步操作
getter: 相当于vue中的computed计算属性
复制代码

大数相加

题目描述:实现一个 add 方法完成两个大数相加


let a = "9007199254740991";let b = "1234567899999999999";
function add(a ,b){ //...}

复制代码


实现代码如下:


function add(a ,b){   //取两个数字的最大长度   let maxLength = Math.max(a.length, b.length);   //用0去补齐长度   a = a.padStart(maxLength , 0);//"0009007199254740991"   b = b.padStart(maxLength , 0);//"1234567899999999999"   //定义加法过程中需要用到的变量   let t = 0;   let f = 0;   //"进位"   let sum = "";   for(let i=maxLength-1 ; i>=0 ; i--){      t = parseInt(a[i]) + parseInt(b[i]) + f;      f = Math.floor(t/10);      sum = t%10 + sum;   }   if(f!==0){      sum = '' + f + sum;   }   return sum;}
复制代码

归并排序--时间复杂度 nlog(n)

题目描述:实现一个时间复杂度为 nlog(n)的排序算法


实现代码如下:


function merge(left, right) {  let res = [];  let i = 0;  let j = 0;  while (i < left.length && j < right.length) {    if (left[i] < right[j]) {      res.push(left[i]);      i++;    } else {      res.push(right[j]);      j++;    }  }  if (i < left.length) {    res.push(...left.slice(i));  } else {    res.push(...right.slice(j));  }  return res;}
function mergeSort(arr) { if (arr.length < 2) { return arr; } const mid = Math.floor(arr.length / 2);
const left = mergeSort(arr.slice(0, mid)); const right = mergeSort(arr.slice(mid)); return merge(left, right);}// console.log(mergeSort([3, 6, 2, 4, 1]));
复制代码

寄生组合继承

题目描述:实现一个你认为不错的 js 继承方式


实现代码如下:


function Parent(name) {  this.name = name;  this.say = () => {    console.log(111);  };}Parent.prototype.play = () => {  console.log(222);};function Children(name) {  Parent.call(this);  this.name = name;}Children.prototype = Object.create(Parent.prototype);Children.prototype.constructor = Children;// let child = new Children("111");// // console.log(child.name);// // child.say();// // child.play();
复制代码

点击刷新按钮或者按 F5、按 Ctrl+F5 (强制刷新)、地址栏回车有什么区别?

  • 点击刷新按钮或者按 F5: 浏览器直接对本地的缓存文件过期,但是会带上 If-Modifed-Since,If-None-Match,这就意味着服务器会对文件检查新鲜度,返回结果可能是 304,也有可能是 200。

  • 用户按 Ctrl+F5(强制刷新): 浏览器不仅会对本地文件过期,而且不会带上 If-Modifed-Since,If-None-Match,相当于之前从来没有请求过,返回结果是 200。

  • 地址栏回车: 浏览器发起请求,按照正常流程,本地检查是否过期,然后服务器检查新鲜度,最后返回内容。

Loader 和 Plugin 有什么区别

Loader:直译为"加载器"。Webpack 将一切文件视为模块,但是 webpack 原生是只能解析 js 文件,如果想将其他文件也打包的话,就会用到loader。 所以 Loader 的作用是让 webpack 拥有了加载和解析非 JavaScript 文件的能力。 Plugin:直译为"插件"。Plugin 可以扩展 webpack 的功能,让 webpack 具有更多的灵活性。 在 Webpack 运行的生命周期中会广播出许多事件,Plugin 可以监听这些事件,在合适的时机通过 Webpack 提供的 API 改变输出结果。

数组能够调用的函数有那些?

  • push

  • pop

  • splice

  • slice

  • shift

  • unshift

  • sort

  • find

  • findIndex

  • map/filter/reduce 等函数式编程方法

  • 还有一些原型链上的方法:toString/valudOf

说一下 HTML5 drag API

  • dragstart:事件主体是被拖放元素,在开始拖放被拖放元素时触发。

  • darg:事件主体是被拖放元素,在正在拖放被拖放元素时触发。

  • dragenter:事件主体是目标元素,在被拖放元素进入某元素时触发。

  • dragover:事件主体是目标元素,在被拖放在某元素内移动时触发。

  • dragleave:事件主体是目标元素,在被拖放元素移出目标元素是触发。

  • drop:事件主体是目标元素,在目标元素完全接受被拖放元素时触发。

  • dragend:事件主体是被拖放元素,在整个拖放操作结束时触发。

BFC

块级格式化上下文,是一个独立的渲染区域,让处于 BFC 内部的元素与外部的元素相互隔离,使内外元素的定位不会相互影响。


IE 下为 Layout,可通过 zoom:1 触发


触发条件:


  • 根元素

  • position: absolute/fixed

  • display: inline-block / table

  • float 元素

  • ovevflow !== visible


规则:


  • 属于同一个 BFC 的两个相邻 Box 垂直排列

  • 属于同一个 BFC 的两个相邻 Boxmargin 会发生重叠

  • BFC 中子元素的 margin box 的左边, 与包含块 (BFC) border box的左边相接触 (子元素 absolute 除外)

  • BFC 的区域不会与 float 的元素区域重叠

  • 计算 BFC 的高度时,浮动子元素也参与计算

  • 文字层不会被浮动层覆盖,环绕于周围


应用:


  • 阻止margin重叠

  • 可以包含浮动元素 —— 清除内部浮动(清除浮动的原理是两个div都位于同一个 BFC 区域之中)

  • 自适应两栏布局

  • 可以阻止元素被浮动元素覆盖

代码输出结果

const p1 = new Promise((resolve) => {  setTimeout(() => {    resolve('resolve3');    console.log('timer1')  }, 0)  resolve('resovle1');  resolve('resolve2');}).then(res => {  console.log(res)  // resolve1  setTimeout(() => {    console.log(p1)  }, 1000)}).finally(res => {  console.log('finally', res)})
复制代码


执行结果为如下:


resolve1finally  undefinedtimer1Promise{<resolved>: undefined}
复制代码


需要注意的是最后一个定时器打印出的 p1 其实是.finally的返回值,我们知道.finally的返回值如果在没有抛出错误的情况下默认会是上一个 Promise 的返回值,而这道题中.finally上一个 Promise 是.then(),但是这个.then()并没有返回值,所以 p1 打印出来的 Promise 的值会是undefined,如果在定时器的下面加上一个return 1,则值就会变成 1。


用户头像

loveX001

关注

还未添加个人签名 2022-09-01 加入

还未添加个人简介

评论

发布
暂无评论
前端经典面试题(有答案)_JavaScript_loveX001_InfoQ写作社区