目录
- 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
- 前端性能如何优化?一般从哪几个方面考虑?
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
就是纯函数,它们具有以下特点:
- 不改变源数组(没有副作用);
- 返回一个数组
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新生成的子元素也不用新为其添加事件了,程序逻辑上比较方便
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()) // 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]
一层数组可以用如下方法拍平:
多层数组需要递归:
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
- 想要动画流畅,更新频率要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技术站其它相关文章!