36 道 前端JavaScript初级面试题

在我们找工作时经常会因为面试问题而头痛,本文就来为大家收集一些初级JavaScrip面试题和答案,希望对大家有一定的帮助。

目录

  1. var 和 let const 的区别
  2. typeof 返回哪些类型
  3. 列举强制类型转换和隐式类型转换
  4. 手写深度比较,模拟 lodash isEqual
  5. split() 和 join() 的区别
  6. 数组的 pop push unshift shift 分别做什么
  7. 数组 slice 和 splice 的区别
  8. [10,20,30].map(parseInt) 返回的结果是什么?
  9. ajax 请求 get 和 post 的区别?
  10. call() apply() bind() 作用和区别
  11. 事件代理(委托)是什么?
  12. 闭包是什么?有什么特性?有什么负面影响?
  13. 如何阻止事件冒泡和默认行为?
  14. 查找,添加,删除,移动DOM节点的方法
  15. 如何减少DOM操作?
  16. 解释 jsonp 的原理,为何它不是真正的ajax ?
  17. document load 和 ready 的区别
  18. ===== 的不同
  19. 函数声明和函数表达式的区别
  20. new Object() 和 Object.create()的区别
  21. 关于 this 的场景题
  22. 关于作用域和自由变量的场景题-1
  23. 判断字符串以字母开头,后面字母数字下划线,长度 6 - 30
  24. 关于作用域和自由变量的场景题-2
  25. 手写字符串 trim 方法,保证浏览器兼容性
  26. 如何获取多个数字中的最大值
  27. 如何用JS实现继承?
  28. 如何捕获JS程序中的异常?
  29. 什么是JSON?
  30. 获取当前页面的url 参数
  31. 将url 参数解析为JS对象
  32. 手写数组 flatern, 考虑多层级
  33. 数组去重
  34. 手写深拷贝
  35. 介绍一下 RAF requestAnimateFrame
  36. 前端性能如何优化?一般从哪几个方面考虑?

1. var 和 let const 的区别

  • var 是 ES5 语法, let const 是 ES6 语法; var 有变量提升
  • var 和 let 是变量,可修改;const 是常量,不可修改
  • let const 有块级作用域(ES6),var 没有

var 的变量提升:

// 变量提升 ES5
console.log(a) // undefined
var a = 200

var a
console.log(a) // undefined
a = 200

let 块级作用域:

// 块级作用域
for (let i = 0; i < 10; i++) {
    let j = i + 1
}
console.log(j)  // 报错

2. typeof 返回哪些类型

  • 值类型:undefined string number boolean symbol
  • 引用类型:object (注意: typeof null === ‘object’)
  • 函数类型:function

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

  • 强制:parseInt parseFloat toString
  • 隐式:if 逻辑运算 == +拼接字符串

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

实现效果如下:

// 块级作用域
for (let i = 0; i < 10; i++) {
    let j = i + 1
}
console.log(j)  // 报错
// 判断是否是对象或数组
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]

5. split() 和 join() 的区别

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

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

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

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]
【扩展】数组的API,有哪些是纯函数?

纯函数:不对外界产生副作用的函数就叫纯函数

concat map filter slice 就是纯函数,它们具有以下特点:

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

非纯函数

  • push pop shift unshift
  • forEach
  • some every
  • reduce

7. 数组 slice 和 splice 区别

  • 功能区别 (slice - 切片,splice - 剪接)
  • 参数和返回值
  • 是否是纯函数
slice 纯函数
const arr = [10, 20, 30, 40, 50]
const arr1 = arr.slice()
console.log(arr1) // [10, 20, 30, 40, 50]
const arr2 = arr.slice(1, 4)
console.log(arr2) // [20, 30, 40]
const arr3 = arr.slice(2)
console.log(arr3) // [30, 40, 50]
const arr4 = arr.slice(-3)
console.log(arr4) // [30, 40, 50]
splice 非纯函数
const arr = [10, 20, 30, 40, 50]
// splice 非纯函数
const spliceRes = arr.splice(1, 2, 'a', 'b', 'c')
console.log(spliceRes, arr) //[20,30] [10, 'a', 'b', 'c', 40, 50]
const spliceRes1 = arr.splice(1, 2)
console.log(spliceRes1, arr)//[20,30] [10, 40, 50]
const spliceRes2 = arr.splice(1, 0, 'a', 'b', 'c')
console.log(spliceRes2, arr)//[] [10, "a", "b", "c", 20, 30, 40, 50]

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

  • map 的参数和返回值
  • parseInt 参数和返回值
const res = [10, 20, 30].map(parseInt)
console.log(res) // [10, NaN, NaN]

拆解后等同于如下:

[10, 20, 30].map((num, index) => {
    return parseInt(num, index)
})

parseInt(10, 0) // 10
parseInt(10, 10) // 10
parseInt(20,1) // NaN
parseInt(30, 2) // NaN

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

Ajax, jQuery ajax, fetch,axios ヾ(⌐■_■)ノ看完 你就明白了~~

  • get 一般用于查询操作,post 一般用于提交操作
  • get 参数拼接在url 上,post 放在请求体内(数据体积可更大)
  • 安全性:post 易于防止CSRF

10. call() apply() bind() 作用和区别

fn.call(this, p1, p2, p3)
fn.apply(this, arguments)
var fn1 = fn.bind(this, p1, p2, p3)
fn1()

相同点:

  • 改变对象的执行上下文(总的来说,就是改变this的指向)。
    this关键字,就是所谓的执行上下文。this关键字在函数中,表示的是一个指向,this的指向永远是一个对象。

  • 都可以指定调用实参。

不同点:

  • call()和bind()的参数是直接连续传递,而apply传递参数是以一个数组传递。
  • bind()会返回一个方法。

注意:
如果call()和apply()的第一个参数是null或者undefined,那么this的指向就是全局变量,在浏览器里就是window对象。

代码演示:

var num = "全局中的num";
var o1 = {
    num:"o1中的num",
    f1:function(){
        console.log(this.num);
    },
    f2:function (a,b) {
        console.log(a+b);
        return a+b;
    }
}

var o2 = {
    num:"o2中的num"
}

o1.f1.call(o2); //输出:o2中的num
o1.f1.apply(o2);//输出:o2中的num
var f3 = o1.f1.bind(o2);
f3();//输出:o2中的num

o1.f2.call(o2,1,2); //输出:3
o1.f2.apply(o2,[1,2]);//输出:3
var f4 = o1.f2.bind(o2,1,2);
f4();//输出:3

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

利用事件冒泡的原理,让自己的所触发的事件,让他的父元素代替执行!

  • js中事件冒泡我们知道,子元素身上的事件会冒泡到父元素身上。
  • 事件代理就是,本来加在子元素身上的事件,加在了其父级身上。
  • 那就产生了问题:父级那么多子元素,怎么区分事件本应该是哪个子元素的?
  • 答案是:event对象里记录的有“事件源”,它就是发生事件的子元素。
  • 它存在兼容性问题,在老的IE下,事件源是 window.event.srcElement,其他浏览器是 event.target
用事件委托有什么好处呢?
  • 第一个好处是效率高,比如,不用for循环为子元素添加事件了
  • 第二个好处是,js新生成的子元素也不用新为其添加事件了,程序逻辑上比较方便

推荐:JS事件绑定 事件冒泡与捕获 事件代理

12. 闭包是什么?有什么特性?有什么负面影响?

JS基础知识:作用域和闭包 ლ(´ڡ`ლ)一样的话题,一样的配方!

  • 作用域和自由变量
  • 闭包应用场景:作为参数被传入,作为返回值被返回
    注意:自由变量的查找,要在函数定义的地方(而非执行的地方)

闭包影响:
变量会常驻内存,得不到释放。闭包不要乱用

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

  • event.stopPropagation()
  • event.preventDefault()

14. 查找,添加,删除,移动DOM节点的方法

DOM操作总结 (。♥ᴗ♥。) 哇!!

15. 如何减少DOM操作?

DOM操作总结 (。♥ᴗ♥。) 哇!!

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

16. 解释 jsonp 的原理,为何它不是真正的ajax ?

同源策略和跨域 (ノ°▽°)ノ 冲鸭!征服她!!!
浏览器的同源策略(服务端没有同源策略)和跨域
后端如何nginx, 我们一般称为转发,不称为跨域

  • 哪些html 标签能绕过跨域?

    • 加载图片 CSS JS 可无视同源策略
    • <img src="跨域的图片地址" />
    • <link href="跨域的css地址" />
    • <script src="跨域的js地址"></script>
  • jsonp原理
    简易代码示例:
    后端被请求文件:jsonp.js (由后端处理拼接生成)

    // jsonp.js 文件内容
    abc(
        { name: 'xxx' }
    )

    前端请求代码:

    <script>
      window.abc = function (data) {
            console.log(data)
        }
    </script>
    <script src="http://localhost:8002/jsonp.js?username=xxx&callback=abc"></script>

17. document load 和 ready 的区别

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

18. ===== 的区别

JS基础知识:变量的类型和计算

  • == 会尝试类型转换
  • === 严格相等
  • 哪些场景才用 == ?

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

  • 函数声明 function fn() {...}
  • 函数表达式 const fn = function() {...}
  • 函数声明会在代码执行前预加载,而函数表达式不会
// 函数声明
const res = sum(10, 20)
console.log(res)     // 30
function sum(x, y) {
    return x + y
}

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

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

前提:要先明白原型和原型链 JS基础知识:原型和原型链

  • {} 等同于 new Object(), 原型 Object.prototype
  • Object.create(null) 没有原型, 必须传个参数
  • Object.create({…}) 可指定原型

Object.create({…}) : 创建一个空对象,把原型__proto__ 指向传入的对象
代码示例:

 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) // obj21 === obj1

const obj3 = Object.create(null)
const obj4 = new Object() // {}

const obj5 = Object.create({
    a: 10,
    b: 20,
    sum() {
        return this.a + this.b
    }
})

const obj6 = Object.create(obj1)  // obj6.__proto__ == obj1

21. 关于 this 的场景题

const User = {
    count: 1,
    getCount: function() {
        return this.count
    }
}


console.log(User.getCount()) // 1
const func = User.getCount()
console.log(func())  //  func is not a function

this 的指向:看它执行的时候

22. 关于作用域和自由变量的场景题-1

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

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

  • const reg = /^[a-zA-Z]\w{5,29}$/
  • 学习正则表达式的规则
  • 手写常见的正则表达式
  • 网上有很多教程,自行学习

24. 关于作用域和自由变量的场景题-2

let a = 100
function test() {
    alert(a)
    a = 10
    alert(a)
}
test()
alert(a)

// 100 10 10

25. 手写字符串 trim 方法,保证浏览器兼容性

作用:去除字符串开头和结尾的空字符串

// 原型,this,正则表达式
String.prototype.trim = function () {
	return this.replace(/^\s+/,'').replace(/\s+$/,'')
}

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

方法一:

// 获取多个数字中的最大值
function max() {
    const nums = Array.prototype.slice.call(arguments) // 变为数组
    let max = 0
    nums.forEach(n => {
        if (n > max) {
            max = n
        }
    })
    return max    
}

方法二:使用Math API

Math.max(10,20,46,5)1

27. 如何用JS实现继承?

  • class 继承 (推荐)
  • prototype 继承 (ES5的继承方式,不推荐使用)

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

方式一:

try {
    // todo
} catch (ex) {
    console.error(ex) // 手动捕获 catch
} finally {
    // todo
}

方式二:

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

29. 什么是JSON?

  • json 是一种数据格式,本质是一段字符串
  • json 格式和JS对象结构一致,对JS语言更加友好
  • window.JSON 是一个全局对象: JSON.stringify :把对象转换成json字符串 JSON.parse :把字符串转换成对象

30. 获取当前页面的url 参数

  • 传统方式:查找 ·location.search
  • 新API, URLSearchParams

代码示例:
浏览器输入:http://127.0.0.1:8080/index.html?a=10&b=20&c=30

// 传统方式
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')
    const res = search.match(reg) // search字符串来匹配reg表达式
    if (res === null) {
        return null
    }
    return res[2]
}
console.log(query('a'))  // 10
// URLSearchParams
function query(name) {
    const search = location.search
    const p = new URLSearchParams(search)
    return p.get(name)
}
console.log( query('b') )  // 20

31. 将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 val = 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
}

32. 手写数组 flatern, 考虑多层级

作用效果如下:

flat([[1,2], 3, [4, 5, [6, 7, [8, 9, [10, 11]]]]])
// [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]

一层数组可以用如下方法拍平:
1.png-600

多层数组需要递归:

function flat(arr) {
    // 验证 arr 中,还有没有深层数组 [1, 2, [3, 4]]
    const isDeep = arr.some(item => item instanceof Array) // arr数组中只要有一个元素符合Array类型
    if (!isDeep) {
        return arr // 已经是 flatern [1, 2, 3, 4]
    }

    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) // [1, 2, 3, 4, 10, 20, 100, 200, 5]

33. 数组去重

  • 传统方式,遍历元素,挨个比较,去重
  • 使用Set
  • 考虑计算效率

方式一:

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

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

方式二:

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

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

建议: 能使用Set就使用Set。 Set比较快,传统方式需要循环。兼容性和速度看需求。

34. 手写深拷贝

JS基础知识:变量的类型和计算

注意: Object.assign 不是深拷贝!只拷贝了一层

/**
 * 深拷贝
 */
const obj1 = {
    age: 20,
    name: 'xxx',
    address: {
        city: 'beijing'
    },
    arr: ['a', 'b', 'c']
}

const obj2 = deepClone(obj1)
obj2.address.city = 'shanghai'
obj2.arr[0] = 'a1'
console.log(obj1.address.city)
console.log(obj1.arr[0])

/**
 * 深拷贝
 * @param {Object} obj 要拷贝的对象
 */
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
}

35. 介绍一下 RAF requestAnimationFrame

  • 想要动画流畅,更新频率要60帧/s, 即 16.67ms 更新一次视图
  • setTimeout 要手动控制频率,而 RAF 浏览器会自动控制
  • 后台标签或隐藏iframe中,RAF会暂停,而 setTimeout 依然执行

代码演示:
html 部分:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>JS 真题演示</title>

        <style>
            #div1 {
                width: 100px;
                height: 50px;
                background-color: red;
            }
        </style>
    </head>
    <body>
        <p>JS 真题演示</p>

        <div id="div1"></div>

        <script src="https://cdn.bootcss.com/jquery/3.4.0/jquery.js"></script>
        <script src="./RAF.js"></script>
    </body>
</html>

JS部分:
方式一:setTimeout

 // 3s 把宽度从 100px 变为 640px ,即增加 540px
// 60帧/s ,3s 180 帧 ,每次变化 3px

const $div1 = $('#div1')
let curWidth = 100
const maxWidth = 640

// setTimeout
function animate() {
    curWidth = curWidth + 3
    $div1.css('width', curWidth)
    if (curWidth < maxWidth) {
        setTimeout(animate, 16.7) // 自己控制时间
    }
}
animate()

方式二:RAF

 // 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()

36. 前端性能如何优化?一般从哪几个方面考虑?

  • 原则:多使用内存,缓存,减少计算,减少网络请求
  • 方向:加载页面,页面渲染,页面操作流畅度

本文转载自:https://blog.csdn.net/weixin_40693643/article/details/104228651

更多web前端开发知识,请查阅 HTML中文网 !!

以上就是36 道 前端JavaScript初级面试题的详细内容,更多请关注0133技术站其它相关文章!

赞(0) 打赏
未经允许不得转载:0133技术站首页 » JavaScript 教程