常遇到的前端面试题

659 阅读11分钟

1.正则 www.jianshu.com/p/81653d3a4…

判断参数的类型:

function isType(params){
    let typeREG = /(?<obj>object) (?<type>[a-zA-Z]+)/
    return typeREG.exec(Object.prototype.toString.call(params)).groups.type
}

let reDate=/(?<year>[0-9]{4})-(?<month>[0-9]{2})-(?<day>[0-9]{2})/,  d = '2019-10-31';
let match = reDate.exec(d)

2.video初研究

在手机(Android还是iOS)中video不能够自动播放,而且在微信中video标签全屏时会出现讨厌的x5内核给我们的东西并不是我们想要的纯净的h5video标签此时需要添加属性 

<video  src="..." poster="..." controls x5-video-player-fullscreen="true"  
webkit-playsinline x5-video-player-type="h5-page"></video>

3.正则

字符串中是否有特定的值: ..includes(**)截取字符串特定的值: ...indexOf(**)  ss.substring(0,aa)正则表达式:reg= new RegExp(/(\{\{)(.*)(\}\})/,'g')   ....match(reg)  ==> ['sss','sfd']

4.拷贝

浅拷贝,深拷贝

拷贝的方式:

  1. Object.assign
  2. JSON.parse JSON.stringify
  3. ladash.cloneDeep()
  4. 递归方式实现

深浅拷贝

5.viewport

var devicePixelRatio = window.devicePixelRatio
var initialScale = 1 / devicePixelRatio
var maximumScale = 1 / devicePixelRatio
var minimumScale = 1 / devicePixelRatio
var meta = document.createElement('meta')
meta.name = 'viewport'meta.content = 'width=device-width,initial-scale=' + initialScale + ',maximum-scale=' + maximumScale + ',minimum-scale=' + minimumScale + ',user-scalable=no'
document.head.appendChild(meta)

6. call,apply 函数实现 

function jawiiSymbol(obj){
    var unique_proper = "00" + Math.random();
    if(obj.hasOwnProperty(unique_proper)){
        arguments.callee(obj);
    }else{
        return unique_proper;
    }
}
Function.prototype.myApply = function(context){
    var context = context || window;
    var fn = jawiiSymbol(obj);    
    context[fn] = this;
    const value = eval('context['+ fn +']('+(arguments[1]||[]).toString()+')');
    delete context[fn];
    return value;}

Function.prototype.myCall = function (){
  return this.myApply(Array.prototype.shift.myApply(arguments),Array.prototype.slice.myApply(arguments,1))
}

Function.prototype.bind = Function.prototype.bind || function(context){
    var me  = this;
    var args = Array.prototype.slice.myCall(arguments,1);
    var F = function (){};
    F.protoype = this.prototype;
    var bound = function (){
        var innerArgs = Array.prototype.slice.myCall(arguments,1);
        var finalArgs = args.concat(innerArgs);
        return me.myApply(this instanceof F ? this : context || this,finnalArgs);
    }
    bound.prototype = new F();
    return bound;
}

7. 类的继承

  • 原型链的继承

    function Animal(name){
        this.name = name || 'Animal'
        this.sleep = function(){
            console.log(this.name + '正在睡觉')
        }
    }
    Animal.prototype.eat = function(food){
        console.log(this.name + '正在吃' + food)
    }
    function Cat(){}
    Cat.prototype = new Animal()
    Cat.prototype.name = 'Cat'
    let cat = new Cat()
    // 特点: 基于原型链,既是父类的实例,也是子类的实例
    // 缺点: 无法实现多继承
    
  • 构造继承

    function Cat(name){
        Animal.call(this)
        this.name = name || 'Tom'
    }
    // 特点: 可以实现多继承
    // 缺点: 只能继承父类实例的属性和方法,不能继承原型上的属性和方法
    
  • 组合继承

    function Cat(name){
        Animal.call(this)
        this.name = name || 'Tom'
    }
    Cat.prototype = new Animal()
    Cat.prototype.constructor = Cat
    // 特点: 可以实现继承实例属性/方法,也可以继承原型属性/方法
    // 缺点: 调用了两次父类构造函数,生成了两份实例集合
    
  • 寄生组合继承

    function Cat(name){
       Animal.call(this)
        this.name = name || 'Tom'
    }
    (function(){
        var Super = function (){}
        Super.prototype = Animal.prototype
        Cat.prototype = new Super()
    })()
    

8.防抖和节流

  • 防抖:在事件触发n秒后在执行回调,如果在这n秒内又被触发,则重新计时

  • 如果短时间内大量触发同一事件,只会执行一次函数。

  • 在固定时间内如果任务没有执行完,又有任务触发,则重新定时

    let debounce = (fn,delay)=>{
        let timer = null    
        return (...args)=>{ 
          clearTimeout(timer)       
          timer = setTimeout(()=>{ 
              fn.apply(this,args)       
          },delay)     
        }
    }
    // 适用场景:
    // 按钮调教场景,防止多次提交按钮,只执行最后提交的一次
    // 搜索场景
    
    
    //带有
    function debounce(fn,wait,immediate){    let timer = null;    return function (){        let args = arguments;        if(timer)(clearTimeout(timer));        if(immediate){            let callNow = !timer;            timer = setTimeout(()=>{                timer = null;            },wait)            if(callNow){                fn.apply(null,args)            }        }else{            timer = setTimeout(()=>{                fn.apply(null,args)            },wait)        }    }}function throttle(fn,wait,immediate){    let timer = null;    return function (){        let args = arguments;        if(timer){return};        timer = setTimeout(()=>{            timer = null;            if(!immediate){                fn.apply(null,args)            }        },wait)        if(immediate){            fn.apply(null,args);        }    }}
    
    
  • 节流:规定在一个单位时间内,只能触发一次函数,如果在这个时间内多次触发,只有一次生效

  • 如果短时间内大量触发同一事件,那么在函数执行一次之后,该函数在指定的时间期限内不再工作,直至过了这段时间才重新生效。

  • 固定时间内只有第一次执行,执行完后才可以执行下一个任务

    const throttle = (fn,delay=500)=>{
    let timer = null;
    return (...args)=>{
        if(timer) return
        timer = setTimeout(()=>{
           fn.apply(this,args)
        },delay)
    }}
    
    // 使用场景:
    // 拖拽场景: 固定时间内只执行一次,防止超高频触发位置变动
    // 缩放场景: 监控浏览器resize
    // 动画场景: 避免短时间内多次触发动画导致性能问题
    

9.订阅和发布

let publisher = {
    handlers:{},
    subscribe:function(name,handler){
        this.handlers[name] = handler
    },
    publish:function(name,...args){
        let handler = this.handlers[name]
        if(handler){
            handler(...args)
        }
    }
}

publisher.subscribe('say',function(something){console.log(something)})
publisher.publish('say','你好')

10.import 和require的区别

  • 规范:

require是AMD规范引入方式

import是es6的一个语法标准,如果要兼容浏览器的话需要转化成es5的语法

  • 调用时间

require是运行时调用,所以require理论上是可以运用在代码的任何地方

import是编译时调用,所以必须放在文件的开头

  • 本质

require是赋值的过程,其实require的结果就是对象,数字,字符串,函数等,再把require的结果赋值给某个变量(值的拷贝)

import是解构过程,但是目前所有的引擎都还没有实现import,我们在node中使用babel支持es6,也是将es6转码为es5在执行,import语法会被转码为require(值的引用)

ES6 模块与 CommonJS 模块的差异

它们有两个重大差异:

① CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用

② CommonJS 模块是运行时加载,ES6 模块是编译时输出接口

第二个差异是因为 CommonJS 加载的是一个对象(即module.exports属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。

下面重点解释第一个差异,我们还是举上面那个CommonJS模块的加载机制例子:

// lib.js
export let counter = 3;
export function incCounter() {
  counter++;
}
// main.js
import { counter, incCounter } from './lib';
console.log(counter); // 3
incCounter();
console.log(counter); // 4

ES6 模块的运行机制与 CommonJS 不一样。ES6 模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块

11.将类数组对象转换成数组

1.Array.prototype.slice.call()

function foo(a,b){
    let res = Array.prototype.slice.call(arguments)
    console.log(res)
}

let json = {
    0:'aaa',
    1:'bbb',
    2:'ccc',
    length:3
}

let resJ = Array.prototype.slice.call(json)

2.Array.from()

function foo(a,b){
    let res = Array.from(arguments)
    console.log(res)
}

let json = {
    0:'aaa',
    1:'bbb',
    2:'ccc',
    length:3
}
let res = Array.from(json)

3.扩展运算符

function foo(a,b){
    let res = [...arguments]
}

let json = {
    0:'aaa',
    1:'bbb',
    2:'ccc',
    length:3
}

此时,扩展运算符对json格式的类数组对象失败,因为该对象没有iterable没有定义遍历器(Iterator)接口

let jsonIterable = {
    0:'aaa',
    1:'bbb',
    2:'ccc',
    length:3,
    [Symbol.iterator]: Array.prototype[Symbol.iterator]
}
就可以了

12.原型和原型链

构造函数,函数,对象

**prototype:**在js中每个函数都有一个prototype属性,它的值对应着该函数的原型对象

**__proto__:**在js中每个对象都有一个__proto__属性,它的值对应着该对象的原型对象

**原型的概念:**每一个js对象(除null外)创建的时候,就会关联另一个对象,这个对象就是我们所说的原型对象,每一个对象都会从原型中继承属性

**constructor:**每个原型对象都有一个constructor属性,指向该关联的构造函数

**原型链:**每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例对象都包含一个指向原型对象的指针。让原型对象等于另一个类型的实例对象,此时,原型对象将包含一个指向另一个原型对象的指针,如此层层递进,就构成了实例与原型对象的链条。

Object.prototype的原型对象是谁呢?

console.log(Object.prototype.__proto__ === null ) // true

 null表示没有对象,即该处不因该有值

Object.prototype.__proto__ 的值为null 和Object.prototype没有原型对象是一个意思

13.typeof 

typeof 运算符可以判断表达式的类型,返回7中结果:number,string,boolean,object,function,symbol,undefined

typeof的用法有两种:

typeof  a  ; ||  typeof(a)

14.instanceof

instanceof运算符用来判断对象是否是指定对象的实例

a(实例对象) instanceof Object(构造函数)

15. for of for in

1.for in 循环 遍历数组的问题

  • index索引为字符串型数字,不能直接进行几何运算
  • 遍历顺序有可能不是按照实际数组的内部顺序
  • 使用for in 会遍历数组所有的可枚举属性,包括原型。例如上面的原型方法和属性
  • 使用for in 更适合遍历对象

for in 遍历的数组的索引而for of 遍历的是数组值

for of 遍历的只是数组内的元素,不包括数组的原型方法和属性

2.遍历对象

遍历对象通常使用for in 来遍历对象的键名

for in 可以遍历到对象的原型方法和属性,如果不想遍历原型的内容,可以在循环内部判断一下,hasOwnProperty方法可以判断某属性是否是该对象的实例属性

for (var key in myObject) {
  if(myObject.hasOwnProperty(key)){
    console.log(key);
  }
}

也可以使用Object.keys()获取对象的键名组成的数组,不包括原型方法和属性

总结:

for ... of 使用遍历数/数组/数组对象/字符串/map/set等拥有迭代器对象的集合,但是不能遍历对象,因为没有迭代对象,与forEach不同的是,它可以正确响应break、Continute和return语句。

for ... in 可以用来遍历对象

16.闭包

环境栈

每个函数或块都有自己的执行环境。当执行流进入一个函数时,函数的环境就会被推入“环境栈”中。函数执行完后,栈将其弹出并销毁变量对象,然后把控制权返回在给之前的执行环境。如果内执行环境的变量对象,被外部执行环境引用,那么内部环境变量对象就无法被销毁(如:闭包)。

17.Object.is()和===的区别

Object.is在严格等于的基础上修复了一些特殊情况下的失误,具体来说就是具体来说就是+0和-0,NaN和NaN。 源码如下:

function is(x, y) {
  if (x === y) {
    //运行到1/x === 1/y的时候x和y都为0,但是1/+0 = +Infinity, 1/-0 = -Infinity, 是不一样的
    return x !== 0 || y !== 0 || 1 / x === 1 / y;
  } else {
    //NaN===NaN是false,这是不对的,我们在这里做一个拦截,x !== x,那么一定是 NaN, y 同理
    //两个都是NaN的时候返回true
    return x !== x && y !== y;
  }

18.BFC是什么?应用的场景,有什么作用,如何形成BFC?

BFC是什么?

  •  块级格式化上下文(Block Formatting Contexts)

有什么作用? 

  • 简单来说BFC就是一个独立不受外界干扰也不干扰外界的盒子. 是web页面中盒模型布局的css渲染模式,一个隔离的独立容器, 

形成BFC的条件 

  1. 浮动:float元素除了none以外的值。
  2. 定位:绝对定位,相对定位。
  3.  display为以下的值之一:inline-block,table-cell,table-captionoverflow除了visible以外的值(hidden,auto,scroll) 

BFC的特性: 

  1. 内部的box会在垂直方向上一个接一个的放置;
  2. 垂直方向上的距离由margin决定。 
  3. BFC区域不会与float元素区域重叠。 
  4. 计算BFC的高度时,浮动元素也参与计算。 
  5. BFC就是页面上的一个独立容器,里面的子元素不会影响外面的元素。外面的元素也不会影响里面的

更多详情

19.数组

1. 随机乱序

[1,2,3,4,5].sort((a,b)=>Math.random()>0.5?1:-1)

2. value为key的数组

Array.from({length:7},(_,i)=>i)
Array.form({length:7}).map((_,i)=>i)
new Array(7).map((_,i)=>i)
Array.of(0,1,2,3,4,5,6)

20. 手写Events事件

class Events{
    constructor(){
        this._events={} 
    }    
    on(name,fn,...argOrg){
        // 必传参数验证        
        if (!name || !fn) { 
             throw new Error(`[Events TypeError] Failed to execute 'Events' on '${name}' :  arguments required`) 
           return        
        }        
        // 阻止重复添加相同的监听       
        let fns = this._events[name] || []       
        if(fns.find(item=> item.fnOrg===fn)){return;}        
        this._events[name] = fns.concat({fn: arg => fn.apply(null, [...argOrg, ...arg]),fnOrg:fn})    
    }    
    once(name, fn, ...argOrg) {        
            const onFn = (...arg) =>{            
                fn.apply(null, arg)            
                this.off(name, onFn)        
            }        
            this.on(name, onFn, ...argOrg)    
    }    
    emit(name, ...arg) {        
        (this._events[name]||[]).forEach(item =>{item.fn(arg)})    
    }    
    off(name,fn) {        
        // 无参数 : 清掉所有监听        
        if(!arguments.length){          
            this._events = Object.create(null)        
        }        
        // 一个参数 : 清掉该事件名下所有监听       
        if(arguments.length== 1){            
            delete this._events[name]        
        }        
        let fns = this._events[name];        
        if(!fns || !fns.length)return;        
        this._events[name] = (fns||[]).filter(item=>{return item.fnOrg !== fn});}
}    
const event = new Events();    
const fn1 = (...args)=>console.log('I want sleep1',...args);    
const fn2 = (...args)=>console.log('I want sleep2',...args);
// 订阅
event.on('sleep', fn1, 1, 2, 3);
event.on('sleep', fn2, 1, 2, 3);
// 发布
event.emit('sleep', 7,8,9);
event.off("sleep",fn1);
event.once("sleep",()=>console.log("I want sleep"));
event.emit("sleep");event.emit("sleep")

I want sleep1 1 2 3 7 8 9
index.html:58 I want sleep2 1 2 3 7 8 9
index.html:58 I want sleep2 1 2 3
index.html:64 I want sleep
index.html:58 I want sleep2 1 2 3

class Event {  _event = new Map();  on(eventName,fn){   if( this._event.has(eventName)){    const fnSet = this._event.get(eventName);   if(!fnSet.has(fn)){     fnSet.add(fn);   }   }else{     const fnset = new Set([fn]);    this._event.set(eventName,fnset);   }  }  emit(eventName,...args){    if(this._event.has(eventName)){     this._event.get(eventName).forEach(fn=>fn.apply(null,[...args]));    }  }  off(name,fn){    if(arguments.length ===0){      this._event.clear();    }    if(arguments.length ===1){      this._event.delete(name)    }    const fnSet = this._event.get(name);    if( typeof fnSet === 'object' ){      fnSet.delete(fn);    }  }  once(name,fn){    const fns = (...args)=>{      fn.apply(null,args);      this.off(name,fns)    }    this.on(name,fns);  }}

21.用闭包实现单例模式

单例:保证一个类仅有一个实例,并提供一个它的全局访问点

主要解决:一个全局使用的类频繁得创建与销毁

何时使用:当想要控制实例数目,节省系统资源的时候

如何解决:判断系统是否有这个实例,有则返回,无则创建

let Singleton = (function(){
   let Head = function(){}
    let instance = null;
    return function(){
        if(instance){
            return instance
        }
        instance = new Head();
        return instance
    }
    
})()

let a = new Singleton()
let b = new Singleton()console.log(Object.is(a,b))

22.对象扁平化

function flatten(value){
    const checkType = (value)=>/(?<obj>object) (?<type>[a-zA-Z]+)/.exec(Object.prototype.toString.call(value)).groups.type;
    const returnObj = {};
    (function getKeysFn(o,char){
       for(let key in o){
            let newChar = char ===""? key:`${char}.${key}`;            
            if(checkType(o[key])==="Object"){
               getKeysFn(o[key],newChar);
            }else{
                returnObj[newChar ] = o[key]            
            }
        }    
    })(value,"")
    return returnObj;
}
let obj = {}
flatten({a:1,b:{c:2,d:{e:3}},f:{g:4}}) // {a:1,'b.c':2,'b.d.e':3,'f.g':4}

23. curry函数柯里化

function createCurry(handler,...args){
    let allArguments = args;
    let fn = function(){
        allArguments.push(...arguments);
        return fn;
    }
    fn.toString = function(){
       return allArguments.reduce(handler)
    }
    return fn;
}

function curry(fn,...args){    const len = fn.length;    return function(...params){        let _args = [...args,...params];        if(_args.length < len){            return curryB.call(this,fn,..._args);        }        return fn.apply(this,_args)    }}

22. ==和===的区别

== 运算符称作相等,用来检测两个操作数是否相等,可以允许进行类型转换

===运算符称作全等,用来检测两个操作数是否严格相等

  1. 对于string,number等基础类型,==和===是有区别的,不同类型间比较,==比较转化成同一类型后的值看值是否相等,===如果类型不同,其结果就不等,同类型比较,直接进行值比较,两者结果一样。
  2. 对于Array,Object等高级类型,==和===是没有区别的
  3. 基础类型和高级类型,==和===是由区别的,对于==,将高级类型转为基础类型,进行值比较,因为类型不同,===结果为false

23.  0.1+0.2为什么不等于0.3,如何解决

在JavaScript中0.1+0.2!==0.3,这是因为JavaScript中的二进制浮点数0.1和0.2并不是十分精确,他们相加的结果并非等于0.3,而是一个比较接近的数字,0.30000000000000004,所以判断结果为false;

最好的办法是设置一个误差范围值,通常称为“机器精度”,而对于JavaScript来说,这个值通常是2^-52,而在ES6中Number.EPSILON,这个值正好等于2^-52,这个时候,我们只需要判断0.1+0.2-0.3小于Number.EPSILON,在这个误差范围内,就可以判定为true

function numberSequal( a , b ){
    return Number.EPSITON ? Math.abs(a-b) < Number.EPSILON : Math.abs(a-b) < Math.pow(2,-52)
}

24.数组扁平化

function arrayFlat(arr){
    while(arr.some(item=>Array.isArray(item))){
        arr = [].concat(...arr)
    }
    return arr;
}

25.箭头函数

1. this指向:

箭头函数不会创建自己的this,它只会从自己的作用域链的上一层继承this

2.通过call,apply,bind调用

由于箭头函数没有自己的this,通过call方法调用一个箭头函数时,只能传递参数,他们的第一个参数会被忽略

3.不绑定arguments

箭头函数不绑定arguments对象,

4.不能用new操作符调用

箭头函数不能用作构造器,和new一起用会抛出错误

5.没有prototype属性

6.箭头函数中通常不能用yield关键字,(除非是嵌套在允许使用的函数内),因此箭头函数不饿能用作生成器

为什么箭头函数不能使用new操作符

new操作符做了4件事情,

1.新建一个空对象 let obj = new Object()

2.将新对象的obj.__proto__ = Fun.prototype

3.将新对象作为函数的this, Fun.call(obj)

4.返回这个新对象 如果构造函数有return 判断return的数据类型,如果是引用类型,则返回return的对象,否则返回新创建的对象。

26.图片加载

 document.addEventListener('DOMContentLoaded',function(){
    var lazyImages = document.getElementsByClassName('lazy-images');  
  var observe = new IntersectionObserver(function(entries,observer){ 
       entries.forEach(function(entry){ 
           if(entry.isIntersecting){             
   var lazyImage = entry.target;               
 lazyImage.src = lazyImage.dataset.src;               
 observer.unobserve(lazyImage);           
 }        
})    
});    
lazyImages.forEach(function(lazyImage){      
  observe.observe(lazyImage)   
 }) 
 });

huruji.github.io/FE-Intervie…