JavaScript常见面试题

358 阅读7分钟

深拷贝和浅拷贝

  • 浅拷贝:浅拷贝是创建一个新对象,这个对象有着原始对象的一份精准拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用属性,拷贝的就是内存地址,所以如果其中一个对象改变了这个地址的值,就会影响到另一个对象。
  • 深拷贝:深拷贝是将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响到原对象。
  • 浅拷贝的实现方法
    • Object.assign():可把任意多个源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。
    • 函数库lodash的_.clone方法
    • 展开运算符
    • Array.prototype.concat();
    • Array.prototype.slice();
  • 深拷贝的实现方法
    • JSON.parse(JSON.stringify()):可以实现数组或对象深拷贝,但不能处理函数和正则。
    • 函数库lodash的_.cloneDeep方法
    • jQuery.extend()方法:$.extend(deepCopy, target, object1, [objectN])//第一个参数为true,就是深拷贝
    • 手写递归方法。
    function deepClone(obj, hash = new WeakMap()) {
    if(obj === null) return obj; // 如果是null或者undefined就不进行拷贝操作
    if(obj instanceof Data) 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;
    }
    

Map 和 WeakMap的区别

  • Map的键可以是任意类型,WeakMap只接受对象作为键(null除外),不接受其他类型的值作为键。
  • Map的键实际上是跟内存地址绑定的,只要内存地址不一样,就视为两个键; WeakMap的键是弱引用。默认创建一个对象,就默认创建一个强引用的对象,我们只有手动将obj = null,它才会被垃圾回收机制进行回收,如果是弱引用对象,垃圾回收机制才会自动进行回收。
  • Map可以被遍历,WeakMap不能被遍历。

new一个函数是发生了什么

  • 创建了一个空对象 let obj = {}
  • 设置新对象的__proto__属性指向构造函数的原型对象 obj.__proto__ = Person.prototype
  • 让构造函数中的this指向新对象,并执行构造函数的函数体 let res = Person.call(obj)
  • 判断构造函数的返回值类型,如果是值类型,则返回新对象。如果是引用类型,就返回这个引用类型的对象。
    if(typeof(res) == "object") {
        p = res;
    }else {
        p = obj;
    }
    

普通函数和箭头函数的区别

  • 箭头函数的定义要比普通函数定义简洁,清晰。
  • 箭头函数没有prototype,所以箭头函数本身没有this
  • 箭头函数没有自己的this,箭头函数的this指向在定义的时候继承外层第一个普通函数的this,所以,箭头函数中this的指向在它被定义的时候就已经确定了,之后永远不会改变。
  • call、apply,bind无法改变箭头函数中this的指向。
  • 箭头函数不能作为构造器使用,用new调用时会报错。
  • 箭头函数不绑定arguments,取而代之用rest参数...代替arguments,来访问箭头函数的参数列表。
  • 箭头函数不能用作Generator函数,不能使用yield关键字。

this指向问题

  • js的this总是指向一个对象,而具体指向那个对象是在运行时基于函数的执行环境动态绑定的,而不是函数被声明时的环境。
  • 确定js中的this的指向问题,一般从6个方面进行考虑:
    • 箭头函数:箭头函数中的this是在创建它时的外层this的指向。创建箭头函数时,就已经确定了它的this指向,箭头函数内的this指向外层的this,如果我们要确定箭头的this,只需要确定外层的this就可以了。
    • new:当使用new关键字调用函数时,函数中的this一定指向js创建的新对象。箭头函数不能当做构造函数,所有不能和new一起执行。
    • bind:bind不会执行函数,只是修改this后返回一个新的函数。
    • apply和call:apply()和call()第一个参数都是this,区别在于通过apply调用的实参是放在数组中的,而通过call调用时实参是逗号分隔开来的。
    • obj. :
    • 直接调用:在函数不满足前面的场景,被直接调用时,this将指向全局对象。

call,bind, apply的区别

  • 三个函数的作用都是将函数绑定到上下文中,用来改变函数中this的指向;三者的不同点在于语法上的不同:
    • apply和call的区别是参数的传递方式不一样,call是多个参数以逗号分割直接传递,apply是以一个数组的形式传递的。
    • bind:直接改变this的指向,指定一个新的函数,并返回这个新的函数。

闭包

闭包是指有权访问另一个函数作用域中的变量的函数。简单理解就是内嵌函数,即函数中嵌套函数

由于在JS中,变量的作用域属于函数作用域,在函数执行后作用域就会被清理、内存也随之回收,但是由于闭包是建立在一个函数内部的子函数,由于其可访问上级作用域的原因,即使上级函数执行完,作用域也不会随之销毁,这时的子函数——也就是闭包,便拥有了访问上级作用域中的变量的权限,即使上级函数执行完后作用域内的值也不会被销毁。

  • 应用场景
    • 构造函数的私有属性
    • 计算缓存
    • 函数节流,防抖。

{},new Object(), Object.create({})的区别

  • 字面量和new Object()创建的是Object的实例,原型就是Object.prototype,继承内置对象Object;
  • Object.create()中第一个参数指的就是新对象的原型对象,是个必填参数,这个参数可以为null,那新对象就彻底是一个空对象,没有继承Object.prototype中任何属性和方法。(propertiesObject:可选参数,指的要添加到新对象的可枚举属性的描述符及相应的属性名称。

防抖和节流

  • 防抖:就是指触发事件后n秒后才执行函数,如果在n秒内有触发了事件,则会重新计算函数执行时间。
  • 节流:就是指连续触发事件但是在n秒中只执行一次。
  • 应用场景
    • 防抖:
      • 搜索框,用户在不断输入值是,用防抖来节约请求资源。
      • window触发resize的时候,不断的调整浏览器窗口会不断的触发这个事件,用防抖来让其只触发一次。
    • 节流:
      • 鼠标不断点击触发,mousedow(单位时间内只触发一次)
      • 监听滚动事件,比如是否滑到底部自动加载更多,用节流来判断。
// 防抖
const debounce = (fn, time) => {
  let timeout = null;
  return function() {
    clearTimeout(timeout)
    timeout = setTimeout(() => {
      fn.apply(this, arguments);
    }, time);
  }
};
// 节流
const throttle = (fn, time) => {
  let flag = true;
  return function() {
    if (!flag) return;
    flag = false;
    setTimeout(() => {
      fn.apply(this, arguments);
      flag = true;
    }, time);
  }
}

数据类型判断的方法?

  • typeof:简单类型用typeOf,复杂类型用instanceOf。用typeof判断简单类型,除了null会返回object,其他都会返回正确的结果,如果判断复杂类型,除了function,一律返回object。
  • instanceOf:用instanceof可以检测某个实例是否是某个对象类型,如果用它来检测简单数据类型则始终返回false,因为基本类型不是对象。instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。
  • Object.prototype.toString()
  • 所有对象都会从它的原型上继承一个 constructor 属性,这个属性指向它的构造函数,可以利用这个属性判断数据类型。所有对象都会从它的原型上继承一个 constructor 属性,这个属性指向它的构造函数,可以利用这个属性判断数据类型。
function getType(obj) {
    return Object.prototype.toSring.call(obj).slice(8, -1)
}

懒加载的原理和实现

懒加载的原理和实现: juejin.cn/post/698948…