Javascript相关问题

目录

一、说说javascript都有哪些数据类型,如何存储的??

二、Bind,call,apply有什么区别?如何实现一个bind方法?

三、如何理解闭包?闭包的应用场景是?

四、JavaScript事件循环机制

五、对于事件循环的理解

六、关于BOM的理解,BOM的核心,作用是什么

七、关于同步异步区别

八、简单说一下什么是事件代理?

九、如何理解重绘和回流?什么场景下才能触发

十、说说对作用域链的理解

十一、Javascript如何实现继承?

原型继承

构造函数继承

 十二、什么是防抖和节流?使用场景有哪些?

防抖

节流

十三、严格模式?以及限制?

十四、如何快速的让一个打乱一个数组的顺序,比如 var arr = [1,2,3,4,5,6,7,8,9,10]


一、说说javascript都有哪些数据类型,如何存储的??

JavaScript共有八种数据类型,分别是 Undefined、Null、Boolean、Number、 String、Object、 Symbol、BigInt。

其中 Symbol和 Biglnt 是ES6 中新增的数据类型:

  • Symbol 代表创建后独一无二且不可变的数据类型,它主要是为了解决可能出现的全局变量冲突的问题.
  • Biglnt 是一种数字类型的数据,它可以表示任意精度格式的整数,使用 Biglnt 可以安全地存储和操作大整数,即使这个数已经超出了 Number 能够表示的安全整数范围。

这些数据可以分为原始数据类型和引用数据类型:

  • 栈: 原始数据类型 (Undefined、Null、Boolean、Number、 String)
  • 堆:引用数据类型 (对象、数组和函数)

两种类型的区别在于存储位置的不同:

  • 原始数据类型直接存储在栈(stack) 中的简单数据段,占据空间小、大小定,属于被频繁使用数据,所以放入栈中存储;
  • 引用数据类型存储在堆heap) 中的对象,占据空间大、大小不固定。如果存储在栈中,将会影响程序运行的性能:引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体

堆和栈的概念存在于数据结构和操作系统内存中,在数据结构中:

  • 在数据结构中,栈中数据的存取方式为先进后出。
  • ·堆是一个优先队列,是按优先级来进行排序的,优先级可以按照大小来规定

在操作系统中,内存被分为栈区和堆区:

  • 栈区内存由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
  • 堆区内存一般由开发着分配释放,若开发者不释放,程序结束时可能由垃圾回收机制回收。

二、Bind,call,apply有什么区别?如何实现一个bind方法?

bind,call,apply三者最主要区别是传参方式不同:

  • call和apply的第一个参数都是需要绑定的对象,区别在于后面传入的参数,apply是数组,call是一组参数。
  • bind不会立即执行函数,而是返回一个新的函数,并永久将this绑定到他的第一个参数上面。
const obj = { name: 'John' };
function sayName() {
  console.log(this.name);
}
const sayNameWithObj = sayName.bind(obj);
sayNameWithObj(); // 输出 John

三、如何理解闭包?闭包的应用场景是?

闭包是指在函数内部创建一个新的作用域,并且该作用域可以访问函数外部的变量。简单来说,闭包就是函数和函数内部能访问到的变量的组合。

闭包的应用场景有很多,其中一些比较常见的包括:

  • 封装变量:由于闭包可以访问函数外部的变量,因此可以使用闭包来封装私有变量,避免变量被外部访问和修改,从而保证程序的安全性和稳定性。
  • 实现模块化:由于闭包可以创建独立的作用域,因此可以用闭包来实现模块化的开发方式,将变量和方法封装在一个闭包中,从而避免命名冲突和变量污染。
  • 延迟执行:由于闭包可以访问函数外部的变量,因此可以用闭包来实现某些需要延迟执行的操作,例如setTimeout等。
  • 缓存变量:由于闭包可以访问函数外部的变量,因此可以用闭包来缓存一些计算结果,避免重复计算,提高程序的性能。
  • 实现回调函数:由于闭包可以访问函数外部的变量,因此可以用闭包来实现一些回调函数的功能,例如事件处理函数等。

总之,闭包是一种非常重要的JavaScript特性,它可以用于实现很多常见的编程需求,例如封装变量、实现模块化、实现回调函数等。但是,由于闭包会占用内存和资源,因此开发者在使用闭包时需要注意内存管理和性能优化。

四、JavaScript事件循环机制

JavaScript 事件循环机制是 JavaScript 引擎用来处理异步操作的核心机制,它负责维护一个执行栈和消息队列,以及在适当的时间将消息队列中的事件添加到执行栈中。

当 JavaScript 引擎执行代码时,会先将同步任务按照代码顺序依次放入执行栈中执行,这些代码的执行过程是同步的。当遇到异步任务时,如定时器、Ajax 请求等,引擎会将异步任务放入消息队列中,等待下一次事件循环时执行。

事件循环机制不断地重复以下过程:

  1. 等待执行栈中当前任务的执行结束
  2. 检查是否有消息队列中的事件
  3. 如果有,将其中的事件添加到执行栈中,执行相应的任务

当消息队列中有多个事件时,也会按照添加的顺序一个一个地执行。

需要注意的是,事件循环是单线程的,即执行栈只有一个,一次只能执行一个任务,如果当前任务执行时间过长,会阻塞后续任务的执行,影响页面的响应速度。因此,在开发中,我们需要尽量避免阻塞主线程,采用异步编程方式,让 JavaScript 引擎在空闲时去处理那些耗时的任务。

五、对于事件循环的理解

事件循环(Event Loop)是 JavaScript 运行时的一种机制,用于协调处理异步任务和用户事件。它是实现 JavaScript 非阻塞 I/O 操作的核心。

当我们写 JavaScript 代码时,其中包含了同步任务(Synchronous Task)和异步任务(Asynchronous Task)。同步任务会在主线程中依次执行,而异步任务则会被放入消息队列中等待执行。

事件循环机制会不断地检查消息队列,如果消息队列中有任务,则将该任务添加到执行栈中执行,执行完毕后再继续检查下一个任务,如此循环往复,直到消息队列为空为止。

需要注意的是,事件循环是单线程的,也就是说,当主线程正在执行某个任务时,其他任务必须等待当前任务执行完毕才能继续执行,因此,在编写 JavaScript 代码时,需要注意不要阻塞主线程,否则可能会导致页面卡顿或崩溃。

在具体实现上,事件循环机制主要由两个部分组成:宿主环境(如浏览器)和 JavaScript 引擎。宿主环境负责提供消息队列和定时器等 API,而 JavaScript 引擎则负责执行 JavaScript 代码,并管理执行栈和变量等。

总之,事件循环是 JavaScript 异步任务处理的核心机制,了解它对于优化 JavaScript 性能和编写高效的异步代码非常重要。

六、关于BOM的理解,BOM的核心,作用是什么

BOM(Browser Object Model)指的是浏览器对象模型,它是 JavaScript 操作浏览器窗口、界面和浏览器本身的接口集合。BOM 和 DOM(Document Object Model)一样都是浏览器提供的 API,但不同于 DOM 提供的是处理网页内容的接口,BOM 对象提供的是操作浏览器窗口和浏览器之间通信的接口。

常见的 BOM 对象有 windowdocumentlocationnavigatorscreen 等。其中,window 是 BOM 的核心对象之一,它表示整个浏览器窗口或标签页。在浏览器中,全局作用域就是 window 对象,因此全局变量和函数都是 window 对象的属性和方法。

BOM 的核心包括以下几个部分:

  1. window 对象:代表浏览器窗口或标签页,提供了很多操作浏览器窗口的方法和属性,如打开新窗口、移动、最小化、最大化和关闭等。

  2. document 对象:代表当前文档或页面,提供了访问和操作文档内容的接口,如查找元素、修改元素和设置样式等。

  3. location 对象:提供了与浏览器当前 URL 相关的信息和方法,如获取和设置 URL、刷新页面和跳转到新页面等。

  4. navigator 对象:提供了与浏览器相关的信息和方法,如获取浏览器类型、版本和语言等。

  5. screen 对象:提供了与用户屏幕相关的信息和方法,如获取屏幕大小和颜色深度等。

BOM 的作用主要有以下几个方面:

  1. 控制浏览器窗口:通过 BOM 可以打开、关闭、移动和调整浏览器窗口的大小等操作。

  2. 与用户交互:BOM 提供了与用户之间的交互接口,如 alert()、confirm() 和 prompt() 等方法。

  3. 操作浏览器历史记录:BOM 提供了操作浏览器历史记录的接口,如 history.back() 和 history.forward() 等方法。

  4. 管理浏览器状态:BOM 提供了浏览器状态管理的接口,如 sessionStorage 和 localStorage 等对象。

总之,BOM 是 JavaScript 操作浏览器窗口和浏览器本身的接口集合,可以通过它来实现与用户界面的交互、控制浏览器窗口和管理浏览器状态等功能。

七、关于同步异步区别

同步和异步是指代码执行的两种不同方式。简单地说,同步指的是在执行某个任务时必须等待该任务完成才能继续执行下一个任务,而异步则是在执行某个任务时可以继续执行下一个任务,等该任务完成后再回来执行回调函数。

具体来说,同步操作会阻塞主线程的执行,直到该操作完成后才能继续执行下一条语句。例如,使用 XMLHttpRequest 发送网络请求时,如果使用同步模式,则会在请求未完成前阻塞浏览器的其他操作,直到请求返回响应或超时才能继续执行下一条语句。

相比之下,异步操作不会阻塞主线程的执行,而是将任务放入事件循环队列中,等待主线程空闲时才会执行。例如,使用 XMLHttpRequest 发送网络请求时,如果使用异步模式,则可以继续执行下一条语句,待请求返回响应时会触发 readystatechange 事件,从而执行回调函数处理该响应。

另外,异步操作还可以通过回调函数、Promise 或 async/await 等方式实现。在这些方式中,回调函数是最基本的一种方式,它可以将异步操作的结果传递给回调函数,并在操作完成后执行该回调函数。Promise 是异步编程的一种更高级的方式,它可以将异步操作包装为一个 Promise 对象,从而实现更为优雅的异步编码方式。async/await 则是 ES2017 引入的一种异步编程方式,以同步的方式写异步代码。

总之,同步和异步是指代码执行的两种不同方式,其中同步会阻塞主线程的执行,而异步则不会,它会将任务放入事件循环队列中等待执行。在实际开发中,我们需要根据具体情况选择合适的方式来编写代码,以提高程序的性能和响应速度。

八、简单说一下什么是事件代理?

事件代理(Event Delegation)是指利用事件冒泡机制,将事件绑定在宿主元素的上级元素上,通过判断触发事件的具体子元素(即事件目标),从而执行相应的处理函数。

举个例子,假设我们有一个 ul 列表,其中包含多个 li 元素。如果我们想为每个 li 元素都绑定 click 事件,传统的做法是遍历每个 li 元素并分别绑定该事件。但是,当列表中的 li 元素非常多时,这种做法会导致性能问题。因此,可以使用事件代理来简化事件处理的过程,只需要将 click 事件绑定到 ul 元素上即可,然后在事件处理函数中判断当前触发事件的元素是否为 li 元素,从而执行相应的逻辑。

事件代理的优点有以下几点:

  1. 减少事件绑定数量:使用事件代理可以将事件绑定到宿主元素的上级元素上,从而避免了为每个子元素都绑定事件的繁琐过程,减少了事件绑定的数量,提高了性能。

  2. 动态元素支持:由于事件代理是基于事件冒泡机制实现的,因此对于后续动态添加的子元素也同样适用,无需再次手动绑定事件,提高了代码的灵活性和可维护性。

  3. 简化代码结构:使用事件代理可以将事件处理的逻辑统一封装在宿主元素的上级元素上,从而简化了代码的结构,提高了代码的可读性和可维护性。

总之,事件代理是一种基于事件冒泡机制实现的优化事件处理方式,它可以减少事件绑定数量、支持动态元素以及简化代码结构等优点,是开发中常用的技术之一。

九、如何理解重绘和回流?什么场景下才能触发

重绘和回流是浏览器渲染页面时的两个关键步骤,它们会消耗大量的计算资源,并严重影响页面的性能。

重绘(Reflow)指的是在渲染过程中,由于修改了 DOM 的结构或样式等属性,导致页面布局发生改变,浏览器需要重新计算元素的尺寸、位置等属性,然后重新绘制出新的布局。例如修改元素的宽度、高度、字体大小等属性都会引起重排。

回流(Repaint)指的是在重排之后,浏览器需要将重新计算后的布局绘制到页面上的过程。例如添加、删除或移动页面元素都可能引起回流。

通常情况下,重排比回流的成本更高,因为重排需要重新计算元素的几何属性以及其相对位置,而回流只需要重新绘制整个页面即可。因此,我们应该尽量避免进行大量的重排和回流操作,从而提高页面的性能。

如何避免重绘和回流?以下是一些常见的方法:

  1. 减少 DOM 操作:减少对 DOM 结构的操作,包括减少 DOM 元素的增删、style 属性的修改等等。

  2. 批量修改样式:避免修改元素的样式属性来达到动画效果,而应该使用 CSS3 的动画效果。

  3. 使用文档片段:使用文档片段可以将 DOM 操作合并为一次,然后再插入到页面中,从而减少重绘和回流的次数。

  4. 优化 CSS 选择器:避免使用复杂的 CSS 选择器,因为这样会增加浏览器计算样式的时间。

  5. 使用 translate 替代 top/left:使用 translate 替代 top/left 等属性来移动元素,因为 translate 不会引起重排和回流。

总之,重绘和回流是浏览器渲染页面时的两个关键步骤,它们会消耗大量的计算资源,并严重影响页面的性能。我们应该避免进行大量的重排和回流操作,从而提高页面的性能。

十、说说对作用域链的理解

作用域链(Scope Chain)是 JavaScript 中一个非常重要的概念,它是由执行上下文(Execution Context)组成的,用于查找变量和函数的定义。

当 JavaScript 代码执行时,每个函数都会创建自己的执行上下文。执行上下文中包含了变量、函数等信息,并且每个执行上下文都会有一个与之对应的作用域链。作用域链由当前执行上下文的变量对象(Variable Object)和所有父级执行上下文的变量对象构成,按照定义时的顺序依次排列。

当在一个函数中访问某个变量时,JavaScript 引擎会先在当前函数的变量对象中查找是否有该变量,如果没有则沿着作用域链依次向上查找,直到全局执行上下文的变量对象。如果在整个作用域链上都没有找到该变量,则会报错。

当在一个函数中定义一个变量或函数时,JavaScript 引擎首先会在当前函数的变量对象中查找是否已经存在同名的变量或函数,如果存在则直接覆盖,如果不存在则添加到当前变量对象中。由于作用域链的原因,不会影响到父级执行上下文的变量对象。

总之,作用域链是 JavaScript 中非常重要的概念,它是由执行上下文组成的,用于查找变量和函数的定义。了解作用域链可以更好地理解 JavaScript 的变量作用域、变量提升等特性,从而更加高效地编写 JavaScript 代码。

十一、Javascript如何实现继承?

在JavaScript中,可以使用原型继承和构造函数继承两种方式来实现继承:

原型继承

原型继承是指通过让一个对象的原型指向另一个对象来实现继承。当访问一个对象的属性或方法时,如果该对象本身没有定义该属性或方法,就会去它的原型链上查找,直到找到为止。

例如,我们可以创建一个父类对象,并将其原型作为子类对象的原型,从而实现继承:

function Parent() {
  this.name = 'parent';
}

Parent.prototype.sayName = function() {
  console.log(this.name);
};

function Child() {}

Child.prototype = new Parent();

var child = new Child();
child.sayName(); // 输出 "parent"

构造函数继承

构造函数继承是指在子类构造函数中调用父类构造函数来实现继承。通过使用callapply方法,可以将父类构造函数中的属性和方法复制到子类实例中,从而达到实现继承的目的。

例如,我们可以定义一个父类构造函数,并在子类构造函数中调用它:

function Parent(name) {
  this.name = name;
}

Parent.prototype.sayName = function() {
  console.log(this.name);
};

function Child(name) {
  Parent.call(this, name);
}

var child = new Child('child');
console.log(child.name); // 输出 "child"

// 父类方法无法继承,需要在子类上重新定义
Child.prototype.sayName = function() {
  console.log(this.name);
};
child.sayName(); // 输出 "child"

 十二、什么是防抖和节流?使用场景有哪些?

防抖和节流都是前端开发中常用的优化性能的技术。

防抖

防抖是指在一段时间内多次触发同一个事件,只执行最后一次触发的函数。例如,在搜索框输入关键字时,可以通过防抖技术来减少请求,提升搜索性能。

实现原理:

在事件处理函数中设置一个定时器,当该定时器到期时,执行事件处理函数。若在定时器到期之前再次触发了同一事件,则清除上一个定时器,并重新设置一个新的定时器。

示例代码:

function debounce(fn, delay) {
  let timer;

  return function() {
    const context = this;
    const args = arguments;

    clearTimeout(timer);
    timer = setTimeout(() => {
      fn.apply(context, args);
    }, delay);
  };
}

// 运用防抖技术来优化搜索框
const searchInput = document.getElementById('search-input');
searchInput.addEventListener('input', debounce(search, 500)); // 在输入 500 毫秒后执行搜索

节流是指在一段时间内多次触发同一个事件,只执行一次事件处理函数。例如,在页面滚动时,可以通过节流技术来控制事件的触发频率,从而减少不必要的计算和渲染,提高页面性能。

实现原理:

在事件处理函数中设置一个定时器,当该定时器到期时,执行事件处理函数,并在执行完后清空定时器。若在定时器到期之前再次触发了同一事件,则不会执行该事件处理函数。

示例代码:

function throttle(fn, delay) {
  let timer;
  let lastTime;

  return function() {
    const context = this;
    const args = arguments;

    const now = new Date().getTime();
    if (lastTime && now - lastTime < delay) {
      clearTimeout(timer);
      timer = setTimeout(function() {
        lastTime = now;
        fn.apply(context, args);
      }, delay);
    } else {
      lastTime = now;
      fn.apply(context, args);
    }
  };
}

// 运用节流技术来优化滚动事件
window.addEventListener('scroll', throttle(handleScroll, 500)); // 每 500 毫秒执行一次 handleScroll 函数

节流

使用场景:

防抖和节流都可以用来控制事件的触发频率,减少不必要的计算和渲染,提高页面性能。常见的场景包括:

  1. 搜索框实时搜索:使用防抖来减少请求次数

  2. 页面滚动加载更多:使用节流来减少请求次数

  3. 窗口resize事件:使用节流来避免频繁地调整布局

总之,防抖和节流是优化前端性能的两个重要技术,可以大大提升用户体验。

十三、严格模式?以及限制?

严格模式是指在JavaScript程序中,通过把整个脚本或单个函数的操作限制在严格模式下运行,来改变一些不安全、不严谨的语法和行为,从而使得代码更加规范化、安全化。

严格模式可以通过在脚本开头或函数中的第一条语句添加'use strict';来启用。示例:

'use strict';

function doSomething() {
  // 严格模式下的函数体
}

具体来说,严格模式对JS的限制包括:

  1. 变量必须先声明再使用:在非严格模式下,如果一个变量没有声明就直接使用,JS会默认为全局变量,并赋值为undefined。但在严格模式下,这种行为会抛出错误。

  2. 禁止删除变量、函数和参数:在严格模式下,使用delete删除变量、函数和参数是被禁止的。

  3. 禁止使用eval和arguments作为变量名:在严格模式下,eval和arguments不能作为变量名或函数名出现。

  4. 禁止给只读属性赋值:在严格模式下, readonly 属性被定义为只能在代码中读取,在运行时不能修改。如果尝试赋值给该属性,则会抛出错误。

  5. 函数内部的 this 指向问题:在非严格模式下,函数内部的 this 指向全局对象。但在严格模式下,非严格模式下的这种行为是被禁止的。

除此之外,严格模式还增强了程序的安全性,比如禁止在函数内部使用with语句,限制了函数名的重名等。

总之,严格模式可以让JS更加规范化、安全化,是一个提高JS代码质量的有效手段。

十四、如何快速的让一个打乱一个数组的顺序,比如 var arr = [1,2,3,4,5,6,7,8,9,10];

可以通过Fisher–Yates shuffle算法(也称为Knuth shuffle)来打乱一个数组的顺序。该算法的思路是,从数组的末尾开始,每次随机选择一个位置,将该位置的元素与数组中的最后一个元素交换,然后将游标向前移动一位,继续随机选择一个位置,并将该位置的元素与游标位置的元素交换,直到游标到达数组开头为止。

示例代码:

function shuffle(array) {
  let currentIndex = array.length;
  let temporaryValue, randomIndex;

  // While there remain elements to shuffle...
  while (0 !== currentIndex) {

    // Pick a remaining element...
    randomIndex = Math.floor(Math.random() * currentIndex);
    currentIndex--;

    // And swap it with the current element.
    temporaryValue = array[currentIndex];
    array[currentIndex] = array[randomIndex];
    array[randomIndex] = temporaryValue;
  }

  return array;
}

在上面的示例代码中,我们定义了一个shuffle函数,它的参数是一个数组,返回值是打乱顺序后的数组。函数内部使用了Fisher–Yates shuffle算法来打乱数组的顺序,随机交换数组中的元素,最终得到一个随机排列的数组。

调用示例:

const arr = [1,2,3,4,5,6,7,8,9,10];
shuffle(arr);
console.log(arr); // 打印出一个随机排列的数组

这样就可以快速地打乱一个数组的顺序了。

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值