背上大大书包准备run之JS篇(含Es6)

word天,整理都半天,这么多,不得背死我。。。

js数据类型?

原始类型(Primitive types):

数字 (Number):例如:42, 3.14.

字符串 (String):例如:"Hello", 'World'.

布尔值 (Boolean):true, false.

空值 (Null):表示空或无效的值,只有一个值 null.

未定义 (Undefined):表示未被赋值的变量,只有一个值 undefined.

符号 (Symbol):在ES6中引入的唯一标识符,用于创建不可变且唯一的属性键。

引用类型(Reference types):

对象 (Object):对象是键值对的集合,可以通过字面量或构造函数创建。

数组 (Array):数组是一组有序的值的集合,可以通过方括号 [] 或 new Array() 创建。

函数 (Function):函数是可执行的代码块,可以通过函数声明或函数表达式定义。

数组常用方法?些改变原数组,些不改变?

改变原数组的方法(会修改原数组):

push(): 在数组末尾添加一个或多个元素。

pop(): 移除并返回数组最后一个元素。

shift(): 移除并返回数组的第一个元素。

unshift(): 在数组开头添加一个或多个元素。

splice(): 从数组中添加、删除或替换元素。

sort(): 对数组进行原地排序。

reverse(): 颠倒数组中元素的顺序。

fill(): 用指定的值填充数组中的所有元素。

不改变原数组的方法(返回新的数组而不修改原数组):

concat(): 连接两个或多个数组,并返回一个新数组。

slice(): 返回选定的元素的浅拷贝副本。

filter(): 根据指定条件过滤数组中的元素,并返回符合条件的元素组成的新数组。

map(): 根据指定规则对数组中的每个元素进行处理,并返回处理后的元素组成的新数组。

reduce(): 将数组元素通过指定的回调函数逐个求值,并返回最终结果。

join(): 将数组中的所有元素以指定的分隔符连接成一个字符串。

indexOf(): 返回数组中第一个匹配元素的索引。

lastIndexOf(): 返回数组中最后一个匹配元素的索引。

请注意,虽然不改变原数组的方法返回新的数组,但它们可能仍然访问原数组的值。

字符串常用方法?

length: 获取字符串的长度。

charAt(index): 返回指定索引处的字符。

charCodeAt(index): 返回指定索引处字符的Unicode编码。

substring(startIndex, endIndex): 返回位于 startIndex 和 endIndex 之间的子字符串。

slice(startIndex, endIndex): 返回位于 startIndex 和 endIndex 之间的子字符串(可以接受负数参数)。

indexOf(substring, fromIndex): 返回子字符串第一次出现的索引位置。

lastIndexOf(substring, fromIndex): 返回子字符串最后一次出现的索引位置。

replace(searchValue, replaceValue): 使用新字符串替换匹配的子字符串。

toUpperCase(): 将字符串转换为大写。

toLowerCase(): 将字符串转换为小写。

concat(str1, str2, ...): 连接两个或多个字符串,并返回一个新字符串。

trim(): 去除字符串两端的空格。

split(separator): 将字符串拆分为数组,根据指定的分隔符分割。

startsWith(searchString): 判断字符串是否以指定的字符串开头。

endsWith(searchString): 判断字符串是否以指定的字符串结尾。

== 和 === 区别,分别在什么情况使用?

== 是相等运算符,它会进行类型转换后比较两个值是否相等。例如,1 == '1' 会返回 true,因为它们的值相等。

=== 是严格相等运算符,它在比较两个值时要求它们既具有相同的值又具有相同的类型。例如,1 === '1' 会返回 false,因为一个是数字而另一个是字符串。

使用 === 可以避免一些类型错误,它更精确和一致。只有在需要进行隐式类型转换并了解其行为时,才使用 == 进行宽松相等比较。

深拷贝浅拷贝?

深拷贝和浅拷贝是两种复制对象或数组的方式,它们之间存在着重要的区别:

浅拷贝(Shallow Copy): 浅拷贝创建了一个新的对象或数组,并将原始对象或数组的引用复制到新对象中。这意味着当修改原始对象或数组时,副本也会受到影响

在JavaScript中,可以使用 Object.assign() 或扩展运算符 ... 来执行浅拷贝。

示例:

// 浅拷贝对象

const originalObj = { name: 'John', age: 30 };

const shallowCopyObj = Object.assign({}, originalObj);

// 或者:const shallowCopyObj = { ...originalObj };

originalObj.name = 'Alice'; // 修改原始对象

console.log(shallowCopyObj.name); // 输出: John,副本不受影响

// 浅拷贝数组

const originalArray = [1, 2, 3];

const shallowCopyArr = [...originalArray];

originalArray[0] = 4; // 修改原始数组

console.log(shallowCopyArr[0]); // 输出: 1,副本不受影响

深拷贝(Deep Copy): 深拷贝创建了一个全新的对象或数组,并递归地复制原始对象或数组及其所有嵌套的子对象或数组。这样,即使修改原始对象或数组,副本也不会受到影响

在JavaScript中,可以使用递归方法或库(如Lodash的cloneDeep())来执行深拷贝。

示例:

// 深拷贝对象

const originalObj = { name: 'John', age: 30 };

const deepCopyObj = JSON.parse(JSON.stringify(originalObj));

originalObj.name = 'Alice'; // 修改原始对象

console.log(deepCopyObj.name); // 输出: John,副本不受影响

// 深拷贝数组

const originalArray = [1, 2, { nested: true }];

const deepCopyArr = JSON.parse(JSON.stringify(originalArray));

originalArray[2].nested = false; // 修改原始数组中嵌套对象的属性

console.log(deepCopyArr[2].nested); // 输出: true,副本不受影响

需要注意的是,使用 JSON.parse(JSON.stringify()) 可以实现简单的深拷贝,但它有一些限制,例如无法处理函数或循环引用。对于复杂的对象或数组,最好使用专门的深拷贝方法或库来确保正确的复制。

JSON.parse() 是 JavaScript 中的一个方法,用于将 JSON 字符串解析为对应的 JavaScript 对象或值。

JSON.stringify() 是 JavaScript 中的一个方法,用于将 JavaScript 对象或值转换为对应的 JSON 字符串。

闭包?闭包使用场景?

有权访问另一个函数作用域中变量的函数

创建闭包的常见方式为在一个函数a内创建函数b,函数b可以访问到函数a的变量属性,函数b为闭包。

闭包的使用场景有很多:

封装私有变量:通过闭包可以创建私有变量,限制对变量的访问。

模块化开发:使用闭包可以实现模块化的封装,避免全局命名冲突。

延迟执行:通过返回一个内部函数作为回调,可以延迟执行特定的逻辑。

缓存数据:闭包可以用于缓存计算结果,提高代码的性能。

实现柯里化函数:通过闭包可以实现柯里化函数,将函数参数分步传递。

作用域?作用域链?

作用域(Scope) 是指在程序中定义变量的区域,决定了变量在何处和如何被访问。作用域可以确保变量的唯一性,并控制变量的可见性和生命周期。

在JavaScript中,有以下几种作用域:

全局作用域(Global Scope):全局作用域是整个程序范围内都可访问的作用域,其中声明的变量在代码的任何地方都是可见的。

函数作用域(Function Scope):函数作用域是在函数内部声明的变量所拥有的作用域。这意味着在函数外部无法访问函数内部的变量。

块级作用域(Block Scope):块级作用域是在代码块(如if语句、for循环、while循环等)内部定义的变量所拥有的作用域。在ES6之前,JavaScript没有块级作用域,但可以通过使用立即执行函数表达式(IIFE)来模拟块级作用域。Es6推出了letconst来实现块级作用域。

作用域链(Scope Chain) 是指在嵌套的作用域中查找变量时所形成的链式结构。当访问一个变量时,会首先在当前作用域中查找,如果找不到,则向上层作用域查找,直到找到该变量为止。这样的链式结构形成了作用域链。

作用域链的目的是为了解决变量访问的优先级问题。它允许内部作用域访问外部作用域的变量,但外部作用域无法访问内部作用域的变量。

例如:

function outer() {

  var x = 10;

  function inner() {

    var y = 5;

    console.log(x + y); // 访问外部作用域的变量

  }

  inner();

}

outer(); // 输出: 15

在这个例子中,inner() 函数可以访问外部函数 outer() 中声明的变量 x,因为它位于 inner() 的作用域链上。但是,outer() 无法访问 inner() 内部的变量 y。

总结:作用域指的是在程序中定义变量的区域,决定了变量的可见性和生命周期。作用域链是一个链式结构,用于在嵌套的作用域中查找变量。它允许内部作用域访问外部作用域的变量,但反之不成立。

原型?原型链?

原型(Prototype) 是 JavaScript 中每个对象都有的一个特殊属性。它是一个对象,用于存储共享的属性和方法。当我们访问一个对象的属性或方法时,如果该对象自身没有定义,则会去它的原型链上查找。

每个对象都有一个 __proto__ 属性指向其原型对象。通过对象的原型,我们可以实现属性和方法的继承,减少重复的定义。

示例:

// 创建一个对象

const person = {

  name: 'John',

  greet() {

    console.log(`Hello, my name is ${this.name}`);

  }

};

// 创建另一个对象,并将其原型设置为 person

const john = Object.create(person);

john.age = 30;

console.log(john.name); // 输出: John,从原型继承而来

john.greet(); // 输出: Hello, my name is John,从原型继承而来

原型链(Prototype Chain) 是多个对象通过原型链接起来形成的链式结构。当我们访问一个对象的属性或方法时,JavaScript 引擎会按照原型链从当前对象开始一层层向上查找,直到找到对应的属性或方法,或者达到原型链的顶端(即 Object.prototype)。

原型链的作用是实现对象之间的继承关系。每个对象都有一个原型,而原型本身也可以有自己的原型,这样就形成了一个原型链。

示例:

// 创建一个对象

const animal = {

  sound: 'Animal sound',

  makeSound() {

    console.log(this.sound);

  }

};

// 创建另一个对象,并将其原型设置为 animal

const dog = Object.create(animal);

dog.sound = 'Woof';

// 创建再一个对象,并将其原型设置为 dog

const bulldog = Object.create(dog);

bulldog.makeSound(); // 输出: Woof,从原型链继承而来

在这个例子中,bulldog 对象通过原型链继承了 dog 对象的属性和方法,而 dog 对象则通过原型链继承了 animal 对象的属性和方法。当调用 makeSound() 方法时,会按照原型链从 bulldog 开始向上查找,最终找到 dog 的 makeSound() 方法。

总结:原型是 JavaScript 中每个对象都有的特殊属性,用于存储共享的属性和方法。原型链是多个对象通过原型链接起来形成的链式结构,它实现了对象之间的属性和方法的继承关系。通过原型链,我们可以在对象之间共享属性和方法,减少重复定义。

this的理解?

this 是一个在 JavaScript 中经常使用的关键字,它表示当前执行上下文中的对象。具体来说,this 引用了当前正在执行的函数所属的对象或者直接调用该函数的对象。

this 关键字的值是动态确定的,它依赖于函数的调用方式。以下是常见的几种情况:

1.全局环境下的 this:在全局环境中,this 指向全局对象(浏览器环境中为 window 对象)。例如:

console.log(this); // 输出: window (in browser)

2.函数调用中的 this:当函数作为独立函数调用时,this 指向全局对象(非严格模式下),或者是 undefined(严格模式下)。例如:

function myFunction() {

  console.log(this);

}

myFunction(); // 输出: window (in non-strict mode) or undefined (in strict mode)

3.方法调用中的 this:当函数作为对象的方法调用时,this 指向调用该方法的对象。

const obj = {

  name: 'John',

  greet() {

    console.log(`Hello, my name is ${this.name}`);

  }

};

obj.greet(); // 输出: Hello, my name is John

4.构造函数中的 this:当函数作为构造函数使用时,this 指向新创建的实例对象。

function Person(name) {

  this.name = name;

}

const john = new Person('John');

console.log(john.name); // 输出: John

5.箭头函数中的 this:箭头函数中的 this 由上一层作用域继承,与普通函数不同。

需要注意的是,在事件处理程序、回调函数或通过 apply() 和 call() 显式调用时,我们可以使用 bind() 方法来改变函数中的 this 的指向。

总结:this 是一个关键字,它表示当前执行上下文中的对象。this 的值是动态确定的,取决于函数的调用方式。在全局环境下,this 指向全局对象;在方法调用中,this 指向调用该方法的对象;在构造函数中,this 指向新创建的实例对象。对于箭头函数,其 this 由上一层作用域继承。

typeof和instanceof区别?

typeof 和 instanceof 是 JavaScript 中用于检测数据类型和对象实例关系的运算符。它们之间有以下区别:

1.typeof运算符

typeof 运算符用于确定变量或表达式的数据类型,返回一个表示数据类型的字符串。常见的结果包括 "undefined"、"boolean"、"number"、"string"、"symbol"、"function" 和 "object"。

但需要注意的是,typeof null 的结果是 "object",这是因为在 JavaScript 的早期版本中,null 被错误地标识为对象类型。这个历史遗留问题无法修复,因为会破坏现有代码的兼容性。

示例:

console.log(typeof 42); // 输出: "number"

console.log(typeof "Hello"); // 输出: "string"

console.log(typeof true); // 输出: "boolean"

console.log(typeof undefined); // 输出: "undefined"

console.log(typeof null); // 输出: "object"(历史遗留问题)

console.log(typeof function() {}); // 输出: "function"

console.log(typeof {}); // 输出: "object"

2.instanceof运算符

instanceof 运算符用于检测对象是否是特定类(构造函数)的实例。它通过检查对象的原型链来确定对象的类型。

如果一个对象是指定类的实例,则返回 true,否则返回 false。

示例:

class Person {}

const john = new Person();

console.log(john instanceof Person); // 输出: true

const obj = {};

console.log(obj instanceof Object); // 输出: true

console.log(obj instanceof Array); // 输出: false

需要注意的是,instanceof 运算符只能用于检查对象是否是某个类的实例,不能判断基本数据类型。

总结:typeof 运算符用于确定变量或表达式的数据类型,返回一个表示数据类型的字符串。instanceof 运算符用于检测对象是否是特定类(构造函数)的实例,通过检查对象的原型链来确定对象的类型。两者的应用场景不同,typeof 适用于检测数据类型,instanceof 适用于检测对象实例关系。

事件代理?应用场景?

事件代理(Event delegation) 是一种常见的 JavaScript 事件处理技术,它利用事件冒泡的特性将事件处理程序添加到父元素上,从而监听其内部子元素的事件。

通常情况下,我们会直接为每个子元素添加事件处理程序。但是,当子元素数量较多或者动态生成时,这种方式可能会导致性能问题。使用事件代理可以解决这些问题,因为我们只需要在父元素上添加一个事件处理程序,就可以统一管理和处理所有子元素的事件。

应用场景:

1.性能优化: 当页面上有大量相似的子元素需要添加事件处理程序时,使用事件代理可以显著提高性能。通过将事件处理程序添加到父元素上,减少了注册和销毁大量事件处理程序的开销。

2.动态元素: 当页面中的元素是通过 AJAX 请求、动态生成或动态添加的,我们可以使用事件代理来处理这些动态元素的事件。因为事件代理基于事件冒泡机制,所以无论何时添加新元素,它们都会自动继承父元素的事件处理程序。

3.节省代码量: 使用事件代理可以减少代码的数量。相比为每个子元素单独绑定事件处理程序,只需为父元素绑定一个事件处理程序即可。

示例:

      

  • Item 1
  •   

  • Item 2
  •   

  • Item 3
  •   

  • Item 4

在上述示例中,为父元素

    添加了一个点击事件处理程序。当用户点击
      下的任何子元素
    • 时,事件会冒泡到父元素,并通过判断 event.target 的标签名来确定是哪个子元素被点击。然后,我们可以根据具体情况执行相应的操作。

      通过事件代理,我们只需要一个事件处理程序就能监听和处理所有子元素的点击事件,无论是现有的还是将来动态添加的子元素。这样可以提高性能并减少代码量。

      委托和事件

      在 JavaScript 中,事件委托(Event delegation)是一种利用事件冒泡机制的技术,将事件处理程序绑定到父元素上,以代理处理子元素的事件。

      事件: 在 JavaScript 中,事件(Event)是指用户与页面元素进行交互时发生的动作,如点击、滚动、鼠标移动等。我们可以通过为元素添加事件处理程序来响应这些事件。

      委托: 委托(Delegation)是指将一个任务或责任委托给另一个对象或函数来处理。在事件处理中,委托是指将事件处理程序绑定到父元素,然后利用事件冒泡机制,在父元素上捕获子元素触发的事件。

      应用场景: 使用事件委托的主要优势是可以减少事件处理程序的数量、提高性能和简化代码。以下是一些常见的应用场景:

      1.动态元素: 当页面中有动态生成或动态添加的元素时,使用事件委托可以节省代码量,并自动适用于新添加的元素。

      2.列表或表格操作: 当页面中存在大量相似的元素(如列表或表格),并且需要对它们进行类似的操作时,使用事件委托可以通过监听父元素的事件来统一处理,而无需为每个元素分别绑定事件处理程序。

      3.性能优化: 在大型页面中,如果为每个子元素绑定事件处理程序,可能会导致性能下降。使用事件委托可以减少事件处理程序的数量,并且利用事件冒泡机制,只需要在父元素上绑定一个事件处理程序。

      示例: 以下是一个使用事件委托的示例:

          

      • Item 1
      •   

      • Item 2
      •   

      • Item 3
      •   

      • Item 4

      在上述示例中,我们将点击事件处理程序绑定到父元素

        上。当用户点击子元素
      • 时,事件会冒泡到父元素,并通过判断 event.target 的标签名来确定是哪个子元素被点击。然后,我们可以根据具体情况执行相应的操作。

        通过事件委托,我们只需要一个事件处理程序就能监听和处理所有子元素的点击事件,无论是现有的还是将来动态添加的子元素。这样可以提高性能并减少代码量。

        事件委托和事件代理是同一个概念

        事件冒泡和事件捕获

        事件冒泡(Event bubbling)和事件捕获(Event capturing)是一种事件传播的机制,用于描述事件在 DOM 树中的传递方式。

        事件冒泡: 事件冒泡是指当一个元素上的某个事件被触发时,事件会从该元素开始向上冒泡至父元素、祖先元素,直至文档根节点。也就是说,事件首先在目标元素上触发,然后按照嵌套关系依次触发其父元素和祖先元素上相同类型的事件处理程序。

        事件捕获: 事件捕获是指当一个元素上的某个事件被触发时,事件会从文档根节点开始,经过父元素、祖先元素,直至达到实际目标元素。也就是说,事件首先在文档根节点触发,然后按照嵌套关系依次触发其父元素和祖先元素上相同类型的事件处理程序,最终到达实际目标元素。

        两者关系: 事件冒泡和事件捕获是相对的概念,它们描述了事件在 DOM 树中的传递顺序。在所有现代浏览器中,事件首先经历事件捕获阶段,然后是目标阶段,最后是事件冒泡阶段。

        当一个元素上触发某个事件时,首先会按照事件捕获的顺序从根节点向下传递,然后在目标元素上触发事件,最后按照事件冒泡的顺序由目标元素向上传递至根节点。

        通过理解事件冒泡和事件捕获两者的机制,我们可以更好地控制事件的传递和处理。通常情况下,我们更多地使用事件冒泡来实现事件委托或事件代理,因为它更方便、常见并且易于理解。

        event.stopPropagation() 方法将阻止事件继续向上冒泡

        事件循环的理解?

        事件循环(Event Loop)是 JavaScript 引擎用于处理异步操作的机制。它负责管理和调度任务的执行顺序,确保 JavaScript 在单线程环境下能够同时处理多个任务。

        JavaScript 是单线程的,意味着在同一时间只能执行一个任务。然而,有时我们需要执行一些耗时的操作(如网络请求、文件读取等),如果这些操作是同步的,会阻塞其他任务的执行,导致界面冻结并无法响应用户的交互。

        为了解决这个问题,JavaScript 引入了异步编程模型。异步操作将任务分成两部分:同步部分和异步部分。同步部分会立即执行,而异步部分则会被放入任务队列(Task Queue)中等待执行。

        事件循环的运行过程如下:

        1.执行同步代码:从上到下依次执行 JavaScript 中的同步代码。

        2.检查任务队列:检查任务队列是否有待执行的异步任务。

        3.执行异步任务:如果任务队列中有待执行的异步任务,按照优先级和先后顺序将任务添加到调用栈(Call Stack)中执行。

        4.等待新任务:当所有的异步任务都执行完毕后,事件循环会重新检查任务队列,继续从步骤2开始。

        这个过程不断重复,形成了一个事件循环。通过事件循环,JavaScript 引擎可以在同一时间处理多个任务,并保持界面的响应性。

        需要注意的是,事件循环中还有微任务队列(Microtask Queue),用于处理 Promise、MutationObserver 等微任务。微任务会在当前宏任务执行完毕后立即执行,优先级高于下一个宏任务。

        总结:事件循环是 JavaScript 引擎用于处理异步操作的机制。它负责管理和调度任务的执行顺序,确保 JavaScript 在单线程环境下能够同时处理多个任务。通过不断检查任务队列和微任务队列,事件循环实现了异步编程模型,保证了 JavaScript 的响应性。

        new操作符具体干了什么?

        new的过程

        1.创建一个空对象

        2.构造函数的this指向空对象

        3.执行构造函数,添加属性

        4.返回新对象

        bind、call、apply区别?

        bind() 方法创建一个新函数,并将原始函数绑定到指定的 this 值。它返回一个绑定了指定 this 值的新函数,不会立即执行。

        call() 方法在指定的上下文中调用函数,并传递参数列表。它直接调用函数并执行,返回调用函数的结果。

        apply() 方法在指定的上下文中调用函数,并传递参数数组。它直接调用函数并执行,返回调用函数的结果。

        这三种方法的主要区别在于参数传递的方式和是否立即执行函数。bind() 返回一个新函数,call() 和 apply() 直接调用函数并执行。另外,bind() 可以预先传递参数,而 call() 和 apply() 需要将参数作为参数列表或数组传递。

        简单来说:

        bind() 创建一个新函数并绑定 this 值,不立即执行。

        call() 在指定的上下文中调用函数,传递参数列表。

        apply() 在指定的上下文中调用函数,传递参数数组。

        这些方法都是用于改变函数的执行上下文,使得我们可以显式地指定函数内部的 this 值。具体使用哪个方法取决于需要传递的参数形式以及是否需要立即执行函数。

        dom常见操作有哪些?

        DOM(文档对象模型)是用于操作网页中的元素和内容的 API。以下是常见的 DOM 操作:

        获取元素:

        document.getElementById(id): 通过元素的 id 属性获取对应的元素。

        document.querySelector(selector): 通过选择器获取匹配的第一个元素。

        document.querySelectorAll(selector): 通过选择器获取所有匹配的元素。

        修改元素内容和属性:

        element.innerHTML: 获取或设置元素的 HTML 内容。

        element.textContent: 获取或设置元素的纯文本内容。

        element.setAttribute(name, value): 设置元素的属性值。

        element.getAttribute(name): 获取元素的属性值。

        添加、修改和删除元素:

        document.createElement(tagName): 创建新的元素节点。

        element.appendChild(childElement): 将子元素添加到父元素中。

        element.removeChild(childElement): 从父元素中移除子元素。

        element.replaceChild(newChild, oldChild): 替换父元素中的子元素。

        修改样式和类名:

        element.style.property = value: 设置元素的样式属性。

        element.classList.add(className): 添加类名。

        element.classList.remove(className): 移除类名。

        element.classList.toggle(className): 切换类名的状态。

        监听事件:

        element.addEventListener(event, callback): 绑定事件监听器。

        element.removeEventListener(event, callback): 解绑事件监听器。

        这些只是 DOM 操作的一部分,还有很多其他方法和属性可用于操作和处理网页中的元素。DOM 提供了丰富的功能来动态地改变页面的内容、样式和行为,使开发者能够与用户交互并响应用户操作。

        JavaScript内存泄露?

        JavaScript 内存泄漏是指在代码中存在无法使用的内存占用,这些内存占用没有被及时释放,导致内存资源被浪费或者永远无法回收。内存泄漏会影响应用程序的性能和稳定性。

        以下是一些常见的 JavaScript 内存泄漏情况:

        1.全局变量引用:如果一个对象被全局变量引用,即使不再需要该对象,它仍然无法被垃圾回收机制自动释放。因此,在不需要时,要确保解除对该对象的引用。

        2.定时器未清除:定时器设置后,如果没有被正确清除,会导致函数持续被调用,从而保留对函数的引用。确保在不需要时清除定时器。

        3.闭包:在函数内部创建的闭包可能会持有对外部作用域的引用,导致外部作用域中的变量无法被释放。使用完闭包后,确保及时解除对外部变量的引用。

        4.DOM 引用:在 JavaScript 中,DOM 元素是通过引用进行操作的。但是,如果删除了 DOM 元素,但仍然保留了对其的引用,那么该元素将无法被垃圾回收。因此,在删除 DOM 元素后,要记得解除对它的引用。

        5.事件监听器未移除:如果在元素上添加了事件监听器,但没有正确地移除它们,那么即使元素被删除,这些事件监听器仍然保留对元素的引用,导致元素无法被释放。因此,在不需要时,要确保移除事件监听器。

        为了避免内存泄漏,应该注意及时清理不再使用的对象、定时器、闭包、DOM 引用和事件监听器等。同时,使用现代的开发工具和浏览器提供的性能分析工具可以帮助检测和识别内存泄漏问题,并采取相应的修复措施。

        垃圾回收机制

        1.内存的声明周期

        1.内存分配 2.内存使用 3.内存回收

        全局变量一般不回收 关闭页面回收

        局部变量的值,不使用后,自动回收

        2.回收的方式

        1.引用计数 目前不用了,嵌套引用的情况下无法清除

        2.标记清除

        它通过标记阶段和清除阶段来识别和回收不再被引用的对象。

        标记阶段:从根对象开始,遍历所有对象,标记仍然被引用的对象。通常,根对象包含全局变量、活动函数调用栈和闭包等。

        清除阶段:遍历所有对象,如果某个对象没有被标记,则表示该对象不再被引用,可以被回收。被回收的对象的内存将被释放,以供后续使用。

        JavaScript精度丢失咋解决?

        JS是弱类型语言,没有对浮点数有个严格的数据类型。

        浮点数在计算机中总共长度是64位,其中最高位为符号位,接下来11位是指数位,最后52位是小数位,也就是有效数字部分。因为浮点数使用64位存储时,最多只能存储52的小数位,对于一些存在无限循环的小数位浮点数,会截取前52位,从而丢失精度。

        解决方法:

        1.把需计算的数字升级(乘以10的n次幂)成计算机能够精确识别的整数,等计算完毕再降级(除以10的n次幂)。

        2.四舍五入或截断:对于特定场景,可以使用 toFixed() 方法来控制输出的小数位数,并对结果进行四舍五入或截断。

        防抖?节流?

        节流:n秒内执行一次,若n秒内重复触发,仅执行一次

        防抖:n秒后执行一次,若n秒内重复触发,则重新计时

        ajax原理?

        Ajax(Asynchronous JavaScript and XML)是一种用于在网页中实现异步数据交互的技术。它允许通过 JavaScript 在不刷新整个页面的情况下,与服务器进行数据交换并更新部分页面内容。

        以下是 Ajax 的工作原理:

        1.创建 XMLHttpRequest 对象:使用 JavaScript 创建一个 XMLHttpRequest 对象,该对象用于与服务器进行通信。

        2.发送请求:使用 XMLHttpRequest 对象发送 HTTP 请求到服务器。请求可以是 GET、POST 或其他方法,可以携带参数和头信息。

        3.接收响应:当服务器接收到请求并处理完成后,将会返回一个响应。XMLHttpRequest 对象会异步地接收到响应,并触发相应的事件。

        4.处理响应:通过监听 XMLHttpRequest 对象的事件,可以获取响应数据。这可以是纯文本、XML、JSON 等形式的数据。然后,可以根据需要对响应数据进行处理。

        5.更新页面:一旦处理了响应数据,可以使用 JavaScript 来更新页面的部分内容,而不需要刷新整个页面。这可以是插入新的 HTML 元素、修改现有元素的内容或样式等。

        通过 Ajax,网页可以在后台与服务器进行数据交换,从而实现动态加载数据、无刷新更新页面内容、提高用户体验等效果。它被广泛应用于各种 Web 应用程序中,如社交媒体、电子商务、在线游戏等。

        Es6新增

        1.块级作用域引入了 let 和 const 关键字,用于声明块级作用域的变量和常量

        2.箭头函数:使用箭头函数语法 => 定义函数,简化了函数的书写,并且绑定了词法作用域内的 this。

        3.模板字符串:可以使用反引号 `` 来创建模板字符串,其中可以插入变量和表达式,提供了更方便的字符串拼接方式。(使用${}来插入变量,表达式和函数调用结果;支持跨越多行,无需使用\n转义)

        4.解构赋值:可以通过解构赋值将数组或对象的元素/属性分配给变量,从而实现快速、方便的数据提取和赋值。

        5.默认参数值:可以在函数定义时为参数指定默认值,简化了函数调用时的参数传递。

        6.扩展运算符:使用扩展运算符 ... 可以将数组或对象进行展开,方便地进行数组合并、对象复制等操作。

        7.类和模块:ES6 引入了 class 关键字,使得面向对象编程更加直观和易用。此外,还引入了 import 和 export 关键字,用于模块的导入和导出,提供了更好的模块化支持。

        8.Promise:通过 Promise 对象可以更好地处理异步操作,避免了回调地狱的问题。它提供了统一、可靠的方式来处理异步操作和获取其结果。

        var let const三个的区别

        1.变量作用域:

        var 声明的变量具有函数作用域,意味着在函数内部声明的变量在整个函数内都是可见的。而在函数外部声明的变量会成为全局变量,对整个程序可见。

        let 和 const 声明的变量具有块级作用域,意味着在声明变量的块(一对花括号 {})内有效。这使得变量在块外部是不可访问的。

        2.变量提升:

        使用 var 声明的变量会被提升到所在作用域的顶部,在变量声明前使用该变量也不会报错。这种行为称为“变量提升”。

        使用 let 和 const 声明的变量不会被提升,如果在变量声明前使用该变量会导致引用错误。

        3.重复声明:

        使用 var 声明的变量可以被多次重复声明,而后面的声明会覆盖前面的声明。

        使用 let 或 const 声明的变量在同一作用域内不能被重复声明,否则会抛出语法错误。

        4.初始值赋值:

        var 和 let 声明的变量可以先声明后赋值,也可以在声明的同时赋初值。

        const 声明的常量必须在声明时进行初始化赋值,并且不能再次赋值。一旦被赋值后,其值就不能被改变。

        5.全局对象属性:

        使用 var 声明的全局变量会成为全局对象(例如浏览器中的 window 对象)的属性,在全局范围内可访问。

        使用 let 或 const 声明的变量不会成为全局对象的属性,只在块级作用域内有效。

        综上所述,let 和 const 相较于 var 更加安全、灵活,并且符合现代 JavaScript 编程的最佳实践。推荐在新项目中使用 let 和 const 来代替 var。同时,根据具体需求选择 let 还是 const,前者用于声明可变变量,后者用于声明不可变(常量)变量。

        箭头函数特点

        1.简单的语法:

        // 传统函数

        function add(a, b) {

          return a + b;

        }

        // 箭头函数

        const add = (a, b) => a + b;

        2隐式返回值:当箭头函数只有一条表达式时,该表达式的结果将自动成为函数的返回值。不需要使用 return 关键字

        // 传统函数

        function multiply(a, b) {

          return a * b;

        }

        // 箭头函数

        const multiply = (a, b) => a * b;

        3.没有自己的 this:箭头函数没有自己的 this 值,它继承外层作用域的 this 值,也就是说箭头函数内部的 this 指向的是定义时所在上下文中的 this。

        示例:

        const obj = {

          name: 'John',

          sayHello: function() {

            setTimeout(() => {

              console.log(`Hello, ${this.name}!`); // 此处的this指向obj对象

            }, 1000);

          }

        };

        obj.sayHello(); // 输出 "Hello, John!",而不是 undefined

        4.不能作为构造函数:箭头函数不能使用 new 关键字调用,因为它们没有自己的 this。

        示例:

        const Person = (name) => {

          this.name = name; // 错误,箭头函数没有自己的this

        };

        const john = new Person('John'); // 错误,箭头函数不能作为构造函数

        箭头函数适用于那些简单和无需绑定自己的 this 值的场景。它们可以提供更简洁、清晰和易读的代码,尤其在处理回调函数和迭代方法时很有用。但需要注意,由于箭头函数没有自己的 this,因此无法通过 call()、apply() 或 bind() 方法改变其上下文。

        构造函数是一种特殊类型的函数,在 JavaScript 中用于创建和初始化对象。当使用 new 关键字调用构造函数时,会创建一个新的对象,并将该对象作为构造函数的上下文来执行构造函数中的代码。

        构造函数有以下特点:

        函数名的首字母通常大写:这是一种命名约定,用于区分普通函数和构造函数。

        使用 new 关键字调用:通过使用 new 关键字,可以实例化一个构造函数并创建一个新的对象。

        初始化对象属性和方法:构造函数中可以使用 this 关键字来引用当前的实例对象,并在构造函数内部给对象添加属性和方法。

        示例:

        function Person(name, age) {

          this.name = name;

          this.age = age;

        }

        // 使用构造函数创建对象

        const john = new Person('John', 30);

        console.log(john.name); // 输出 "John"

        console.log(john.age); // 输出 30

        在上面的示例中,Person 是一个构造函数,它接受 name 和 age 作为参数,并将它们赋值给 this.name 和 this.age 属性。通过使用 new 关键字来调用构造函数,我们创建了一个名为 john 的新对象,并将其属性初始化为传递的值。

        构造函数允许我们通过定义一个公共模板来创建多个具有相同属性和方法的对象。每个实例对象都会有自己的属性值,并且可以调用构造函数中定义的方法。

        需要注意的是,构造函数没有返回值,它们的主要作用是初始化对象。如果在构造函数中使用了 return 语句并返回了一个非对象类型的值,那么该值将被忽略,仍然会返回创建的新对象。

        解构赋值

        ES6 解构赋值是一种从数组或对象中提取值并将其赋给变量的语法。它允许我们以简洁的方式从复杂的数据结构中获取特定的值,并将这些值保存到独立的变量中。

        JavaScript 中有两种类型的解构赋值:数组解构和对象解构。

        数组解构:

        const numbers = [1, 2, 3];

        const [a, b, c] = numbers;

        console.log(a); // 输出 1

        console.log(b); // 输出 2

        console.log(c); // 输出 3

        对象解构:

        const person = { name: 'John', age: 30 };

        const { name, age } = person;

        console.log(name); // 输出 "John"

        console.log(age); // 输出 30

        扩展运算符

        ...

        ES6 扩展运算符是一种语法,用于展开数组或对象,并将其元素或属性拷贝到另一个数组或对象中。

        简单来说,ES6 扩展运算符有以下几个关键点:

        1.数组扩展:

        使用 ... 来表示扩展运算符。

        在数组字面量中使用扩展运算符,可以将一个数组展开为独立的元素。

        可以与其他元素一起使用,从而将多个数组合并成一个新数组。

        示例:

        const array1 = [1, 2, 3];

        const array2 = [4, 5, 6];

        const mergedArray = [...array1, ...array2];

        console.log(mergedArray); // 输出 [1, 2, 3, 4, 5, 6]

        2.对象扩展:

        使用 ... 来表示扩展运算符。

        在对象字面量中使用扩展运算符,可以将一个对象展开为独立的属性。

        可以与其他属性一起使用,从而将多个对象合并成一个新对象。

        示例:

        const object1 = { a: 1, b: 2 };

        const object2 = { c: 3, d: 4 };

        const mergedObject = { ...object1, ...object2 };

        console.log(mergedObject); // 输出 { a: 1, b: 2, c: 3, d: 4 }

        Promise

        Promise、async 和 await 是 JavaScript 中用于处理异步操作的特性。

        1.Promise: Promise 是一种表示异步操作的对象,它可以有三种状态:pending(进行中)、fulfilled(已成功)和 rejected(已失败)。当异步操作完成时,Promise 可以返回一个结果值或抛出一个错误。

        使用 Promise,我们可以处理异步操作,而不需要使用传统的回调函数嵌套。它提供了链式调用的方式,让代码更易读和维护。

        示例:

        const fetchData = new Promise((resolve, reject) => {

          // 异步操作

          setTimeout(() => {

            const data = 'Data fetched successfully';

            resolve(data); // 异步操作成功

            // 或者

            // reject('Error occurred'); // 异步操作失败

          }, 2000);

        });

        fetchData.then(response => {

          console.log(response); // 输出 'Data fetched successfully'

        }).catch(error => {

          console.log(error); // 输出 'Error occurred'

        });

        2.async/await: async 函数是一种声明异步函数的语法糖,它通过在函数前面加上 async 关键字来定义。async 函数返回一个 Promise 对象,可以使用 await 关键字来等待 Promise 的解决或拒绝。

        await 关键字只能在 async 函数内部使用。它会暂停函数执行,并等待 Promise 对象的解决或拒绝。如果 Promise 被解决,则将解决的值作为 await 表达式的结果返回;如果 Promise 被拒绝,则抛出一个错误。

        示例:

        async function fetchData() {

          return new Promise((resolve, reject) => {

            // 异步操作

            setTimeout(() => {

              const data = 'Data fetched successfully';

              resolve(data); // 异步操作成功

              // 或者

              // reject('Error occurred'); // 异步操作失败

            }, 2000);

          });

        }

        async function getData() {

          try {

            const response = await fetchData();

            console.log(response); // 输出 'Data fetched successfully'

          } catch (error) {

            console.log(error); // 输出 'Error occurred'

          }

        }

        getData();

        在上面的示例中,fetchData 是一个异步函数,返回一个 Promise 对象。在 getData 函数中,我们使用 await 关键字等待 fetchData 函数的解决或拒绝,并根据结果进行处理。

        Promise、async 和 await 是一种更优雅和简洁地处理异步任务的方式。使用它们可以让代码更易读和维护,并且避免了回调地狱的问题。

        Es6的Set和Map

        ES6 中引入了 Set 和 Map 这两个新的数据结构,用于存储和操作数据。

        1.Set: Set 是一种集合,它只存储唯一的值,不允许重复。Set 内部使用类似哈希表的数据结构来存储值,并提供了一系列方法来操作这些值。

        示例:

        const set = new Set();

        set.add(1);

        set.add(2);

        set.add(3);

        set.add(2); // 重复的值不会被添加

        console.log(set.size); // 输出 3

        console.log(set.has(2)); // 输出 true

        set.delete(2);

        console.log(set.size); // 输出 2

        set.clear()

        console.log(set.size); // 输出 0

        2.Map: Map 是一种键值对的集合,可以根据键快速查找对应的值。Map 允许使用任意类型的值作为键,并且保持插入顺序。

        示例:

        const map = new Map();

        map.set('name', 'John');

        map.set('age', 30);

        console.log(map.get('name')); // 输出 'John'

        console.log(map.has('age')); // 输出 true

        map.delete('age');

        console.log(map.size); // 输出 1

        map.clear();

        console.log(map.size); // 输出 0

        Map 提供了一组方法来操作键值对,包括设置值、获取值、检查键是否存在以及删除键值对等操作。

        Set 和 Map 都是有序的数据结构,并且提供了高效的查找、插入和删除操作。它们在需要存储唯一值或建立键值对映射关系的场景中非常有用。使用 Set 和 Map 可以简化代码并提高性能,它们是 ES6 中很有价值的新增特性。

你可能感兴趣的:(面试题,杂,javascript,前端,前端面试)