「这是我参与11月更文挑战的第13天,活动详情查看:2021最后一次更文挑战」
本文内容
- 搜集网上的高频JS 初级面试题
- 验证和复习之前学过的知识
- 补充其他技能,如正则表达式、数组API
阅读前的提示
- 题目没有按照知识点或者难度排序,混排
- 只筛选了初级面试题,即本课程知识体系之内的
- 如您遇到了其他未讲到的面试题,欢迎提供
1. 何为变量提升
1.1 var 和 let const 的区别
- var 是 ES5语法,let const 是ES6 语法,var 有变量提升
- var 和 let 是变量,可修改,const 是常量不可修改
- let const 有块级作用域,var 没有
// 变量提升 ES5
console.log(a) // undefined
var a = 200
var a
console.log(a) // undefined
a = 200
// 块级作用域
for (let i = 0; i < 10; i++) {
let j = i + 1
}
console.log(j)
1.2 typeof能判断哪些类型
- 值类型
- 引用类型
- function(不作为引用类型的数据来使用,作为工具和方法来使用)
1.3 列举强制类型转换和隐式类型转换
- 强制: parseInt parseFloat toString 等
- 隐士: if、逻辑运算、==、+拼接字符串
2. 题目2
2.1 手写深度比较、模拟ladash.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.2 split() 和 join() 的区别
2.3 数组的 pop push unshift shift 分别做什么
- 功能是什么
- 返回是什么
- 是否会对原数组造成影响
// const arr = [10, 20, 30, 40]
// // pop
// const popRes = arr.pop()
// console.log(popRes, arr)
// // shift
// const shiftRes = arr.shift()
// console.log(shiftRes, arr)
// // push
// const pushRes = arr.push(50) // 返回 length
// console.log(pushRes, arr)
// // unshift
// const unshiftRes = arr.unshift(5) // 返回 length
// console.log(unshiftRes, arr)
纯函数:
- 不改变源数组(没有副作用);
- 返回一个数组
concat、map、filter、slice
// 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()
非纯函数
- push pop shift unshift
- forEach
- some every
- reduce
// const arr = [10, 20, 30, 40, 50]
// // slice 纯函数 (含头不含尾)
// const arr1 = arr.slice() // 类似拷贝元数组
// const arr2 = arr.slice(1, 4)
// const arr3 = arr.slice(2)
// const arr4 = arr.slice(-3) // [30, 40, 50]
// // splice 非纯函数
// const spliceRes = arr.splice(1, 2, 'a', 'b', 'c')
// // const spliceRes1 = arr.splice(1, 2)
// // const spliceRes2 = arr.splice(1, 0, 'a', 'b', 'c')
// console.log(spliceRes, arr)
3. 题目3
3.1 数组slice 和 splice 的区别
- 功能区别 (slice - 切片, splice - 剪接)
- 参数和返回值
- 是否是纯函数?
// const arr = [10, 20, 30, 40, 50]
// // slice 纯函数 (含头不含尾)
// const arr1 = arr.slice() // 类似拷贝元数组
// const arr2 = arr.slice(1, 4)
// const arr3 = arr.slice(2)
// const arr4 = arr.slice(-3) // [30, 40, 50]
// // splice 非纯函数
// const spliceRes = arr.splice(1, 2, 'a', 'b', 'c')
// // const spliceRes1 = arr.splice(1, 2)
// // const spliceRes2 = arr.splice(1, 0, 'a', 'b', 'c')
// console.log(spliceRes, arr)
3.2 [10, 20, 30].map(parseInt)返回的结果是什么【网红题】
const res = [10, 20, 30].map(parseInt)
console.log(res)
// 拆解
[10, 20, 30].map((num, index) => {
return parseInt(num, index)
})
3.3 ajax 请求get 和 post 的区别
- get用于查询操作
- post用于提交操作
- get参数拼接在URL中
- post参数放在请求体中
- 安全性: post 易于预防CSRF
post 的参数也会比 get 长一些
get 请求参数过长 接口会返回413
状态码
4. 题目4:再学闭包
4.1 函数call 和 apply 的区别
4.2 事件代理(委托)是什么?
4.3 闭包是什么,有什么特性?有什么负面影响?
- 回顾作用域和自由变量
- 回顾闭包的应用场景:作为参数被传入,作为返回值被返回
- 回顾:自由变量的查找,要在函数定义的地方(而非执行的地方)
4.3.1 闭包的影响
变量会变成常驻内存,得不到释放。闭包不要乱用
4.3.2 自由变量示例 —— 内存会被释放
// 自由变量示例 —— 内存会被释放
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()
4.3.3 闭包 函数作为返回值 —— 内存不会被释放
//闭包 函数作为返回值 —— 内存不会被释放
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
5. 题目5:回顾 DOM 操作和优化
5.1 如何阻止事件冒泡的默认行为
- 阻止事件冒泡:
event.stopPropagation
- 阻止默认行为:
event.preventDefault
5.2 查找、添加、删除、移动DOM 节点的方法
查找元素、修改样式、修改属性
// const div1 = document.getElementById('div1')
// console.log('div1', div1)
// const divList = document.getElementsByTagName('div') // 集合
// console.log('divList.length', divList.length)
// console.log('divList[1]', divList[1])
// const containerList = document.getElementsByClassName('container') // 集合
// console.log('containerList.length', containerList.length)
// console.log('containerList[1]', containerList[1])
// const pList = document.querySelectorAll('p')
// console.log('pList', pList)
// const pList = document.querySelectorAll('p')
// const p1 = pList[0]
// // property 形式
// p1.style.width = '100px'
// console.log( p1.style.width )
// p1.className = 'red'
// console.log( p1.className )
// console.log(p1.nodeName)
// console.log(p1.nodeType) // 1
// // attribute
// p1.setAttribute('data-name', 'imooc')
// console.log( p1.getAttribute('data-name') )
// p1.setAttribute('style', 'font-size: 50px;')
// console.log( p1.getAttribute('style') )
新建节点、插入节点、移动节点、获取父元素、获取子元素列表
const div1 = document.getElementById('div1')
const div2 = document.getElementById('div2')
// 新建节点
const newP = document.createElement('p')
newP.innerHTML = 'this is newP'
// 插入节点
div1.appendChild(newP)
// 移动节点
const p1 = document.getElementById('p1')
div2.appendChild(p1)
// 获取父元素
console.log( p1.parentNode )
// 获取子元素列表
const div1ChildNodes = div1.childNodes
console.log( div1.childNodes )
const div1ChildNodesP = Array.prototype.slice.call(div1.childNodes).filter(child => {
if (child.nodeType === 1) {
return true
}
return false
})
console.log('div1ChildNodesP', div1ChildNodesP)
div1.removeChild( div1ChildNodesP[0] )
5.3 如何减少DOM 操作
- 缓存DOM 查询结果
- 多次DOM操作,合并到一次插入
- ...
更加详细内容--->传送门:
6. 题目6
6.1 解释 jsonp 的原理,为何它不是真正的 ajax
6.1.1 浏览器的同源策略(服务端没有同源策略)和跨域
- 后端Nginx 配置称之为转发,没有同源策略
- 同源策略 是浏览器的安全措施
6.1.2 哪些标签可以绕过跨域?
- `<img />` `<sctipt></sctipt>`
6.1.3 jsonp展示
6.2 document load 和 ready 的区别
6.3 == 和 === 的不同
- == 会尝试类型转换
- === 严格相等
7. 题目7
7.1 函数声明和函数表达式的区别
- 函数式声明
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) // Uncaught TypeError: sum is not a function
var sum = function (x, y) {
return x + y
}
var res = sum(10, 20)
console.log(res) // Uncaught ReferenceError: sum is not defined
// const 声明
const sum = function (x, y) {
return x + y
}
7.2 new Object() 和 Object.create()的区别
-
{}
等同于new Object()
,原型Object.prototype
-
Object.create(null);
// 没有原型 -
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() // {}
const obj5 = Object.create({
a: 10,
b: 20,
sum() {
return this.a + this.b
}
})
const obj6 = Object.create(obj1)
obj3
obj4
obj6
7.3 关于 this的场景
const User = { count: 1, getCount: function() { return this.count}}
console.log(User.getCount()) // 1
const func = User.getCount;
console.log(func()) // undefined
作为函数执行的时候,this执行window,window没有count属性,所以是undefined
const User1 = {
count: 1,
getCount: () => {
return this.count
}
}
console.log(User.getCount()) // undefined,箭头函数执行的上下文
const func = User.getCount;
console.log(func()) // undefined