前端面试题———JS高级

132 阅读9分钟

new关键字

new关键字用于创建一个对象的实例

使用new关键字的基本步骤

  1. 创建一个新对象:

    • 当使用new关键字调用一个构造函数时,JavaScript 会首先创建一个新的空对象。
  2. 设置原型链:

    • 将新对象的原型(__proto__)设置为构造函数的原型对象。这样,新对象就可以访问构造函数原型上的属性和方法。
  3. 执行构造函数:

    • 使用新对象作为this上下文来执行构造函数。构造函数可以在这个上下文中初始化新对象的属性和方法。
  4. 返回新对象:

    • 如果构造函数没有显式地返回一个对象,new关键字会自动返回新创建的对象。如果构造函数显式地返回一个对象,new关键字会返回这个显式返回的对象。

原型和原型链

每个构造函数都有一个prototype属性,这个属性指向一个对象,称为原型对象。(构造函数和原型里的this指向实例化的对象)

每个原型对象里面都有个constructor属性,指向该原型对象的构造函数。

使用构造函数创建一个实例对象时,这个实例对象会有一个属性__proto__指向构造函数的prototype原型对象。

实例对象__proto__对象原型里也有个constructor属性,指向创建该实例对象的构造函数。

image.png

原型链

  1. 当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性
  2. 如果没有就查找它的原型(__proto__指向的prototype原型对象)
  3. 如果还没有就查找原型对象的原型(Object原型对象)
  4. 依此类推一直找到Object为止

所有对象的原型链最终都会指向Object.prototypeObject.prototype的原型是null,这标志着原型链的顶端。

image.png

作用域和作用域链

作用域是指在程序中定义变量和函数的可访问范围

分类

  • 全局作用域:在代码的任何地方都可以访问的变量和函数属于全局作用域。在浏览器环境中,全局作用域通常是指window对象。在 Node.js 环境中,全局作用域是指global对象。
  • 局部作用域:分为函数作用域块级作用域
    • 函数作用域:在函数内部定义的变量和函数只有在函数内部才能访问,外部无法直接访问。
    • 块级作用域:letconst声明的变量具有块级作用域,即在代码块(如if语句、for循环等)内部声明的letconst变量只在该代码块内部可见。

作用域链:从当前作用域开始一层一层向上寻找某个变量,直至访问到全局作用域,这一层层的关系就是作用域链。

闭包

定义

闭包是指有权访问另一个函数作用域中变量的函数(一个函数能够访问其外部函数作用域中的变量)

形成过程

当一个内部函数在外部函数中被定义,并在外部函数执行完毕后仍然可以被访问时,就形成了闭包。内部函数可以访问外部函数的变量,并且这些变量会被保存在内存中,直到内部函数不再被引用或者垃圾回收器回收其内存空间。

作用和用途

  1. 数据封装和隐藏:闭包可以用来封装数据,使得外部无法直接访问内部函数的变量,只能通过暴露的接口来操作数据。
  2. 模拟私有变量和方法: 在 JavaScript 中,没有真正的私有变量和方法。但是,可以使用闭包来模拟私有变量和方法,使得外部无法直接访问和修改内部的变量和方法。
  3. 函数柯里化和部分应用:将一个多参数的函数转换为一系列单参数的函数,或者固定一部分参数的值,返回一个新的函数,这可以使函数更加灵活和可复用。
 function add(a, b) {
     return a + b;
 }
 function curryAdd(a) {
     return function(b) {
         return add(a, b);
     };
 }
 const addFive = curryAdd(5);
 console.log(addFive(3)); // 8

缺点

  • 由于闭包会保留对外部函数变量的引用,可能会导致内存泄漏。如果闭包不再需要,应该及时将其引用设置为null,以便垃圾回收器回收其占用的内存。
  • 性能问题:闭包的使用可能会影响性能,特别是在大量使用闭包或者闭包中的代码比较复杂的情况下。因为闭包需要保留对外部函数变量的引用,这可能会增加内存占用和函数调用的开销。

this/call/apply/bind

关于this

  • 全局环境中的this:在全局环境中,this指向全局对象。在浏览器环境中,全局对象是window;在 Node.js 环境中,全局对象是global
  • 函数调用中的this:当一个函数被直接调用时,this通常指向全局对象。
  • 对象方法中的this:当一个函数作为对象的方法被调用时,this指向该对象。
  • 构造函数中的this:在构造函数中,this指向正在创建的新对象。
  • 箭头函数中的this:箭头函数没有自己的this指向,this指向外层第一个函数的this。
  • 使用callapplybind方法改变this指向
  • 严格模式下的this:在严格模式下,函数中的this如果没有被明确指定,将是undefined而不是全局对象。
function test() {
    'use strict';
    console.log(this);
}
test(); // undefined

关于call、apply、bind

在 JavaScript 中,callapplybind都是用于改变函数执行上下文(即this指向)的方法。

一、call方法

  1. 语法和参数

    • function.call(thisArg, arg1, arg2,...)
    • thisArg:指定函数执行时的this值,可以是任何值,如果是原始类型的值,会被自动包装为对应的对象。
    • arg1, arg2,...:传递给函数的参数,可以是任意数量的参数。
    • 会立即执行函数

二、apply方法

  1. 语法和参数

    • function.apply(thisArg, [argsArray])
    • thisArg:与call方法中的thisArg相同。
    • argsArray:一个数组,包含传递给函数的参数。
    • 会立即执行函数

三、bind方法

  1. 语法和参数

    • function.bind(thisArg, arg1, arg2,...)
    • thisArg和参数的含义与call方法相同。
    • 不会立即执行函数,而是返回一个新的函数,这个新函数的this值被绑定为指定的值,并且可以预先传递一些参数。

浅拷贝和深拷贝

浅拷贝

  1. 定义和特点

    • 浅拷贝创建一个新的对象或数组,但是新对象或数组中的元素只是原对象或数组中元素的引用。这意味着,如果原对象或数组中的某个元素是一个对象或数组,那么浅拷贝后的新对象或数组中的对应元素也会指向同一个对象或数组。
    • 浅拷贝通常只复制对象或数组的第一层结构,不会深入复制嵌套的对象或数组。
  2. 实现方法

    • Object.assign():可以将一个或多个源对象的属性复制到目标对象,并返回目标对象。
    • 展开运算符...
    • lodash的_.clone方法
    • Array.prototype.concat()
    • Array.prototype.slice()

深拷贝

  1. 定义和特点

    • 深拷贝创建一个完全独立的新对象或数组,新对象或数组中的所有元素都是原对象或数组中对应元素的独立副本,而不是引用。这意味着,对原对象或数组中的任何元素进行修改都不会影响到深拷贝后的新对象或数组。
    • 深拷贝会递归地复制对象或数组的所有层次,包括嵌套的对象和数组。
  2. 实现方法

    • JSON.parse(JSOPN.stringify()):可以将对象转换为 JSON 字符串,然后再将 JSON 字符串解析为新的对象。但是,这种方法有一些限制,例如不能复制函数、正则表达式等特殊类型的对象
    • lodash的_.cloneDeep方法
    • 手写函数递归实现
functon deepClone(obj){
    if(type obj !== 'object' || obj === null)  return obj;
    let copy
    // 首先判断是不是数组
    if(Array.isArray(obj)){
        copy = [];
        for(let i = 0; i< obj.length; i++){
            copy[i] = deepClone(obj[i]);
        }
    }else{
        copy = {};
        for(let key in obj){
            if(obj.haoOwnProperty(key)){
                copy[key] = deepClone(obj[key]);
            }
        }
    }
    return copy;
}

节流和防抖

节流

  1. 定义和作用

    • 节流是指在一定时间内,无论触发多少次事件,函数只会执行一次。(单位时间内,频繁触发事件,只执行一次)
    • 它可以限制函数的执行频率,避免在短时间内频繁执行函数,从而减少不必要的计算和资源消耗。

image.png

  1. 应用场景

鼠标移动mousemove,页面尺寸缩放resize,滚动条滚动scroll...

  1. 实现方法

    • lodash中的 _.throttle(func, [wait=0],[options=])
    • 可以使用定时器来实现节流。当事件触发时,设置一个定时器,如果在定时器时间内再次触发事件,则不会执行函数,直到定时器时间结束。如果在定时器时间内没有再次触发事件,则执行函数,并重新设置定时器。
function throttle(fn, time){
    let timer = null;
    return function(){
        if(!timer){
            timer = setTimeout(()=>{
                fn();
                timer = null;
            }, time)
        }
    }
}

防抖

  1. 定义和作用

    • 防抖是指在事件触发后,等待一定时间,如果在这段时间内没有再次触发事件,则执行函数。如果在等待时间内再次触发事件,则重新开始计时,直到最后一次触发事件后,等待时间结束才执行函数。(单位时间内,多次触发事件,只执行最后一次)
    • 它可以避免在短时间内频繁触发事件导致的函数多次执行,通常用于处理用户输入、窗口大小调整等频繁触发的事件。

image.png

  1. 应用场景

按钮点击事件,用户输入,窗口大小调整...

  1. 实现方法

    • lodash中的 _.debounce(func, [wait=0],[options=])
    • 可以使用定时器来实现防抖。当事件触发时,清除之前的定时器,并设置一个新的定时器。如果在定时器时间内再次触发事件,则清除定时器并重新设置。如果在定时器时间结束后没有再次触发事件,则执行函数。
function debounce(fn, time){
    let timer = null;
    return function(){
        clearTimeout(timer);
        timer = setTimeout(()=>{
            fn();
        }, time)
    }
}