JS相关面试题

JS相关面试题

1、javaScript中的数据类型?存储上的差别?

JS中的数据类型主要分为基本数据类型和引用数据类型

基本数据类型:

  • Number
  • String
  • Boolean
  • Undefined
  • null
  • symbol

引用数据类型:

  • Object
  • Array
  • Function

区别:

1、基本数据类型存储在栈中;引用数据类型存储在堆中,值是存储在堆中,每个堆内存对象都有对应的引用地址指向它,引用地址存放再栈中。

2、再赋值变量的时候,基本数据类型是生成相同的值,两个对象对应不同的地址;引用数据类型是将保存对象的内存地址赋值给另一个变量,也就是两个变量指向堆内存中的同一个对象。

2、数组的常用方法?

添加:

  • push()接受任意数量的参数,并且将它们添加至数组的末尾,返回数组的最新长度
  • unshift()在数组开头添加任意多个值,然后返回新的数组长度
  • splice()传入三个参数,分别是开始位置、0(要删除的元素数量)、插入的元素,返回空数组
  • concat()首先会创建一个当前数组的副本,然后再把它的参数添加到副本末尾,最后返回这个新构建的数组,不会影响原始数组

删除:

  • pop() 方法用于删除数组的最后一项,同时减少数组的length 值,返回被删除的项
  • shift()方法用于删除数组的第一项,同时减少数组的length 值,返回被删除的项
  • splice()传入两个参数,分别是开始位置,删除元素的数量,返回包含删除元素的数组
  • slice() 用于创建一个包含原有数组中一个或多个元素的新数组,不会影响原始数组

修改:

  • splice()传入三个参数,分别是开始位置,要删除元素的数量,要插入的任意多个元素,返回删除元素的数组,对原数组产生影响

查找:

  • indexOf()返回要查找的元素在数组中的位置,如果没找到则返回 -1
  • includes()返回要查找的元素在数组中的位置,找到返回true,否则false
  • find()返回第一个匹配的元素

排序:

  • reverse()顾名思义,将数组元素方向反转
  • sort()方法接受一个比较函数,用于判断哪个值应该排在前面

转换:

  • join() 方法接收一个参数,即字符串分隔符,返回包含所有项的字符串

迭代方法:

  • some()对数组每一项都运行传入的测试函数,如果至少有1个元素返回 true ,则这个方法返回 true
  • every()对数组每一项都运行传入的测试函数,如果所有元素都返回 true ,则这个方法返回 true
  • forEach()对数组每一项都运行传入的函数,没有返回值
  • filter()对数组每一项都运行传入的函数,函数返回 true 的项会组成数组之后返回
  • map()对数组每一项都运行传入的函数,返回由每次函数调用的结果构成的数组

3、字符串中的常用方法?

添加:

  • concat用于将一个或多个字符串拼接成一个新字符串

删除:

  • slice()
  • substr()
  • substring()
  • 这三个方法都返回调用它们的字符串的一个子字符串,而且都接收一或两个参数。

修改:

  • 这里改的意思也不是改变原字符串,而是创建字符串的一个副本,再进行操作

    常见的有:

    • trim()、trimLeft()、trimRight():删除前、后或前后所有空格符,再返回新的字符串
    • repeat():接收一个整数参数,表示要将字符串复制多少次,然后返回拼接所有副本后的结果
    • padStart()、padEnd():复制字符串,如果小于指定长度,则在相应一边填充字符,直至满足长度条件
    • toLowerCase()、 toUpperCase():大小写转化

查找:

  • chatAt():返回给定索引位置的字符,由传给方法的整数参数指定
  • indexOf():从字符串开头去搜索传入的字符串,并返回位置(如果没找到,则返回 -1 )
  • startWith():从字符串中搜索传入的字符串,并返回一个表示是否包含的布尔值
  • includes():从字符串中搜索传入的字符串,并返回一个表示是否包含的布尔值

转换方法:

  • split:把字符串按照指定的分割符,拆分成数组中的每一项

模板匹配:

  • match():接收一个参数,可以是一个正则表达式字符串,也可以是一个RegExp对象,返回数组
  • search():接收一个参数,可以是一个正则表达式字符串,也可以是一个RegExp对象,找到则返回匹配索引,否则返回 -1
  • replace():接收两个参数,第一个参数为匹配的内容,第二个参数为替换的元素(可用函数)

4、JavaScript中的类型转换机制?

  • JS中的类型转换机制常见的有显示转换(强制转换)和隐式转换(自动转换)
  • 显示转换:
    • Number():将任意类型的值转化为数值
    • parseInt():parseInt相比Number,就没那么严格了,parseInt函数逐个解析字符,遇到不能转换的字符就停下来
    • String():可以将任意类型的值转化成字符串
    • Boolean():可以将任意类型的值转为布尔值
  • 隐式转换
    • 比较运算(==!=><)、ifwhile需要布尔值地方
    • 算术运算(+-*/%

5、=====有什么区别?

  • 等于==
    • 等于操作符用两个等于号( == )表示,如果操作数相等,则会返回 true;在JavaScript中存在隐式转换。等于操作符(==)在比较中会先进行类型转换,再确定操作数是否相等
  • 全等===
    • 全等操作符由 3 个等于号( === )表示,只有两个操作数在不转换的前提下相等才返回 true。即类型相同,值也需相同
  • 最主要的区别就是等于操作符存在类型转换,全等操作符不会做类型转换

6、深拷贝和浅拷贝的区别?如何实现一个深拷贝?

  • 浅拷贝,指的是创建新的数据,这个数据有着原始数据属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值。如果属性是引用类型,拷贝的就是内存地址

    即浅拷贝是拷贝一层,深层次的引用类型则共享内存地址

    JavaScript中,存在浅拷贝的现象有:

    • Object.assign
    • Array.prototype.slice(), Array.prototype.concat()
    • 使用拓展运算符实现的复制
//实现一个浅拷贝
function shallowClone(obj) {
    const newObj = {};
    for(let prop in obj) {
        if(obj.hasOwnProperty(prop)){
            newObj[prop] = obj[prop];
        }
    }
    return newObj;
}
  • 深拷贝开辟一个新的栈,两个对象属完成相同,但是对应两个不同的地址,修改一个对象的属性,不会改变另一个对象的属性

    常见的深拷贝方式有:

    • _.cloneDeep()
    • jQuery.extend()
    • JSON.stringify()
    • 手写循环递归
//递归循环实现深拷贝
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;
}

区别:

  • 浅拷贝是拷贝一层,属性为对象时,浅拷贝是复制,两个对象指向同一个地址
  • 深拷贝是递归拷贝深层次,属性为对象时,深拷贝是新开栈,两个对象指向不同的地址

7、谈一谈对于闭包的理解?以及闭包的使用场景?

  • 一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包。也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域
    - 使用场景:
    • 创建私有变量
    • 延长变量生命周期
    • 函数柯里化
    • 模拟闭包私有方法
  • 如何实现一个闭包?
    • 在一个函数内再生成一个函数
    • 将一个函数作为另一个函数的返回值调用
    • 将一个函数当作参数传递给另一个函数

8、说说对于作用域链的理解?

1、当在Javascript中使用一个变量的时候,首先Javascript引擎会尝试在当前作用域下去寻找该变量,如果没找到,再到它的上层作用域寻找,以此类推直到找到该变量或是已经到了全局作用域

如果在全局作用域里仍然找不到该变量,它就会在全局范围内隐式声明该变量(非严格模式下)或是直接报错

9、JavaScript中原型、原型链是什么?有什么特点?

  • 在JavaScript中,每个对象都有一个原型(prototype),原型是一种用于实现对象属性继承的机制。每个JavaScript对象都有一个原型对象,它充当了对象的模板,并提供了共享属性和方法。
  • 当我们访问一个对象的属性或方法时,如果该对象本身没有这个属性或方法,JavaScript会去它的原型中查找,如果原型中也没有,则会继续查找原型的原型,直到找到或者到达原型链的末端。
  • 原型链是由对象的原型组成的链式结构,它描述了对象之间的继承关系。每个对象都有一个隐式的原型指针(proto),指向它的原型对象。通过原型链,我们可以实现对象的属性和方法的继承。
  • 原型链的特点包括:
  • 对象之间通过原型链实现属性和方法的继承,可以减少重复代码,提高代码的复用性。
  • 原型链是一个单向的链式结构,子对象可以访问父对象的属性和方法,但父对象不能访问子对象的属性和方法。
  • 原型链的末端是Object.prototype,它是所有对象的最顶层原型,包含了一些常用的方法,如toString()、valueOf()等
  • 如果在原型链上的某个对象上找到了需要的属性或方法,就会停止查找,不会继续向上查找。

10、JavaScript如何实现继承?

下面给出JavaScripy常见的继承方式:

- 原型链继承:原型链继承是比较常见的继承方式之一,其中涉及的构造函数、原型和实例,三者之间存在着一定的关系,即每一个构造函数都有一个原型对象,原型对象又包含一个指向构造函数的指针,而实例则包含一个原型对象的指针

function Parent() {
    this.name = 'parent1';
    this.play = [1, 2, 3]
  }
  function Child() {
    this.type = 'child2';
  }
  Child1.prototype = new Parent();
  console.log(new Child())
  • 构造函数继承(借助 call):相比第一种原型链继承方式,父类的引用属性不会被共享,优化了第一种继承方式的弊端,但是只能继承父类的实例属性和方法,不能继承原型属性或者方法
function Parent(){
    this.name = 'parent1';
}

Parent.prototype.getName = function () {
    return this.name;
}

function Child(){
    Parent1.call(this);
    this.type = 'child'
}

let child = new Child();
console.log(child);  // 没问题
console.log(child.getName());  // 会报错

- 组合继承:组合继承则将前两种方式继承起来

function Parent3 () {
    this.name = 'parent3';
    this.play = [1, 2, 3];
}

Parent3.prototype.getName = function () {
    return this.name;
}
function Child3() {
    // 第二次调用 Parent3()
    Parent3.call(this);
    this.type = 'child3';
}

// 第一次调用 Parent3()
Child3.prototype = new Parent3();
// 手动挂上构造器,指向自己的构造函数
Child3.prototype.constructor = Child3;
var s3 = new Child3();
var s4 = new Child3();
s3.play.push(4);
console.log(s3.play, s4.play);  // 不互相影响
console.log(s3.getName()); // 正常输出'parent3'
console.log(s4.getName()); // 正常输出'parent3'
  • 原型式继承:这里主要借助Object.create方法实现普通对象的继承
  • 缺点也很明显,因为Object.create方法实现的是浅拷贝,多个实例的引用类型属性指向相同的内存,存在篡改的可能
let parent4 = {
    name: "parent4",
    friends: ["p1", "p2", "p3"],
    getName: function() {
      return this.name;
    }
  };

  let person4 = Object.create(parent4);
  person4.name = "tom";
  person4.friends.push("jerry");

  let person5 = Object.create(parent4);
  person5.friends.push("lucy");

  console.log(person4.name); // tom
  console.log(person4.name === person4.getName()); // true
  console.log(person5.name); // parent4
  console.log(person4.friends); // ["p1", "p2", "p3","jerry","lucy"]
  console.log(person5.friends); // ["p1", "p2", "p3","jerry","lucy"]
  • 寄生式继承:寄生式继承在上面继承基础上进行优化,利用这个浅拷贝的能力再进行增强,添加一些方法
let parent5 = {
    name: "parent5",
    friends: ["p1", "p2", "p3"],
    getName: function() {
        return this.name;
    }
};

function clone(original) {
    let clone = Object.create(original);
    clone.getFriends = function() {
        return this.friends;
    };
    return clone;
}

let person5 = clone(parent5);

console.log(person5.getName()); // parent5
console.log(person5.getFriends()); // ["p1", "p2", "p3"]

  • 寄生组合式继承:寄生组合式继承,借助解决普通对象的继承问题的Object.create 方法,在前面几种继承方式的优缺点基础上进行改造,这也是所有继承方式里面相对最优的继承方式
function clone (parent, child) {
    // 这里改用 Object.create 就可以减少组合继承中多进行一次构造的过程
    child.prototype = Object.create(parent.prototype);
    child.prototype.constructor = child;
}

function Parent6() {
    this.name = 'parent6';
    this.play = [1, 2, 3];
}
Parent6.prototype.getName = function () {
    return this.name;
}
function Child6() {
    Parent6.call(this);
    this.friends = 'child5';
}

clone(Parent6, Child6);

Child6.prototype.getFriends = function () {
    return this.friends;
}

let person6 = new Child6();
console.log(person6); //{friends:"child5",name:"child5",play:[1,2,3],__proto__:Parent6}
console.log(person6.getName()); // parent6
console.log(person6.getFriends()); // child5

11、谈谈对this对象的理解?

在 JavaScript 中,关键字 this 指向当前执行上下文中的对象。它是动态绑定的,具体的指向取决于函数被调用的方式和位置。

this 的指向有以下几种情况:

  1. 默认绑定:当一个函数独立调用时,this 默认绑定到全局对象(在浏览器环境中为 window 对象,在 Node.js 环境中为 global 对象)。

    function foo() {
      console.log(this);
    }
    
    foo(); // 在浏览器环境中输出 window,在 Node.js 环境中输出 global
    
  2. 隐式绑定:当函数作为对象的方法调用时,this 绑定到调用该方法的对象。

    const obj = {
      name: 'Alice',
      sayHello: function() {
        console.log('Hello, ' + this.name);
      }
    };
    
    obj.sayHello(); // 输出 "Hello, Alice"
    
  3. 显式绑定:通过 call()apply()bind() 方法显式指定一个对象作为函数执行时的 this 值。

    function greet() {
      console.log('Hello, ' + this.name);
    }
    
    const person = { name: 'Bob' };
    
    greet.call(person); // 输出 "Hello, Bob"
    
  4. new 绑定:当使用 new 关键字调用构造函数创建一个新对象时,this 绑定到新创建的对象。

    function Person(name) {
      this.name = name;
    }
    
    const john = new Person('John');
    console.log(john.name); // 输出 "John"
    
  5. 箭头函数:箭头函数没有自己的 this,它继承外部作用域的 this 值。

    javascript复制代码const obj = {
      name: 'Alice',
      sayHello: function() {
        const greet = () => {
          console.log('Hello, ' + this.name);
        };
        greet();
      }
    };
    
    obj.sayHello(); // 输出 "Hello, Alice"
    

需要注意的是,在某些特殊情况下(如事件处理程序、定时器回调等),this 的指向可能会发生变化,也可以使用箭头函数来维持所需的 this 值。此外,使用严格模式('use strict')时,全局对象不能默认绑定到 this,而是绑定到 undefined

12、typeof和instanceof有什么区别?

typeof运算符和instanceof运算符在JavaScript中用于检测变量的类型,但它们有不同的应用场景和功能。

  1. typeof

    运算符:

    • typeof用于判断一个值的基本类型(原始类型)。
    • 它返回一个表示值类型的字符串。常见的返回值包括:“number”、“string”、“boolean”、“undefined”、“object”、"function"以及 “symbol”(ES6新增)。
    • typeof null 返回 “object” 是因为历史原因造成的错误,实际上 null 是一个特殊的原始值,而不是对象。

示例:

typeof 42; // "number"
typeof "Hello"; // "string"
typeof true; // "boolean"
typeof undefined; // "undefined"
typeof null; // "object" (错误的结果)
  1. instanceof

    运算符:

    • instanceof用于判断一个对象是否是某个构造函数的实例。
    • 它基于原型链进行判断,即判断对象的原型链中是否出现了指定构造函数的原型对象。
    • 如果对象是指定构造函数的实例,则返回 true,否则返回 false

示例:

const obj = {};
obj instanceof Object; // true

function Person() {}
const person = new Person();
person instanceof Person; // true

const arr = [];
arr instanceof Array; // true
arr instanceof Object; // true,因为Array继承自Object

const str = "Hello";
str instanceof String; // false,因为str是基本类型而不是字符串对象

总结:

  • typeof用于判断基本类型。
  • instanceof用于判断对象的构造函数实例。
  • 两者针对的对象范围不同,typeof适用于原始类型和函数(也是对象),instanceof适用于对象及其派生对象。

13、什么是事件代理?应用场景?

事件代理(Event delegation)是一种常用的JavaScript设计模式,它利用事件冒泡机制将事件处理程序添加到父元素上,从而代理处理其子元素触发的事件。换句话说,事件处理被委托给父元素来处理。

工作原理:

当一个事件在DOM树中的元素上触发时,该事件会在触发元素上产生,并且自动向上层传播,即向祖先元素逐级冒泡。通过将事件处理程序添加到父元素或更高层次的元素上,我们可以捕获到子元素触发的事件,然后根据需要进行处理。

应用场景:

  1. 效率优化:对于包含大量子元素的列表、表格等结构,如果为每个子元素都绑定事件处理程序,会消耗大量内存和性能。使用事件代理,只需在父元素上绑定一个事件处理程序,减少了内存占用和DOM操作次数。
  2. 动态添加元素:当通过JavaScript动态添加新的子元素时,无需再次绑定事件处理程序,因为事件冒泡机制已经将事件传递给父元素,父元素上的事件处理程序会自动处理新添加的子元素。
  3. 处理未来元素:如果需要在页面加载后处理尚不存在的元素的事件,例如异步加载内容或延迟执行脚本,事件代理可以派上用场。通过将事件处理程序添加到已存在的父元素上,可以捕获尚不存在的元素触发的事件。

14、说一下new操作符具体干了什么?

new 操作符用于创建一个对象实例,它执行了以下几个具体的步骤:

  1. 创建一个空对象:首先,new 操作符会创建一个新的空对象。
  2. 设置原型链:接下来,通过将新对象的 [[Prototype]] 属性设置为构造函数的 prototype 属性,建立对象与原型之间的连接。这样新对象就可以访问构造函数原型上的属性和方法。
  3. 绑定this指向:将构造函数内部的 this 关键字绑定到新创建的对象上,使构造函数内部的代码可以操作该对象的属性和方法。
  4. 执行构造函数代码:接着,执行构造函数体内的代码,给新对象添加属性和方法。通过在构造函数内使用 this 关键字,可以将新对象的属性和方法绑定到该对象上。
  5. 返回新对象:如果构造函数没有显式返回其他对象,则返回新创建的对象实例;否则,返回构造函数显式返回的对象。

15、ajax的原理?如何实现?

Ajax(Asynchronous JavaScript and XML)是一种在客户端和服务器之间进行异步数据交互的技术,它允许通过在后台发送HTTP请求来更新网页内容,而无需刷新整个页面。下面是Ajax的原理和实现方式:

原理:

  1. 前端代码使用JavaScript创建XMLHttpRequest对象,它是浏览器提供的内置对象,用于发送HTTP请求和接收响应。
  2. 通过XMLHttpRequest对象发送异步请求到服务器,可以是GET、POST等HTTP请求。
  3. 服务器接收到请求后处理,并将需要返回的数据以合适的格式(如JSON、XML)发送回前端。
  4. 前端通过XMLHttpRequest对象接收到服务器返回的数据。
  5. 根据接收到的数据,前端进行相应的处理,如更新页面内容、修改DOM元素等。

实现:以下是一个基本的Ajax实现步骤:

1、创建XMLHttpRequest对象:

var xhr = new XMLHttpRequest();

2、设置请求参数和回调函数:

xhr.open('GET', 'url'); // 设置请求方法和URL
xhr.setRequestHeader('Content-Type', 'application/json'); // 设置请求头
xhr.onreadystatechange = function() {
  if (xhr.readyState === 4 && xhr.status === 200) {
    // 请求完成且成功返回时的回调处理
    var responseData = JSON.parse(xhr.responseText);
    // 处理responseData
  }
};

3、发送请求:

xhr.send();

16、bind、call、apply区别?

bind、call和apply都是用于改变函数的执行上下文(即函数内部的this指向)的方法,它们的区别如下:

bind:bind() 方法创建一个新的函数,并将指定的值作为新函数的 this 值。bind() 方法并不会立即调用函数,而是返回一个绑定了指定上下文的新函数。

示例:

var obj = { name: 'Alice' };

function sayHello() {
  console.log('Hello, ' + this.name);
}

var boundFn = sayHello.bind(obj); // 创建一个绑定了obj作为this的新函数

boundFn(); // 输出: Hello, Alice

call:call() 方法调用一个函数,并将指定的对象作为函数执行时的上下文环境(即this指向),同时可以传递多个参数给函数。

示例:

var obj = { name: 'Alice' };

function sayHello() {
  console.log('Hello, ' + this.name);
}

sayHello.call(obj); // 将obj作为上下文调用sayHello函数,输出: Hello, Alice

apply:apply() 方法调用一个函数,并将指定的对象作为函数执行时的上下文环境(即this指向),同时接收一个数组或类数组对象作为参数列表。

示例:

var obj = { name: 'Alice' };

function sayHello(greeting) {
  console.log(greeting + ', ' + this.name);
}

sayHello.apply(obj, ['Hello']); // 将obj作为上下文调用sayHello函数,并传递参数数组,输出: Hello, Alice

相同点:bind、call和apply都可以改变函数的执行上下文,即this的指向。

不同点:bind在绑定上下文后返回一个新函数,而call和apply是立即调用函数。另外,call接收多个参数列表,而apply接收一个参数数组。

17、说一下对事件循环的理解?

事件循环是 JavaScript 中用于处理异步代码执行的机制,它负责管理和调度代码的执行顺序,确保在单线程环境下能够处理异步任务。

事件循环的主要流程如下:

  1. 执行同步代码:从程序的入口开始执行同步代码,按照顺序执行函数调用、变量赋值等操作。
  2. 处理微任务队列:在同步代码执行过程中,如果遇到微任务(Promise、MutationObserver等),则将其添加到微任务队列中。同步代码执行完毕后,在下一个事件循环迭代开始前,会检查并按顺序执行微任务队列中的任务,直到微任务队列为空。
  3. 处理宏任务队列:检查是否有宏任务(setTimeout、setInterval、I/O等)需要执行,如果有,则按顺序执行宏任务队列中的任务。每次只执行一个宏任务,直到宏任务队列为空。
  4. 进入下一轮事件循环:重复上述步骤,不断处理微任务队列和宏任务队列,直到程序结束或手动停止事件循环。

18、DOM常见的操作有哪些?

DOM(Document Object Model)是指文档对象模型,它是HTML或XML文档的编程接口,提供了对文档结构、内容和样式的访问和操作。DOM 提供了一系列常见的操作方法,其中包括:

  1. 访问元素:
    • getElementById(id):通过元素的唯一ID获取元素。
    • getElementsByClassName(className):通过类名获取一组元素。
    • getElementsByTagName(tagName):通过标签名获取一组元素。
    • querySelector(selector):通过选择器获取匹配的第一个元素。
    • querySelectorAll(selector):通过选择器获取所有匹配的元素。
  2. 操作元素属性和内容:
    • getAttribute(name):获取指定属性的值。
    • setAttribute(name, value):设置指定属性的值。
    • innerHTML:获取或设置元素的 HTML 内容。
    • textContent:获取或设置元素的纯文本内容。
    • classList:用于添加、移除和切换元素的 CSS 类。
  3. 操作元素样式:
    • style.property:直接设置元素的行内样式属性。
    • classList.add(className):向元素添加类名。
    • classList.remove(className):从元素移除类名。
    • classList.toggle(className):切换元素的类名状态。
  4. 创建和修改元素节点:
    • createElement(tagName):创建新的元素节点。
    • appendChild(node):向父节点添加子节点。
    • removeChild(node):从父节点移除子节点。
    • replaceChild(newNode, oldNode):用新节点替换旧节点。
  5. 添加事件监听器:
    • addEventListener(event, listener):向元素添加事件监听器。
    • removeEventListener(event, listener):从元素移除事件监听器。

19、常见的BOM对象有哪些?对于BOM的理解?

BOM(Browser Object Model)指的是浏览器对象模型,它是一组浏览器提供的对象和方法,用于与浏览器窗口进行交互。常见的BOM对象包括:

  1. window 对象:表示浏览器窗口或框架。
  2. navigator 对象:提供关于浏览器的信息。
  3. screen 对象:提供关于用户屏幕的信息。
  4. location 对象:提供当前文档的 URL 信息。
  5. history 对象:提供浏览器访问历史记录的功能。
  6. document 对象:表示当前文档的内容。
  7. console 对象:提供控制台用于调试和输出信息。

BOM 是 JavaScript 和浏览器之间的接口,通过 BOM 对象可以实现对浏览器窗口、导航、屏幕等进行操作和获取信息。BOM 提供了一系列对象和方法,使开发者可以与浏览器进行交互,例如修改页面 URL、执行页面跳转、操作浏览器历史记录、显示警告或错误信息等。

20、js中内存泄漏的情况有哪些?

JavaScript 中的内存泄漏是指应用程序中已不再使用的内存仍然被占用,导致内存资源浪费以下是一些常见的 JavaScript 内存泄漏情况:

  1. 未正确释放引用:当对象不再需要时,仍然保留对它的引用。这可能发生在全局变量、闭包、事件监听器或定时器等方面。
  2. 循环引用:两个或更多的对象之间存在相互引用,导致无法通过垃圾回收机制释放这些对象。特别是在使用闭包时容易出现循环引用的问题。
  3. 被遗忘的定时器或回调函数:如果定时器或回调函数没有被正确清理和取消,它们会继续持有对对象的引用,阻止它们被垃圾回收。
  4. DOM 引用未释放:删除了 DOM 元素,但相关的 JavaScript 对象(如事件处理程序)仍然存在引用,导致内存泄漏。
  5. 大量数据存储:在客户端中存储大量数据(如缓存数据)而不进行及时清理,会导致内存占用过高。
  6. 不合理的缓存使用:缓存的使用需要谨慎,如果缓存对象长时间不被清理或无法回收,将导致内存泄漏。
  7. 第三方库的问题:某些第三方库可能存在内存泄漏的问题,需要仔细评估和处理。

为避免内存泄漏,可以采取以下几种方法:

  • 及时释放不再使用的对象引用。
  • 注意闭包中的变量引用,确保正确地释放资源。
  • 使用合适的定时器和事件监听器,在不需要时取消它们。
  • 确保删除 DOM 元素后,相关的 JavaScript 对象也被正确清理。
  • 合理管理数据缓存,并定期进行清理。
  • 使用浏览器开发者工具进行性能分析和内存

21、什么是防抖和节流?有什么区别?

防抖(Debouncing)和节流(Throttling)是两种常用的优化 JavaScript 函数执行的技术,用于限制函数的调用频率。它们的主要区别在于对函数的执行时机进行控制的方式不同。

防抖(Debouncing)是指在连续触发某个事件时,在规定的延迟时间内只执行一次函数。如果在延迟时间内再次触发了该事件,则重新计时延迟时间。

应用场景:当用户连续输入搜索关键字时,可以使用防抖技术来限制搜索请求的发送频率。只有在用户停止输入一段时间后,才会实际发送搜索请求。

节流(Throttling)是指按照一定的时间间隔固定执行某个函数。无论事件触发频率多高,都会按照设定的时间间隔执行一次函数。

应用场景:在处理页面滚动事件时,可以使用节流技术来限制滚动事件的处理频率。每隔一定时间执行一次滚动事件的处理逻辑,避免频繁触发事件导致性能问题。

区别:

  1. 触发时机:防抖在连续触发事件时,只在规定延迟时间内最后一次触发后执行,而节流则是按照固定的时间间隔进行执行。
  2. 执行次数:防抖只执行最后一次触发的函数,节流会按照固定间隔时间执行多次函数,但频率有限制。
  3. 响应速度:防抖在延迟时间内只执行一次函数,相对会更快响应最新的触发事件,而节流则是按照固定时间间隔执行函数。

22、javaScript本地存储方式有哪些?有什么区别以及应用场景?

JavaScript提供了几种本地存储方式,包括以下三种主要方式:

  1. Cookie(Cookie):是浏览器端保存少量数据的一种机制。可以通过document.cookie属性读取和设置Cookie的值。Cookie的大小有限制(一般4KB),并且会随着每次请求都携带到服务器,因此对于大量或敏感数据不适合使用。常用于存储用户登录状态、记住用户偏好设置等场景。
  2. Web Storage(Web 存储):包括 localStorage 和 sessionStorage 两种机制。localStorage 可以用来保存持久化的数据,而 sessionStorage 则只在当前会话有效,浏览器关闭或页面刷新后会清除。它们可以通过localStoragesessionStorage对象进行读写操作。这两种机制支持存储较大的数据(一般几MB),并且只在浏览器与网站之间传输,不会自动发送到服务器。适用于长期保存数据,实现离线缓存等场景。
  3. IndexedDB(索引数据库):是一个功能强大的客户端数据库,允许存储大量结构化数据。它使用异步 API 进行操作,并支持事务控制。IndexedDB 是在浏览器中创建和维护的本地数据库,适用于需要高级查询或复杂数据结构的应用,比如存储大量离线数据、缓存数据等。

这些本地存储方式有以下区别和应用场景:

  • Cookie 适合存储少量数据,并且需要在每次请求中发送到服务器。常用于用户身份认证、记住登录状态等。
  • Web Storage(localStorage 和 sessionStorage)适合存储较大的数据,不需要频繁与服务器交互,并且希望在浏览器关闭后仍然可用。适用于缓存数据、保存用户偏好设置等场景。
  • IndexedDB 适合存储大量结构化数据,并提供复杂查询和事务支持。适用于需要离线数据、高级查询或复杂数据操作的应用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值