js面试题

2024-01-06  本文已影响0人  maomizone

1.for in和for of的区别

for in

  • 遍历对象可枚举的属性,包括原型链上面的属性

for of

  • 适用可迭代对象(包括 Array,Map,Set,String,TypedArray,arguments 对象等等)实现了[symbol.iterator]接口
  • 但是不能遍历对象,因为没有迭代器对象
  • 只会输出当前对象的值,不会去找原型链
const arr = [1,2,3,4]
 
// for ... in 输出索引
for (const key in arr){
    console.log(key) // 输出 0,1,2,3
}
 
// for ... of 输出value
for (const key of arr){
    console.log(key) // 输出 1,2,3,4
}

2.数组和伪数组的区别

伪数组的关联对象不是数组的prototype属性指向的对象,所以没有数组的方法

3.浅拷贝和深拷贝的区别

  • 浅拷贝---浅拷贝只拷贝一层,更深层次对象级别的只拷贝引用
  • 深拷贝---深拷贝拷贝多层,每一级别的数据都会拷贝
    // ES5浅拷贝
    let obj = {
                id: 1,
                name: 'andy',
                msg: { title: '标题' },
                color: ['red', 'green']
            };

   let o = {};

   for(let k in obj){
       o[k] = obj[k];
   }

   // ES6 浅拷贝方法1
   // Object.assign()方法用于对象的合并,将源对象(source)的所有可枚举属性,不包括原型链,复制到目标对象(target)
   let m = {};
   Object.assign(m, obj);

   // ES6 浅拷贝方法2
   let n = { ...obj };

    // ES5深拷贝方法1
    function deepCopy(oldObj, newObj){
        for(let k in oldObj){
            const value = oldObj[k]
            if(Array.isArray(value)){
                newObj[k] = []
                deepCopy(value, newObj[k])
            }else if(typeof value === 'object'){
                newObj[k] = {}
                deepCopy(value, newObj[k])
            }else {
                newObj[k] = value
            }
        }
    }

    // ES5深拷贝方法2
    const json = JSON.stringify(obj)
    const obj2 = JSON.parse(json)

4.ES6中Symbol的应用

  • 作为对象的键,对象的键可以是字符串和symbol值
  • Symbol.iterator
  • Symbol.toPrimitive -> 对象转数字Number([val])   对象转字符串String([val])的第一部都是看看有没有这个属性,有的话执行该方法,看看获得的是原始值就转换成功了
  • 使用Symbol来替代常量

Symbol.toPrimitive

    // 一个没有提供 Symbol.toPrimitive 属性的对象,参与运算时的输出结果
    const obj1 = {};
    console.log(+obj1);     // NaN
    console.log(`${obj1}`); // "[object Object]"
    console.log(obj1 + ""); // "[object Object]"

   // 手动赋予了 Symbol.toPrimitive 属性,再来查看输出结果
    const object1 = {
        [Symbol.toPrimitive](hint) {
            console.log(hint);
            if (hint === 'number') {
                return 42;
            }else if(hint === 'string'){
                return 'hi';
            }
            return null;
        }
    };

    // 左边一个+号就表示执行Number([val])
    console.log(+object1);     // 10      -- hint 参数值是 "number"
    console.log(`${object1}`); // "hi"    -- hint 参数值是 "string"
    console.log(object1 + ""); // null    -- hint 参数值是 "default"

使用Symbol来替代常量

const tabTypes = {
    basic: Symbol(),
    super: Symbol(),
}
 
if (type === tabTypes.basic) {
    return <div>basic tab</div>
}
 
if (type === tabTypes.super) {
    return <div>super tab</div>
}

5.对象的遍历方法

  • for in:遍历对象的key,所有可枚举,包含原型链
  • Object.keys():返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键名。
  • Object.values():返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值。
  • Object.entries():返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值对数组。
    // 会过滤属性名为 Symbol 值的属性
    const obj1 = { [Symbol()]: 123, foo: 'abc' }
    for(let k in obj1){
        console.log(k); // 'foo'
    }
    console.log(Object.keys(obj1)); // ['foo']

6.比较两个值是否相等

  • == / === / Object.is()
  • ES5 比较两个值是否相等,只有两个运算符:相等运算符(==)和严格相等运算符(===)。它们都有缺点,前者会自动转换数据类型,后者的NaN不等于自身,以及+0等于-0。JavaScript 缺乏一种运算,在所有环境中,只要两个值是一样的,它们就应该相等。
  • ES6 提出“Same-value equality”(同值相等)算法,用来解决这个问题。Object.is就是部署这个算法的新方法。它用来比较两个值是否严格相等,与严格比较运算符(===)的行为基本一致。不同之处只有两个:一是+0不等于-0,二是NaN等于自身。

7.手写防抖和节流

  • 占位

8.commonJS和es6 module的区别

  • 占位

9.进程和线程

  • 进程:计算机已经运行的程序
  • 线程:操作系统能够运行运算调度的最小单位
  • 说人话:运行一个程序,就默认启动一个进程(可以多进程),每个进程都会启动一个线程来执行程序的代码,这个就叫主线程,所以进程是线程的容器
  • 浏览器多数是多进程的,每开启一个页面就开一个新的进程,避免页面卡死造成全部页面无法响应,需要退出浏览器,而每个进程有多个线程,其中一个线程来负责执行js代码
  • js是单线程的,setTimeout/ajax/dom监听/ui redering是调用浏览器内置api(web api),等浏览器执行完毕后,会将回调函数放入宏任务事件队列中

10.promise 和 async/await

  • async、await是Promise的一个语法糖
  • async修饰函数,会根据函数的返回值把它包装成Promise对象,返回出去,这样调用函数之后就可以链式调用了
  • 我们可以将await关键字后面执行的代码,看做是包裹在(resolve, reject) => {函数执行}中的代码,这个在源码中叫做excutor函数,是同步函数,立即执行的,目的是将promise实例的resovle方法和reject方法传递给用户,传递的时候用bind显示绑定了this为实例对象
  • await的下一条语句,可以看做是then(res => {函数执行})中的代码,也就是成功的回调,如果状态是rejected则执行不到这里,需要catch里面处理(在源码中then的unfufilled方法和unrejected方法为微任务)
  • 要会手写Promise.all(),Promise源码的then()这个方法要理解
  // 测试async,会将函数的返回值包装成Promise实例后返回
  const test =  async () => {
    // return Promise.resolve('success')
    return Promise.reject('fail')
    // return 123
  }

  test().then(value => {
    console.log(value);}, // success/123
  reason => {
    console.log(reason); // fail
  })
  Promise.myAll =  args => {
    const result = []
    let fullCount = 0
    let iteratorIndex = 0

    const promise2 = new Promise((resolve, reject) => {
      for(let item of args){
        iteratorIndex++

        // 包装一层,避免item不是promise实例
        Promise.resolve(item).then(value => {
          result.push(value)
          fullCount++

          // 对于数组是length,map之类是size,所以得自己定义一个iteratorIndex
          if(fullCount === iteratorIndex){
            resolve(result)
          }
        }, reason => {
          reject(reason)
        })
      }

      if(iteratorIndex === 0){
        resolve(result)
      }
    })

    return promise2
  }

11.宏任务和微任务

事件循环中维护着以下两个队列:

  • 宏任务队列(macrotask queue):ajax、setTimeout、setInterval、DOM监听、UI Rendering等
  • 微任务队列(microtask queue):Promise的then回调(new Promise传入的函数为excutor函数,是在主线程立即执行的,建议看下源码)、 Mutation Observer API、queueMicrotask()等
  • 宏任务执行之前,必须保证微任务队列是空的,如果不为空,那么优先执行微任务队列中的任务(回调)

12.事件循环

  • 浏览器的事件循环是一个我们编写的JavaScript代码和浏览器API调用(setTimeout/AJAX/监听事件等)的一个桥梁, 桥梁之间他们通过回调函数进行沟通。
  • 具体参考coderwhy大神的这篇文章https://juejin.cn/post/6978019623316750349,把今日头条面试题做出来就说明你理解了大部分

13.forEach和map的区别

  • forEach无返回值
  • map返回一个新的数组
  • 当数组的元素的基本类型数据,则在循环中改变item并不会影响源数组,如果元素是引用类型数据,则循环中改变元素,会改变源数组

14.set和map的区别

  • set存储值,或者说键值相同,不能重复,没有get方法
  • map存储键值对,有get方法
上一篇下一篇

猜你喜欢

热点阅读