Skip to content
本页目录

真题模拟

该章节会结合之前所有的知识点进行连续问答,题目都是无序的

1. var 和 let const 的区别

  • var 是 ES5 及之前的语法,let const 是 ES6 语法
  • var 和 let 是变量,可修改;const 是常量,不可修改
  • var 有变量提升,let const 没有
  • var 没有块级作用域,let const 有 (ES6 语法有块级作用域)
js
// var 变量提升
console.log('a', a)
var a = 100

// let 没有变量提升
console.log('b', b)
let b = 200
js
// var 没有块级作用域
for (var i = 0; i < 10; i++) {
    var j = 1 + i
}
console.log(i, j)

// let 有块级作用域
for (let x = 0; x < 10; x++) {
    let y = 1 + x
}
console.log(x, y)

2. typeof 有哪些返回类型?

js
// 判断所有值类型
let a
console.log(a) // 'undefined'
const str = 'abc'
typeof str  // 'string'
const n = 100
typeof n // 'number'
const b = true
typeof b // 'boolean'
const s = Symbol('s')
typeof s // 'symbol'

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

  • 强制 parseInt parseFloat
  • 隐式 if==+ 拼接字符串

4. 手写深度比较,如 lodash isEqual

js
// 实现如下效果
const obj1 = {a: 10, b: { x: 100, y: 200 }}
const obj2 = {a: 10, b: { x: 100, y: 200 }}
isEqual(obj1, obj2) === true
js
// 判断是否是 object
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. 先取出 obje2 obj2 的 keys,比较个数
    const obj1Keys = Object.keys(obj1)
    const obj2Keys = Object.keys(obj2)
    if (obj1Keys.length !== obj2Keys.length) {
        // keys 个数不相等,则不是全等
        return false
    }
    // 2. 以 obj1 为基准,和 obj2 依次递归比较
    for (let key in obj1) {
        // 递归比较
        const res = isEqual(obj1[key], obj2[key])
        if (!res) {
            // 遇到一个不相等的,则直接返回 false
            return false
        }
    }
    // 3. 都相等,则返回 true
    return true
}

5. split()join() 的区别

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

6. 数组的 pop push unshift shift 分别做什么

注意以下几点

  • 函数作用是什么?
  • 返回值是什么?
  • 对原数组是否造成影响?
  • 如何对原数组不造成影响?concat slice map filter

【扩展】数组 API 的纯函数和非纯函数

纯函数 —— 1. 不改变来源的数组; 2. 返回一个数组

  • concat
  • map
  • filter
  • slice
js
const arr = [100, 200, 300]
const arr1 = arr.concat([400, 500])
const arr2 = arr.map(num => num * 10)
const arr3 = arr.filter(num => num > 100)
const arr4 = arr.slice(-1)

非纯函数

情况1,改变了原数组

  • push
  • reverse
  • sort
  • splice

情况2,未返回数组

  • push
  • forEach
  • reduce
  • some

7. 数组 slice 和 splice 的区别?

slice - 切片;splice - 剪接;

js
// slice()
const arr1 = [10, 20, 30, 40, 50]
const arr2 = arr1.slice() // arr2 和 arr1 不是一个地址,纯函数,重要!!!

// arr.slice(start, end)
const arr1 = [10, 20, 30, 40, 50]
const arr2 = arr1.slice(1, 4) // [20, 30, 40]

// arr.slice(start)
const arr1 = [10, 20, 30, 40, 50]
const arr2 = arr1.slice(2) // [30, 40, 50]

// 负值
const arr1 = [10, 20, 30, 40, 50]
const arr2 = arr1.slice(-2) // [40, 50]
js
// arr.splice(index, howmany, item1, ....., itemX)
const arr1 = [10, 20, 30, 40, 50]
const arr2 = arr1.splice(1, 2, 'a', 'b', 'c') // [20, 30]
// arr1 会被修改,不是纯函数,即有副作用

8. [10, 20, 30].map(parseInt) 的结果是什么?

js
// 拆解开就是
[10, 20, 30].map((num, index) => {
    return parseInt(num, index)
    // parseInt 第二个参数是进制
    // 如果省略该参数或其值为 0,则数字将以 10 为基础来解析。如果它以 “0x” 或 “0X” 开头,将以 16 为基数。
    // 如果该参数小于 2 或者大于 36,则 parseInt() 将返回 NaN
})
js
// 可以对比
[10, 20, 30].map((num, index) => {
    return parseInt(num, 10)
})

9. ajax 请求中 get 和 post 的区别

  • get 一般用于查询操作,post 一般用于提交操作
  • get 参数在 url 上,post 在请求体内
  • 安全性:post 请求易于防止 CSRF

10. call 和 apply 的区别

  • fn.call(this, p1, p2, p3)
  • fn.apply(this, arguments)

11. 事件委托(代理)是什么

js
const p1 = document.getElementById('p1')
const body = document.body
bindEvent(p1, 'click', e => {
    e.stopPropagation() // 注释掉这一行,来体会事件冒泡
    alert('激活')
})
bindEvent(body, 'click', e => {
    alert('取消')
})

原理: 通过事件冒泡的机制 从而在大范围绑定事件时只给上级元素绑定 也可以触发对应事件

12. 闭包是什么,有什么特性,对页面有什么影响

知识点回顾

  • 回顾作用域和自由变量
  • 闭包的应用场景:函数作为参数被传入,函数作为返回值被返回
  • 关键点:自由变量的查找,要在函数定义的地方,而不是执行的地方

对页面的影响

  • 变量内存得不到释放,可能会造成内存积累(不一定是泄露)
js
// 自由变量示例 —— 内存会被释放
let a = 0
function fn1() {
    let a1 = 100

    function fn2() {
        let a2 = 200

        function fn3() {
            let a3 = 300
            return a + a1 + a2 + a3
        }
        fn3()
    }
    fn2()
}
fn1()
js
// 闭包 函数作为返回值 —— 内存不会被释放
function create() {
    let a = 100
    return function () {
        console.log(a)
    }
}
let fn = create()
let a = 200
fn() // 100

// 闭包 函数作为参数 —— 内存不会被释放
function print(fn) {
    let a = 200
    fn()
}
let a = 100
function fn() {
    console.log(a)
}
print(fn) // 100

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

event.stopPropagation()event.preventDefault()

14. 添加 删除 替换 插入 移动 DOM 节点的方法

(粘贴一下代码片段,作为回顾)

15. 怎样减少 DOM 操作?

  • 缓存 DOM 查询结果
  • 多次操作,合并到一次插入

(粘贴一下代码片段,作为回顾)

16. 解释 jsonp 的原理,以及为什么不是真正的 ajax

  • 浏览器的同源策略,什么是跨域?
  • 哪些 html 标签能绕过跨域?
  • jsonp 原理

17. document load 和 document ready 的区别

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

18. ===== 的不同

  • == 会尝试进行类型转换
  • === 严格相等

19. 函数声明与函数表达式的区别?

js
const res = sum(10, 20)
console.log(res) // 30

// 函数声明
function sum(x, y) {
    return x + y
}
js
const res = sum(100, 200)
console.log(res) // 报错!!!

// 函数表达式
const sum = function(x, y) {
    return x + y
}

20. new Object()Object.create() 的区别

示例一

js
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 obj3 = Object.create({
    a: 10,
    b: 20,
    sum() {
        return this.a + this.b
    }
})
// 分别打印看结构

示例二

js
const obj1 = {
    a: 10,
    b: 20,
    sum() {
        return this.a + this.b
    }
}
const obj2 = new Object(obj1)
console.log(obj1 === obj2) // true
const obj3 = Object.create(obj1)
console.log(obj1 === obj3) // false

const obj4 = Object.create(obj1)
console.log(obj3 === obj4) // false

// 然后修改 obj1 ,看 obj2 obj3 和 obj4
obj1.printA = function () {
    console.log(this.a)
}

21. 对作用域上下文和 this 的理解,场景题

js
const User = {
    count: 1,
    getCount: function() {
        return this.count
    }
}
console.log(User.getCount()) // what?
const func = User.getCount
console.log( func() ) // what?

22. 对作用域和自由变量的理解,场景题

js
let i
for(i = 1; i <= 3; i++) {
  setTimeout(function(){
      console.log(i)
  }, 0)
}
// what?

23. 判断字符串以字母开头,后面可以是数字,下划线,字母,长度为 6-30

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

js
/\d{6}/ // 邮政编码
/^[a-z]+$/ // 小写英文字母
/^[A-Za-z]+$/ // 英文字母
/^\d{4}-\d{1,2}-\d{1,2}$/ // 日期格式
/^[a-zA-Z]\w{5,17}$/ // 用户名(字母开头,字母数字下划线,5-17位)
/\d+\.\d+\.\d+\.\d+/ // 简单的 IP 地址格式

24. 以下代码,分别 alert 出什么?

js
let a = 100
function test() {  
    alert(a)
    a = 10
    alert(a)
}
test()
alert(a)
// what?

25. 手写 trim 函数,保证浏览器兼容性

js
String.prototype.trim= function (){
    return this.replace(/^\s+/,"").replace(/\s+$/,"")
}

知识点:原型,this,正则

26. 如何获取多个数值中的最大值?

js
Math.max(10, 30, 20, 40)
// 以及 Math.min
js
function max() {
    const nums = Array.prototype.slice.call(arguments) // 变为数组
    let max = 0
    nums.forEach(n => {
        if (n > max) {
            max = n
        }
    })
    return max
}

27. 如何用 JS 实现继承?

class 代码

28. 程序中捕获异常的方法

js
try {
    // todo
} catch (ex) {
    console.error(ex) // 手动捕获 catch
} finally {
    // todo
}
js
// 自动捕获 catch(但对跨域的 js 如 CDN 的,不会有详细的报错信息)
window.onerror = function (message, source, lineNom, colNom, error) {
}

29. 什么是 JSON ?

首先,json 是一种数据格式标准,本质是一段字符串,独立于任何语言和平台。注意,json 中的字符串都必须用双引号。

json
{
    "name": "张三",
    "info": {
        "single": true,
        "age": 30,
        "city": "北京"
    },
    "like": ["篮球", "音乐"]
}

其次,JSON 是 js 中一个内置的全局变量,有 JSON.parseJSON.stringify 两个 API 。

30. 获取当前页面 url 参数

自己实现

js
// const url = 'https://www.xxx.com/path/index.html?a=100&b=200&c=300#anchor'
function query(name) {
    const search = location.search.substr(1) // 去掉前面的 `?`
    const reg = new RegExp(`(^|&)${name}=([^&]*)(&|$)`, 'i')
    const res = search.match(reg)
    if (res === null) {
        return null
    }
    return decodeURIComponent(res[2])
}
console.log( query('a') )
console.log( query('c') )

新的 API URLSearchParams

js
const pList = new URLSearchParams(location.search)
pList.get('a')

31. 将 url 参数解析为 JS 对象?

自己编写

js
function queryToObj() {
    const res = {}
    const search = location.search.substr(1) // 去掉前面的 `?`
    search.split('&').forEach(paramStr => {
        const arr = paramStr.split('=')
        const key = arr[0]
        const val = arr[1]
        res[key] = val
    })
    return res
}

新的 API URLSearchParams

js
function queryToObj() {
    const res = {}
    const pList = new URLSearchParams(location.search)
    pList.forEach((val, key) => {
        res[key] = val
    })
    return res
}

32. 实现数组 flat ,考虑多层级

js
function flat(arr) {
    // 验证 arr 中,还有没有深层数组,如 [1, [2, 3], 4]
    const isDeep = arr.some(item => item instanceof Array)
    if (!isDeep) {
        return arr // 没有深层的,则返回
    }

    // 多深层的,则 concat 拼接
    const res = Array.prototype.concat.apply([], arr) // 回归上文,apply 和 call 的区别
    return flat(res) // 递归调用,考虑多层
}
flat([[1,2], 3, [4,5, [6,7, [8, 9, [10, 11]]]]])

33. 数组去重

要考虑:

  • 顺序是否一致?
  • 时间复杂度

ES5 语法手写

js
// 写法一
function unique(arr) {
    const obj = {}
    arr.forEach(item => {
        obj[item] = 1 // 用 Object ,去重计算高效,但顺序不能保证。以及,非字符串会被转换为字符串!!!
    })
    return Object.keys(obj)
}
unique([30, 10, 20, 30, 40, 10])
js
// 写法二
function unique(arr) {
    const res = []
    arr.forEach(item => {
        if (res.indexOf(item) < 0) { // 用数组,每次都得判断是否重复(低效),但能保证顺序
            res.push(item)
        }
    })
    return res
}
unique([30, 10, 20, 30, 40, 10])

用 ES6 Set

js
// 数组去重
function unique(arr) {
    const set = new Set(arr)
    return [...set]
}
unique([30, 10, 20, 30, 40, 10])

34. 手写深拷贝

粘贴代码

【注意】Object.assign 不是深拷贝,可以顺带讲一下用法

  • Object.assign(obj1, {...})
  • const obj2 = Object.assign({}, obj1, {...})

35. 介绍一下 RAF requestAnimationFrame

想用 JS 去实现动画,老旧的方式是用 setTimeout 实时刷新,但这样效率非常低,而且可能会出现卡顿。

  • 想要动画流畅,更新频率是 60帧/s ,即每 16.6ms 更新一次试图。太慢了,肉眼会感觉卡顿,太快了肉眼也感觉不到,资源浪费。
  • 用 setTimeout 需要自己控制这个频率,而 requestAnimationFrame 不用自己控制,浏览器会自动控制
  • 在后台标签或者隐藏的<iframe>中,setTimeout 依然会执行,而 requestAnimationFrame 会自动暂停,节省计算资源

(代码演示)

36. 对前端性能优化有什么了解?一般都通过那几个方面去优化的?

原则

  • 多使用内存、缓存或者其他方法
  • 减少 CPU 计算、较少网络

方向

  • 加载页面和静态资源
  • 页面渲染

37. 有序与无序

形象的比喻

假象一个场景:一个大操场,一万个学生,你是管理这些学生的老师。

无序结构:放羊式散养,但你要认识每个人(费脑子),有一个花名册

  • 新增一个学生:记录下这个学生的名字,把学生丢进操场,爱去哪儿去哪儿,别跑了就行。
  • 查找一个学生:根据学生的名字,你站在高处一眼把学生认出来。(或者,那一根线把学生的手和名单上的名字牵起来,查找时根据名字,牵线拉出来)

(此处画个图。。。)

有序结构:排好队,一个挨一个,你不用认识每个人,也无需要花名册

  • 最前面插入一个学生:让 10000 个人挨个往后挪一个位置,空出最前面的位置,插入学生
  • 中间插入一个学生:找位置加塞进去,后面每个学生都往后挪一个位置(5000 个人挨个往后挪)
  • 最后面插入一个学生:比较简单,直接排队尾即可
  • 查找学生:从第一名学生开始,挨个往后查找,直到找到为止

(此处画个图。。。)

Array 和 Object

抛开 ES6 的 Map 和 Set 不谈。其实 ES5 中数组和对象,就是有序和无序。

js
// 有序
const arr = [ /* 一万个学生 */ ]
arr.unshift('张三') // 最前面插入一位
arr.splice(4999, 0, '李四') // 中间插入一位
arr.push('王五') // 最后插入一位
arr.includes('xxx') // 查找一个学生

// 无序
const obj = {
    'zhangsan': true, // 光记录名字,其他信息没有,就随便用 true 代替了
    'lisi': true.
    // 一共一万个学生
}
obj['wangwu'] = true // 插入一个学生
'xxx' in obj // 查找一个学生

应用场景

虽然大家平常都用数组和对象,但对有序和无序的场景并不一定有考虑到。举一个 vnode 例子。

js
{ // tag attrs children 可以是无序的
    tag: 'div',
    attrs: { // id className style 可以是无序的
        id: 'div1',
        className: 'container'
        style: 'color: red;'
    },
    children: [ // children 必须是有序的!!!否则渲染就乱了!!!
        {
            tag: 'img',
            attrs: { src: 'xxx.png', alt: 'xxx' }
        },
        'hello',
        'world'
    ]
}

还有:栈和队列,必须是有序的,顺序不能乱。。。

总结

  • 有序结构
    • 优点:组织有序,不混乱
    • 缺点:插入、查找太慢
  • 无序结构
    • 优点:插入、查找很快
    • 缺点:无序

【思考】有没有一种数据结构能整合两者的优点呢?—— 二叉树,以及二叉树的其他变种

38. Map 和 Object 的不同

API 不同

js
// 初始化
const m = new Map([
    ['key1', 'hello'],
    ['key2', 100],
    ['key3', { x: 10 }]
])

// 新增
m.set('name', 'zzz')

// 删除
m.delete('key1')

// 判断
m.has('key2')

// 遍历
m.forEach((value, key) => console.log(key, value))

// 长度(Map 是有序的,下文会讲,所有有长度概念)
m.size

以任意类型为 key

js
const m = new Map()
const o = { p: 'Hello World' }

m.set(o, 'content')
m.get(o) // "content"

m.has(o) // true
m.delete(o) // true
m.has(o) // false

Map 是有序结构

Object key 不能按照既定顺序输出

js
// Object keys 是无序的
const data1 = {'1':'aaa','2':'bbb','3':'ccc','测试':'000'}
Object.keys(data1) // ["1", "2", "3", "测试"]

const data2 = {'测试':'000','1':'aaa','3':'ccc','2':'bbb'};
Object.keys(data2); // ["1", "2", "3", "测试"]

Map key 可以按照既定顺序输出

js
const m1 = new Map([
    ['1', 'aaa'],
    ['2', 'bbb'],
    ['3', 'ccc'],
    ['测试', '000']
])
m1.forEach((val, key) => { console.log(key, val) })
const m2 = new Map([
    ['测试', '000'],
    ['1', 'aaa'],
    ['3', 'ccc'],
    ['2', 'bbb']
])
m2.forEach((val, key) => { console.log(key, val) })

Map 很快

Map 作为纯净的 key-value 数据结构,它比 Object 承载了更少的功能。
Map 虽然有序,但操作很快,和 Object 效率相当。

js
// Map
const m = new Map()
for (let i = 0; i < 1000 * 10000; i++) {
    m.set(i + '', i)
}
console.time('map find')
m.has('2000000')
console.timeEnd('map find')
console.time('map delete')
m.delete('2000000')
console.timeEnd('map delete')
js
// Object
const obj = {}
for (let i = 0; i < 1000 * 10000; i++) {
    obj[i + ''] = i
}
console.time('obj find')
obj['200000']
console.timeEnd('obj find')
console.time('obj delete')
delete obj['200000']
console.timeEnd('obj delete')

另外,Map 有序,指的是 key 能按照构架顺序输出,并不是说它像数组一样是一个有序结构 —— 否则就不会这么快了
但这就足够满足我们的需求了。

39. Set 和数组的区别

Set 元素不能重复

js
const arr = [10, 20, 30, 30, 40]
const set = new Set([10, 20, 30, 30, 40]) // 会去重
console.log(set) // Set(4) {10, 20, 30, 40}
js
// 数组去重
function unique(arr) {
    const set = new Set(arr)
    return [...set]
}
unique([10, 20, 30, 30, 40])

API 不一样

js
// 初始化
const set = new Set([10, 20, 30, 30, 40]) 

// 新增(没有 push unshift ,因为 Set 是无序的,下文会讲)
set.add(50)

// 删除
set.delete(10)

// 判断
set.has(20)

// 长度
set.size

// 遍历
set.forEach(val => console.log(val))

// set 没有 index ,因为是无序的

Set 是无序的,而数组是有序的

先看几个测试

  • 数组:前面插入元素 vs 后面插入元素
  • 数组插入元素 vs Set 插入元素
  • 数组寻找元素 vs Set 寻找元素
js
// 构造一个大数组
const arr = []
for (let i = 0; i < 1000000; i++) {
    arr.push(i)
}

// 数组 前面插入一个元素
console.time('arr unshift')
arr.unshift('a')
console.timeEnd('arr unshift') // unshift 非常慢
// 数组 后面插入一个元素
console.time('arr push')
arr.push('a')
console.timeEnd('arr push') // push 很快

// 构造一个大 set
const set = new Set()
for (let i = 0; i < 1000000; i++) {
    set.add(i)
}

// set 插入一个元素
console.time('set test')
set.add('a')
console.timeEnd('set test') // add 很快

// 最后,同时在 set 和数组中,寻找一个元素
console.time('set find')
set.has(490000)
console.timeEnd('set find') // set 寻找非常快
console.time('arr find')
arr.includes(490000)
console.timeEnd('arr find') // arr 寻找较慢

什么是无序,什么是有序?参考 x1-有序和无序.md

  • 无序:插入、查找更快
  • 有序:插入、查找更慢

因此,如果没有强有序的需求,请用 Set ,会让你更快更爽!

40. 数组求和

传统方式

js
function sum(arr) {
    let res = 0
    arr.forEach(n => res = res + n)
    return res
}
const arr = [10, 20, 30]
console.log( sum(arr) )

reduce 方法的使用

js
// 累加器
const arr1 = [10, 20, 30, 40, 50]
const arr1Sum = arr1.reduce((sum, curVal, index, arr) => {
    console.log('reduce function ......')
    console.log('sum', sum)
    console.log('curVal', curVal)
    console.log('index', index)
    console.log('arr', arr)

    return sum + curVal // 返回值,会作为下一次执行的 sum
}, 0)
console.log('arr1Sum', arr1Sum)

reduce 的其他用法

js
// 计数
function count(arr, value) {
    // 计算 arr 中有几个和 value 相等的数
    return arr.reduce((c, item) => {
        return item === value ? c + 1 : c
    }, 0)
}
const arr2 = [10, 20, 30, 40, 50, 10, 20, 10]
console.log( count(arr2, 20) )
js
// 数组输出字符串
const arr3 = [
    { name: 'xialuo', number: '100' },
    { name: 'madongmei', number: '101' },
    { name: 'zhangyang', number: '102' }
]
// // 普通做法 1(需要声明变量,不好)
// let arr3Str = ''
// arr3.forEach(item => {
//     arr3Str += `${item.name} - ${item.number}\n`
// })
// console.log(arr3Str)
// // 普通做法 2(map 生成数组,再进行 join 计算)
// console.log(
//     arr3.map(item => {
//         return `${item.name} - ${item.number}`
//     }).join('\n')
// )
// reduce 做法(只遍历一次,即可返回结果)
console.log(
    arr3.reduce((str, item) => {
        return `${str}${item.name} - ${item.number}\n`
    }, '')
)

41. 手写 Promise

要从 0 实现一个 Promise/A+ 的所有功能,是非常复杂的。面试一共 1h ,不可能考这么详细。
再者,绝大部分程序员也无法短时间、高质量的写出一个完整的 Promise 。因为日常不写,只用。

所以,面试考察“手写 Promise”考察的就是一个设计思路,编码能力。并不是真让你写出来使用。

Promise 基本功能(面试时能写出这些,就足够了)

  • 初始化
  • 异步执行
  • then catch 和 链式调用
  • .resolve .reject
  • .all .race

写代码……


const v = fn1(this.value) 返回值可能是一个 promise 实例