面试题
var 和 let const 的区别
- var 是 ES5 及之前的语法,let const 是 ES6 语法
- var 和 let 是变量,可修改;const 是常量,不可修改
- var 有变量提升,let const 没有
- var 没有块级作用域,let const 有 (ES6 语法有块级作用域)
// var 变量提升console.log('a', a)var a = 100// let 没有变量提升console.log('b', b)let b = 200
// var 没有块级作用域for (var i = 0; i < 10; i++) {var j = 1 + i}console.log(i, j)// let 有块级作用域for (let x = 0; x < 10; x++) {let y = 1 + x}console.log(x, y)
typeof 有哪些返回类型?
// 判断所有值类型let aconsole.log(a) // 'undefined'const str = 'abc'typeof str// 'string'const n = 100typeof n // 'number'const b = truetypeof b // 'boolean'const s = Symbol('s')typeof s // 'symbol'
列举强制类型转换和隐式类型转换
- 强制
parseInt
parseFloat
- 隐式
if
,==
,+
拼接字符串
手写深度比较,如 lodash isEqual
// 实现如下效果const obj1 = {a: 10, b: { x: 100, y: 200 }}const obj2 = {a: 10, b: { x: 100, y: 200 }}isEqual(obj1, obj2) === true
// 判断是否是 objectfunction 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. 先取出 obje2 obj2 的 keys,比较个数const obj1Keys = Object.keys(obj1)const obj2Keys = Object.keys(obj2)if (obj1Keys.length !== obj2Keys.length) {// keys 个数不相等,则不是全等return false}// 2. 以 obj1 为基准,和 obj2 依次递归比较for (let key in obj1) {// 递归比较const res = isEqual(obj1[key], obj2[key])if (!res) {// 遇到一个不相等的,则直接返回 falsereturn false}}// 3. 都相等,则返回 truereturn true}
split()
和 join()
的区别
'1-2-3'.split('-')[1,2,3].join('-')
数组的 pop
push
unshift
shift
分别做什么
注意以下几点
- 函数作用是什么?
- 返回值是什么?
- 对原数组是否造成影响?
- 如何对原数组不造成影响?
concat
slice
map
filter
【扩展】数组 API 的纯函数和非纯函数
纯函数 —— 1. 不改变来源的数组; 2. 返回一个数组
- concat
- map
- filter
- slice
const arr = [100, 200, 300]const arr1 = arr.concat([400, 500])const arr2 = arr.map(num => num * 10)const arr3 = arr.filter(num => num > 100)const arr4 = arr.slice(-1)
非纯函数
情况1,改变了原数组
- push
- reverse
- sort
- splice
情况2,未返回数组
- push
- forEach
- reduce
- some
数组 slice 和 splice 的区别?
slice – 切片;splice – 剪接;
// slice()const arr1 = [10, 20, 30, 40, 50]const arr2 = arr1.slice() // arr2 和 arr1 不是一个地址,纯函数,重要!!!// arr.slice(start, end)const arr1 = [10, 20, 30, 40, 50]const arr2 = arr1.slice(1, 4) // [20, 30, 40]// arr.slice(start)const arr1 = [10, 20, 30, 40, 50]const arr2 = arr1.slice(2) // [30, 40, 50]// 负值const arr1 = [10, 20, 30, 40, 50]const arr2 = arr1.slice(-2) // [40, 50]
// arr.splice(index, howmany, item1, ....., itemX)const arr1 = [10, 20, 30, 40, 50]const arr2 = arr1.splice(1, 2, 'a', 'b', 'c') // [20, 30]// arr1 会被修改,不是纯函数,即有副作用
[10, 20, 30].map(parseInt)
的结果是什么?
// 拆解开就是[10, 20, 30].map((num, index) => {return parseInt(num, index)// parseInt 第二个参数是进制// 如果省略该参数或其值为 0,则数字将以 10 为基础来解析。如果它以 “0x” 或 “0X” 开头,将以 16 为基数。// 如果该参数小于 2 或者大于 36,则 parseInt() 将返回 NaN})
// 可以对比[10, 20, 30].map((num, index) => {return parseInt(num, 10)})
ajax 请求中 get 和 post 的区别
- get 一般用于查询操作,post 一般用于提交操作
- get 参数在 url 上,post 在请求体内
- 安全性:post 请求易于防止 CSRF
(post 代码演示:网页,postname)
call 和 apply 的区别
fn.call(this, p1, p2, p3)
fn.apply(this, arguments)
事件委托(代理)是什么
const p1 = document.getElementById('p1')const body = document.bodybindEvent(p1, 'click', e => {e.stopPropagation() // 注释掉这一行,来体会事件冒泡alert('激活')})bindEvent(body, 'click', e => {alert('取消')})
闭包是什么,有什么特性,对页面有什么影响
- 回归作用域和自由变量
- 闭包的应用场景:函数作为参数被传入,函数作为返回值被返回
- 关键点:自由变量的查找,要在函数定义的地方,而不是执行的地方
对页面的影响
- 变量内存得不到释放,可能会造成内存积累(不一定是泄露)
// 自由变量示例 —— 内存会被释放let a = 0function fn1() {let a1 = 100function fn2() {let a2 = 200function fn3() {let a3 = 300return a + a1 + a2 + a3}fn3()}fn2()}fn1()
// 闭包 函数作为返回值 —— 内存不会被释放function create() {let a = 100return function () {console.log(a)}}let fn = create()let a = 200fn() // 100// 闭包 函数作为参数 —— 内存不会被释放function print(fn) {let a = 200fn()}let a = 100function fn() {console.log(a)}print(fn) // 100
如何阻止事件冒泡和默认行为
event.stopPropagation()
event.preventDefault()
解释 jsonp 的原理,以及为什么不是真正的 ajax
- 浏览器的同源策略,什么是跨域?
- 哪些 html 标签能绕过跨域?
- jsonp 原理
document load 和 document ready 的区别
window.addEventListener('load', function () {// 页面的全部资源加载完才会执行,包括图片、视频等})document.addEventListener('DOMContentLoaded', function () {// DOM 渲染完即可执行,此时图片、视频还可能没有加载完})
==
和 ===
的不同
- == 会尝试进行类型转换
- === 严格相等
函数声明与函数表达式的区别?
const res = sum(10, 20)console.log(res) // 30// 函数声明function sum(x, y) {return x + y}
const res = sum(100, 200)console.log(res) // 报错!!!// 函数表达式const sum = function(x, y) {return x + y}
new Object()
和 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 obj3 = Object.create({a: 10,b: 20,sum() {return this.a + this.b}})// 分别打印看结构
示例二
const obj1 = {a: 10,b: 20,sum() {return this.a + this.b}}const obj2 = new Object(obj1)console.log(obj1 === obj2) // trueconst obj3 = Object.create(obj1)console.log(obj1 === obj3) // falseconst obj4 = Object.create(obj1)console.log(obj3 === obj4) // false// 然后修改 obj1 ,看 obj2 obj3 和 obj4obj1.printA = function () {console.log(this.a)}
对作用域上下文和 this 的理解,场景题
const User = {count: 1,getCount: function() {return this.count}}console.log(User.getCount()) // what?const func = User.getCountconsole.log( func() ) // what?
对作用域和自由变量的理解,场景题
let ifor(i = 1; i <= 3; i++) {setTimeout(function(){console.log(i)}, 0)}// what?
判断字符串以字母开头,后面可以是数字,下划线,字母,长度为 6-30
const reg = /^[a-zA-Z]\w{5,29}$/
- 查看正则表达式规则 https://www.runoob.com/regexp/regexp-syntax.html
- 查看常见正则表达式
/\d{6}/ // 邮政编码/^[a-z]+$/ // 小写英文字母/^[A-Za-z]+$/ // 英文字母/^\d{4}-\d{1,2}-\d{1,2}$/ // 日期格式/^[a-zA-Z]\w{5,17}$/ // 用户名(字母开头,字母数字下划线,5-17位)/\d+\.\d+\.\d+\.\d+/ // 简单的 IP 地址格式
以下代码,分别 alert 出什么?
let a = 100function test() {alert(a)a = 10alert(a)}test()alert(a)// what?
手写 trim 函数,保证浏览器兼容性
String.prototype.trim= function (){return this.replace(/^\s+/,"").replace(/\s+$/,"")}
知识点:原型,this,正则
如何获取多个数值中的最大值?
Math.max(10, 30, 20, 40)// 以及 Math.min
function max() {const nums = Array.prototype.slice.call(arguments) // 变为数组let max = 0nums.forEach(n => {if (n > max) {max = n}})return max}
如何用 JS 实现继承?
class 代码
程序中捕获异常的方法
try {// todo} catch (ex) {console.error(ex) // 手动捕获 catch} finally {// todo}
// 自动捕获 catch(但对跨域的 js 如 CDN 的,不会有详细的报错信息)window.onerror = function (message, source, lineNom, colNom, error) {}
什么是 JSON ?
首先,json 是一种数据格式标准,本质是一段字符串,独立于任何语言和平台。注意,json 中的字符串都必须用双引号。
{"name": "张三","info": {"single": true,"age": 30,"city": "北京"},"like": ["篮球", "音乐"]}
其次,JSON 是 js 中一个内置的全局变量,有 JSON.parse
和 JSON.stringify
两个 API 。
获取当前页面 url 参数
自己实现
// const url = 'https://www.xxx.com/path/index.html?a=100&b=200&c=300#anchor'function query(name) {const search = location.search.substr(1) // 去掉前面的 `?`const reg = new RegExp(`(^|&)${name}=([^&]*)(&|$)`, 'i')const res = search.match(reg)if (res === null) {return null}return decodeURIComponent(res[2])}console.log( query('a') )console.log( query('c') )
新的 API URLSearchParams
const pList = new URLSearchParams(location.search)pList.get('a')
将 url 参数解析为 JS 对象?
自己编写
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}
新的 API URLSearchParams
function queryToObj() {const res = {}const pList = new URLSearchParams(location.search)pList.forEach((val, key) => {res[key] = val})return res}
实现数组 flatern ,考虑多层级
function flat(arr) {// 验证 arr 中,还有没有深层数组,如 [1, [2, 3], 4]const isDeep = arr.some(item => item instanceof Array)if (!isDeep) {return arr // 没有深层的,则返回}// 多深层的,则 concat 拼接const res = Array.prototype.concat.apply([], arr) // 回归上文,apply 和 call 的区别return flat(res) // 递归调用,考虑多层}flat([[1,2], 3, [4,5, [6,7, [8, 9, [10, 11]]]]])
数组去重
要考虑:
- 顺序是否一致?
- 时间复杂度
ES5 语法手写。
// 写法一function unique(arr) {const obj = {}arr.forEach(item => {obj[item] = 1 // 用 Object ,去重计算高效,但顺序不能保证。以及,非字符串会被转换为字符串!!!})return Object.keys(obj)}unique([30, 10, 20, 30, 40, 10])
// 写法二function unique(arr) {const res = []arr.forEach(item => {if (res.indexOf(item) < 0) { // 用数组,每次都得判断是否重复(低效),但能保证顺序res.push(item)}})return res}unique([30, 10, 20, 30, 40, 10])
用 ES6 Set
// 数组去重function unique(arr) {const set = new Set(arr)return [...set]}unique([30, 10, 20, 30, 40, 10])
手写深拷贝
粘贴代码
【注意】Object.assign
不是深拷贝,可以顺带讲一下用法
Object.assign(obj1, {...})
const obj2 = Object.assign({}, obj1, {...})
介绍一下 RAF requestAnimationFrame
想用 JS 去实现动画,老旧的方式是用 setTimeout 实时刷新,但这样效率非常低,而且可能会出现卡顿。
- 想要动画流畅,更新频率是 60帧/s ,即每 16.6ms 更新一次试图。太慢了,肉眼会感觉卡顿,太快了肉眼也感觉不到,资源浪费。
- 用 setTimeout 需要自己控制这个频率,而 requestAnimationFrame 不用自己控制,浏览器会自动控制
- 在后台标签或者隐藏的
中,setTimeout 依然会执行,而 requestAnimationFrame 会自动暂停,节省计算资源
(代码演示)
对前端性能优化有什么了解?一般都通过那几个方面去优化的?
原则
- 多使用内存、缓存或者其他方法
- 减少 CPU 计算、较少网络
方向
- 加载页面和静态资源
- 页面渲染