目录
1、vue的双向绑定原理是什么?里面的关键点在哪里?
2、实现水平垂直居中的方式?
3、常用伪元素有哪一些?
4、移动端如何适配不同屏幕尺寸?
5、本地存储有哪一些?他们三者有什么区别?
6、JS的数据类型?如何判断js的数据类型?
7、说一下ES6的新特性有哪些?
8、Let、const、var三者有什么区别?
9、数组去重有哪些办法?
10、说一下深拷贝和浅拷贝,如何自己实现一个深拷贝?
11、Vue的生命周期有哪一些?说一下它们每个阶段做什么操作?
12、组件通讯方式有哪一些?
13、Vuex有几个属性及作用?
14、Vue的监听属性和计算属性有什么区别?
15、说一下防抖和节流。怎么实现?
16、Vue的导航守卫有哪一些?
17、登录拦截怎么实现的?
18、Call和applay有什么区别?
19、闭包是什么?如何实现?
20、Vue2.0和vue3.0有什么区别?
21、Vue常用的指令有哪些?
22、v-If和v-show有什么区别?
23、v-for为什么要加一个key?
24、举例封装一个组件的?
25、弹性布局,一行两列,一列固定宽,如何实现?
26、mixins有几个生命周期阶段?
27、父子组件生命周期执行顺序是怎么样的?
28、ue中普通的生命周期大概有哪些?
29、Url到浏览器的一个过程有哪些步骤?
30、如何实现小程序的request封装及拦截?
31、在vue的项目应用中,不使用框架,怎么封装?
32、什么是Js原型?原型链是什么?
33、组件通讯方式有哪些?
34、 js中的遍历方法
js中的遍历方法_js 折线图 遍历data-CSDN博客文章浏览阅读147次。js 前端遍历方法整理_js 折线图 遍历datahttps://blog.csdn.net/galaxyJING/article/details/129100608?spm=1001.2014.3001.5501
36、操作数组的方式有哪些?
JavaScript 数组方法整理-CSDN博客文章浏览阅读103次。JavaScript 数组方法整理https://blog.csdn.net/galaxyJING/article/details/129129761?spm=1001.2014.3001.5501
37、0.1 + 0.2 等于 0.3吗?为什么?如何解决?
38、keep-alive是什么?有哪几个生命周期阶段?
39、判断一个变量是否是数组,有哪些办法?
40、判断一个变量是否是对象,有哪些办法?
41、对象常用方法有哪些?
42、创建一个空数组/空对象有哪些方式?
43、哪些遍历方式会改变原数组?
44、Set和Map各是什么?
45、介绍一下promise。
46、Promise通常会解决三种问题
47、如何改变一个函数a的上下文?
1、vue的双向绑定原理是什么?里面的关键点在哪里?
Vue.js 的双向绑定(也被称为双向数据绑定或双向数据同步)是其核心特性之一,它允许你在视图(Vue模板)和模型(JavaScript对象)之间保持同步。这种同步是自动的,意味着当你在视图中改变某些内容时,模型中的数据也会相应地更新,反之亦然。
Vue.js 的双向绑定主要基于以下原理:
- 数据劫持:Vue 使用 Object.defineProperty() 方法劫持各个属性的 setter 和 getter,在数据变动时发布消息给订阅者,触发相应的监听回调。
- 观察者模式:每个 Vue 实例在被创建时,会遍历 data 中的所有属性,并使用 Object.defineProperty() 将这些属性转化为 getter/setter,以便在数据变动时能够通知 Vue 实例进行更新。这些 getter/setter 构成了 Vue 实例的观察者。
- 编译阶段:在编译阶段,Vue 会将模板解析为一个指令对象树,每个节点对应一个指令,指令中包含了原始模板中的信息,如标签名、属性名、属性值等。
- 指令绑定:在指令绑定阶段,Vue 会遍历指令对象树,为每个指令创建对应的绑定函数,并将这些函数存储在 Vue 实例的 _bindings 属性中。这些绑定函数会在数据变动时被调用,从而更新视图。
- 发布-订阅模式:Vue 的双向绑定还采用了发布-订阅模式。当数据发生变化时,观察者会发布消息,所有订阅了该消息的回调函数都会被调用。在 Vue 中,这些回调函数就是视图的更新函数。
关键点如下:
- 数据劫持:通过 Object.defineProperty() 劫持数据属性的 setter 和 getter,实现数据变动时的通知机制。
- 观察者模式:将每个 Vue 实例转化为一个观察者,监听数据的变化。
- 编译阶段:将模板解析为指令对象树,为指令绑定相应的处理函数。
- 发布-订阅模式:当数据发生变化时,通过发布-订阅模式通知所有订阅者进行更新。
通过以上原理和关键点,Vue.js 实现了高效的双向数据绑定,使得视图和模型之间的同步变得简单而直观。
2、实现水平垂直居中的方式?
在CSS中,有多种方法可以实现元素在容器中的水平和垂直居中。以下是其中的一些方法:
- Flexbox(弹性盒子)
Flexbox是一个非常强大的布局模型,可以轻松实现元素的水平和垂直居中。下面是一个例子:
css.container {
display: flex;
justify-content: center; /* 水平居中 */
align-items: center; /* 垂直居中 */
height: 100vh; /* 为了确保垂直居中,需要设置容器的高度 */
}
- Grid(网格)
CSS Grid也是一个强大的布局系统,它也可以用来实现元素的水平和垂直居中:
css.container {
display: grid;
justify-items: center; /* 水平居中 */
align-items: center; /* 垂直居中 */
height: 100vh; /* 为了确保垂直居中,需要设置容器的高度 */
}
- Transform
这种方法通过使用CSS的transform
属性和position
属性来实现元素的居中。这种方法的一个优点是它不需要知道元素的大小:
css.container {
position: relative;
}
.centered {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
- 使用定位和边距
如果你知道元素的大小,你可以使用定位和边距来实现居中:
css.container {
position: relative;
}
.centered {
position: absolute;
top: 50%;
left: 50%;
margin-top: -25px; /* 元素高度的一半 */
margin-left: -50px; /* 元素宽度的一半 */
}
以上就是实现元素在容器中水平和垂直居中的几种方式。你可以根据你的需求和具体的布局情况选择最适合的方法。
3、常用伪元素有哪一些?
常用的伪元素有 ::after、::before、::first-line、::first-letter、::selection、::backdrop1。
伪元素用于在css渲染中向元素的头部或尾部插入内容,它们不受文档约束,也不影响文档本身,只影响最终样式。这些添加的内容不会出现在DOM中,仅仅是在css渲染层中加入
4、移动端如何适配不同屏幕尺寸?
移动端适配不同屏幕尺寸的方法有很多种,以下为您推荐:
- 使用Viewport 。通过设置Viewport元标签来控制页面在移动设备上的显示1。
- 使用CSS Flexbox或Grid布局 。这些布局技术可以使元素在不同屏幕尺寸下自动调整位置和大小1。
- 使用CSS媒体查询 。通过使用@media规则,可以根据不同的屏幕尺寸应用不同的CSS样式1。
- 使用流式布局 。使用百分比单位和自适应容器来创建流式布局,使页面元素根据屏幕尺寸自动调整大小1。
- 使用图片适配 。使用srcset和sizes属性来为不同屏幕尺寸提供不同大小的图片,以减少加载时间和带宽消耗1。
5、本地存储有哪一些?他们三者有什么区别?
本地存储有localStorage、sessionStorage和cookie1。
三者的区别如下12:
- 存储大小:localStorage和sessionStorage存储数据大小可以达到5M或更大,而cookie数据不能超过4k。
- 数据有效期:localStorage存储的数据始终有效,浏览器关闭后数据也不丢失;sessionStorage存储的数据仅在当前浏览器窗口关闭前有效,浏览器关闭后数据自动删除;cookie在设置的过期时间之前一直有效,即使窗口或浏览器关闭。
- 存储位置:localStorage和sessionStorage是把数据存储到本地,而cookie是把数据存储在服务端。
6、JS的数据类型?如何判断js的数据类型?
JavaScript 的数据类型主要有以下几种:
- Number:用于表示整数和浮点数。
- String:用于表示文本数据。
- Boolean:有两个值,true 和 false,通常用于条件判断。
- Null:表示一个空值。
- Undefined:当一个变量未被定义时,它的值就是 undefined。
- Object:一种复杂的数据类型,可以包含其他数据类型,包括数组和函数。
- Symbol:一种唯一且不可变的数据类型,通常用于对象属性的键。
- BigInt:可以表示任意大的整数。
在 JavaScript 中,有多种方法可以判断一个变量的数据类型:
- typeof 运算符:返回表示未经计算的操作数的类型的字符串。例如,
typeof 123
返回"number"
,typeof "abc"
返回"string"
,typeof true
返回"boolean"
,typeof null
返回"object"
(这是一个历史错误,null 实际上不是对象),typeof undefined
返回"undefined"
,typeof {}
返回"object"
,typeof []
返回"object"
,typeof function(){}
返回"function"
。 - instanceof 运算符:用于检测构造函数的
prototype
属性是否出现在对象的原型链中的任何位置。例如,[] instanceof Array
返回true
,new Date() instanceof Date
返回true
。 - Object.prototype.toString.call() 方法:这是一个更精确的方法,可以获取到更详细的类型信息。例如,
Object.prototype.toString.call(null)
返回"[object Null]"
,Object.prototype.toString.call(undefined)
返回"[object Undefined]"
,Object.prototype.toString.call(123)
返回"[object Number]"
,Object.prototype.toString.call("abc")
返回"[object String]"
,Object.prototype.toString.call(true)
返回"[object Boolean]"
,Object.prototype.toString.call([])
返回"[object Array]"
,Object.prototype.toString.call(new Date())
返回"[object Date]"
,Object.prototype.toString.call(function(){})
返回"[object Function]"
。
7、说一下ES6的新特性有哪些?
ES6的新特性主要有12:
- 块级作用域和常量声明:通过let和const关键字声明变量和常量,且不会受到变量提升的影响。
- 箭头函数:可以使用=>替代传统的function关键字,具有更简洁的语法和绑定this的功能。
- 默认参数值:允许在函数定义中为参数设置默认值,简化了函数调用时的参数传递。
- 扩展操作符:可以将数组或对象展开,提取出其中的元素。
- 解构赋值:可以从数组或对象中提取值并赋值给变量。
- 类和模块:可以使用class关键字定义类,且ES6引入了模块化开发。
- 模板字面量:使用反引号()定义模板字符串,可以嵌入表达式。
- 迭代器和生成器:可以遍历数据集合的全部元素,而不需要暴露该集合的底层表示。
- Promise对象:用于处理异步操作,可以表示一个最终可能完成(也可能被拒绝)的异步操作及其结果值。
8、Let、const、var三者有什么区别?
在JavaScript中,let
、const
和var
都是用于声明变量的关键字,但它们之间有一些重要的区别。
-
作用域(Scope):
var
:有函数作用域或全局作用域。在函数内部使用var
声明的变量,其作用域是整个函数体。在函数外部使用var
声明的变量,其作用域是整个代码块。let
:有块级作用域。let
声明的变量只在声明它的块或子块中有效。const
:同样有块级作用域,且声明的变量是常量,其值在声明后不能更改。
-
变量提升(Variable Hoisting):
var
:有变量提升(hoisting)现象,即变量可以在声明之前使用,但此时变量的值为undefined
。let
和const
:没有变量提升。如果在声明前使用这些关键字声明的变量,会抛出一个错误。
-
重复声明:
var
:允许在同一作用域内重复声明同一个变量。let
和const
:不允许在同一作用域内重复声明同一个变量。
-
暂时性死区(Temporal Dead Zone):
let
和const
:在声明之前的区域是暂时性死区,如果在这些区域中使用这些变量,会导致错误。var
:没有暂时性死区的概念。
-
赋值限制:
const
:声明的变量必须立即赋值,且之后不能再更改。let
和var
:可以在声明后随时赋值,且之后可以更改。
总结来说,let
和const
提供了更好的块级作用域和更严格的错误检查,而var
则具有函数作用域和变量提升的特性。在现代JavaScript编程中,通常推荐使用let
和const
,因为它们提供了更好的控制和错误检查。
9、数组去重有哪些办法?
- 双层循环 。外层循环元素,内层循环时比较值,如果有相同的值则跳过,不相同则push进数组。
- 利用splice直接在原数组进行操作 。双层循环,外层循环元素,内层循环时比较值,值相同时删去这个值,注意删除元素后要将数组的长度减1。
- for循环+findIndex 。主要利用findIndex的特性,查找元素找不到就返回-1,然后判断,如果是-1,说明没找到,就往新数组里面添加元素1。
- sort排序 。首先利用sort方法进行排序,然后进行循环,如果原数组的第i项和新数组的i-1项不一致,就push进去1。
10、说一下深拷贝和浅拷贝,如何自己实现一个深拷贝?
深拷贝和浅拷贝是两种在编程中常见的复制对象的方式,主要区别在于复制的层次和深度。
浅拷贝:
浅拷贝只复制对象的顶层属性,如果对象的属性是引用类型(如数组、对象等),则复制的是内存中的地址,而不是实际的对象。因此,原始对象及其副本仍然引用同一个对象。
深拷贝:
深拷贝会复制对象的所有层级属性,包括其引用的对象。也就是说,它会创建一个新的对象,并将原始对象的所有属性(包括嵌套的对象和数组)复制到新对象中。因此,原始对象及其副本是完全独立的,修改一个不会影响另一个。
下面是在 JavaScript 中实现深拷贝和浅拷贝的简单示例:
浅拷贝的实现:
JavaScript 中的 Object.assign()
方法可以实现浅拷贝。例如:
javascriptlet original = { a: 1, b: 2 };
let copied = Object.assign({}, original);
console.log(copied); // { a: 1, b: 2 }
深拷贝的实现:
深拷贝的实现比较复杂,因为需要考虑各种数据类型和嵌套情况。以下是一个简单的递归实现,但需要注意,这个实现并不完全,对于一些特殊的 JavaScript 对象(如函数、日期、正则表达式等)和循环引用的情况并不能正确处理。
javascriptfunction deepCopy(obj, hash = new WeakMap()) {
if (obj == null) return obj;
// 如果是原始类型,直接返回
if (typeof obj !== 'object') return obj;
// 如果是日期或正则,直接返回新的对象
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj);
// 如果是数组或对象,进行递归复制
let t = Array.isArray(obj) ? [] : {};
// 如果对象已经被复制过,直接返回之前的复制结果
if (hash.has(obj)) return hash.get(obj);
hash.set(obj, t);
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
t[key] = deepCopy(obj[key], hash);
}
}
return t;
}
let original = { a: 1, b: { c: 2 } };
let copied = deepCopy(original);
console.log(copied); // { a: 1, b: { c: 2 } }
11、Vue的生命周期有哪一些?说一下它们每个阶段做什么操作?
- beforeCreate:在实例初始化之后,数据观测 (data observer) 和事件/监听事件配置之前被调用。此阶段完成了数据观测,属性和方法的运算,以及 watch/event 事件回调。然而,挂载阶段还没开始,$el 属性目前不可见。
- created:实例创建完成后调用,此阶段完成了数据观测,属性和方法的运算,以及 watch/event 事件回调。el属性还没有显示出来,el属性还没有显示出来,refs 属性目标也没有显示出来,但是 $root 属性有值。
- beforeMount:在挂载开始之前被调用,相关的 render 函数首次被调用。此时还没有开始挂载节点,$el 属性目标不会有任何变化,但虚拟 DOM 已经构建完成。
- mounted:el 被新创建的 vm.el替换,并挂载到实例上去之后调用该钩子。如果实例被挂载到一个文档内元素上,当mounted被调用时VM.el替换,并挂载到实例上去之后调用该钩子。如果实例被挂载到一个文档内元素上,当mounted被调用时vm.el 也在文档内。在这个阶段,你可以访问到组件的 DOM 元素,并进行一些依赖于 DOM 的操作。
- beforeUpdate:数据更新时调用,发生在虚拟 DOM 打补丁之前。这里适合在更新之前访问现有的 DOM,比如手动移除已添加的事件监听器。
- updated:由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。当这个钩子被调用时,组件 DOM 已经更新,所以你现在可以执行依赖于 DOM 的操作。然而在大多数情况下,你应该避免在此期间更改状态,因为这可能会导致更新无限循环。
- beforeDestroy:实例销毁之前调用。在这一步,实例仍然完全可用。
- destroyed:Vue 实例销毁后调用。调用后,所有的事件监听器会被移除,所有的子实例也会被销毁。
12、组件通讯方式有哪一些?
- 父向子传值:通过 props,父组件可以向子组件传递数据。
- 子向父传值:通过自定义事件,子组件可以向父组件传递数据。
- 非父子组件传值:可以通过 eventBus(事件总线),创建一个事件中心,相当于中转站,可以用它来传递事件和接收事件。
- provide 和 inject:父组件通过 provide 提供变量,子组件中通过 inject 注入变量,不论嵌套了几层子组件,都能通过 inject 来调用 provide 的数据。
- 通过本地存储:将数据保存到本地存储中(如 localStorage、sessionStorage),然后在需要通信的组件中读取数据。
13、Vuex有几个属性及作用?
Vuex有五个属性,分别是state、getters、mutations、actions和modules1。
五个属性的具体作用如下:
- state。用于存储变量,是vuex的基本数据。
- getters。相当于state的计算属性,是从基本数据(state)派生的数据。
- mutations。用于提交更新数据,是提交更新数据的方法,必须是同步的。
- actions。和mutation的功能大致相同,不同之处在于actions提交的是mutation,而不是直接变更状态,并且可以包含任意异步操作。
- modules。用于模块化vuex,将单一状态树分割成多个模块。
14、Vue的监听属性和计算属性有什么区别?
Vue的监听属性和计算属性有以下几点区别:
- 性能:计算属性性能更优。一个监听属性只能监听一个属性的变化,如果要同时监听多个,就要写多个监听属性,而计算属性可以同时监听多个数据的变化。
- 返回值:监听属性没有返回值,计算属性有返回值。
- 触发:监听属性是一旦值变更就会触发回调函数,而计算属性只有在它的相关属性(即依赖)发生变化时才会触发重新计算。
- 用途:监听属性适用于监听某个值的变化后做出相应的操作,而计算属性适用于基于其他状态来计算出新的状态,并且在计算结果发生变化时自动更新其值。
15、说一下防抖和节流。怎么实现?
防抖和节流是优化高频率执行代码的一种手段,主要用于限制函数的执行频率1。
防抖是指当持续触发事件时,一定时间段内只执行一次事件处理函数。如果在这个时间段内又触发了这个事件,则会重新计算执行时间。
节流是指在一定时间范围内,无论触发多少次事件,都只执行一次事件处理函数1。
防抖和节流的实现方式有多种,以下是其中两种实现方式:
- 防抖的实现方式:使用setTimeout定时器,在事件触发时延迟执行函数,如果在这个延迟期间再次触发事件,则清除上次的定时器并重新设置。
- 节流的实现方式:使用setTimeout定时器,在事件第一次触发时执行函数,并在一定时间内禁止再次执行。如果在这个时间内再次触发事件,则不执行函数,直到时间间隔结束后再重新计算。
16、Vue的导航守卫有哪一些?
Vue.js 中的导航守卫主要用在 Vue Router 中,用于控制路由的访问权限、页面跳转前的数据加载等。导航守卫包括全局守卫、路由独享守卫和组件内守卫。以下是 Vue Router 中的一些主要导航守卫:
-
全局守卫:
beforeEach
:全局前置守卫,在路由改变前被调用。可以访问路由对象,以及即将要进入和离开的组件。beforeResolve
:全局解析守卫,在beforeEach
之后调用。afterEach
:全局后置守卫,在路由改变后被调用。
-
路由独享守卫:
beforeEnter
:在路由配置中单独给某个路由独享的守卫。
-
组件内守卫:
beforeRouteEnter
:在路由进入该组件前被调用。此时,组件实例尚未被创建,因此不能访问this
。可以通过next
回调函数传入一个配置对象来访问组件实例。beforeRouteUpdate
:在当前路由改变,但是该组件被复用时被调用。可以访问组件实例this
。beforeRouteLeave
:在路由离开该组件时被调用。可以访问组件实例this
。
这些守卫可以按照特定的顺序被调用,以确保在路由发生变化时执行相应的逻辑。你可以使用这些守卫来执行诸如检查用户权限、加载数据、重定向到其他路由等操作。
17、登录拦截怎么实现的?
在 Vue.js 中,登录拦截通常是通过 Vue Router 和 Vuex(或其他状态管理库)结合使用来实现的。这种拦截通常涉及以下步骤:
-
设置全局前置守卫:使用 Vue Router 的
beforeEach
守卫来检查用户的登录状态。 -
检查用户状态:在
beforeEach
守卫中,通过 Vuex 或其他状态管理库检查用户是否已经登录。 -
未登录时的处理:如果用户未登录,可以重定向他们到登录页面,或者显示一个登录模态框。
-
已登录时的处理:如果用户已登录,允许他们继续访问目标路由。
-
动态路由:根据需要,可以动态地添加或删除路由,以反映用户的权限。
下面是一个简单的示例,展示如何使用 Vue Router 和 Vuex 实现登录拦截:
Vuex (store.js)
javascriptimport Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
export default new Vuex.Store({
state: {
isLoggedIn: false, // 假设这是用户登录状态
},
mutations: {
setLoginStatus(state, status) {
state.isLoggedIn = status;
},
},
actions: {
login({ commit }, status) {
commit('setLoginStatus', status);
},
},
});
Vue Router (router.js)
javascriptimport Vue from 'vue';
import Router from 'vue-router';
import store from './store'; // 引入 Vuex store
Vue.use(Router);
const router = new Router({
routes: [
{
path: '/login',
name: 'Login',
component: () => import('@/views/Login.vue'),
},
{
path: '/dashboard',
name: 'Dashboard',
component: () => import('@/views/Dashboard.vue'),
meta: { requiresAuth: true }, // 添加元信息以标识需要认证
},
// 其他路由...
],
});
router.beforeEach((to, from, next) => {
// 检查路由是否需要认证
if (to.matched.some(record => record.meta.requiresAuth)) {
// 如果需要认证,检查用户是否已登录
if (store.state.isLoggedIn) {
// 已登录,继续导航
next();
} else {
// 未登录,重定向到登录页面
next({ name: 'Login', query: { redirect: to.fullPath } });
}
} else {
// 不需要认证,继续导航
next();
}
});
export default router;
Login.vue
在登录组件中,成功登录后,可以通过 Vuex action 更新登录状态,并跳转到之前尝试访问的页面:
javascript// ...
methods: {
async login() {
try {
// 假设有一个 login API 调用
await this.$api.login(this.credentials);
// 登录成功,通过 Vuex action 更新登录状态
this.$store.dispatch('login', true);
// 检查是否有重定向查询参数
const redirect = this.$route.query.redirect;
if (redirect) {
// 重定向到之前尝试访问的页面
this.$router.push(redirect);
} else {
// 否则默认导航到 Dashboard
this.$router.push('Dashboard');
}
} catch (error) {
// 处理登录错误
}
},
},
// ...
在这个例子中,beforeEach
守卫检查每个路由是否需要认证(通过 meta.requiresAuth
标识)。如果用户未登录,他们将被重定向到登录页面。登录成功后,通过 Vuex 更新登录状态,并根据需要重定向到之前尝试访问的页面。
请注意,实际的登录逻辑(例如与后端 API 的交互)将取决于你的应用程序的具体需求和后端服务的实现。
18、Call和applay有什么区别?
两者作用一致,都是把obj(即this)绑定到thisObj,这时候thisObj具备了obj的属性和方法。或者说thisObj『继承』了obj的属性和方法。
唯一区别是apply接受的是数组参数,call接受的是连续参数。
call()方法和apply()方法的作用相同,他们的区别在于接收参数的方式不同。对于call(),第一个参数是this值没有变化,变化的是其余参数都直接传递给函数。(在使用call()方法时,传递给函数的参数必须逐个列举出来。使用apply()时,传递给函数的是参数数组)如下代码做出解释:
JavaScript 中,某个函数的参数数量是不固定的,因此要说适用条件的话,当你的参数是明确知道数量时,用 call,而不确定的时候,用 apply,然后把参数 push 进数组传递进去。当参数数量不确定时,函数内部也可以通过 arguments 这个数组来遍历所有的参数。
在JavaScript中,call
和apply
都是Function.prototype
上的方法,它们都可以用来调用一个函数,并显式地设置函数执行时的this
值。但是,它们在参数传递方式上有所不同。
call
call
方法接受一个参数列表,第一个参数是函数执行时的this
值,后面的参数则直接传递给函数。
javascriptfunction greet(greeting, punctuation) {
console.log(greeting + ', ' + this.name + punctuation);
}
const person = { name: 'Alice' };
greet.call(person, 'Hello', '!'); // 输出 "Hello, Alice!"
在上面的例子中,greet
函数通过call
方法被调用,并且this
被设置为person
对象。call
接受两个参数:person
对象和'Hello'
字符串,以及第三个参数'!'
。
apply
apply
方法也接受两个参数,第一个参数同样是函数执行时的this
值,但第二个参数是一个数组或类数组对象,其中的数组元素将作为单独的参数传递给函数。
javascriptfunction greet(greeting, punctuation) {
console.log(greeting + ', ' + this.name + punctuation);
}
const person = { name: 'Alice' };
greet.apply(person, ['Hello', '!']); // 输出 "Hello, Alice!"
在这个例子中,greet
函数通过apply
方法被调用,并且this
被设置为person
对象。apply
接受两个参数:person
对象和一个包含两个元素的数组['Hello', '!']
。这个数组的元素被当作两个单独的参数传递给greet
函数。
区别总结
call
接受一个参数列表,其中第一个参数是this
值,后面跟着要传递给函数的参数。apply
接受两个参数,第一个参数是this
值,第二个参数是一个数组或类数组对象,其中的元素将作为参数传递给函数。
两者都可以用来改变函数的执行上下文(this
的值),但apply
在传递参数时更为灵活,特别是当参数数量不确定或需要以数组形式传递时。
19、闭包是什么?如何实现?
闭包(Closure)是 JavaScript 中的一个重要概念,它是函数和其相关引用环境组合的整体。简单来说,闭包可以使得一个函数内部的变量(包括局部变量、参数、外部函数变量等)在函数执行结束后仍然能够被访问。这是通过词法作用域(lexical scoping)实现的,即函数的作用域在其定义时就已经确定,而不是在其执行时确定。
要实现闭包,你需要满足两个条件:
- 函数嵌套函数:即在一个函数内部定义另一个函数。
- 内部函数引用外部函数的变量:内部函数需要访问外部函数的变量或参数。
下面是一个简单的闭包示例:
javascriptfunction outerFunction(outerVariable) {
return function innerFunction(innerVariable) {
console.log('outerVariable:', outerVariable);
console.log('innerVariable:', innerVariable);
}
}
const newFunction = outerFunction('outside');
newFunction('inside'); // logs: outerVariable: outside, innerVariable: inside
在这个例子中,outerFunction
是一个外部函数,它接受一个参数 outerVariable
。在 outerFunction
内部,我们定义了一个名为 innerFunction
的内部函数,它接受一个参数 innerVariable
。innerFunction
可以访问 outerFunction
的作用域,因此它可以访问 outerVariable
。
当我们调用 outerFunction('outside')
时,它返回 innerFunction
的一个实例,这个实例记住了 outerVariable
的值(即 'outside')。然后,我们可以调用这个返回的函数(我们将其存储在 newFunction
中),并传入一个新的参数 'inside'。当我们这样做时,innerFunction
可以访问并打印出两个变量的值:'outside'(来自外部函数)和 'inside'(来自内部函数)。
20、Vue2.0和vue3.0有什么区别?
Vue2.0和Vue3.0有较大区别,二者区别如下:
- 响应式系统 。Vue3.0使用ES6的Proxy对象重构响应式系统,相比于Vue2.0的Object.defineProperty,Proxy具有更好的性能和更多的功能,如捕捉数组变化、属性重命名等。
- 组件化开发 。Vue3.0通过组合API解决Vue2.0的数据复用、逻辑复杂等问题。组合API是一组函数式的API,提供了更加灵活的代码组织方式和组件复用方式。
- 类型检测 。Vue3.0引入了TypeScript,对Vue应用程序的类型检查提供了更好的支持,可以在开发阶段避免一些类型错误。
- 性能 。Vue3.0在挂载和更新性能上有显著提升,内存使用减少了大约50%
21、Vue常用的指令有哪些?
Vue常用的指令如下12:
- v-once:能执行一次性地插值,当数据改变时,插值处的内容不会更新。
- v-if和v-show:条件渲染指令,v-if是“真正”的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建;v-show就简单得多,不论初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 进行切换。
- v-for:用来遍历数组或对象,并根据每个元素生成相应的DOM元素。
- v-bind:用于动态绑定HTML属性。
- v-on:用于监听DOM事件,并执行相应的JavaScript代码。
- v-model:用于实现双向数据绑定。
- v-text:用于将数据绑定到元素的textContent属性上,可以理解为将数据以文本的形式插入到元素中。
- v-html:用于将数据绑定到元素的innerHTML属性上,可以将数据作为HTML解析并插入到元素中。
22、v-If和v-show有什么区别?
v-if和v-show的区别如下12:
- 实现方式:v-if是“惰性渲染”,在条件为假时,会直接移除对应的组件或元素,减少内存占用;v-show是通过CSS的display属性来控制元素的显示和隐藏。
- 性能:v-if对于频繁切换的元素具有较高的开销,每次切换都需要重新渲染元素及其所有子元素;v-show对于频繁切换的元素具有更好的性能,因为元素的渲染不需要重复进行。
- 适用场景:v-if适用于只有在满足特定条件时才需要渲染的元素,可以节省不必要的DOM元素和子元素的渲染开销;v-show适用于需要频繁切换显示/隐藏状态的元素。
23、v-for为什么要加一个key?
在Vue中,当使用v-for
指令进行列表渲染时,为每个节点添加一个唯一的key
属性是非常重要的。这个key
属性有助于Vue更高效地更新虚拟DOM,并提升渲染性能。
以下是为什么在使用v-for
时需要添加key
的原因:
- 节点识别:在Vue的虚拟DOM中,每个节点都需要一个唯一的标识符,以便Vue能够跟踪和识别每个节点的身份。
key
属性为Vue提供了一个可靠的标识符,使得Vue能够准确地找到每个节点,并在需要时对其进行更新、移动或删除。 - 性能优化:使用
key
可以帮助Vue进行高效的DOM更新。当数据发生变化时,Vue会根据key
的值来判断哪些节点需要更新、哪些节点需要被销毁并重新创建。如果没有key
,Vue会使用一种更简单的策略,即就地复用节点,这可能会导致一些不可预见的问题,如组件状态丢失、事件监听器错误等。 - 列表排序和过滤:当列表数据发生排序或过滤时,
key
可以帮助Vue准确地找到每个节点,并正确地对其进行更新。如果没有key
,Vue可能无法正确识别节点的身份,从而导致渲染错误。
总之,为v-for
列表中的每个节点添加唯一的key
属性是Vue推荐的做法,它有助于确保正确的节点更新和性能优化。在实际开发中,应该始终遵循这个最佳实践。
24、举例封装一个组件的?
封装一个Vue组件通常涉及到创建一个Vue实例,定义其模板、属性、方法和事件。下面是一个简单的Vue组件的封装示例,该组件名为MyButton
,是一个可点击的按钮。
首先,我们创建一个名为MyButton.vue
的文件,该文件将包含组件的完整定义:
vue<template>
<button class="my-button" @click="handleClick">
{{ buttonText }}
</button>
</template>
<script>
export default {
name: 'MyButton',
props: {
buttonText: {
type: String,
required: true,
default: 'Click me'
}
},
methods: {
handleClick() {
this.$emit('click'); // 触发自定义事件
}
}
}
</script>
<style scoped>
.my-button {
padding: 10px 20px;
font-size: 16px;
color: white;
background-color: blue;
border: none;
cursor: pointer;
}
</style>
在这个例子中,我们定义了一个MyButton
组件,它接收一个名为buttonText
的属性,该属性用于显示按钮上的文本。组件还定义了一个handleClick
方法,该方法在按钮被点击时触发,并发出一个名为click
的自定义事件。
MyButton
组件的样式被定义在<style scoped>
标签内,scoped
属性意味着这些样式只会应用于该组件,不会影响其他组件或页面的样式。
现在,我们可以在其他Vue组件或页面中使用这个MyButton
组件:
vue<template>
<div>
<MyButton buttonText="My Custom Button" @click="buttonClicked"></MyButton>
</div>
</template>
<script>
import MyButton from './MyButton.vue';
export default {
components: {
MyButton
},
methods: {
buttonClicked() {
console.log('Button was clicked!');
}
}
}
</script>
25、弹性布局,一行两列,一列固定宽,如何实现?
要实现弹性布局中的一行两列,其中一列固定宽度,你可以使用CSS的Flexbox模型。以下是一个简单的示例:
HTML部分:
html<div class="flex-container">
<div class="flex-item fixed-width">固定宽度列</div>
<div class="flex-item">自适应列</div>
</div>
CSS部分:
css.flex-container {
display: flex;
}
.flex-item {
flex: 1;
}
.fixed-width {
width: 100px; /* 或者你需要的任何固定宽度 */
}
在这个例子中,.flex-container
是弹性容器,它包含两个弹性项 .flex-item
。其中,.fixed-width
类被应用于第一个弹性项,使其具有固定宽度。第二个弹性项则使用 flex: 1
,这意味着它将占据剩余的空间。
26、mixins有几个生命周期阶段?
八个
分别是:beforeCreate、created、beforeMount、mounted、beforeUpdate、updated、beforeDestroy、destroyed1。
mixins生命周期执行顺序为:mixin的beforeCreate、父beforeCreate、mixin的created、父created、mixin的beforeMount、父beforeMount、子beforeCreate、子created、子beforeMount、子mounted、mixin的mounted、父mounted
27、父子组件生命周期执行顺序是怎么样的?
在Vue.js中,父子组件的生命周期执行顺序如下:
- 父组件的beforeCreate和created:首先,父组件的
beforeCreate
和created
生命周期钩子会被调用。这两个钩子在组件实例初始化之后、数据观测(data observer)和事件/监听的配置之前被调用。 - 子组件的beforeCreate和created:然后,子组件的
beforeCreate
和created
生命周期钩子会被调用。 - 父组件的beforeMount:在父组件的
beforeMount
钩子中,模板已经被编译和挂载,但是还没有开始渲染。此时,子组件实例已经被创建,但还没有挂载或渲染。 - 子组件的beforeMount和mounted:接下来,子组件的
beforeMount
和mounted
生命周期钩子会被调用。beforeMount
在挂载开始之前被调用,mounted
在el被新创建的vm.$el替换,并挂载到实例上去之后调用该钩子。 - 父组件的mounted:最后,父组件的
mounted
生命周期钩子会被调用。此时,所有的子组件都已经挂载完成。
如果父组件在mounted
生命周期钩子中进行了某些DOM操作或子组件的更新,那么这些操作或更新可能会影响到子组件的显示或状态。
在组件销毁时,父子组件的生命周期执行顺序是相反的,即首先销毁子组件,然后销毁父组件。具体的销毁钩子函数为beforeDestroy
和destroyed
,它们的调用顺序与上述的生命周期钩子函数相同。
28、ue中普通的生命周期大概有哪些?
UE(Unreal Engine)中普通的生命周期主要有三个阶段,分别是PrePhysics、StartPhysics和DuringPhysics1。
PrePhysics是物理启动的生命周期,主要包括动画、actor等的Tick执行;StartPhysics是启动物理模拟状态StartPhysicsSim;DuringPhysics是物理模拟并行执行的生命周期,一般Game的Tick会等物理模拟执行完之后才算完成一帧,因此可以将一些逻辑移动到During阶段和物理模拟并行执行,提高主线程CPU利用率1。
29、Url到浏览器的一个过程有哪些步骤?
URL到浏览器的一个过程包括以下步骤1:
- 输入网址 。也就是输入要访问的网站网址,俗称URL。
- 缓存解析 。浏览器获取URL后进行解析,先在缓存中查看有无该网址数据,如有则直接从缓存中显示页面,如没有则进行下一步。
- 域名解析 。也就是DNS解析,即域名到IP地址的转换过程。域名的解析工作由DNS服务器完成,解析后可以获取域名相应的IP地址。
- TCP连接 。根据该IP地址建立TCP连接,客户端发送HTTP请求,服务器返回报文,然后关闭TCP连接。
- 页面渲染 。浏览器会将各层的信息发送给GPU,GPU会将各层合成并显示在屏幕上。
30、如何实现小程序的request封装及拦截?
小程序的 request
封装和拦截可以通过自定义一个函数来实现。以下是一个基本的示例,展示了如何在小程序中封装和拦截 request
请求。
首先,你需要创建一个新的 JavaScript 文件,例如 request.js
,然后在这个文件中编写你的封装和拦截逻辑。
javascript// request.js
function request(url, method, data = {}, header = {}) {
return new Promise((resolve, reject) => {
wx.request({
url: url,
method: method,
data: data,
header: header,
success: (res) => {
// 在这里进行拦截处理
if (res.data && res.data.code === 0) { // 假设code为0表示请求成功
resolve(res.data);
} else {
reject(res.data.message || '请求失败');
}
},
fail: (err) => {
reject(err);
}
});
});
}
export default request;
在这个示例中,我们创建了一个 request
函数,它接受四个参数:url
(请求的 URL),method
(请求的 HTTP 方法),data
(发送到服务器的数据),以及 header
(请求的头部信息)。
在 wx.request
的 success
回调中,我们对返回的数据进行了拦截处理。如果返回的数据中的 code
字段为 0,我们认为请求成功,并使用 resolve
函数将数据传递给 Promise 的下一个状态。否则,我们使用 reject
函数返回一个错误消息。
在 wx.request
的 fail
回调中,如果请求失败,我们也会调用 reject
函数来返回一个错误。
接下来,你可以在你的小程序的其他文件中导入并使用这个封装和拦截过的 request
函数。
javascript// some-page.js
import request from './request.js';
Page({
data: {
// ...
},
onLoad: function() {
request('https://api.example.com/data', 'GET').then((data) => {
// 处理请求成功的情况
this.setData({
// ...
});
}).catch((error) => {
// 处理请求失败的情况
console.error(error);
});
},
// ...
});
31、在vue的项目应用中,不使用框架,怎么封装?
在Vue项目中,即使不使用额外的框架,你仍然可以封装组件、工具函数、混合(mixins)、指令等,以提高代码的可重用性和可维护性。下面是一些基本的封装方法:
-
组件封装:
- 全局组件:在
main.js
或app.js
中,使用Vue.component()
方法注册全局组件。 - 局部组件:在需要使用的组件文件中,使用
import
导入组件,然后在components
选项中注册。 - **使用
<script setup>
**:对于简单的组件,可以使用Vue 3的<script setup>
语法,使得组件代码更加简洁。
- 全局组件:在
-
工具函数封装:
- 创建一个
utils
目录,并在其中编写工具函数。 - 使用
export
导出函数,然后在需要的地方使用import
导入。
- 创建一个
-
混合(Mixins):
- 当你需要在多个组件中重用某些代码时,可以使用混合。
- 创建一个混合对象,包含你想要重用的代码,然后使用
Vue.mixin()
全局注册,或者在组件中局部注册。
-
自定义指令:
- 使用
Vue.directive()
全局注册自定义指令,或在组件中局部注册。 - 自定义指令可以用于操作DOM、绑定事件等。
- 使用
-
Vuex状态管理:
- 尽管你不使用额外的框架,但Vuex是Vue官方推荐的状态管理库,可以帮助你管理应用的状态。
- 创建
store
,定义mutations
、actions
和getters
,然后在组件中使用this.$store
或useStore()
(Vue 3 Composition API)来访问状态。
-
路由管理:
- 使用Vue Router进行路由管理。
- 定义路由规则,创建路由实例,并在
main.js
中挂载到Vue实例上。 - 在组件中使用
<router-link>
和<router-view>
进行导航和视图渲染。
-
API封装:
- 创建一个
api
目录,用于封装与后端通信的API。 - 使用
axios
或fetch
等库进行HTTP请求,并处理响应和错误。 - 导出封装好的API函数,在组件中调用。
- 创建一个
这些只是一些基本的封装方法,具体的封装方式取决于你的项目需求和团队规范。封装代码的目的是提高代码的可重用性、可维护性和可读性,因此在实际开发中,你可能需要根据实际情况进行适当的调整和优化。
32、什么是Js原型?原型链是什么?
JavaScript中的原型是指每个构造函数都有一个prototype属性,这个属性是一个指针,指向一个对象,该对象的用途是包含可以由特定类型的所有实例共享的属性和方法
。每当定义一个对象(函数也是对象)时,就会生成一个__proto__属性,这个__proto__属性指向的是这个对象的构造函数的prototype,被称为显式原型。
原型链是当访问一个对象的某个属性时,如果这个对象内部不存在这个属性,那么就会去它的__proto__(也就是它的构造函数的prototype)中查找,如果还没有就继续往上找,直到找到为止,这样一个查找链就叫做原型链。
JavaScript是一门基于原型的语言,在软件设计模式中,有一种模式叫做原型模式,JavaScript正是利用这种模式而被创建出来。原型模式是用于创建重复的对象,同时又能保证性能,这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式
33、组件通讯方式有哪些?
组件通讯的方式有很多种,以下为您推荐:
- 父组件向子组件通讯 。父组件通过属性(props)向子组件传递数据或方法。
- 子组件向父组件通讯 。子组件通过事件($emit)向父组件传递数据或方法。
- 非父子组件、兄弟组件、跨层级组件通讯 。可以使用Vuex进行状态管理,来实现组件之间的通讯。
- provide和inject 。父组件通过provide提供变量,子组件中通过inject注入变量,不论嵌套了几层子组件,都能通过inject来调用provide的数据1。
- children和refs 。在子组件内可以直接通过children对子组件进行操作;在父组件中可以通过children对子组件进行操作;在父组件中可以通过refs获取子组件实例对象,从而直接访问组件的任意数据和方法
34、 js中的遍历方法
也写过比较详细的:
js中的遍历方法_js 折线图 遍历data-CSDN博客文章浏览阅读147次。js 前端遍历方法整理_js 折线图 遍历datahttps://blog.csdn.net/galaxyJING/article/details/129100608?spm=1001.2014.3001.5501
35、Evenbus是什么东西?
EventBus在前端中是一个开源库,由GreenRobot开发而来,用于Android开发的
"事件发布-订阅总线",用来进行模块间通信、解耦1。它可以使用很少的代码,来实现多组件之间的通信1。
EventBus是一个事件总线(Event
Bus),是一种广泛用于软件架构中的设计模式,用于实现解耦和松散耦合的通信机制。它可以帮助组织和管理应用程序中不同组件之间的通信,以提高应用程序的可维护性、可扩展性和灵活性。在事件总线模式中,不同的组件通过订阅和发布事件来进行通信。发布者发布一个事件,订阅者可以订阅该事件并在事件发生时执行相关的操作
36、操作数组的方式有哪些?
之前出过一个很详细很全的:
JavaScript 数组方法整理-CSDN博客文章浏览阅读103次。JavaScript 数组方法整理https://blog.csdn.net/galaxyJING/article/details/129129761?spm=1001.2014.3001.5501
37、0.1 + 0.2 等于 0.3吗?为什么?如何解决?
计算结果为:0.3
所以,0.1 + 0.2 不等于 0.3。这是由于计算机在表示浮点数时存在精度问题。
为了解决这个问题,我们可以使用decimal模块来进行高精度的计算。
使用decimal模块计算的结果为:0.3
所以,使用decimal模块可以准确地计算出0.1 + 0.2 等于 0.3。
38、keep-alive是什么?有哪几个生命周期阶段?
keep-alive是Vue的内置组件,用于缓存组件内部状态,避免重新渲染12。
它是一个抽象组件,自身不会渲染一个DOM元素,也不会出现在组件的父组件链中2。
当组件被keep-alive包裹时,会多出两个生命周期的钩子函数,分别是activated和deactivated。其中activated钩子函数在组件第一次渲染时会被调用,之后在每次缓存组件被激活时也会被调用;而deactivated钩子函数在组件被停用时会被调用。
39、判断一个变量是否是数组,有哪些办法?
在JavaScript中,有多种方法可以用来判断一个变量是否是数组。以下是一些常见的方法:
- **使用
Array.isArray()
**:这是最直接也是最常见的方法。Array.isArray()
方法返回一个布尔值,表示参数是否是数组。
javascriptlet arr = [1, 2, 3];
console.log(Array.isArray(arr)); // 输出:true
- 使用
instanceof
操作符:这个方法检查对象是否是特定类的实例。对于数组,它检查对象是否是Array
的实例。
javascriptlet arr = [1, 2, 3];
console.log(arr instanceof Array); // 输出:true
需要注意的是,instanceof
操作符可能不适用于所有环境,特别是在浏览器中的不同框架或窗口中。
- **使用
Object.prototype.toString.call()
**:Object.prototype.toString.call()
方法返回对象的字符串表示形式,可以通过这个字符串来判断对象是否是数组。
javascriptlet arr = [1, 2, 3];
console.log(Object.prototype.toString.call(arr) === '[object Array]'); // 输出:true
这种方法的好处是它可以准确地判断对象类型,不受对象构造函数的影响。
- 使用
constructor
属性:每个JavaScript对象都有一个constructor
属性,它引用了创建该对象实例的构造函数。对于数组,这个构造函数就是Array
。
javascriptlet arr = [1, 2, 3];
console.log(arr.constructor === Array); // 输出:true
这种方法虽然简单,但可能会受到对象原型链的影响,如果对象的原型链被修改过,那么这个方法可能就不准确了。
40、判断一个变量是否是对象,有哪些办法?
在JavaScript中,你可以使用以下几种方法来判断一个变量是否是对象:
- 使用
typeof
操作符:typeof
操作符返回一个表示变量类型的字符串。对于对象,它将返回"object"
。但是需要注意的是,null
也会被typeof
识别为"object"
,因此这种方法并不完全可靠。
javascriptlet obj = {};
console.log(typeof obj); // 输出:object
- 使用
instanceof
操作符:instanceof
操作符用于检测构造函数的prototype
属性是否出现在对象的原型链中的任何位置。如果对象是由特定构造函数创建的,那么instanceof
会返回true
。
javascriptlet obj = {};
console.log(obj instanceof Object); // 输出:true
- 使用
Object.prototype.toString.call()
方法:Object.prototype.toString.call()
方法返回一个表示对象的字符串,这个字符串的格式是[object Type]
,其中Type
是对象的类型。
javascriptlet obj = {};
console.log(Object.prototype.toString.call(obj) === '[object Object]'); // 输出:true
- 使用
constructor
属性:每个JavaScript对象都有一个constructor
属性,它引用了创建该对象实例的构造函数。如果constructor
属性是Object
,那么该变量可能是一个对象。
javascriptlet obj = {};
console.log(obj.constructor === Object); // 输出:true
但是,需要注意的是,constructor
属性可以被修改,因此它不是一个完全可靠的方法。
- 使用
Array.isArray()
方法:虽然这不是直接用来检测一个变量是否是对象的,但是如果你想要确保变量不是数组,你可以使用!Array.isArray(variable)
。
javascriptlet obj = {};
console.log(!Array.isArray(obj)); // 输出:true
- 使用
Object.prototype.isPrototypeOf()
方法:如果对象原型链的某个位置存在指定的原型对象,则isPrototypeOf()
方法返回true
。
javascriptlet obj = {};
console.log(Object.prototype.isPrototypeOf(obj)); // 输出:true
41、对象常用方法有哪些?
对象的常用方法有很多,以下列举了一些常见的方法:
-
**toString()**:返回表示该对象的字符串。默认情况下,它会返回一个类型字符串,但对于一些内置对象(如数组、函数等),可以覆盖该方法以返回自定义的字符串表示。
-
**valueOf()**:返回该对象的原始值。对于原始值(如数字、字符串、布尔值),valueOf()通常返回对象本身;对于对象,该方法可以被覆盖以返回特定的原始值。
-
**hasOwnProperty()**:返回一个布尔值,指示对象自身(不包括原型链)是否具有指定的属性。
-
**isPrototypeOf()**:返回一个布尔值,指示该对象是否是另一个对象的原型。
-
**propertyIsEnumerable()**:返回一个布尔值,指示对象的指定属性是否可以通过for...in循环或Object.keys()方法枚举1。
-
constructor:指向创建该对象实例的构造函数。
除了以上方法外,对象还有许多其他方法,如Object.assign()、Object.create()、Object.defineProperty()、Object.keys()、Object.values()等。这些方法可以用于操作对象的属性、复制对象、定义新对象等
42、创建一个空数组/空对象有哪些方式?
在JavaScript中,创建空数组和空对象有多种方式。以下是创建它们的一些常见方法:
创建空数组
-
使用数组字面量:
javascript
let emptyArray = [];
-
使用
Array
构造函数:javascript
let emptyArray = new Array();
-
使用
Array.of()
方法(尽管这通常用于创建一个具有一个或多个元素的数组,但也可以用来创建空数组):javascript
let emptyArray = Array.of();
创建空对象
-
使用对象字面量:
javascript
let emptyObject = {};
-
使用
Object
构造函数:javascript
let emptyObject = new Object();
-
使用
Object.create()
方法(这个方法创建一个新对象,使用现有的对象作为新创建的对象的__proto__
):javascript
let emptyObject = Object.create(null); // 创建一个没有原型(即 null 为其原型)的空对象
-
使用ES6的
Object.assign()
方法(尽管这个方法通常用于合并对象,但也可以用来创建空对象):javascript
let emptyObject = Object.assign({});
43、哪些遍历方式会改变原数组?
会改变原数组的遍历方式有:pop()、push()、shift()、unshift()1。
pop()方法用于删除数组的最后一个元素并返回删除的元素。push()方法可向数组的末尾添加一个或多个元素,并返回新的长度。shift()方法用于把数组的第一个元素从其中删除,并返回第一个元素的值。unshift()方法可向数组的开头添加一个或更多元素。
44、Set和Map各是什么?
Set和Map是ES6中提供的两种新的数据结构,具体如下12:
- Set:是一种叫做集合的数据结构,它类似于数组,但成员的值都是唯一的,没有重复的值。Set本身是一个构造函数,用来生成Set数据结构。
- Map:是一种叫做字典的数据结构,它是以[key,value]的形式储存,可以储存不重复的值。
45、介绍一下promise。
Promise是ES6引入的,进行异步编程的一种新的解决方案1。
Promise对象用来封装一个异步操作并可以获取其成功或失败的结果值。Promise可以解决JavaScript中回调地狱(Callback Hell)问题,使异步代码更易于编写和管理。Promise对象有三种状态,分别是:Pending(进行中)、Fulfilled(已成功)、Rejected(已失败
46、Promise通常会解决三种问题
(1)链式回调
(2)同时发起几个异步请求,谁先有结果就拿谁的
(3)发起多个请求,等到所有请求后再做下一步处理
这三种方式promise是怎么处理的?
Promise确实能够优雅地解决上述三种常见的异步编程问题。以下是Promise如何针对每种问题进行处理:
(1) 链式回调 (回调地狱 Callback Hell)
Promise通过链式调用的.then()
方法解决了回调地狱的问题。每个.then()
方法返回一个新的Promise,这使得我们可以将多个异步操作串联起来,每个.then()
处理上一个Promise的结果,并返回一个新的Promise,从而形成链式调用。这样做不仅使得代码更易读,也减少了嵌套层级。
javascriptdoSomething()
.then(result => doSomethingElse(result))
.then(newResult => doThirdThing(newResult))
.then(finalResult => {
console.log('Got the final result: ' + finalResult);
})
.catch(failureCallback);
(2) 同时发起几个异步请求,谁先有结果就拿谁的
Promise.race()方法可以解决这个问题。它接受一个Promise对象的数组作为参数,只要数组中有一个实例率先改变状态,Promise.race()
的返回值就会改变状态。这使得我们可以同时发起多个异步请求,并处理最先返回结果的那个。
javascriptPromise.race([
fetch('/api/resource1'),
fetch('/api/resource2')
]).then(response => {
// 处理最先返回的响应
}).catch(error => {
// 处理错误
});
(3) 发起多个请求,等到所有请求后再做下一步处理
Promise.all()方法用于处理这种情况。这个方法接受一个Promise对象的数组作为参数,只有当所有Promise都成功时,它才会返回一个新的已解决的Promise。这意味着我们可以发起多个异步请求,并等待所有请求都完成后才进行下一步操作。
javascriptPromise.all([
fetch('/api/resource1'),
fetch('/api/resource2'),
fetch('/api/resource3')
]).then(responses => {
// 所有请求都已完成,responses是一个包含所有响应的数组
const results = responses.map(response => response.json());
// 进一步处理results
}).catch(errors => {
// 如果任何一个请求失败,errors会包含失败的原因
});
Promise.all()还可以与Promise.allSettled()结合使用,后者会等待所有Promise都完成(无论是fulfilled还是rejected),然后返回一个数组,其中包含每个Promise的结果。
通过Promise的链式调用、Promise.race()
和Promise.all()
(或Promise.allSettled()
),我们可以更优雅地处理异步编程中的复杂逻辑。
47、如何改变一个函数a的上下文?
在JavaScript中,一个函数的上下文通常指的是this
的值。this
的值在函数被调用时确定,取决于函数的调用方式。你可以通过几种不同的方法来改变一个函数的上下文,也就是改变this
的值。
- 函数调用模式:在普通的函数调用中,
this
通常指向全局对象(在浏览器中是window
)。
javascriptfunction a() {
console.log(this);
}
a(); // 输出:Window
- 方法调用模式:当一个函数作为一个对象的方法被调用时,
this
指向该对象。
javascriptlet obj = {
a: function() {
console.log(this);
}
};
obj.a(); // 输出:obj
- 构造函数调用模式:当一个函数被当作构造函数使用(即使用
new
关键字)时,this
指向新创建的对象。
javascriptfunction A() {
this.value = 42;
}
let a = new A();
console.log(a.value); // 输出:42
call
和apply
方法:你可以使用call
或apply
方法来显式地设置this
的值。这两个方法都接受一个对象作为第一个参数,该对象将成为函数中的this
。call
方法接受额外的参数,这些参数将作为函数的参数传入。apply
方法接受一个数组或类数组对象作为第二个参数,该数组的元素将作为函数的参数传入。
javascriptfunction a(x, y) {
console.log(this.name + ' ' + x + ' ' + y);
}
let obj = {name: 'John'};
a.call(obj, 'Hello', 'World'); // 输出:John Hello World
- 箭头函数:箭头函数不绑定自己的
this
,它继承自外围作用域。因此,你不能通过call
、apply
或bind
来改变箭头函数的this
。
javascriptlet obj = {
value: 42,
a: function() {
let arrowFunc = () => {
console.log(this.value);
};
arrowFunc();
}
};
obj.a()