JS基础知识学习--真题总结

JS真题

题目一

1. var和let const的区别

答:

  • var是ES5语法,let、const是ES6语法; var有变量提升

  • var和let是变量,可修改; const是常量,不可修改;

  • let、const有块级作用域,var没有

2. typeof返回哪些类型

答:

  • undefined、string、number、boolean、symbol

  • object(注意,typeof null === ‘object’)

  • function

3. 列举强制类型转换和隐式类型转换

答:

  • 强制:parseInt、parseFloat、toString等

  • 隐式:if、逻辑运算、==、+拼接字符串

题目二

1. 手写深度比较,模拟lodash的isEqual

function isObject(obj){
  return typeof obj === 'object' && obj !== null
}

function isEqual(obj1, obj2) {
  if(!isObject(obj1) || !isObject(obj2)){
    // 值类型
    return obj1 === obj2
  }
  if(obj1 === obj2){
    return true
  }
  // 两个都是对象或者数组,而且不相等

  // 1. 先判断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) {
    // 递归比较
    const res = isEqual(obj1[key], obj2[key])
    if (!res) {
      return false
    }
  }
  return true
}

2. split()和join()的区别

​ 答:split是字符串的方法,用来分割字符串为数组,'1-2-3'.split('-') // [1, 2, 3]

​ join是数组方法,用来拼接数组为字符串,[1, 2, 3].join('-') // '1-2-3'

3. 数组的pop、push、unshift、shift分别做什么

  • pop : 从 数组末尾弹出一个元素,无参,返回弹出的元素,原位操作

  • push : 从数组末尾加入元素,参数是加入的元素,按顺序加入,返回数组长度,原位操作

  • shift : 从数组的开头弹出一个元素,无参,返回弹出的元素,原位操作

  • unshift : 从数组的开头加入元素,参数是加入的元素,按顺序一起加入,返回数组的长度,原位操作

    const arr = [10, 20, 30, 40]
    // pop
    // const popRes = arr.pop()
    // console.log(arr, popRes) // [10, 20, 30] 40
    
    // push
    // const pushRes = arr.push(50, 60)
    // console.log(arr, pushRes) // [ 10, 20, 30, 40, 50, 60 ] 6
    
    // unshift
    // const unshiftRes = arr.unshift(-10, -20)
    // console.log(arr, unshiftRes) // [ -10, -20, 10, 20, 30, 40 ] 6
    
    // shift
    // const shiftRes = arr.shift()
    // console.log(arr, shiftRes) // [ 20, 30, 40 ] 10
    

扩展:数组的API,有哪些纯函数?

​ 纯函数:1. 不改变源数组(没有副作用); 2. 返回一个数组

  • concat

  • map

  • filter

  • slice

    // concat
    // const arr1 = arr.concat([50, 60, 70])
    // console.log(arr, arr1) // [ 10, 20, 30, 40 ] [10, 20, 30, 40, 50, 60, 70]
    
    // map
    // const arr2 = arr.map(val => val * 10)
    // console.log(arr, arr2) // [ 10, 20, 30, 40 ] [ 100, 200, 300, 400 ]
    
    // filter
    // const arr3 = arr.filter(val => val > 25)
    // console.log(arr, arr3) // [ 10, 20, 30, 40 ] [ 30, 40 ]
    
    // slice
    // const arr4 = arr.slice()
    // console.log(arr, arr4) // [ 10, 20, 30, 40 ] [ 10, 20, 30, 40 ]
    

非纯函数: push pop shift unshift forEach some every

题目三

1. 数组slice和splice的区别

  • 功能区别(slice-切片, splice-剪切)

  • 参数和返回值

  • 是否纯函数

    // slice 纯函数
    // const arr4 = arr.slice()
    // console.log(arr, arr4) // [ 10, 20, 30, 40 ] [ 10, 20, 30, 40 ]
    // const arr4 = arr.slice(2, 4) // start, end
    // console.log(arr, arr4) // [ 10, 20, 30, 40 ] [ 30, 40 ]
    // const arr4 = arr.slice(1) // start
    // console.log(arr, arr4) // [ 10, 20, 30, 40 ] [ 20, 30, 40 ]
    // const arr4 = arr.slice(-2) // lastest start
    // console.log(arr, arr4) // [ 10, 20, 30, 40 ] [ 30, 40 ]
    // const arr4 = arr.slice(-3, -2) // lastest start, lastest end
    // console.log(arr, arr4) // [ 10, 20, 30, 40 ] [ 20 ]
    
    // splice 非纯函数
    // const spliceRes = arr.splice(1, 2, 'a', 'b', 'c') // start, length, 
    // console.log(arr, spliceRes) // [ 10, 'a', 'b', 'c', 40 ] [ 20, 30 ]
    // const spliceRes = arr.splice(1, 2)
    // console.log(arr, spliceRes) // [ 10, 40 ] [ 20, 30 ]
    // const spliceRes = arr.splice(1, 0, 'a', 'b', 'c') // 只增加不剪切
    // console.log(arr, spliceRes) // [ 10, 'a', 'b', 'c', 20, 30,  40 ] []
    

2. [10, 10, 10, 10].map(parseInt) 返回结果是什么

  • map的参数和返回值:参数:数组的每个元素,对应元素下标。返回值:新数组

  • parseInt参数和返回值:参数:待转换的该进制数字,进制。返回值:转换过的十进制数

    const res = [10, 10, 10, 10].map(parseInt)
    // 拆解,相当于:
    // const res = [10, 10, 10, 10].map((item, index) => {
    //   return parseInt(item, index)
    // })
    // parseInt(num, base = 10)  其中base = 0时,相当于base = 10,也就是相当于10进制转换
    console.log(res) // [ 10, NaN, 2, 3 ]
    

3. ajax请求的get和post的区别

  • get一般用于查询操作,post一般用于用户提交操作
  • get参数拼接在url上,post放在请求体内(数据体积可更大)
  • 安全性:post易于防止CSRF

题目四

1. 函数call和apply的区别

​ 答:call的参数是直接传入,而apply的参数是放到数组中

​ fn.call(this, p1, p2, p3)

​ fn.apply(this, arguments)

2. 事件代理(委托)是什么

​ 答:在父元素上定义一个事件,监听子元素触发时的冒泡。 如果子元素想要阻止冒泡,则使用:e.stopPropagation()

3. 闭包是什么,有什么特性,有什么负面影响

  • 回顾作用域和自由变量
  • 回顾闭包应用场景:作为参数被传入,作为返回值被返回
  • 回顾:自由变量的查找,要在函数定义的地方(而非执行的地方)
  • 影响:变量会常驻内存,得不到释放(但不一定会造成内存泄漏,内存泄漏一般属于Bug,而闭包不是Bug,而是故意为之)。闭包不要乱放。

题目五

1. 如何阻止事件冒泡和默认行为

  • event.stoppropagation()
  • event.preventDefault()

2. 查找、添加、删除、移动DOM节点的方法

  • 查找节点:document.getElementById(“id”)、document.getElementsByTagName(“h1”)、document.getElementsByClassName(“container”)、document.querySelector("#div")、document.querySelectorAll("#div")
  • 添加节点:document.createElement(“span”)
  • 插入、移动节点:ele.appendChild§
  • 获取父元素:ele.parentNode
  • 获取子元素:ele.childNodes(含有文本节点)、ele.children(不含文本节点)
  • 删除子元素:ele.removeChild

3. 如何减少DOM操作

  • 缓存DOM查询结果

  • 多次DOM操作,合并到一次插入

    const list = document.getElementById('list')
    // 创建一个文档片段
    const fragment = document.createDocumentFragment()
    for(let i = 0; i < 10; i++){
      const li = document.createElement('li')
      li.innerHTML = i
      // 插入到文档片段,文档片段是在内存中
      fragment.appendChild(li)
    }
    // 将文档片段一次性插入
    list.appendChild(fragment)
    

题目六

1. 解释jsonp的原理,为何它不是真正的ajax?

  • 浏览器的同源策略(服务端没有同源策略)和跨域

  • 哪些HTML标签能绕过跨域? script、link、img

  • ajax是通过XMLHTTPRequest实现的,jsonp是通过script标签实现的

  • 实现跨域必须得到服务器端的允许和配合

    // jsonp  
    	<script>
        window.callback = function (data) {
          console.log(data)
        }
      </script>
    
      <script src="http://localhost:8002/JSONP.js?username=xxx&callback=abc"></script>
    

2. document load和ready的区别

  • load是页面的资源全部加载完才执行

  • ready是DOM渲染完就执行,此时图片、视频还可能没有加载完

    window.addEventListener('load', function () {
      // 页面的全部资源加载完才执行,包括图片、视频等
    })
    document.addEventListener('DOMContentLoaded', function () {
      // DOM 渲染完即可执行,此时图片、视频还可能没有加载完
    })
    

3. == 和 === 的不同

  • == 会尝试类型转换
  • === 严格相等
  • 哪些场景采用== :判断对象属性是否为null:a.xxx == null。 其他场合一律用===

题目七

1. 函数声明和函数表达式的区别

  • 函数声明:function fn () {…}
  • 函数表达式: const fn = function () {…}
  • 函数声明会在代码执行前预加载,而函数表达式不会

2. new Object()和Object.create()的区别

  • {}等同于new Object(),原型Object.prototype

  • Object .create(null)没有原型

  • Object.create({…})可指定原型

    const obj1 = {a: 1}
    const obj2 = new Object(obj1) // obj1 === obj2
    
    const obj1 = {a: 1}
    const obj2 = new Object({a: 1}) // obj1 !== obj2, isEqual(obj1, obj2) === true
    
    const obj1 = {a: 1}
    const obj2 = Object.create(obj1) // obj2.__proto__ === obj1
    

3. this的场景题

  • this的取值在执行的时候再知道
const User = {
  count: 1,
  getCount: function () {
    return this.count
  }
}
console.log(User.getCount()) // 1

const func = User.getCount
console.log(func()) // undefined

题目八

1. 关于作用域和自由变量的场景题 -1

let i
for(i = 1; i <= 3; i++) {
setTimeout(function () {
  console.log(i)
}, 0)
}
// 4 4 4

2. 判断字符串以字母开头,后面字母数组下划线,长度6-30

  • const reg = /1\w{5, 29}$/

    // 小写英文字母
    /^[a-z]+$/
    
    // 英文字母
    /^[a-zA-Z]+$/
      
    // 日期格式 2019-12-1
    /^\d{4}-\d{1,2}-\d{1,2}$/
    
    // 用户名
    /^[a-zA-Z]\w{5,17}$/
    
    // 简单的IP地址匹配
    /\d+\.\d+\.\d+\.\d+/
    

3. 关于作用域和自由变量的场景题 -2

let a = 100
function test() {
  console.log(a)
  a = 10
  console.log(a)
}
test()
console.log(a)
// 100 10 10

题目九

1. 手写字符串trim方法,保证浏览器兼容性

String.prototype.trim = function () {
  return this.replace(/^\s+/, '').replace(/\s+$/, '')
}
// 原型、this、正则表达式

2. 如何获取多个数字中的最大值

function max() {
  const arr = Array.prototype.slice.call(arguments)
  let res = arr[0]
  arr.forEach(item => {
    if(item > res)res = item
  })
  return res
}
console.log(max(3, -1, 20, 4, 34))
console.log(Math.max(3, -1, 20, 4, 34))

3. 如何用JS实现继承

  • class继承
  • prototype继承

题目十

1. 如何捕获JS程序中的异常

try {
  // TODO
} catch(e) {
  console.log(e) // 手动捕获异常
} finally {
  // TODO
}

// 自动捕获
window.onerror = function (message, source, lineNum, colNum, error) {
  // 第一,对跨域的js, 如CDN的,不会有详细的报错信息
  // 第二,对于压缩的js,还要配合sourceMap,反查到未压缩代码的行、列
}

2. 什么是JSON

  • json是一种数据格式,本质是一段字符串
  • json格式和JS对象结构一致,对JS语言更友好
  • Window.JSON是一个全局对象:JSON.stringify将json转化成字符串,JSON.parse将字符串转化成json

3. 获取当前页面url参数

  • 传统方式,查找location.search

    // 传统方式
    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)
      console.log(res)
      // 数组第一个是完全匹配,第二个是匹配的第一个括号里面的内容,第3个是匹配的第2个括号里面的内容,第4个是匹配的第3个括号里面的内容,
      // ["&b=20&", "&", "20", "&"]
      if(res === null) {
        return null
      }
      return res[2]
    }
    console.log(query('b'))
    
  • 新的API,URLSearchParams

    // URLSearchParams
    function query(name) {
      const search = location.search
      const p = new URLSearchParams(search)
      return p.get(name)
    }
    console.log(query('b'))
    

题目11

1. 将url参数解析为JS对象

// 传统方式,分析search
function queryToObj() {
  const res = {}
  const search = location.search.substr(1) // 去掉前面的?
  search.split('&').map(paramStr => {
    const arr = paramStr.split('=')
    res[arr[0]] = arr[1]
  })
  return res
}
console.log(queryToObj()) // {a: "10", b: "20", c: "30"}

// 使用URLSearchParams
function queryToObj2(){
  const res = {}
  const pList = new URLSearchParams(location.search)
  pList.forEach((val, key) => {
    res[key] = val
  })
  return res
}
console.log(queryToObj2()) // {a: "10", b: "20", c: "30"}

2. 手写数组flatern,考虑多层级

function flat(arr){
  // 验证arr中有没有深层数组
  const isDeep = arr.some(item => item instanceof Array)
  if(!isDeep)return arr
  const res = Array.prototype.concat.apply([], arr)
  return flat(res) // 递归
}

const res = flat([1, 2, [3, [4]], 5])
console.log(res)

3. 数组去重

// 传统方式 比较慢 可兼容
function unique(arr) {
  const res = []
  arr.forEach(item => {
    if(res.indexOf(item) < 0) {
      res.push(item)
    }
  })
  return res
}

console.log(unique([30, 20, 40, 50, 20, 30]))

// set方式 (无序结构,不能重复) 快 新的API,可能有兼容性问题
function unique2(arr){
  const set = new Set(arr)
  return [...set]
}
console.log(unique2([30, 20, 40, 50, 20, 30]))

题目12

1. 手写深拷贝

/**
 * 深拷贝
 * @param {Object} obj 要拷贝的对象
 */
function deepClone(obj) {
  if (typeof obj !== 'object' || obj === null) return obj
  let newObj
  if (obj instanceof Array) {
    newObj = []
  } else {
    newObj = {}
  }
  for (let key in obj) {
    if (obj.hasOwnProperty(key))
      newObj[key] = deepClone(obj[key])
  }
  return newObj
}

// Object.assign是浅拷贝,只拷贝第一层级,不要踩坑

2. 介绍一下RAF: window.requestAnimationFrame

  • 想要动画流畅,更新频率要60帧/s,即16.67ms更新一次视图

  • setTimeout要手动控制频率,而RAF浏览器会自动控制

  • 后台标签或隐藏iframe中,RAF会暂停,而setTimeout依然执行

    // 3s 把宽度从100px变为640px,即增加540px
    // 60帧/s, 3s 180帧,每次变化3px
    let curWidth = 100
    const maxWidth = 1000
    const $div1 = $("#div1")
    
    // setTimeout
    // function animate(){
    //   curWidth += 3
    //   $div1.css('width', curWidth)
    //   if(curWidth < maxWidth) {
    //     setTimeout(animate, 16.7); // 得自己控制时间
    //   }
    // }
    // animate()
    
    function animate(){
      curWidth += 3
      $div1.css('width', curWidth)
      if(curWidth < maxWidth) {
        window.requestAnimationFrame(animate)
      }
    }
    animate()
    

3. 前端性能如何优化?一般从哪几个方面考虑?

  • 原则:多使用内存、缓存、减少计算、减少网络请求
  • 方向:加载页面,页面渲染,页面操作流畅度

  1. a-zA-Z ↩︎

猜你喜欢

转载自blog.csdn.net/jal517486222/article/details/105837236