前端基础面试题

255 阅读7分钟

题目1:typeof

基础类型 null,undefined,number,string,boolean,
function,
object

题目2:闭包

// 函数作为返回值
function create(){
    let a = 100
    return function(){
      console.log(a)
    }
}
len fn = create()
fn()

// 函数作为参数
function print(fn){
    fn()
}
let a =100
function fn(){
  console.log(a)
}
print(fn)

// 概念
函数或一个封闭的作用域代码执行时,其内部声明的函数或对象,通过传参,return等方式,直接或间接的暴露到该作用域外。
当函数或该封闭作用域的代码执行完后,暴露出去的函数或对象依旧持有对该作用域的引用,该作用域并未销毁

题目3:bind

// 实现
Function.prototype.bind = function(...args){
    const oThis = args.shift()
    const selfFn = this
    const Fn = function(...args1){
        let flag = this instance of Fn
        if(flag){
            return selfFn.apply(this,[...args,...args1])
        }
        return selfFn.apply(oThis,[...args,...args1])
    }
    Fn.prototype = Object.create(selfFn.prototype)
    return Fn
}

题目4:document.createDocumentFragment()

const frag = document.createDocumentFragment()
frag.appendChild(dom)
dom.appendChild(frag)

题目4 jsonp封装

 function jsonp(url, data={}) {
     const p1 = new Promise((resolve, reject) => {
         let fnName = "KKB_"+Math.random().toString().substr(2);
         window[fnName] = (data) => {
             // window[fnName] = null // 销毁?
             resolve(data);
         }
         let script = document.createElement('script')
         let query = parse(data)
         script.src = `${url}?callback=${fnName}&${query}`
         script.onerror = () => reject('加载失败') 
         script.onload =() => {
             document.body.removeChild(script)
         }
         document.body.appendChild(script) 
     }) 
     const p2 = new Promise(()=>{
         setTimeout(()=>{
             reject()
         },50000)
     })
     return Promise.race([p1,p2])
 }

题目5:ajax

function ajax(options){
    const xhr = new XMLHttpRequest() // ie兼容
    const {method,url,data} = options
    if(method === 'GET'){
        const url =`${url}?${parse(data)}`
        xhr.open('GET', url, async = true)
    }else if(method === 'post'){
        xhr.setRequestHeader("Content-Type","application/x-www-form-urlencoded")
        xhr.open('POST', url ,async)
        xhr.send(qs.stringify(data))
    }
    
    xhr.onreadystatechange = ()=>{
        if(xhr.readyState ===4){
            if([200,206,304].includes(xhr.status)){
                options.success(xhr.responseText)
            }
        }
    }
}

题目5:cookie

// 缺点
1.存储大小4kb
2. http请求发送到服务端,增加请求数据量
3. 只能用document.cookie ,服务端Set-Cookie
// 属性
domain 
path
Expires/Max-age
HttpOnly
// 跨子域
子域设置cookie Set-Cookie的domain为上级域名

题目6:window.onload和DOMContentLoaded

window.addEventListener('load',function(){
    // 页面的全部资源加载完才执行,包括图片,视频等
})
document.addEventListener('DOMContentLoaded',function(){
    // Dom渲染完即可执行,此时图片,视频还可能没加载完
})

题目7:性能优化

// 让加载更快
// 让渲染更快
# css放head,js放body最下面
// 避免先渲染一遍默认样式,再解析css又渲染一遍
// js放在head里面,会堵塞DOM的生成。使用就无法获取通过选择器获取DOM元素进行操作
// 首屏优化
# 尽早开始执行js, 用DOMContentLoaded触发
# 懒加载
# 节流,防抖
# 合并dom操作执行,
# dom查询缓存
# 服务端渲染ssr

题目8:防抖和节流

// 防抖:用户输入结束或暂停时,才触发事件
function debounce(fn,delay=500){
    let timer = null;
    return function(...args){
        if(timer){
            clearTimeout(timer)
        }
        timer=setTimeout(()=>{
            fn.apply(this,args)
            timer = null
        },delay)
    }
}
// 节流:无论拖拽速度多快,都会每隔100ms触发一次
function throttle(fn,delay=500){
    let timer = null
    return function(...args){
        if(timer) return
        timer = setTimeout(()=>{
            fn.apply(this,args)
            timer=null
        },deley)
    }
}

题目9: 数组扁平化

function flat(arr){
    const isDeep=arr.some(item=>item instanceof Array)
    if(!isDeep){
        return arr
    }
    const res = Array.prototype.concat.apply([],arr)
    return flat(res)
}

题目10:手写深度拷贝

function deepClone(obj={}){
    if(typeof obj !== 'object' || obj == null){
        return obj
    }
    let result
    if(obj instanceof Array){
        result = []
    } else {
        result = {}
    }
    for(let key in obj){
        if(obj.hasOwnProperty(key)){
            result[key] = deepClone(obj[key])
        }
    }
    return result
}

题目11: requestAnimationFrame

// 介绍
1. 要想动画帧流畅,更新频率要60帧/s,即16.67ms更新一次视图
2. RAF自动控制频率
3. 后台标签或隐藏iframe中,RAF会暂停
// demo
function animate(){
    curWidth += 3
    dom.css('width',curWidth)
    if(curWidth < maxWidth){
        window.requestAnimationFrame(animate) // 循环
    }
}
animate()

题目12:手写isEqual

function isObject(obj){
    return typeof obj === 'object' && obj!==null
}
function isEqual(obj1,obj2){
    if(!isObject(obj1)&&!isObject(obj2)){
        return obj1 === obj2
    }
    if(obj1 === obj2) return true
    const obj1Keys = Object.keys(obj1)
    const obj2Keys = Object.keys(obj2)
    if(obj1keys.length !== obj2keys.length) return false
    for(let key in obj1){
        const res = isEqual(obj1[key],obj2[key])
        if(!res) return false
    }
    return true
}

题目13:手写promise

// https://zhuanlan.zhihu.com/p/144058361
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
function Promise(executor) {
    var _this = this
    this.state = PENDING; //状态
    this.value = undefined; //成功结果
    this.reason = undefined; //失败原因

    this.onFulfilled = [];//成功的回调
    this.onRejected = []; //失败的回调
    function resolve(value) {
        if(_this.state === PENDING){
            _this.state = FULFILLED
            _this.value = value
            _this.onFulfilled.forEach(fn => fn(value))
        }
    }
    function reject(reason) {
        if(_this.state === PENDING){
            _this.state = REJECTED
            _this.reason = reason
            _this.onRejected.forEach(fn => fn(reason))
        }
    }
    try {
        executor(resolve, reject);
    }
    catch (e) {
        reject(e);
    }
}

Promise.prototype.then = function (onFulfilled, onRejected){
    //_this是promise1的实例对象
    var _this = this;
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
    onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason };
    
    var promise2 = new Promise((resolve, reject) => {
        if (_this.state === FULFILLED) {
            setTimeout(() => {
                try {
                    let x = onFulfilled(_this.value);
                    resolvePromise(promise2, x, resolve, reject);
                }
                catch (error) {
                    reject(error)
                }
            });
        }
        else if (_this.state === REJECTED) {
            setTimeout(()=>{
                try {                    
                    let x = onRejected(_this.reason);
                    resolvePromise(promise2, x ,resolve, reject);
                }
                catch (error) {
                    reject(error);
                }
            });
        }
        else if(_this.state === PENDING) {
            _this.onFulfilled.push(() => {
                setTimeout(() => {
                    try {                        
                        let x = onFulfilled(_this.value);
                        resolvePromise(promise2, x, resolve, reject);
                    }
                    catch (error) {
                        reject(error);
                    }
                });
            });
            _this.onRejected.push(() => {
                setTimeout(() => {
                    try {                        
                        let x = onRejected(_this.reason);
                        resolvePromise(promise2, x ,resolve, reject);
                    }
                    catch (error) {
                        reject(error);
                    }
                })
            });
        }
    });

    return promise2;
}

function resolvePromise(promise2, x, resolve, reject){
    if (promise2 === x) {
        reject(new TypeError('Chaining cycle'))
    }
    if (x && typeof x === 'object' || typeof x === 'function') {
        let used;
        try {
            // 对于在then里头,return 一个新的promise或thenable那种
            // 调用x.then拿到其返回值,传给resolvePromise
            let then = x.then
            if (typeof then === 'function') {
                then.call(
                    x,
                    y => {
                        if (used) return;
                        used = true
                        resolvePromise(promise2, y, resolve, reject)
                    },
                    r =>{
                        if (used) return;
                        used = true;
                        reject(r);
                    }
                );
            }
            else {
                if (used) return;
                used = true;
                resolve(x);
            }
        }
        catch(e) {
            if (used) return;
            used = true;
            reject(e);
        }
    }
    else {
        resolve(x);
    }
}
    // 添加catch方法
    catch (onRejected) {
      return this.then(undefined, onRejected)
    }
    // 添加静态resolve方法
    static resolve (value) {
      // 如果参数是MyPromise实例,直接返回这个实例
      if (value instanceof MyPromise) return value
      return new MyPromise(resolve => resolve(value))
    }
    // 添加静态reject方法
    static reject (value) {
      return new MyPromise((resolve ,reject) => reject(value))
    }
    // 添加静态all方法
    static all (list) {
      return new MyPromise((resolve, reject) => {
        /**
         * 返回值的集合
         */
        let values = []
        let count = 0
        for (let [i, p] of list.entries()) {
          // 数组参数如果不是MyPromise实例,先调用MyPromise.resolve
          this.resolve(p).then(res => {
            values[i] = res
            count++
            // 所有状态都变成fulfilled时返回的MyPromise状态就变成fulfilled
            if (count === list.length) resolve(values)
          }, err => {
            // 有一个被rejected时返回的MyPromise状态就变成rejected
            reject(err)
          })
        }
      })
    }
    // 添加静态race方法
    static race (list) {
      return new MyPromise((resolve, reject) => {
        for (let p of list) {
          // 只要有一个实例率先改变状态,新的MyPromise的状态就跟着改变
          this.resolve(p).then(res => {
            resolve(res)
          }, err => {
            reject(err)
          })
        }
      })
    }
    finally (cb) {
      return this.then(
        value  => MyPromise.resolve(cb()).then(() => value),
        reason => MyPromise.resolve(cb()).then(() => { throw reason })
      );
    }
  }

题目14: Promise

参考 Promise

Promise面试题

// Promise.race
// Promise.all
function all(arr){
    return new Promise((resolve,reject)=>{
        let isComplete = false;
        const resolveDataList = []
        const onFullfilled = (data ,i )=>{
            if(isComplete)return
            resolveDataList[i] = data
            if(resolveDataList.length===arr.length){
                isComplete= true
                resolve(resolveDataList)
            }
        }
        
        const onRejected = reason =>{
            if(isComplete)return
            isComplete= true
            reject(reason)
        }
        
        arr.forEach((promise,index)=>{
            promise.then(
                data => onFullfilled(data,index),
                onRejected
            )
        })
    })
}
// Promise.appSettled
if (!Promise.allSettled) {
  Promise.allSettled = function (promises) {
    return new Promise(resolve => {
      const data = [], len = promises.length;
      let count = len;
      for (let i = 0; i < len; i += 1) {
        const promise = promises[i];
        promise.then(res => {
          data[i] = { status: 'fulfilled', value: res };
        }, error => {
          data[i] = { status: 'rejected', reason: error };
        }).finally(() => { // promise has been settled
          if (!--count) {
            resolve(data);
          }
        });
      }
    });
  }
}
// Promise.fail
// Promise.any 有一个resolve或者所有都reject
function any(arr) {
    return new Promise((resolve, reject) => {
        let isComplete = false;
        const rejectDataList = new Array(arr.length).fill(undefined);
        const onFullfilled = data => {
            if (isComplete) {
                return;
            }
            isComplete = true;
            resolve(data);
        };
        const onRejected = (reason, i) => {
            if (isComplete) {
                return;
            }
            rejectDataList[i] = reason;
            if (rejectDataList.every(item => item !== undefined)) {
                reject('AggregateError: All promises were rejected');
            }
        }
        arr.forEach((promise, index) => {
            promise.then(
                onFullfilled,
                reason => {onRejected(reason, index);}
            );
        });
    });
}

题目15:柯里化

参考 柯里化

// 参数复用(闭包)
function curry(x){
    return function(y){x+y}
}
// 提前确认
// 延迟执行
let fn = add(1)(2)(3) // 收集参数
fn() // 执行

function curry(fn){
    let args = []
    return function cb(...args1){
        if(args1.length < 1){ 
            return fn(...args) 
        }
        args=[...args,...args1]
        return cb 
    }
}
// apply实现
Function.prototype.apply = function(oThis, ...args){
    oThis.FN = this
    // apply
    const res = oThis.FN(...args)
    delete oThis.FN
    return res
    
    // bind 
    return (...args1)=>{
        const res = oThis.FN([...args, ...args])
        delete oThis.FN
        return res  
    }
}

题目16:前端模块化

模块化

模块化

模块化

模块化面试题

-   CommonJS规范主要用于服务端编程,加载模块是同步的,这并不适合在浏览器环境,因为同步意味着阻塞加载,浏览器资源是异步加载的,因此有了AMD CMD解决方案。

-   AMD规范在浏览器环境中异步加载模块,而且可以并行加载多个模块。不过,AMD规范开发成本高,代码的阅读和书写比较困难,模块定义方式的语义不顺畅。

-   CMD规范与AMD规范很相似,都用于浏览器编程,依赖就近,延迟执行,可以很容易在Node.js中运行。不过,依赖SPM 打包,模块的加载逻辑偏重

-   **ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代 CommonJSAMD 规范,成为浏览器和服务器通用的模块解决方案**

题目17:深拷贝循环应用如何处理

参考 深拷贝循环引用

// WeakMap
// Set

题目18:垃圾回收机制

参考 垃圾回收

// 内存泄漏
# 程序中己动态分配的[堆内存]由于某种原因程序未释放或无法释放引发的各种问题(变慢,崩溃,延迟大)
# 原因
    全局变量,定时器,闭包,dom 清空时,还存在引用
    
// 垃圾回收机制
(自动)不停歇的寻找这些不再使用的变量,并且释放掉它所指向的内存

// 两种方式
# 标记清除
    将垃圾回收过程分为 标记 和 清除 两个阶段。在标记阶段,从根对象(全局对象)出发遍历所有对象,将所有可达对象 做上标记。在清除阶段,同样会遍历所有对象,对没有标记的对象进行清除操作
    当变量进入执行环境(声明变量)的时候,垃圾回收器将该变量进行了标记,当该变量离开环境的时候,将其再度标记,随之进行删除
    
# 引用计数
    跟踪某一个值得引用次数,当引用次数为0时,变进行回收
    
// 两个概念
# 根对象
# 可达对象 (从对象出发,能够通过层层引用被访问到的对象)