前端面试题汇总(理论篇三)

Dom事件流的顺序?什么是事件委托?

当页面上的一个元素被点击时,先从document向下一层层捕获到该元素。然后再向上冒泡,一层层触发。

事件委托是将事件写在父级元素上,e.target是事件捕获时那个最小的元素,即选中的元素。所以可以根据e.target操作选中的元素。这样不需要给每个子元素绑定事件,代码更加简约。

事件代理/事件委托

  1. js中事件冒泡我们知道,子元素身上的事件会冒泡到父元素身上。
  2. 事件代理就是,本来应该加在子元素身上的事件,我们却把事件加在了其父级身上。
  3. 那就产生了问题:父级那么多子元素,怎么区分事件本应该是哪个子元素的?
  4. 答案是:event对象里记录的有“事件源”,它就是发生事件的子元素。
  5. 它存在兼容性问题,在老的IE下,事件源是 window.event.srcElement,其他浏览器是 event.target
  6. 用事件委托有什么好处呢?
  7. 第一个好处是效率高,比如,不用for循环为子元素添加事件了
  8. 第二个好处是,js新生成的子元素也不用新为其添加事件了,程序逻辑上比较方便

闭包

闭包就是指有权访问另一个函数作用域中的变量的函数
MDN 上面这么说:闭包是一种特殊的对象。
闭包的作用域链包含着它自己的作用域,以及包含它的函数的作用域和全局作用域。闭包的注意事项
通常,函数的作用域及其所有变量都会在函数执行结束后被销毁。但是,在创建了一个闭包以后,这个函数的作用域就会一直保存到闭包不存在为止。
我们首先知道闭包有3个特性


①函数嵌套函数
②函数内部可以引用函数外部的参数和变量
③参数和变量不会被垃圾回收机制回收

优点:

①保护函数内的变量安全 ,实现封装,防止变量流入其他环境发生命名冲突
②在内存中维持一个变量,可以做缓存(但使用多了同时也是一项缺点,消耗内存)
③匿名自执行函数可以减少内存消耗
闭包的缺点就是常驻内存会增大内存使用量,并且使用不当很容易造成内存泄露。
如果不是因为某些特殊任务而需要闭包,在没有必要的情况下,在其它函数中创建函数是不明智的,因为闭包对脚本性能具有负面影响,包括处理速度和内存消耗。

深拷贝和浅拷贝

拷贝的层级不同,深拷贝是指每一层数据的改动都不会影响原对象和新对象,浅拷贝只有第一层的属性变动不互相影响,深层的数据变动还会互相影响。

浅拷贝:Object.assign
深拷贝:JSON.stringify和JSON.parse
2、JSON的stringify和parse处理的缺点?

如果对象中有属性是function或者undefined,处理后会被过滤掉;
如果属性值是对象,且由构造函数生成的实例对象,会丢弃对象的constructor;
3、$.extend()

使用jquey的extend方法不仅能实现深度拷贝,还能实现深度合并。具体用法

深度拷贝:$.extend({},targetObject) // targetObject是需要复制的对象

深度合并:$.extend(true,{},targetObject1,targetObject2) // 可以将两个对象深度合并后再返回出一个新对象

diff算法

diff算法可以看做是一种对比算法,对比对象的新旧虚拟DOM。顾名思义,diff算法可找到新旧虚拟DOM之间的差异,但diff算法不仅是对你新旧虚拟DOM,还根据对比的结果更新到真实DOM.

key的作用?key缺少或者重复问题会出现什么问题?

1、key 的作用主要是 更高效的对比虚拟DOM中的某个节点是否是相同节点,实现高效的更新虚拟 DOM,提高性能。

2、Vue在patch过程中判断两个节点是否是相同节点key是一个必要条件,渲染一组列表时,key往往是唯一标识,所以如果不定义key的话,Vue只能认为比较的两个节点是同一个,哪怕它们实际上不是,这导致了频繁更新元素,使得整个patch过程比较低效,影响性能。

3、实际使用中在渲染一组列表时key必须设置,而且必须是唯一标识,应该避免使用数组索引作为key,这可能导致一些隐蔽的bug;Vue中在使用相同标签元素过渡切换时,也会使用key属性,其目的也是为了让Vue可以区分它们,否则Vue只会替换其内部属性而不会触发过渡效果。

4、从源码中可以知道,Vue判断两个节点是否相同时主要判断两者的key和元素类型等,因此如果不设置,它的值就是是undefined,则可能永远认为这是两个相同节点,只能去做更新操作,这造成了大量的DOM更新操作,明显是不可取的。

重复的 key 会造成渲染错误

Vue 中 key 的作用

key 的特殊属性主要用在 Vue 的虚拟 DOM 算法,在新旧 nodes 对比时辨识 VNodes。如果不使用 key,Vue 会使用一种最大限度减少动态元素并且尽可能的尝试修复/再利用相同类型元素的算法。使用 key,它会基于 key 的变化重新排列元素顺序,并且会移除 key 不存在的元素。

有相同父元素的子元素必须有独特的 key。重复的 key 会造成渲染错误。

向数组某个位置中插入一个元素

  1. 不使用key:
    数据插入后,后面的不同的所有数据都要替换,最后还需要插入一次数据
  2. 使用key:
    相比较,如果值相同,不做任何操作,只做一次插入操作

如果没有key,比较新老标签相同,标签会相同,则会判断为相同,就会替换强制更新

闭包是什么?利弊?如何解决弊端?

闭包是什么:JS中内层函数可以访问外层函数的变量,外层函数无法操作内存函数的变量的特性。我们把这个特性称作闭包。

闭包的好处:

隔离作用域,保护私有变量;有了闭包才有局部变量,要不然都是全局变量了。
让我们可以使用回调,操作其他函数内部;
变量长期驻扎在内存中,不会被内存回收机制回收,即延长变量的生命周期;
闭包的弊端:内层函数引用外层函数变量,内层函数占用内存。如果不释放内存,过多时,易引起内存泄露。

解决办法:无法自动销户,就及时手动回收,使用后将函数的引用赋null。

es6新特性

1、let 和 const
let 表示申明变量。const 表示申明常量
常量定义了就不能改了。对象除外,因为对象指向的地址没变。
const在申明是必须被赋值。
两者都为块级作用域。
2、模板字符串
3、解构
4、函数的默认值
5、Spread / Rest 操作符,三个点…
6、箭头函数
7、for of
for of遍历的是键值对中的值
for in遍历的是键值对中的键
8、class类,原型链的语法糖表现形式
9、导入导出
导入improt
导出export default
10、promise
Promise 用于更优雅地处理异步请求。
11、async/await
比promise更好的解决了回调地狱
12、Symbol,新的基本类型
13、Set集合
存储任何类型的唯一值,即集合中所保存的元素是不重复的。类数组结构。
let arrNew = new Set(待去重的数组)

 对原型链的认识 

js通过原型链模拟实现面向对象,比如通过实例化一个构造函数可以给每个对象挂载自己专属的属性,通过给类的prototype上赋方法是所有对象所共有的方法。每次实例化不再赋值原型链上的方法

  1. this
     

this 指的是调用函数的那个对象
this 在没有运行之前不能知道代表谁;js的this 指向是不确定的;
和定义没有关系,和执行有关.执行的时候,点前面是谁,this 就是谁;
自执行函数里面的this 代表的是 window
定时器书写的时候,window可以省略掉;定时器执行的时候,里面的this 代表的也是 window ;
this 是js的一个关键字,随着函数使用场合不同,this 的值会发生变化。
如果有new关键字,this指向new出来的对象
在事件中,this指向触发这个事件的对象

js中new操作符主要干了什么

创建一个空对象,并且this变量引用该对象,同事还继承了该函数的原型
属性和方法被加入到this引用的对象中
新创建的对象this所引用,并且最后隐式返回this

什么是作用域

概念:代码(变量)可以使用的范围就是作用域。主要是为了提高程序的可靠性,也是为了减少命名冲突
全局作用域和局部作用域
全局作用域:指的是整个js文件,定义在全局中的全局变量,可以在局部作用域中使用,函数内部没有声明直接赋值的变量也叫全局变量
局部作用域:主要指的是函数作用域,函数内部也就是局部作用域
在函数内部var定义的变量-,叫做局部变量,局部变量无法被外部获取

什么是事件冒泡?怎么阻止事件冒泡?

概念:当我们点击子元素触发父元素的事件,这种现象,我们叫做事件冒泡,即由子元素向祖先元素传播,就像气泡从水底上浮
event.stopPropagation();阻止事件冒泡

 Dom事件流的顺序?什么是事件委托?

当页面上的一个元素被点击时,先从document向下一层层捕获到该元素。然后再向上冒泡,一层层触发。

事件委托是将事件写在父级元素上,e.target是事件捕获时那个最小的元素,即选中的元素。所以可以根据e.target操作选中的元素。这样不需要给每个子元素绑定事件,代码更加简约。

事件委托原理: 不要给每个子节点单独设置事件监听器,而是事件监听设置在其父节点上,然后利用冒泡原理影响设置每个子节点

async、await

async 是一个通过异步执行并隐式返回 Promise 作为结果的函数。是Generator函数的语法糖,并对Generator函数进行了改进。

改进:

  • 内置执行器,无需手动执行 next() 方法。
  • 更好的语义
  • 更广的适用性:co模块约定,yield命令后面只能是 Thunk 函数或 Promise 对象,而async函数的await命令后面,可以是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时会自动转成立即 resolved 的 Promise 对象)。
  • 返回值是 Promise,比 Generator 函数返回的 Iterator 对象方便,可以直接使用 then() 方法进行调用。
  • async 隐式返回 Promise 作为结果的函数,那么可以简单理解为,await后面的函数执行完毕时,await会产生一个微任务(Promise.then是微任务)。

async await实现原理:

当调用一个 async 函数时,会返回一个 Promise 对象 (关键)
async 函数中可能会有 await 表达式,await表达式 会使 async 函数暂停执行,直到表达式中的Promise解析完成后继续执行 async 中 await 后面的代码并返回解决结果。
既然返回的是Promise 对象,所以在最外层不能直接获取其返回值,那么肯定可以用原来的方式:then() 链来处理这个 Promise 对象
原理:
async/await 函数其实就是一种语法糖
async/await 是基于promise实现的,async 函数其实就是把 promise 做了一个包装
await 返回值是一个 Promise 对象,它只是把 await 后面的代码放到了 Promise.then()

async await 和 promise 的区别
async/await 出现的异常是无法捕获的,需要借助 try/catch 来捕获异常
任何一个await后面的promise对象变为reject,那么整个async都会被中断
使用 async await 的话,catch 能处理 JSON.parse 错误
 

// 当调用一个 async 函数时,会返回一个 Promise 对象 (关键)
// async/await 出现的异常是无法捕获的,需要借助 try/catch 来捕获异常
function sleep(flag) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            if(flag){
                resolve('success')
            }else{
                reject('Error')
            }
        }, 2000)
    })
}

// async await 的用法
async function fn(flag) {
    try {
        let result = await sleep(flag)
        return result
    } catch (err) {
        return err
    }
}
// 返回的 a,b 是一个 promise 对象
var a = fn(true)
var b = fn(false)
a.then((res)=>{
    console.log(res) // success
})
b.then((res)=>{
    console.log(res) // Error
})

猜你喜欢

转载自blog.csdn.net/Holly31/article/details/130740903