快速搞定前端JS面试 -- 第十三章 面试真题总结(要反复看的JS基础面试题)

问答题

1. var和let 和const区别?

2. Typeof返回哪些类型?

3. 列举强制类型转换和隐式类型转换

4. split()和join()区别

5. 数组的pop, push, unshift, shift分别是什么

6. 数组API中有哪些是纯函数?

7. 数组slice和splice的区别

8. Ajax请求get和post区别

9. [10, 20, 30].map(parseInt)

10. 闭包是什么?有何特性?有何影响?

11. 如何阻止事件冒泡和默认行为

12. 如何减少DOM操作

13. 解释jsonp的原理,为什么不是真正的Ajax

14. 函数声明和函数表达式的区别

15. new Object()和Object.create()区别

16. 正则表达式

17. 如何捕获JS程序中的异常

18. 什么是JSON

19. 获取当前页面url参数

20. 介绍一下RAF requestAnimationFrame

手写代码题

1. 手写深度比较,模拟lodash.isEqual

2. 手写字符串trim方法,保证兼容(正则表达式)

3.将url参数解析为JS对象

4. 手写flatern考虑多层级

5. 数组去重

6. 手写深拷贝

7. 手写一个简易的jQuery,考虑插件和扩展性

8. 手写bind函数

9. 通用的事件监听函数

10. 手写防抖和节流


问答题

1. var和let 和const区别?

Var是ES5语法;let const是ES6语法;var有变量提升(变量可以在使用后声明,也就是变量可以先使用再声明);

Var和let是变量可以修改;const是常量,不可修改;

Let const有块级作用域,var没有

2. Typeof返回哪些类型?

值类型:undefined、string、number、Boolean、symbol

引用类型:object(注意 typeof null === 'object')

function

3. 列举强制类型转换和隐式类型转换

强制:parseInt 、parseFloat、 toString等

隐式:if 、逻辑运算、== 、+拼接字符串

4. split()和join()区别

互相反转,拆分vs拼接

'1-2-3'.split('-') //[1, 2, 3]
[1,2,3].join('-')  // '1-2-3'

5. 数组的pop, push, unshift, shift分别是什么

(功能是什么、返回值是什么、是否会对原数组造成影响)

(1)pop函数 用于弹出数组最后一个元素,返回值是弹出的元素值,

(2)push函数 用于向数组最后添加一个元素,返回值为数组的长度

(3)shift函数 用于弹出数组第一个元素,返回值为弹出数值

(4)unshift函数 用于向数组首位之前插入一个元素,返回值为数组长度

这几个函数都改变了原来的数组,可能会有副作用

const arr = [10, 20, 30, 40]
// pop
const popRes = arr.pop()   
console.log(popRes, arr)   // 40  [10,20,30]
// shift
const shiftRes = arr.shift()
console.log(shiftRes, arr)   // 10 [20,30,40]
// push
const pushRes = arr.push(50) // 返回 length
console.log(pushRes, arr)  // 5 [10,20,30,40,50]
// unshift
const unshiftRes = arr.unshift(5) // 返回 length
console.log(unshiftRes, arr)  // 5 [5,10,20,30,40]

6. 数组API中有哪些是纯函数?

(1)纯函数: 不改变原数组(没有副作用),且返回一个数组

concat函数(向数组后面追加一个数组)、map函数、filter函数(过滤)、slice函数(类似于深拷贝)

// 纯函数:1. 不改变源数组(没有副作用);2. 返回一个数组
const arr = [10, 20, 30, 40]
// concat
const arr1 = arr.concat([50, 60, 70])
// map
const arr2 = arr.map(num => num * 10)
// filter
const arr3 = arr.filter(num => num > 25)
// slice
const arr4 = arr.slice()

(2)非纯函数:push pop shift unshift、forEach、some 、every、 reduce

7. 数组slice和splice的区别

(1)功能区别:slice切片、splice剪接

(2)参数和返回值:返回值都为数组

(3)是否为纯函数:slice纯函数、splice非纯函数

const arr = [10, 20, 30, 40, 50]
// slice 纯函数
const arr1 = arr.slice()   // 10,20,30,40,50
const arr2 = arr.slice(1, 4)  // 20,30,40
const arr3 = arr.slice(2)   // 30,40,50
const arr4 = arr.slice(-2)    //40,50
// splice 非纯函数
const spliceRes = arr.splice(1, 2, 'a', 'b', 'c')   // 把1-2的位置移除再放入a,b,c
// const spliceRes1 = arr.splice(1, 2)    // 把1-2位置移除
// const spliceRes2 = arr.splice(1, 0, 'a', 'b', 'c')
console.log(spliceRes, arr)

8. Ajax请求get和post区别

get一般用于查询操作,post一般用于用户提交操作

get参数拼接在url上,post放在请求体内(数据体积可以更大),因此当进行上传提交操作时如果较大就是用post

安全性:post易于防止CSRF

9. [10, 20, 30].map(parseInt)

map() 方法返回一个新数组,数组中的元素为原始数组元素调用函数处理后的值。

parseInt() 函数可解析一个字符串,并返回一个整数 parseInt(string, radix),将string以radix进制转化为10进制

const res = [10, 20, 30].map(parseInt)
console.log(res)   //[10, NAN, NAN]
// 拆解
[10, 20, 30].map((num, index) => {
    return parseInt(num, index)
})

10. 闭包是什么?有何特性?有何影响?

闭包就是:有权访问另一个函数作用域变量的函数都是闭包

应用场景:函数作为参数被传递(函数在一个地方定义好之后,到另一个地方去执行)

                  函数作为返回值被返回(函数定义好之后会被返回到另一个地方执行)

机制:当我们调用一个闭包函数,在函数执行时,其上下文有个Scope属性,该属性作为一个作用域链包含有该函数被定义时所有外层的变量对象的引用,所以定义了闭包的函数虽然销毁了,但是其变量对象依然被绑定在函数inner上,保留在内存中。

注意:所有的自由变量的查找,是在函数定义的地方,向上级作用域查找,不是在执行的地方

影响:变量会常驻内存,得不到释放。因此闭包不要乱用,可能会影响性能(一般情况一个函数(函数作用域)执行完毕,里面声明的变量会全部释放,被垃圾回收器回收。但闭包让作用域里面的变量,在函数执行完之后依旧保存没有被垃圾回收处掉。)

11. 如何阻止事件冒泡和默认行为

event.stopPropagation()

event.preventDefault()

12. 如何减少DOM操作

DOM操作非常‘昂贵’(占用CPU,可能会造成浏览器重排,耗时),因此尽量避免频繁的DOM操作;

(1)缓存DOM查询结果

(2)多次DOM操作合并到一起

13. 解释jsonp的原理,为什么不是真正的Ajax

14. 函数声明和函数表达式的区别

函数声明 function fn() { ... }

函数表达式 const fn = function() { ... }

函数声明会在代码执行前预加载(类似变量提升),而函数表达式不会

// 函数声明
const res = sum(10, 20)
console.log(res)
function sum(x, y) {
    return x + y
}

// 函数表达式
var sum = function (x, y) {
    return x + y
}
var res = sum(10, 20)
console.log(res)

15. new Object()和Object.create()区别

{} 等同于new Object(), 原型 Object.prototype

ob1 = Object.create(null) 没有原型  obj2 = new Object()有原型  Object.create({ ... })可以指定原型 

const obj1 = {
    a: 10,
    b: 20,
    sum() {
        return this.a + this.b
    }
}
const obj2 = new Object({
    a: 10,
    b: 20,
    sum() {
        return this.a + this.b
    }
})
const obj21 = new Object(obj1) // obj1 === obj21
const obj3 = Object.create(null)  // {}没有属性没有原型
const obj4 = new Object() // {}有原型
// 意思创建一个空对象,把原型挂载到create内容上
const obj5 = Object.create({
    a: 10,
    b: 20,
    sum() {
        return this.a + this.b
    }
})
// 通过ob1创建obj6,那么obj6的原型指向obj1 obj6._proto_ === obj1
const obj6 = Object.create(obj1)  // obj6的原型指向obj1

16. 正则表达式

(1)用户名 字符串 字母开头 后面字母数字下划线,长度为6-30

const reg = / ^[a-zA-Z] \w {5,29} $ /

^开始 [ ] 选择 \w 字母数字下划线  {} 长度范围 $结尾 \d数字  +一次或多次   .匹配任意字符

(2)邮政编码   / \d{6} /

(3)小写英文字母   /^[a-z]+$ /     

(4)英文字母 /^[a-zA-Z]+$/

(5)日期  /^\d{4}-\d{1,2}-\d{1,2}$/

(6)简单IP地址 /\d+\.\d+.\d+.\d+/          \. 表示.   

常用正则表达式教程  https://www.runoob.com/regexp/regexp-syntax.html

17. 如何捕获JS程序中的异常

(1)手动捕获异常try-catch

(2)自动捕获异常 window.onerror

// 自动捕获
window.onerror = function (message, source, lineNum, colNum, error) {
     // 第一,对于跨域的js,如CDN的,不会有详细的报错信息
     // 第二,对于压缩的js,还要配合sourceMap反查到未压缩代码行列
}

18. 什么是JSON

json是一种数据格式,本质是一段字符串

json格式和JS对象结构一致,对JS语言更友好

window.JSON是全局对象(key都需要双引号),JSON.stringify JSON.parse

19. 获取当前页面url参数

(1)传统方法,查找Location.search

(2)URLSearchParams

// 传统方式
function query(name) {
    const search = location.search.substr(1) // 类似 array.slice(1)除去第一个
    // search: 'a=10&b=20&c=30'
    const reg = new RegExp(`(^|&)${name}=([^&]*)(&|$)`, 'i') // i表示大小写不区分
    const res = search.match(reg)
    if (res === null) {
        return null
    }
    return res[2]  // 固定写法 输出b对应的值20
}
query('b')

// URLSearchParams
function query(name) {
    const search = location.search
    const p = new URLSearchParams(search)
    return p.get(name)
}
console.log( query('b') )

20. 介绍一下RAF requestAnimationFrame

     在Web应用中,实现动画效果的方法比较多,Javascript 中可以通过定时器 setTimeout或者setInterval 来实现,css3 可以使用 transition 和 animation 来实现,html5 中的 canvas 也可以实现。除此之外,html5 还提供一个专门用于请求动画的API,那就是 requestAnimationFrame,顾名思义就是请求动画帧。

    要想动画流畅,更新频率要60帧/秒,即16.67ms更新一次视图

    setTimeout需要手动控制频率,而RAF浏览器会自动控制

    后台标签或者隐藏iframe中(最小化),RAF会暂停,而setTimeout依然执行

// 3s 把宽度从 100px 变为 640px ,即增加 540px
// 60帧/s ,3s 180 帧 ,每次变化 3px
const $div1 = $('#div1')
let curWidth = 100
const maxWidth = 640
// RAF
function animate() {
    curWidth = curWidth + 3
    $div1.css('width', curWidth)
    if (curWidth < maxWidth) {
        window.requestAnimationFrame(animate) // 时间不用自己控制
    }
}
animate()

手写代码题

1. 手写深度比较,模拟lodash.isEqual

// 判断是否是对象或数组(不考虑函数)
function isObject(obj) {
    return typeof obj === 'object' && obj !== null
}
// 全相等(深度)
function isEqual(obj1, obj2) {
    // 首先判断是否是对象
    if (!isObject(obj1) || !isObject(obj2)) {
        // 值类型(注意,参与 equal 的一般不会是函数)
        return obj1 === obj2
    }
    if (obj1 === obj2) {
        return true
    }
    // 两个都是对象或数组,而且不相等
    // 1. 先取出 obj1 和 obj2 的 keys ,比较个数
    const obj1Keys = Object.keys(obj1)
    const obj2Keys = Object.keys(obj2)
    if (obj1Keys.length !== obj2Keys.length) {
        return false
    }
    // 2. 以 obj1 为基准,和 obj2 依次递归比较
    for (let key in obj1) {
        // 比较当前 key 的 val —— 递归!!!
        const res = isEqual(obj1[key], obj2[key])
        if (!res) {
            return false
        }
    }
    // 3. 全相等
    return true
}

// 测试
const obj1 = {
    a: 100,
    b: {
        x: 100,
        y: 200
    }
}
const obj2 = {
    a: 100,
    b: {
        x: 100,
        y: 200
    }
}
// console.log( obj1 === obj2 )
console.log( isEqual(obj1, obj2) )

const arr1 = [1, 2, 3]
const arr2 = [1, 2, 3, 4]

2. 手写字符串trim方法,保证兼容(正则表达式)

trim  掐头去尾去空格

String.prototype.trim = unction () {
    return this.replace(/^\s+/,'').replace(/\s+$/,'')  // \s字符
}

3.将url参数解析为JS对象

// 传统方法,分析search
function queryToObj() {
    const res = {}
    const search = location.search.substr(1)  // 去掉?
    search.split('&').forEach(paramStr => {
        const arr = paramStr.split('=')
        const key = arr[0]
        const key = arr[1]
        res[key] = val
    })
    return res
}
// 使用URLSearchParams
function queryToObj() {
    const res = {}
    const pList = new URLSearchParams(location.search)
    pList.forEach((val, key) => {
        res[key] = val
    })
    return res
}

4. 手写flatern考虑多层级

将[1, 2, [3, 4, [10, 20, [100, 200]]], 5]变为[1, 2, 3, 4, 10, 20, 100, 200, 5]

function flat(arr) {
    // 验证 arr 中,还有没有深层数组 [1, 2, [3, 4]]
    const isDeep = arr.some(item => item instanceof Array)
    if (!isDeep) {
        return arr // 已经是 flatern [1, 2, 3, 4]
    }
    // oncat只能解决单层[]
    const res = Array.prototype.concat.apply([], arr)
    return flat(res) // 递归
}

const res = flat( [1, 2, [3, 4, [10, 20, [100, 200]]], 5] )
console.log(res)

5. 数组去重

// 传统方式
function unique(arr) {
    const res = []
    arr.forEach(item => {
        if (res.indexOf(item) < 0) { // 没有当前元素
            res.push(item)
        }
    })
    return res
}

// 使用 Set (无序,不能重复)
function unique(arr) {
    const set = new Set(arr)
    return [...set]   // 解构
}

const res = unique([30, 10, 20, 30, 40, 10])
console.log(res)

6. 手写深拷贝

function deepClone(obj = {}) {
    if (typeof obj !== 'object' || obj == null) {
        // obj 是 null ,或者不是对象和数组,直接返回
        return obj
    }
    // 初始化返回结果
    let result
    if (obj instanceof Array) {    // 判断是否为数组
        result = []
    } else {
        result = {}
    }
    for (let key in obj) {
        // 保证 key 不是原型的属性
        if (obj.hasOwnProperty(key)) {
            // 递归调用!!!
            result[key] = deepClone(obj[key])
        }
    }
    // 返回结果
    return result
}

7. 手写一个简易的jQuery,考虑插件和扩展性

class jQuery {
    constructor(selector) {
        const result = document.querySelectorAll(selector)
        const length = result.length
        for (let i = 0; i < length; i++) {
            this[i] = result[i]
        }
        this.length = length
        this.selector = selector
    }
    get(index) {
        return this[index]
    }
    each(fn) {
        for (let i = 0; i < this.length; i++) {
            const elem = this[i]
            fn(elem)
        }
    }
    on(type, fn) {
        return this.each(elem => {
            elem.addEventListener(type, fn, false)
        })
    }
    // 扩展很多 DOM API
}
 
// 插件
jQuery.prototype.dialog = function (info) {
    alert(info)
}
 
// 扩展 “造轮子”
class myJQuery extends jQuery {
    constructor(selector) {
        super(selector)
    }
    // 扩展自己的方法
    addClass(className) {
    }
    style(data) {
    }
}

8. 手写bind函数

// 模拟 bind
Function.prototype.bind1 = function () {
    // 将参数拆解为数组
    const args = Array.prototype.slice.call(arguments)
    // 获取 this(数组第一项)
    const t = args.shift()     // 拿走数组第一项
    // fn1.bind(...) 中的 fn1
    const self = this
    // bind返回一个函数
    return function () {
        return self.apply(t, args)
    }
}
function fn1(a, b, c) {
    console.log('this', this)
    console.log(a, b, c)
    return 'this is fn1'
}
const fn2 = fn1.bind1({x: 100}, 10, 20, 30)
const res = fn2()
console.log(res)

9. 通用的事件监听函数

function bindEvent(elem, type, selector, fn) {  // selector是个css选择器
    if (fn == null) {   // 判断只传入三个参数
        fn = selector   
        selector = null
    }
    elem.addEventListener(type, event => {
        const target = event.target    
        if (selector) {
            // 有selector时是代理绑定
            if (target.matches(selector)) {  // 判断DOM元素是否符合css选择器
                fn.call(target, event)
            }
        } else {
            // selector为空,只有三个参数,是普通绑定
            fn.call(target, event)
        }
    })
}
 
// 普通绑定
const btn1 = document.getElementById('btn1')
bindEvent(btn1, 'click', function (event) {    // 注意不能使用箭头函数,否则获取的是上级window
    // console.log(event.target) // 获取触发的元素
    event.preventDefault() 
    alert(this.innerHTML)     // this
})
 
// 代理绑定
const div3 = document.getElementById('div3')
bindEvent(div3, 'click', 'a', function (event) {
    event.preventDefault()    // 阻止默认行为(页面页面调转)
    alert(this.innerHTML)    // 如果使用箭头函数的写法 event.target.innerHTML
})

10. 手写防抖和节流

// 防抖封装
function debounce(fn, delay = 500) {
    // timer 是闭包中的
    let timer = null
    // 返回一个函数
    return function () {
        if (timer) {
            clearTimeout(timer)
        }
        timer = setTimeout(() => {
            fn.apply(this, arguments)  
            timer = null
        }, delay)
    }
}
 
input1.addEventListener('keyup', debounce(function (e) {
    console.log(e.target)
    console.log(input1.value)
}, 600))
// 节流函数
function throttle(fn, delay = 100) {
    let timer = null
 
    return function () {
        if (timer) {
            return
        }
        timer = setTimeout(() => {
            fn.apply(this, arguments)
            timer = null
        }, delay)
    }
}
 
div1.addEventListener('drag', throttle(function (e) {
    console.log(e.offsetX, e.offsetY)
}, 200))

 

你可能感兴趣的:(Javascript)