• js面试题


    for...of、for...in

    一般来说,for...of 循环更适合遍历数组和可迭代对象,而 for...in 循环更适合遍历对象的属性。

    1. const arr = [1, 2, 3];
    2. // 使用 for...of 循环遍历数组
    3. for (let item of arr) {
    4. console.log(item); // 输出:1, 2, 3
    5. }
    6. // 使用 for...in 循环遍历数组(不建议)
    7. for (let item in arr) {
    8. console.log(arr[item]); // 输出:1, 2, 3
    9. }
    1. const obj = { a: 1, b: 2, c: 3 };
    2. // 使用 for...of 循环遍历对象(会报错,因为对象不是可迭代对象)
    3. // for (let item of obj) { ... } // 报错
    4. // 使用 for...in 循环遍历对象
    5. for (let item in obj) {
    6. if (obj.hasOwnProperty(item)) {
    7. console.log(obj[item]); // 输出:1, 2, 3
    8. }
    9. }

    一、说说你对作用域和作用域链的理解

    在 JavaScript 中,作用域是变量和函数的可访问区域或集合。它决定了代码块中变量和其他资源的可见性。作用域可以分为全局作用域、局部作用域(函数作用域)和块级作用域。

    全局作用域是在函数或代码块之外声明的变量,它们在整个程序中都是可见的。而局部作用域,也称为函数作用域,是在函数内部声明的变量,只能在函数内部访问。

    当在 JavaScript 中使用一个变量时,JavaScript 引擎会首先在当前作用域中查找该变量,如果找不到,它会继续在上层作用域中查找,以此类推,直到找到该变量或到达全局作用域。如果在全局作用域中仍然找不到该变量,它会在非严格模式下在全局范围内隐式声明该变量,或者在严格模式下直接报错。这个过程被称为作用域链。

    值得注意的是,JavaScript 采用的是静态作用域,这意味着函数的作用域在函数定义时就确定了。此外,JavaScript 没有块级作用域,由花括号封闭的代码块不会创建新的作用域。

    二、你是如何理解闭包,闭包到底是什么

    闭包是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分

    在JavaScript中,闭包是由函数和对应的引用环境组合而成的实体。闭包让我们可以从函数内部访问其外部函数的作用域。每当函数创建,闭包就会被创建。为了使用闭包,我们可以简单的将一个函数定义在另一个函数的内部,然后将其暴露给外部,返回这个函数或者是把它传给另一个函数。内部函数会拥有访问外部函数作用域中变量的能力,即使是外部函数已经执行完毕并销毁。

    三、闭包为什么会产生内存泄漏,以及如何解决

    在JavaScript中,闭包可能会导致内存泄漏,因为当一个函数返回了一个闭包,并且这个闭包引用了外部函数的变量时,即使外部函数已经执行完毕并被销毁,但由于闭包仍然存在,它仍然可以访问外部函数的变量。因此,外部函数的变量不会被垃圾回收机制回收,从而导致内存泄漏。

    为了解决这个问题,可以使用以下几种方法:

    1. 手动解除引用:在不需要使用闭包时,手动将闭包的引用设为null,这样垃圾回收机制就可以回收闭包及其引用的变量。
    2. 使用WeakMap:WeakMap是一种特殊的Map对象,它的键必须是对象,而值可以是任意类型。WeakMap的键对象在不再被引用时会自动被垃圾回收。因此,可以使用WeakMap来存储闭包引用的外部变量,以避免内存泄漏。
    3. 避免不必要的闭包:在设计代码时,尽量避免创建不必要的闭包,尤其是在循环或大量创建对象的场景中。如果必须创建闭包,确保在不需要使用时及时解除引用。

    四、手写apply、call、bind函数

    Object.defineProperty() 是 JavaScript 中的一个方法,用于在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回这个对象。

    Object.defineProperty(obj, prop, descriptor)
    

    参数说明:

    • obj:需要定义属性的对象。
    • prop:需要定义的属性名。
    • descriptor:属性描述符,用于描述属性的特性。

    属性描述符(descriptor)可以是一个数据描述符或一个存取描述符,但不能同时是两者。数据描述符和存取描述符都具有以下可选键值:

    • configurable:当且仅当属性的 configurable 键值为 true 时,该属性描述符才能够被改变,同时该属性也能从对应的对象中被删除。默认为 false
    • enumerable:当且仅当属性的 enumerable 键值为 true 时,该属性才会出现在对象的枚举属性中。默认为 false
    • value:包含了这个属性的数据值。默认为 undefined
    • writable:当且仅当属性的 writable 键值为 true 时,属性的值,也就是 value,才能被改变。默认为 false
    • get:一个给属性提供 getter 的方法,如果没有 getter 则为 undefined。当访问该属性时,会调用此函数。执行时不传入任何参数,但是会传入 this 对象(属性所在的对象)。默认值为 undefined
    • set:一个给属性提供 setter 的方法,如果没有 setter 则为 undefined。当属性值修改时,会调用这个函数。这个方法接受唯一参数,即该属性新的参数值。默认值为 undefined
    1. let obj = {};
    2. // 定义一个数据描述符
    3. Object.defineProperty(obj, 'name', {
    4. value: 'John',
    5. writable: true,
    6. enumerable: true,
    7. configurable: true
    8. });
    9. console.log(obj.name); // 输出:John
    10. obj.name = 'David'; // 修改属性值
    11. console.log(obj.name); // 输出:David
    12. // 定义一个存取描述符
    13. let _age = 25; // 私有变量
    14. Object.defineProperty(obj, 'age', {
    15. get: function () {
    16. return _age;
    17. },
    18. set: function (value) {
    19. _age = value;
    20. },
    21. enumerable: true,
    22. configurable: true
    23. });
    24. console.log(obj.age); // 输出:25
    25. obj.age = 30; // 修改属性值
    26. console.log(obj.age); // 输出:30

     手写apply、call

    手写bind

     

     四、什么是原型和原型链

    原型(Prototype)和原型链(Prototype Chain)是 JavaScript 中的核心概念,用于实现对象之间的继承关系。

    每个 JavaScript 对象都有一个原型(prototype)属性,该属性指向另一个对象,称为原型对象。原型对象本身也有一个原型,通过这种方式形成了原型链。当访问一个对象的属性或方法时,如果该对象本身没有这个属性或方法,JavaScript 引擎会沿着原型链向上查找,直到找到该属性或方法或者到达原型链的末端(null)。

    所有的引用类型(函数、对象、数组),都有一个__proto__属性(隐形原型),属性值是一个普通的对象。所有的引用类型(函数、对象、数组),__proto__属性值指向它的构造函数的prototype(显性原型)属性值。

    通过这种方式,可以实现对象之间的继承。当一个对象需要访问另一个对象的属性或方法时,如果该属性或方法在当前对象本身上不存在,就会通过原型链查找到原型对象中对应的属性或方法,从而实现了继承。这种继承方式被称为原型继承。

    //

     对象的原型

    JavaScript当中每个对象都有一个特殊的内置属性 [[prototype]],这个特殊的对象可以指向另外一个对象。

    那么这个对象有什么用呢?

    当我们通过引用对象的属性key来获取一个value时,它会触发 [[Get]]的操作;

    这个操作会首先检查该属性是否有对应的属性,如果有的话就使用它;

    如果对象中没有改属性,那么会访问对象[[prototype]]内置属性指向的对象上的属性

    获取的方式有两种:

    方式一:通过对象的 __proto__ 属性可以获取到(但是这个是早期浏览器自己添加的,存在一定的兼容性问 题);

    方式二:通过 Object.getPrototypeOf 方法可以获取到;

    函数的原型 prototype

    函数也是一个对象

    函数作为对象来说, 它也是有[[prototype]] 隐式原型

    console.log(foo.__proto__) 

    函数它因为是一个函数, 所以它还会多出来一个显示原型属性: prototype

    console.log(foo.prototype)

    五、深拷贝

    深拷贝是指,拷贝对象的具体内容,深拷贝在计算机中开辟一块新的内存地址用于存放复制的对象。 源数据改变不会影响复制的数据。

    在JavaScript中,可以使用以下方式进行深拷贝:

    1. 使用JSON.parse(JSON.stringify(object)):这是最简单的方法,但只适用于可序列化的对象,并且无法处理函数和undefined值。
    2. 使用lodash的_.cloneDeep方法:这是一个通用的深拷贝方法,可以处理复杂的对象结构,包括函数和undefined值。
    3. 使用自定义的深拷贝函数:可以自己编写一个深拷贝函数,递归地遍历对象的所有属性并进行复制。这种方法可以根据需要进行定制,但需要注意处理循环引用和特殊值的情况。

    六、在输入url那一瞬间浏览器做了什么

    1. 解析URL:浏览器首先会对输入的URL进行解析,提取出其中的协议、主机名、端口号、路径等信息。
    2. DNS查询:浏览器会向本地DNS服务器发起查询,将主机名转换为对应的IP地址。如果本地DNS服务器无法解析该主机名,则会向更上一级的DNS服务器发起查询,直到最终解析出IP地址。
    3. 建立TCP连接:一旦获取到IP地址,浏览器会尝试与服务器建立TCP连接。这个过程包括三次握手,以确保双方都能够正常通信。
    4. 发送HTTP请求:一旦TCP连接建立成功,浏览器会向服务器发送HTTP请求。这个请求包括请求方法(如GET或POST)、请求头、请求体等信息。
    5. 等待服务器响应:浏览器会等待服务器对HTTP请求进行响应。响应通常包括状态码、响应头、响应体等信息。
    6. 解析HTML文档:一旦收到服务器的响应,浏览器会开始解析HTML文档。这个过程包括解析HTML标签、加载外部资源(如CSS和JavaScript文件)、渲染页面等。
    7. 渲染页面:浏览器会根据HTML文档的内容渲染页面。这个过程包括布局、绘制、合成等步骤,最终将页面呈现给用户。
  • 相关阅读:
    牛客网:NC116 把数字翻译成字符串
    Mysql挂掉怎么办
    HTML中的语义化标签
    Redis篇(5)——持久化
    kubeadm快速部署K8S
    letcode 2171. 拿出最少数目的魔法豆
    Android | ArcGIS入门
    高效的工单管理系统具备哪些特点?“的修”工单系统对民宿运营管理有什么好处?
    JTS:03 创建Geometry对象
    空域变换-直方图均衡化(直方图修正)
  • 原文地址:https://blog.csdn.net/qq_24767091/article/details/133467857