前端面试之JS

396 阅读8分钟

es5原型继承的例子

// 组合继承
function Father(value){
 this.val = value
}
Father.prototype.getVal = function(){
    return this.val
}

function Son(value){
    Father.apply(this,arguments)
}

Son.prototype = new Father() 
var son = new Son(1234)

console.log(son)

// 寄生组合继承
function Father(value){
    this.val = value
}
Father.prototype.getVal = function(){
    console.log(this.val)
}

function Son(){
    Father.apply(this,arguments)
}

Son.prototype = Object.create(Father.prototype)
Son.prototype.constructor = Son

// Son.prototype = Object.create(Parent.prototype,{
//     constructor:{
//         value:Son,
//         enumerable:false,
//         writeable:true,
//         configurable:true
//     }
// })

//es6的写法
    class Person {
        constructor(value) {
            this.val = value
        }

        get() {
            console.log(this.val)
        }
    }


    class son extends Person {
        constructor(value,name) {
            super(value)
            this.name = name
        }
    }

手写promise

    const PENDING = 'pedeing'
    const RESOLVED = 'resolved'
    const REJECTED = 'rejected'

    function myPromise(fn) {
        const that = this
        that.state = PENDING
        that.value = null
        that.reslovedCallbacks = []
        that.rejectedCallbacks = []
        function resolve(value) {
            if (that.state === PENDING) {
                that.state = RESOLVED
                that.value = value
                //  完整then里面的第一个回调
                that.reslovedCallbacks.map(thenResolve => thenResolve(that.value))
            }
        }
        function reject(value) {
            if (that.state === PENDING) {
                that.state = REJECTED
                that.value = value
                // 完整then里面的第一个回调
                that.rejectedCallbacks.map(thenReject => thenReject(that.value))
            }
        }
        fn(resolve, reject)
    }

    myPromise.prototype.then = function(onFullfilled, onRejected){
        const that = this
        onFullfilled = typeof onFullfilled === 'function' ? onFullfilled : v => v
        onRejected = typeof onRejected === 'function' ? onRejected : r => { throw r }
        if(that.state === PENDING){
            that.reslovedCallbacks.push(onFullfilled)
            that.rejectedCallbacks.push(onRejected)
        }
        if(that.state === RESOLVED){
            onFullfilled(that.value)
        }
        if(that.state === REJECTED){
            onRejected(that.value)
        }
    }

原生XHR

   var xhr = new XMLHttpRequest()
    xhr.open('GET','api',false)
    xhr.onreadystatechange = function(){
      if(xhr.readyState === 4){
          if(xhr.status === 200){

          }
      }
    }
    xhr.send(null)

原生实现事件代理

function bindEvent(elem, type, selecter, fn) {
    if (!fn) {
      fn = selecter
      selecter = null
    }
    elem.addEventListener(type, (e) => {
      var target
      if (selecter) {
        target = e.target
        if (target.matches(selecter)) {
          fn.call(target, e)
        }
      } else {
        fn(e)
      }
    })
  }

节流和防抖

1、防抖就是控制函数在高频触发的状态下,在最后一次事件触发后延迟一定时间执行

var debounce = function(time , func){
    let lastTimer = 0
    return function(...args){
        clearTimeout(lastTimer)
        lastTimer = setTimeout(()=>{
            func.apply(this,args)
        },time)
    }
}

2、节流就是在高频请求中,将请求控制在时间范围之外,以减少请求次数

var throttle = function(func, time){
    let  lastTime = 0
    return function(...args){
        let  now = +new Date()
        if(now-lastTime > time){
            lastTime = now
            func.apply(this,args)
        }
    }
}

手动实现前端路由

function Router(){
  this.currentUrl='';
  this.routes={};
}
Router.prototype.route = function(path,callback){
  this.routes[path] = callback || function(){}
}
Router.prototype.refresh = function(){
  this.currentUrl = location.hash.slice(1) || '/';
  this.routes[this.currentUrl]();
}
Router.prototype.init = function(){
  window.addEventListener('load',this.refresh.bind(this),false);
  window.addEventListener('hashchange',this.refresh.bind(this),false);
  // console.log(location.hash)
  // if(location.hash.slice(1)!=='/')
  //     location.hash = '#/';
}


 // hash  hashChage
// history  pushState replaceState

vue实现数据响应式的原理

vue在2.0时代使用object.definedProperty()实现数据的响应,通过此函数监听get() 和 set() 事件,3.0时代将采用proxy实现,一下是代码

function observe(obj){
    // 边界
    if(!obj || typeof obj !== 'object')
    return
    
    Object.keys(obj).forEach(key => {
        definedReacted(obj,key,obj[key])
    })
}

function definedReacted(obj,key,value){
    observe(value)
    Object.definedProperty(obj,key,{
        // 可枚举
        enumerabeke:true,
        //可配置
        configurable:true,
        get:()=>{
            console.log(value)
            return value
        },
        set:(newValue)=>{
            console.log(newValue)
            val = newValue
        }
    })
}


function observer(obj){

    // 递归边界
    if(!obj || typeof obj !== 'object'){
        return
    }

    Object.keys(obj).forEach((item,index,arr)=>{
        defineReactive(obj,item,obj[item])
        observer(obj[item])
    })

}


function defineReactive(data,key,val){
    Object.defineProperty(data,key ,{
        enumerable:true,
        configurale:true,
        get:function(){


            // 需要在这里添加订阅者
            return val
        },
        set:function(newValue){
            val = newValue
            console.log('属性'+key+'被监听了'+'现在的值为'+newValue)

            // 在这里通知订阅者去更新视图
        }
    })
}


var library = {
    book1: {
        name: ''
    },
    book2: ''
};

observer(library)

library.book1.name = 'vue权威指南'; // 属性name已经被监听了,现在值为:“vue权威指南”
library.book2 = '没有此书籍';

两个数不使用四则运算得出和

function sum(a, b) {
    if (a == 0) return b
    if (b == 0) return a
    let newA = a ^ b   // 获得个数值
    let newB = (a & b) << 1 // 获得进位的数值
    return sum(newA, newB)  // 最后执行自己 
}

冒泡排序

    function isArray(array) {
        if (Object.prototype.toString.call(array) !== '[object Array]') {
            return array
        }
    }

    function swap(array, left,right) {
        var temp = array[left]
        array[left] = array[right]
        array[right] = temp
    }

    function bubble(arr) {
        isArray(arr)
        for (let i = arr.length - 1; i > 0; i--) {
            for (let j = 0; j < i; j++) {
                if (arr[j] > arr[j + 1]) swap(arr,j,j+1)
            }
        }
        return arr
    }

插入排序

function insert(array){
    isArray(array)
    for(let i = 0 ;i<array.length-1;i++){
        for(let j=i;j>=0&&array[j]>array[j+1];j--){
            swap(array,j,j+1)
        }
    }
    return array
}

选择排序

const choose = (arr) => {
    isArray(arr)
    for (let i = 0; i < arr.length - 1; i++) {
       let minIndex = i
        for (let j = i + 1; j < arr.length; j++) {
            minIndex = arr[j] < arr[minIndex] ? j : minIndex
        }
        if (i !== minIndex) {
            swap(arr, i, minIndex)
        }
    }
    return arr
}

快速排序

// 阮一峰版本
function quicksort(arr){
    isArray(arr)
    if(arr.length<=1) return arr
    // const pivotIndex = Math.floor(Math.random()*arr.length)
    const pivotIndex = Math.floor(arr.length/2)
    const pivot = arr.splice(pivotIndex,1)[0]
    console.log(pivot,arr)
    const left = []
    const right = []
    for(let i = 0; i<arr.length;i++){
        if(arr[i]>pivot){
            right.push(arr[i])
        }else{
            left.push(arr[i])
        }
    }
    return quicksort(left).concat([pivot],quicksort(right))
}

console.log(quicksort([2,4,1,88,2,5,90,564,32]))

手写call apply bind

let a = {
    name: 'leolei',
    fn: function (a, b) {
        console.log(this.name + a + b)
    }
}

const b = {
    name: 'cuisiyao'
}

Function.prototype.test = function (){
    // 查看在prototype上添加方法 在调用时this是什么
    console.log(this === a.fun.__proto__)
}

Function.prototype.Mycall = function () {
    if (typeof this === 'Function') {
        throw new TypeError('Error')
    }

    // 处理没有参数的默认情况
    const context = arguments[0] || window
    console.log(this)
    context.fn = this
    const args = [...arguments].slice(1)
    console.log(args)
    const results = context.fn(...args)
    delete context.fn
    return results
}

Function.prototype.Myapply = function () {
    if (typeof this === 'Function') {
        throw new TypeError('Error')
    }
    const context = arguments[0] || window
    console.log(this)
    context.fn = this
    const args = arguments[1]
    console.log(args)
    const results = context.fn(...args)
    delete context.fn
    return results
}

Function.prototype.Mybind = function () {
    const context = arguments[0] || window
    const args1 = [...arguments].slice(1)
    const _this = this
    return function F(...args) {
        // 处理new function 的情况
        if (this instanceof F) {
            return new _this(...args, ...args1)
        }
        return _this.Myapply(context, args.concat(args1))
    }
}

a.fn.Mycall(b, 123, 456)
a.fn.Myapply(b, [123, 456])

// 当方法执行
a.fn.Mybind(b, 123, 456)()
a.fn.Mybind(b)(123, 456)

a = {
    name: 'leolei',
    fn: function (a, b) {
        console.log(a + b)
    }
}
// 使用new执行
new a.fn(123, 456)
const test = new (a.fn.Mybind(b))(123, 456)

讲述一个new的过程

  • 先创建一个空的obj
  • 然后将需要构造的函数的绑定到空对象的obj原型原型上
  • 在新建空对象的环境下执行构造函数,绑定this为这个空对象
  • 返回this,并确保this为Object
function creat(){
    const obj = {}
    const con = [].shift.call(arguments)
    obj.__proto__ = con
    const results = con.aplly(obj,arguments)
    return results instanceof Object ? results : obj
}

常见的浏览器安全漏洞

  • XSS攻击,分为两种。
    • 一种是持久行的,持久型的XSS攻击主要是通过可执行的网页代码,将数据写到后台数据库中,比如评论时写入<script>aler(1)</script>,将此评论注入到数据库中,如果不做处理,页面在访问时会受到攻击。
    • 第二种非持久型的,主要通过导航栏工具http://www.domain.com?name=<script>alert(1)</script>. 解决方案:1. 一般采用转义字符来解决,比如转义<&lt
  • CSRP 跨站伪造请求。
    • 当用户登录A页面时,已登录网站,未关闭,此时存在了cookie。用户在此时打开了另外一个攻击网站B,B盗取A的cookie后对A页面的后台进行请求,并攻击。
    • 解决方案:在http头中设置SameSite,让cookie不随跨域请求发送。验证refrer字段 判断 请求来源,禁止第三方请求发送。使用token通过服务器验证用户登录是否有效。
  • 点击劫持。解决方案,在响应头设置X—FRSME—OPTIONS。
  • 中间人攻击 容易出现在公用产所的 wifi,解决方案就是使用https协议。

vue路由守卫

全局守卫

  • beforeEnter(to,from,next)
  • afterEnter(....)
  • beforeResolve

路由独享守卫

  • beforeEnter

组件内的守卫

  • beforeRouteEnter
  • beforeRouteUpdata
  • beforeRouteLeave

完整的解析过程

  • 导航被触发
  • 上一个导航的组件里调用beaforeRouteLeave
  • 激活全局beforeEnter
  • 如果是重用组件,那就会调用beforeRouteUpdata
  • 然后进入路由配置的beforEnter
  • 解析异步路由组件
  • 调用全局的beforeResolve
  • 导航被确认
  • 调用全局的afterEnter
  • 触发更新dom
  • 用创建好的实例调用beforeRouteEnter,并把组件实例作为使用next的回调

设计模式

  • 工厂模式 隐藏对象创建的过程
  • 单例模式 js可以借助闭包的方式完成
  • 适配器模式 在不对原有接口做任何改动的情况下,包装一层新的接口,返回最终数据
  • 代理模式 proxy 在vue3.0中正在使用这个特性实现数据的双向绑定,现在是使用Object.definedProperty实现的,
  • 观察者模式
  • 发布-订阅者模式,这两种模式看似相同其实不同 观察者模式是观察者直接通知对象,双方知道对方的存在,发布-订阅者模式,双方不知道对方存在,使用中间对象对发布的信息进行删选然后通知订阅者。
  • 装饰模式 在不改变以后接口,对方法进行包装。
  • 外观模式,比如说写一个原生函数创建个浏览器环境下的XHR对象

vue生命周期

  • beforeCreate
  • created
  • beforeMount
  • mounted
  • beforeupdate
  • updated
  • beforeDestroy
  • disrtoryed

对于使用keep-alive的组件 有独有的deactived和actived 生命周期,在切换组件时此组件不会被销毁,而是缓存到内存并执行deactived ,命中缓存之后会调用actived

js中的原始类型

  • boolean
  • null
  • undfined
  • Number
  • String
  • Symbol

typeof有几种结果

  • undefined
  • boolean
  • Object
  • Number
  • String
  • Function

JS中有哪些内置函数

上面的这几种加上 Array Regexp Date Error

移动端首屏优化

  • 采用服务器渲染ssr
  • 按需加载配合webpack分块打包
  • 很有必要将script标签➕异步
  • 有轮播图 最好给个默认 另外要处理图片懒加载
  • 打包线上也要注意去掉map 文件
  • 组件懒加载
  • 路由懒加载
  • webpack的一切配置 肯定是必须的 这个百度去 做到js css 以及依赖库分离
  • 强烈建议不要在应用依赖里面去下载jQuery库和一些Ui库
  • 压缩图片 https://tinypng.com/
  • 建议还是用webpack的图片压缩插件
  • 使用pagespeed看看有哪些可优化的选项

最优二叉树

class Node {
    constructor(value) {
        this.value = value
        this.left = null
        this.right = null
    }
}

class BST {
    constructor() {
        this.root = null
        this.size = 0
    }

    getSize() {
        return this.size
    }

    isEmpty() {
        return this.size === 0
    }

    addNode(value) {
        this.root = this._addchild(this.root, value)
    }

    _addchild(node, value) {
        if (!node) {
            this.size++
            return new Node(value)
        }

        if (node.value > value) {
            node.left = this._addchild(node.left, value)
        } else{
            node.right = this._addchild(node.right, value)
        }

        return node
    }
}

class Stack{
    constructor(){
        this.stack = []
    }
    push(val){
        this.stack.unshift(val)
    }
    pop(){
        this.stack.shift()
    }
    peek(){
        return this.stack[this.getCount()-1]
    }
    getCount(){
        return this.stack.length
    }
}
// 这里其实可以使用数组直接模拟栈 队列其实也是一样的

// 检测下列字符串是否是回文字符串
var str = 'leel'

function isValied(str){
    var strArr = str.split('')

    var stack = []
    for(let i =0;i<strArr.length;i++){
    
        if(stack.indexOf(strArr[i]) === -1){
            stack.push(strArr[i])
        }else{
            if(stack[stack.length-1] === strArr[i]){
                stack.pop()
            }
        }
    }
    
    if(stack.length !== 0 ){
        return false
    }else{
        return true
    }
}

isValied(str)