题组1
var和let const的区别
- var是ES5语法,let const是ES6语法;var有变量提升
- var和let是变量,可修改;const是常量,不可修改;
-
typeof返回哪些类型
值类型:undefined string number boolean symbol
- 引用类型:object
-
列举强制类型转换和隐式类型转换
强制:parseInt、parseFloat、toString等
- 隐式:if、逻辑运算、==、+号拼接字符串
题组2
手写深度比较,模拟lodash.isEqual
// 判断是不是对象
function isObject (obj) {
return typeof obj === 'object' && Object !== null
}
// 深度比较
function isEqual (obj1, obj2) {
if (!isObject(obj1) || !isObject(obj2)) {
// 如果其中一个不是对象或数组,直接进行判断,一般是值类型
return obj1 === obj2
}
if (obj1 === obj2) {
return true
}
// 两个都是对象或数组,而且不相等
// 先比较object的属性个数,不一样的话肯定不相等
const obj1Keys = Object.keys(obj1)
const obj2Keys = Object.keys(obj2)
if (obj1Keys.length !== obj2Keys.length) {
return false
}
// 以obj1为基准,和obj2依次递归比较
for (let key in obj1) {
// 比较当前key的value 递归
const res = isEqual(obj1[key], obj2[key])
if (!res) {
return false
}
}
return true
}
split()和join()的区别
- split把字符串拆分成数组
-
数组的pop push unshift shift分别做什么
功能是什么?返回值是什么?是否会对原数组造成影响?
- pop:把数组最后一个元素弹出,返回值是弹出的这个元素,会修改原数组
- push:往数组追加一个元素,返回值是修改后的数组长度,会修改原数组
- unshift:往数组第一个位置插入元素,返回值是修改后的数组长度,会修改原数组
shift:把数组第一个元素弹出,返回值是弹出的这个元素,会修改原数组
数组哪些API是纯函数
纯函数:不改变原数组(没有副作用)、返回一个数组
- concat:在数组后面再追加一个数组,返回新数组
- map:给数组每个元素都执行一个函数,返回新数组
- filter:过滤数组元素,返回新数组
- slice:截取从start到end(不包含该元素)的数组元素,返回新数组
- 非纯函数:pop push shift unshift forEach some every reduce
题组3
数组slice和splice的区别
- 功能区别:slice是切片,splice是剪接
- slice(start,end):截取从start到end(不包含该元素)。
如果只有一个参数,就从start截取到数组结束,
如果参数是负数,就从数组末尾开始截取。 - splice看这篇博客:https://blog.csdn.net/weixin_45726044/article/details/120151153
-
[10,20,30].map(parseInt)返回结果是什么?
结果:[ 10, NaN, NaN ]
拆解
[10, 20, 30].map((num, index) => {
return parseInt(num, index)
})
parseInt第二个参数:表示要解析的数字的基数。该值介于 2 ~ 36 之间。
如果省略该参数或其值为 0,则数字将以 10 为基础来解析。
如果它以 “0x” 或 “0X” 开头,将以 16 为基数,也就是16进制。
如果该参数小于 2 或者大于 36,则 parseInt() 将返回 NaN。、参考博客:https://www.jianshu.com/p/bec353b4eaaf
ajax请求get和post的区别?
get一般用于查询操作,post一般用户提交操作
- get参数拼接在URL上,post放在请求体内(数据体积可更大)
- 安全性:post易于防止CSRF攻击
题组4
函数call和apply的区别?
-
事件代理(委托)是什么
-
闭包是什么?有什么特性?有什么负面影响?
回顾作用域和自由变量
- 回顾闭包应用场景:作为参数被传入,作为返回值被返回
- 回顾自由变量的查找:在函数定义的地方查找(而非执行的地方)
- 影响:变量会常驻内存,得不到释放(不一定是内存泄露)。闭包不要乱用。
- 代码示例 ```javascript // // 自由变量示例 —— 内存会被释放 // 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()
// // 闭包 函数作为返回值 —— 内存不会被释放 // 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
---
<a name="HlAIW"></a>
## 题组5
<a name="zpW36"></a>
### 如何阻止事件冒泡和默认行为?
1. 阻止冒泡:event.stopPropagation()
2. 阻止默认行为:event.preventDefault()
<a name="phx36"></a>
### 查找、添加、删除、移动DOM节点的方法?
1. 查找:getElementById、getElementsByTagName、getElementsByClassName、querySelectorAll
2. 添加:createElement、appendChild
3. 移动:先获取再插入
4. 删除:removeChild
<a name="HJ6pg"></a>
### 如何减少DOM操作?
1. 缓存DOM查询结果
2. 多次DOM操作,合并到一次插入
---
<a name="P9SoL"></a>
## 题组6
<a name="vClwP"></a>
### 解释JSONP的原理,为何它不是真正的Ajax?
1. 浏览器的同源策略(服务端没有同源策略)和跨域
2. script img link标签可以绕过跨域
3. jsonp的原理就是定义一个全局函数去访问JS,通过script标签
4. Ajax是通过XMLHttpRequest
<a name="CfYYo"></a>
### document load 和 ready 的区别
1. ![image.png](https://cdn.nlark.com/yuque/0/2022/png/32815999/1664720772469-b792b570-ea3f-4e96-b7ef-f1a8653a5102.png#clientId=u0131cbc5-bee7-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=180&id=u02a17f06&margin=%5Bobject%20Object%5D&name=image.png&originHeight=322&originWidth=1202&originalType=binary&ratio=1&rotation=0&showTitle=false&size=224466&status=done&style=none&taskId=u8eb16827-9cf1-4b8c-85c5-68443559720&title=&width=673)
<a name="CLY4F"></a>
### == 和 === 的不同
1. ==会尝试类型转换
2. ===是值和类型完全相等
3. 只有判断null的时候才用==
---
<a name="uHfGF"></a>
## 题组7
<a name="QEsyX"></a>
### 函数声明和函数表达式的区别
1. 函数声明function fn(){…}
2. 函数表达式const fn=function() {...}
3. 函数声明会在代码执行前预加载(和变量提升有些相似),而函数表达式不会
<a name="SphQ5"></a>
### new Object() 和 Object.create() 的区别
1. {} 等同于 new Object(),原型Object.prototype
2. Object.create(null)没有原型
3. Object.create({...})可以指定原型
4. 代码示例
```javascript
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
}
})
// obj1和obj2并不全等,只是值一样
const obj21 = new Object(obj1)
// obj1 === obj21 内存地址也相等,两者同时改变
const obj3 = Object.create(null) // {}
const obj4 = new Object() // {}
// 虽然两者都是{},但是obj3没有属性没有原型,obj4没有属性有原型
const obj5 = Object.create({
a: 10,
b: 20,
sum() {
return this.a + this.b
}
})
// obj5是把传入的东西当做原型使用,不是属性
const obj6 = Object.create(obj1)
// obj6的原型指向obj1,两者同时改变
// obj6.__proto__ === obj1
题组8
关于作用域的自由变量的场景题1
- 输出三个4,因为i的作用域是全局,当启动定时器的时候,i是4
如果改成for (let i =1; i <= 3 ; i++)就会输出1 2 3
关于作用域的自由变量的场景题2
-
判断字符串以字母开头,后面字母数字下划线,长度6-30
正则表达式使用
const reg = /^[a-zA-Z]\w{5,29}$/
- ^表示以什么开头
- [a-zA-Z]表示大小写字母
- \w是命中字母数字下划线
- {5,29}表示长度
- $是以什么结尾
- 学习:https://www.runoob.com/regexp/regexp-syntax.html
- 测试工具:https://tool.oschina.net/regex#
- https://c.runoob.com/front-end/854/
题组9
手写字符串trim方法,保证浏览器兼容性
String.prototype.trim = function () {
// 利用正则表达式把字符串前后的空格换成空字符
return this.replace(/^s+/, '').replace(/\s+$/, '')
}
如何获取多个数字的最大值
- 使用Math.max()
手写
function max () {
const nums = Array.prototype.call(arguments) // 变成数组
let max = -Infinity
nums.forEach(n => {
if (n > max) {
max = n
}
})
return max
}
如何用JS实现继承?
class继承
- prototype继承
题组10
如何捕获JS程序中的异常?
- try… catch 手动捕获异常
-
什么是JSON?
JSON是一种数据格式,本质是一段字符串。属性名用双引号包裹
- JSON格式和JS对象结构一致,对JS语言更友好
window.JSON是一个全局对象。JSON.stringify和JSON.parse
获取当前页面URL参数
传统方式,查找location.search
新API,URLSearchParams
// 传统方式
function query(name){
const search = location.search.substr(1) // 把查询参数第一位问号截取掉
// search: 'a=10&b=20&c=30'
const reg = new RegExp(`(^|&)${name}=([^&]*)(&|$)`,'i')
const res = search.match(reg)
if(res == null){
return null
}
return res[2] // 是数值
}
// URLSearchParams
function query(name){
const search = location.search
const p = new URLSearchParams(search)
return p.get(name)
}
题组11
将URL参数解析为JS对象
- 传统方式
-
手写数组flatern,考虑多层级
contact方法可以摊平 ```javascript function flat (arr) { // 判断还有没有深层数组 const isDeep = arr.some(iten => iten instanceof Array) // 已经摊平 if (!isDeep) {
return arr
} // 利用contact可以摊平一层深度的数组 const res = Array.prototype.concat.apply([], arr) // 继续递归 return flat(res) }
const res = flat([1, 2, [3, 4, [10, 20]], 5]) console.log(res);
<a name="lWLwG"></a>
### 数组去重
1. 传统方式,遍历元素挨个比较、去重
2. 使用Set
3. 考虑计算效率,建议使用Set
```javascript
// 传统方式
function unique (arr) {
const res = []
arr.forEach(item => {
// 如果找不到这个item对应的索引就加入
if (res.indexOf(item) < 0) {
res.push(item)
}
})
return res
}
// 使用Set(无序,不能重复)
function unique (arr) {
const set = new Set(arr)
return [...set]
}
const res = [1, 1, 2, 3, 4, 2, 5]
console.log(unique(res));
题组12
手写深拷贝
Object.assign不是深拷贝,一般用于给对象追加属性
function deepClone (obj = {}) {
if (typeof obj !== 'object' || obj == null) {
// 如果不是对象或者数组,直接返回值
return obj
}
// 初始化返回结果
let result
if (obj instanceof Array) {
result = []
} else {
result = {}
}
// 遍历obj key是键名
for (let key in obj) {
// 保证key不是原型的属性
if (obj.hasOwnProperty(key)) {
// 递归调用 遇到对象或者数组会再深入一层
result[key] = deepClone(obj[key])
}
}
// 返回结果
return result
}
介绍一下RAF(requestAnimationFrame)
要想动画流畅,更新频率要60帧/s,即16.67ms更新一次视图
- setTimeout要手动控制频率,而RAF浏览器会自动控制
后台标签或隐藏iframe中,RAF会暂停,而setTimeout依然执行
// 3秒之内把宽度从100px变成640px,即增加540px
// 60帧每秒,三秒180帧,每秒变化3px
let currentWidth = 100
const maxWidth = 640
const div1 = document.getElementById('div1')
/* function animate () {
currentWidth += 3
div1.style.width = currentWidth + 'px'
if (currentWidth < maxWidth) {
// 需要自己控制事件
setTimeout(animate, 16.67)
}
} */
function animate(){
currentWidth += 3
div1.style.width = currentWidth + 'px'
if (currentWidth < maxWidth) {
// 浏览器自动控制
window.requestAnimationFrame(animate)
}
}
animate()
前端性能如何优化?从哪几个方面考虑
原则:多使用内存、缓存,减少计算、减少网络请求
- 方向:加载页面,页面渲染,页面操作流畅度
Map和Set
有序和无序
- 案例分析
- 对象Object就是一个无序结构,里面的属性位置随意。
-
Map和Object的区别
API不同,Map可以以任意类型为key。函数、对象、字符串都可以
- Map是有序结构(重要)
-
Set和Array的区别
API不同
- Set元素不能重复
- Set是无序结构,操作很快。
WeakMap和WeakSet(进阶)
- 弱引用,防止内存泄露
- WeakMap只能也用对象作为key,WeakSet只能用对象做value
- 没有forEach和size,只能用add delete has
数组reduce的用法
- 数组求和 ```javascript // 传统方式 / function sum (arr) { let res = 0 arr.forEach(n => res = res + n) return res } /
const arr = [10, 20, 30, 40] // 完整写法 /* const res = arr.reduce((sum, curVal, index, arr) => { console.log(‘sum’, sum); console.log(‘curVal’, curVal); console.log(‘index’, index); console.log(‘arr’, arr);
return sum + curVal // 返回值,会作为下一次执行的第一个参数sum的值
}, 0) // 0是sum的初始值 */
// 简化写法 const res = arr.reduce((sum, curVal) => sum + curVal, 0) console.log(‘res’,res);
2. 计数
```javascript
const arr2 = [10, 20, 30, 20, 100, 20]
const n = 20
const count = arr2.reduce((count, val) => {
return val === n ? count + 1 : count
}, 0)
console.log(count);
- 拼接字符串
const arr3 = [
{ name: '张三', age: '20' },
{ name: '李四', age: '21' },
{ name: '小明', age: '22' },
]
const result = arr3.reduce((str,item) => {
return `${str}${item.name} - ${item.age}\n`
}, '')
console.log(result);