目录
- var 和 let const 的区别
- typeof 返回哪些类型
- 列举强制类型转换和隐式类型转换
- 手写深度比较,模拟 lodash isEqual
- split() 和 join() 的区别
- 数组的 pop push unshift shift 分别做什么
- 数组 slice 和 splice 的区别
- [10,20,30].map(parseInt) 返回的结果是什么?
- ajax 请求 get 和 post 的区别?
- call() apply() bind() 作用和区别
- 事件代理(委托)是什么?
- 闭包是什么?有什么特性?有什么负面影响?
- 如何阻止事件冒泡和默认行为?
- 查找,添加,删除,移动DOM节点的方法
- 如何减少DOM操作?
- 解释 jsonp 的原理,为何它不是真正的ajax ?
- document load 和 ready 的区别
==
和===
的不同- 函数声明和函数表达式的区别
- new Object() 和 Object.create()的区别
- 关于 this 的场景题
- 关于作用域和自由变量的场景题-1
- 判断字符串以字母开头,后面字母数字下划线,长度 6 - 30
- 关于作用域和自由变量的场景题-2
- 手写字符串
trim
方法,保证浏览器兼容性 - 如何获取多个数字中的最大值
- 如何用JS实现继承?
- 如何捕获JS程序中的异常?
- 什么是JSON?
- 获取当前页面的url 参数
- 将url 参数解析为JS对象
- 手写数组 flatern, 考虑多层级
- 数组去重
- 手写深拷贝
- 介绍一下 RAF
requestAnimateFrame
- 前端性能如何优化?一般从哪几个方面考虑?
- 下一篇:前端JS初级面试题二 (。•ˇ‸ˇ•。)老铁们!快来瞧瞧自己都会了么
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
,会是这样:
console.log(a) // ReferenceError: a is not defined
let a = 200
let 块级作用域:
// 块级作用域
for (let i = 0; i < 10; i++) {
let j = i + 1
}
console.log(j) // ReferenceError: j is not defined
2. typeof 返回哪些类型
- 值类型:
undefined
string
number
boolean
symbol
- 引用类型:
object
(注意: typeof null === ‘object’) - 函数类型:
function
3. 列举强制类型转换和隐式类型转换
- 强制:
parseInt
parseFloat
toString
等 - 隐式:
if
逻辑运算
==
+拼接字符串
4. 手写深度比较,模拟 lodash isEqual
实现效果如下:
// 实现如下效果
const obj1 = {a:10, b:{ x: 100, y: 200}}
const obj1 = {a:10, b:{ x: 100, y: 200}}
isEqual(obj1, obj2) === true
// 判断是否是对象或数组
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
就是纯函数,它们具有以下特点:
- 不改变源数组(没有副作用);
- 返回一个数组
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()
函数可解析一个字符串,并返回一个整数。
参数 | 描述 |
---|---|
string | 必需。要被解析的字符串。 |
radix | 可选。表示要解析的数字的基数。该值介于 2 ~ 36 之间。 |
如果string
不是一个字符串,就会将其转换为字符串(toString);
当参数 radix
的值为 0,或没有设置该参数时,parseInt()
会根据 string
来判断数字的基数;
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新生成的子元素也不用新为其添加事件了,程序逻辑上比较方便
12. 闭包是什么?有什么特性?有什么负面影响?
JS基础知识:作用域和闭包 ლ(´ڡ`ლ)一样的话题,一样的配方!
- 作用域和自由变量
- 闭包应用场景:作为参数被传入,作为返回值被返回
注意:自由变量的查找,要在函数定义的地方(而非执行的地方)
闭包影响:
变量会常驻内存,得不到释放。闭包不要乱用
13. 如何阻止事件冒泡和默认行为?
- event.stopPropagation()
- event.preventDefault()
14. 查找,添加,删除,移动DOM节点的方法
15. 如何减少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.
==
和
===
的区别
==
和
===
的区别
==
会尝试类型转换===
严格相等- 哪些场景才用
==
?
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()) // undefined
this 的指向:看它执行的时候
func
是在window
上下文中被执行的,func
中的this
指向的是window
,所以不会访问到count
属性
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() {
console.log(a)
a = 10
console.log(a)
}
test()
console.log(a)
// 100 10 10
25. 手写字符串
trim
方法,保证浏览器兼容性
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)
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
, 考虑多层级
flatern
, 考虑多层级
作用效果如下:
flat([[1,2], 3, [4, 5, [6, 7, [8, 9, [10, 11]]]]])
// [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
一层数组可以用如下方法拍平:
多层数组需要递归:
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. 手写深拷贝
注意: 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
requestAnimationFrame
- 想要动画流畅,更新频率要60帧/s, 即 16.67ms 更新一次视图
setTimeout
要手动控制频率,而 RAF 浏览器会自动控制- 后台标签或隐藏
iframe
中,RAF会暂停,而setTimeout
依然执行
代码演示:
html 部分:
<!DOCTYPE html>
<html lang="en">
<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. 前端性能如何优化?一般从哪几个方面考虑?
- 原则:多使用内存,缓存,减少计算,减少网络请求
- 方向:加载页面,页面渲染,页面操作流畅度
【前端性能优化】 不懂优化的前端 不是好前端…
【前端性能优化】JS优化,难么?
HTML和CSS代码结构优化
静态资源优化:图片优化
下一篇:前端JS初级面试题二 (。•ˇ‸ˇ•。)老铁们!快来瞧瞧自己都会了么