乐趣区

关于javascript:前端经典面试题有答案

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 的属性值及其作用

属性值 作用
none 元素不显示,并且会从文档流中移除。
block 块类型。默认宽度为父元素宽度,可设置宽高,换行显示。
inline 行内元素类型。默认宽度为内容宽度,不可设置宽高,同行显示。
inline-block 默认宽度为内容宽度,能够设置宽高,同行显示。
list-item 像块类型元素一样显示,并增加款式列表标记。
table 此元素会作为块级表格来显示。
inherit 规定应该从父元素继承 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.js
export const a = 1
// index.js
import {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.js
export const a = 1
export const b = 2
// index.js
import {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)
})

执行后果为如下:

resolve1
finally  undefined
timer1
Promise{<resolved>: undefined}

须要留神的是最初一个定时器打印出的 p1 其实是 .finally 的返回值,咱们晓得 .finally 的返回值如果在没有抛出谬误的状况下默认会是上一个 Promise 的返回值,而这道题中 .finally 上一个 Promise 是 .then(),然而这个.then() 并没有返回值,所以 p1 打印进去的 Promise 的值会是undefined,如果在定时器的上面加上一个return 1,则值就会变成 1。

退出移动版