JavaScript面试常见问题

1. JavaScript 执行机制 event loop

原理:

  1. js是单线程执行的
  2. 先主任务,在执行回调任务,promise为同步任务,then是异步任务
  3. 然后是执行微任务(promise,requestAnimationFrame)
  4. 再执行宏任务(setTimeout,setInterval, UI render)

例子:

setTimeout(() => {
    console.log('1');
    setInterval(()=>{
        console.log('6')
    })
    Promise.resolve().then( () =>{
        console.log('2');
    })
}, 0);

new  Promise((resolve, reject) => { // 同步执行
    console.log('3');
    resolve()
})
.then(res =>{
    console.log('4');
})
console.log('5');
// 3,5,4,1,2,6

闭包

function init() {
    var name = "Mozilla"; // name 是一个被 init 创建的局部变量
    function displayName() { // displayName() 是内部函数,一个闭包
        alert(name); // 使用了父函数中声明的变量
    }
    displayName();
}
init();

计数器、延迟调用、回调等闭包的应用,其核心思想还是创建私有变量和延长变量的生命周期

对象

原型链和继承

this

this 关键字是函数运行时自动生成的一个内部对象,只能在函数内部使用,总指向调用它的对象

根据不同的使用场合,this有不同的值,主要分为下面几种情况:

  • 默认绑定
    • 严格模式下,不能将全局对象用于默认绑定,this会绑定到 undefined,只有函数运行在非严格模式下,默认绑定才能绑定到全局对象
  • 隐式绑定
    • 函数作为某个对象的方法调用时,this 就指这个上级对象
  • new绑定
    • 通过构建函数 new关键字生成一个实例对象,此时 this指向这个实例对象
    • new过程遇到 return一个对象,此时 this指向为返回的对象
  • 显示绑定
    • apply、call、bind

箭头函数

绑定事件监听和在原型上添加方法时候,此时 this指向 window

箭头函数不能作为构建函数

优先级

new绑定优先级 > 显示绑定优先级 > 隐式绑定优先级 > 默认绑定优先级

call apply和bind的区别

  • 三者都可以改变函数的 this对象指向
  • 三者第一个参数都是 this要指向的对象,如果如果没有这个参数或参数为 undefinednull,则默认指向全局 window
  • 三者都可以传参,但是 apply是数组,而 call是参数列表,且 applycall是一次性传入参数,而 bind可以分为多次传入
  • bind是返回绑定this之后的函数,applycall 则是立即执行

防抖和节流

防抖

防抖,就是指触发事件后 n 秒后才执行函数,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。

	// 防抖
      function debounce(fn, delay) {
        var timer = null;
        return function () {
          if (timer) {
            clearTimeout(timer);
          }
          // timer = setTimeout(fn,delay); // 和下面代码效果一样,但解决了this指向问题
          timer = setTimeout(() => {
            fn.apply(context, args)
          }, delay);
        };
      }
      window.onscroll = debounce(handlerScroll, 200);

      // 方法
      function handlerScroll() {
        var scrollTop =
          document.documentElement.scrollTop;
        console.log(scrollTop);
      }

应用场景

  • 输入框频繁输入内容,搜索或者提交信息。
  • 频繁点击按钮,触发某个事件。
  • 监听浏览器滚动事件。
  • 监听用户缩放浏览器resize事件。

节流

节流,就是指连续触发事件但是在 n 秒中只执行一次函数。

节流函数会按照一定的频率来执行函数


      // 节流
      function throttle(fn, delay) {
        var valid = true;
        return function () {
          if (!valid) {
            return false;
          }
          valid = false;
          setTimeout(function () {
            fn();
            valid = true;
          }, delay);
        };
      }
      window.onscroll = throttle(handlerScroll, 200);

      // 方法
      function handlerScroll() {
        var scrollTop =
          document.documentElement.scrollTop;
        console.log(scrollTop);
      }

应用场景

  • 时间戳
  • 定时器

作用域(作用域链)

作用域,即变量(变量作用域又称上下文)和函数生效(能被访问)的区域或集合

作用域分成:

  • 全局作用域
  • 函数作用域
  • 块级作用域

全局作用域

任何不在函数中或是大括号中声明的变量

函数作用域

函数作用域也叫局部作用域,如果一个变量是在函数内部声明的它就在一个函数作用域下面。这些变量只能在函数内部访问,不能在函数以外去访问

块级作用域

ES6引入了 letconst关键字,和 var关键字不同,在大括号中使用 letconst声明的变量存在于块级作用域中。在大括号之外不能访问这些变量

原型链

__proto__作为不同对象之间的桥梁,用来指向创建它的构造函数的原型对象的

每个对象的 __proto__都是指向它的构造函数的原型对象 prototype

person1.__proto__ === Person.prototype

总结

  • 一切对象都是继承自 Object对象,Object 对象直接继承根源对象 null
  • 一切的函数对象(包括 Object 对象),都是继承自 Function 对象
  • Object 对象直接继承自 Function 对象
  • Function对象的 __proto__会指向自己的原型对象,最终还是继承自 Object对象

Promise & async / await 异步编程

async就是用来声明一个异步方法,而 await是用来等待异步方法执行

async function async1() {
    console.log('async1 start')
    await async2()
    console.log('async1 end')
}
async function async2() {
    console.log('async2')
}
console.log('script start')
setTimeout(function () {
    console.log('settimeout')
})
async1()
new Promise(function (resolve) {
    console.log('promise1')
    resolve()
}).then(function () {
    console.log('promise2')
})
console.log('script end')

分析过程:

  1. 执行整段代码,遇到 console.log('script start') 直接打印结果,输出 script start
  2. 遇到定时器了,它是宏任务,先放着不执行
  3. 遇到 async1(),执行 async1 函数,先打印 async1 start,下面遇到 await怎么办?先执行 async2,打印 async2,然后阻塞下面代码(即加入微任务列表),跳出去执行同步代码
  4. 跳到 new Promise 这里,直接执行,打印 promise1,下面遇到 .then(),它是微任务,放到微任务列表等待执行
  5. 最后一行直接打印 script end,现在同步代码执行完了,开始执行微任务,即 await下面的代码,打印 async1 end
  6. 继续执行下一个微任务,即执行 then 的回调,打印 promise2
  7. 上一个宏任务所有事都做完了,开始下一个宏任务,就是定时器,打印 settimeout

所以最后的结果是:script startasync1 startasync2promise1script endasync1 endpromise2settimeout

深copy和浅copy

浅copy

如果属性是基本类型,拷贝的就是基本类型的值。如果属性是引用类型,拷贝的就是内存地址

  • Object.assign
  • Array.prototype.slice(), Array.prototype.concat()
  • 使用拓展运算符实现的复制
// slice(0) 表示不截取,复制
const fxArr = ["One", "Two", "Three"]
const fxArrs = fxArr.slice(0)
fxArrs[1] = "love";
console.log(fxArr) // ["One", "Two", "Three"]
console.log(fxArrs) // ["One", "love", "Three"]

// concat
const fxArr = ["One", "Two", "Three"]
const fxArrs = fxArr.concat()
fxArrs[1] = "love";
console.log(fxArr) // ["One", "Two", "Three"]
console.log(fxArrs) // ["One", "love", "Three"]
// 展开运算符
const fxArr = ["One", "Two", "Three"]
const fxArrs = fxArr.concat()
fxArrs[1] = "love";
console.log(fxArr) // ["One", "Two", "Three"]
console.log(fxArrs) // ["One", "love", "Three"]

深copy

深拷贝开辟一个新的栈,两个对象属完成相同,但是对应两个不同的地址,修改一个对象的属性,不会改变另一个对象的属性

常见的深拷贝方式有:

  • _.cloneDeep() – lodash
  • jQuery.extend() – jquery
  • JSON.stringify() — 会忽略 undefinedsymbol函数
  • 手写循环递归
// 手写循环递归
function deepClone(obj, hash = new WeakMap()) {
  if (obj === null) return obj; // 如果是null或者undefined我就不进行拷贝操作
  if (obj instanceof Date) return new Date(obj);
  if (obj instanceof RegExp) return new RegExp(obj);
  // 可能是对象或者普通的值  如果是函数的话是不需要深拷贝
  if (typeof obj !== "object") return obj;
  // 是对象的话就要进行深拷贝
  if (hash.get(obj)) return hash.get(obj);
  let cloneObj = new obj.constructor();
  // 找到的是所属类原型上的constructor,而原型上的 constructor指向的是当前类本身
  hash.set(obj, cloneObj);
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      // 实现一个递归拷贝
      cloneObj[key] = deepClone(obj[key], hash);
    }
  }
  return cloneObj;
}

浅拷贝只复制属性指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存,修改对象属性会影响原对象

深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象

浏览器 DOM 事件流 / 事件委托

DOM 事件流

传播按顺序分为三个阶段:捕获阶段、目标阶段、冒泡阶段

Dom标准事件流的触发的先后顺序为:先捕获再冒泡

**
e.stopPropagation()** 取消冒泡

事件委托

事件委托也称为 事件代理。就是利用 事件冒泡,把子元素的事件都绑定到父元素上。如果子元素阻止了事件冒泡,那么委托就无法实现。

实现原理

不是每个子节点单独设置事件监听器,而是事件监听器设置在其父节点上,然后利用冒泡原理影响设置每个子节点。

优点

  • 替代循环绑定事件的操作,减少内存消耗,提高性能。比如:

    • table上代理所有 tdclick事件。
    • ul上代理所有 liclick事件。
  • 简化了 dom节点更新时,相应事件的更新。比如:

    • 不用在新添加的 li上绑定 click事件。
    • 当删除某个 li时,不用移解绑上面的 click事件。

var、let、const之间的区别

let与const

  • let是用于替代var来声明变量(var是ES6之前用来声明变量的关键词)
  • const是用来声明常量的

区别:

  • var,let声明变量时,变量一旦初始化之后,还可以重新赋值,
  • const声明常量,一旦初始化,就不能重新赋值了,否则会报错
  • 使用const声明常量,一旦声明,就必须立即初始化

let、const、var的区别

①重复声明 ②变量提升 ③暂时性死区 ④块级作用域 ⑤window对象的属性和方法(全局作用域中)

  • var允许重复声明,let、const不允许
  • var会提升变量的声明到作用域的顶部,但let和const不会
  • 只要作用域内存在let、const,它们所声明的变量或常量就会自动“绑定”这个区域,不再受外部作用域的影响
  • 全局作用域中,var声明的变量,通过function声明的函数,会自动变为window对象的变量,属性或方法,但const和let不会
  • var没有块级作用域,let和const有块级作用域

# HTTP 请求过程

⭐️ 常见状态码

  • 1xx 信息 接受的请求正在处理
  • 2xx 成功 接受的请求正在处理
  • 3xx 重定向
    • 301 永久性重定向
    • 302 临时性重定向
    • 303 资源的URI已更新,你是否能临时按新的URI访问
    • 304 资源已找到,但未符合条件请求
  • 4xx 客户端错误
    • 400 Bad Request 语义有误
    • 401 Unauthorized 服务器认证失败
    • 403 Forbidden 服务器拒绝访问
    • 404 Forbidden 地址错误
  • 5xx 服务器错误
    • 500 服务器遇到意外
    • 503 服务器停机维护或者已超载
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值