前端面试准备(Day4)

17.谈谈对promise理解。

1)promise是为解决异步处理回调金字塔问题而产生的

2)两个特点:Promise状态不受外界影响,(pending,fulfilled,rejuected);Promise状态一旦改变,就不会再变

3)三个缺点:

1.无法取消Promise,一旦新建它就会立即执行,无法中途取消;

2.如果不设置回调函数,Promise内部抛出的错误,不会反映到外部
3.当处于pending状态时,无法得知目前进展到哪一个阶段,是刚刚开始还是即将完成

4)Promise在哪存放成功回调序列和失败回调序列?

    1.onResolvedCallbacks 成功后要执行的回调序列 是一个数组

2.onRejectedCallbacks 失败后要执行的回调序列 是一个数组

5)手写promise

class Promise{

    constructor(executor){

        this.state = 'pending';

        this.value = undefined;

        this.reason = undefined;

        // 成功回调函数数组和失败回调函数数组

        this.onResolveCallbacks = [];

        this.onRejectedCallbacks = [];

        let resolve = value => {

            if(this.state === 'pending'){

                this.state = 'fulfilled';

                this.value = value;

                // 成功的话遍历成功回调函数数组然后执行这些函数

                this.onResolvedCallbacks.forEach(fn=>fn());

            }

        };

        let reject = reason => {

            if(this.state === 'pending'){

                this.state = 'rejected';

                this.reason = reason;

                // 失败的话遍历失败回调函数数组然后执行这些函数

                this.onRejectedCallbacks.forEach(fn=>fn());

            }

        };

        try{

            executor(resolve,reject)

        }catch(err){

            reject(err);

        }

       

    }

}

Promise.prototype.then=function(onFulfilled, onRejected){

    if(this.state === 'fulfilled'){

        onFulfilled(this.value);

    }

    if(this.state === 'rejected'){

        onRejected(this.reason);

    }

    // 当状态为等待态时,我们要将成功/失败的回调函数加入到对应的数组中

    if(this.state === 'pending'){

        // onFulfilled传入到成功数组

        this.onResolvedCallbacks.push(()=>{

            onFulfilled(this.value);

        })

        // onRejected传入到失败数组

        this.onRejectedCallbacks.push(()=>{

            onRejeced(this.reason);

        })

    }

}

6)手写Promise.all方法(返回一个promise对象)

Promise2.all = function(arrP) {

  let list = []

  let len = 0

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

    for(let i = 0; i < arrP.length; i++) {

      arrP[i].then( data=> {

        list[i] = data

        len++

        if(len === arrP.length) resolve(list)

      }, error => {

        reject(error)

      })

    }

  })

}

7)手写PromiseRace

// Promise.race

Promise.race=function(promiseArr){

    function isPromise(val){

        if(!val && typeof val==='object'|| typeof val==='function'){

            if(typeof val.then==='function'){

                return true

            }

        } else{

            return false

        }

    }

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

        for(let i=0;i<promiseArr.length;i++){

            let value=promiseArr[i];

            if(value&&isPromise(value)){

                value.then(v=>resolve(v)).catch(e=>reject(e))

            }else{

                resolve(value)

            }

        }

    })

}

18.Typescript中type和interface的区别是什么?

1)接口是通过继承的方式来扩展,类型别名是通过 & 来扩展。

2)接口可以自动合并,而类型别名不行

19.柯里化是什么?有什么用?怎么实现?

1)是什么:把一个多参数的函数转化为单参数函数的方法。

2)作用:

 1.惰性求值 (Lazy Evaluation):柯里化收的函数是分步执行的,第一次调用返回的是一个函数,第二次调用的时候才会进行计算。起到延时计算的作用,通过延时计算求值,称之为惰性求值。

function sum(a){

    return function(b){

        return a+b

    }

}

2.动态生成函数

function power(n){

    return function (number){

        let result = 1;

        for(let i = 0; i < n; ++i){

            result *= number;

        }

        return result;

    }

}

20.CmmonJS和ESM区别?

1)CommonJs: module.exports= {} 导出; require导入

1.CommonJs可以动态加载语句,代码发生在运行时

2.CommonJs混合导出如果使用exports导出单个值之后,就不能在导出一个对象值,3.CommonJs导出值是拷贝,可以修改导出的值,这在代码出错时,不好排查引起变量污染

2)Es Module: export default导出,import导入

1.Es Module是静态的,不可以动态加载语句,只能声明在该文件的最顶部,代码发生在编译时

2.Es Module混合导出,单个导出,默认导出,完全互不影响

3.Es Module导出和引用值之前都存在映射关系,并且值都是可读的,不能修改

20.js垃圾回收机制。

js内存分为栈内存和堆内存,栈JavaScript用于存储静态数据,静态数据是引擎在编译时知道大小的数据。这包括指向对象和函数的引用和原始值(strings, numbers, booleans, undefined 和 null);由于JS引擎知道大小不会变,所以它们将为每个值分配固定大小的内存。堆是用于存储JavaScript对象和函数。与栈不同,JS引擎不会为这些对象分配固定大小的内存空间。栈包含对堆中对象数据的引用。

栈垃圾回收的方式非常简便,当一个函数执行结束之后,JavaScript 引擎销毁该函数保存在栈中的执行上下文。当函数直接结束,栈空间处理完成了,但是堆空间的数据还是存储在堆空间中,需要垃圾回收器将堆空间中的垃圾数据回收。在 V8 中会把堆分为新生代和老生代两个区域,新生代中存放的是生存时间短的对象,老生代中存放的生存时间久的对象。 新生区中使用Scavenge算法,老生区中使用标记-清除算法和标记-整理算法。

 Scavenge算法: 1. 标记:对对象区域中的垃圾进行标记 2. 清除垃圾数据和整理碎片化内存:副垃圾回收器会把这些存活的对象复制到空闲区域中,并且有序的排列起来,复制后空闲区域就没有内存碎片了 3. 角色翻转:完成复制后,对象区域与空闲区域进行角色翻转,也就是原来的对象区域变成空闲区域,原来的空闲区域变成了对象区域

标记-清除算法: 1. 标记:标记阶段就是从一组根元素开始,递归遍历这组根元素,在这个遍历过程中,能到达的元素称为活动对象,没有到达的元素就可以判断为垃圾数据。 2. 清除:将垃圾数据进行清除。 3. 产生内存碎片:对一块内存多次执行标记 - 清除算法后,会产生大量不连续的内存碎片。而碎片过多会导致大对象无法分配到足够的连续内存。

标记-整理算法 1. 标记:和标记 - 清除的标记过程一样,从一组根元素开始,递归遍历这组根元素,在这个遍历过程中,能到达的元素标记为活动对象。 2. 整理:让所有存活的对象都向内存的一端移动 3. 清除:清理掉端边界以外的

21.实现一个发布订阅。

// 定义事件中心类

class MyEvent {

  handlers = {} // 存放事件 map,发布者,存放订阅者

// 绑定事件

  $on(type, fn) {

    if (!Reflect.has(this.handlers, type)) { // 如果没有定义过该事件,初始化该订阅者列表,Reflect.has检查一个对象是否含有某个属性

      this.handlers[type] = []

    }

    this.handlers[type].push(fn) // 存放订阅的消息

  }

// 触发事件

  $emit(type, ...params) {

    if (!Reflect.has(this.handlers, type)) { // 如果没有该事件,抛出错误

      throw new Error(`未注册该事件${type}`)

    }

    this.handlers[type].forEach((fn) => { // 循环事件列表,执行每一个事件,相当于向订阅者发送消息

      fn(...params)

    })

  }

// 取消订阅

  $remove(type, fn) {

    if (!Reflect.has(this.handlers, type)) {

      throw new Error(`无效事件${type}`)

    }

    if (!fn) { // 如果没有传入方法,表示需要将该该类型的所有消息取消订阅

      return Reflect.deleteProperty(this.handlers, type)

    } else {

      const inx = this.handlers[type].findIndex((handler) => handler === fn)

      if (inx === -1) { // 如果该事件不在事件列表中,则抛出错误

        throw new Error('无效事件')

      }

      this.handlers[type].splice(inx, 1) // 从事件列表中删除该事件

      if (!this.handlers[type].length) { // 如果该类事件列表中没有事件了,则删除该类事件

        return Reflect.deleteProperty(this.handlers, type)

      }

    }

  }

}

22. EventLoop 事件循环

JS是单线程的,为了防止一个函数执行时间过长阻塞后面的代码,所以会先将同步代码压入执行栈中,依次执行,将异步代码推入异步队列,异步队列又分为宏任务队列和微任务队列,因为宏任务队列的执行时间较长,所以微任务队列要优先于宏任务队列。

宏任务:setTimeout setInterval I/O UI渲染 setimmediate

微任务:process.nextick promise MutaionObserver

process.nextick会再下次事件循环的开始前被调用;而setTimeout 和 setInterval是在下一次事件循环队列中

23.浏览器进程

  

image-20200320151227013

GUI线程就是渲染页面的,他解析HTML和CSS,然后将他们构建成DOM树和渲染树就是这个线程负责的。

JS引擎线程就是负责执行JS的主线程,前面说的"JS是单线程的"就是指的这个线程。的Chrome V8引擎就是在这个线程运行的。需要注意的是,这个线程跟GUI线程是互斥的。

23.如何判断元素是否在视口

  1) offsetTop, scrollTop

offsetTop当前元素顶端距离父元素顶端距离,、scrollTop当前元素顶端距离窗口顶端距离 el.offsetTop - e.scrollTop <= viewPortHeight

2)getBoundingClientRect()

bottom: height: left: right: top: width:

top 大于等于 0 left 大于等于 0

bottom 小于等于视窗高度 right 小于等于视窗宽度

3)IntersectionObserver

  是以new的形式声明一个对象,接收两个参数callbackoptions

callback是添加监听后,当监听目标发生滚动变化时触发的回调函数。接收一个参数entries,即IntersectionObserverEntry实例

  options是一个对象:配置 root  rootMargin threshold

24.for 0f 和for in

1)for..of适用遍历数/数组对象/字符串/map/set等拥有迭代器对象的集合.但是不能遍历对象,因为没有迭代器对象.与forEach()不同的是,它可以正确响应break、continue和return语句 ,for-of循环不支持普通对象

2)for-in可以迭代一个对象的属性,使用for in会遍历数组所有的可枚举属性,包括原型,

25.map,weakmap,set,weakset

介绍下 Set、Map、WeakSet 和 WeakMap - 掘金 (juejin.cn)

map,weakmap,set,weakset ,ES6 新增的一种新的数据结构,Set 是一种叫做集合的数据结构,Map 是一种叫做字典的数据结构

1)Set类似于数组,但成员是唯一且无序的,没有重复的值。常用于数组去重

let set1 = new Set([1, 2, 3])

let set2 = new Set([4, 3, 2])

交集(Union):let intersect = new Set([...set1].filter(value => set2.has(value)))

并集(Union):let union = new Set([...set1, ...set2])

差集(Difference):let difference = new Set([...set1].filter(value => !set2.has(value)))

操作方法

add(value):新增,相当于 array里的push

delete(value):存在即删除集合中value

has(value):判断集合中是否存在 value

clear():清空集合

遍历方法(遍历顺序为插入顺序)

keys():返回一个包含集合中所有键的迭代器

values():返回一个包含集合中所有值得迭代器

entries():返回一个包含Set对象中所有元素得键值对迭代器

forEach(callbackFn, thisArg):用于对集合成员执行callbackFn操作,如果提供了 thisArg 参数,回调中的this会是这个参数,没有返回值

2)WeakSet 结构与 Set 类似,也是不重复的值的集合。 WeakSet 能储存对象引用,不能存放值。值都是被弱引用的,即垃圾回收机制不考虑 WeakSet 对该对象的应用,如果没有其他的变量或属性引用这个对象值,则这个对象将会被垃圾回收掉(不考虑该对象还存在于 WeakSet 中),所以,WeakSet 对象里有多少个成员元素,取决于垃圾回收机制有没有运行,运行前后成员个数可能不一致,ES6规定 WeakSet 对象是无法被遍历的

3)JavaScript 的对象(Object),本质上是键值对的集合(Hash 结构),但是传统上只能用字符串当作键。这给它的使用带来了很大的限制。ES6 提供了 Map 数据结构,类似于对象,也是键值对的集合,但是 “键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。Map 的键实际上是跟内存地址绑定的

4)WeakMap结构与Map结构类似,也是用于生成键值对的集合,WeakMap只接受对象作为键名(null除外),不接受其他类型的值作为键名,WeakMap的键名所指向的对象,不计入垃圾回收机制。,一是没有遍历操作(即没有keys()、values()和entries()方法),也没有size属性WeakMap只有四个方法可用:get()、set()、has()、delete()

26.Gnerators

ES6 新增了 Generator 函数,可以通过 yield 关键字,把函数的执行流挂起,从而可以改变执行流程,或者可以暂停或者延迟执行。有人说他是懒惰的迭代器。使用 Generator 函数和一般函数一样,是在函数名后面加上 (),但是 Generator 函数不会像普通函数那样马上执行,而是会返回一个指针,这个指针指向对象的内部状态,所以要调用遍历器对象 Iterator 的 next 方法,指针就会从函数头部或者上一次停下来的地方开始执行。为不具备 Iterator 接口的对象提供遍历方法。

27.async和await

 Promise,用then链来解决多层回调问题,但是来连续调用then,会使得代码很冗长,代码的可读性不好,所以在ES7中就提出了async和await来解决这一问题,他其实就是promise的语法糖。async 会将其后的函数(函数表达式或 Lambda)的返回值封装成一个 Promise 对象,而 await 会等待这个 Promise 完成,并将其结果返回出来。

28.事件冒泡

   DOM事件流(event  flow )存在三个阶段:事件捕获阶段、处于目标阶段、事件冒泡阶段。

事件捕获event  capturing当鼠标点击或者触发dom事件时,浏览器会从根节点开始由外到内进行事件传播。即点击了子元素,如果父元素对应的事件的话,会先触发父元素绑定的事件。

事件冒泡dubbed  bubbling鼠标点击或者触发dom事件时,浏览器会从当前节点是由内到外进行事件传播,直到根节点。

29.事件委托

  事件委托就是把原本需要绑定在子元素上的事件(onclickonkeydown 等)委托给它的父元素,让父元素来监听子元素的冒泡事件,并在子元素发生事件冒泡时找到这个子元素。

30.JS类型转换

显示转换

Number()pasreInt() pasreFloat()

String() String() toString()

Boolean()

隐式转换

31.01.+02为什么不等于0.3

因为浮点数运算的精度问题。在计算机运行过程中,需要将数据转化成二进制,然后再进行计算。小数转化为IEEE754的过程:先转化为二进制的形式,然后再用科学计数法表示,接着把通过科学计数法表示的二进制数用IEEE754标准表示。js中的Number类型遵循IEEE754标准,在IEEE754标准的64位浮点数相加,因为浮点数自身小数位数的限制而截断的二进制在转化为十进制,就变成0.30000000000000004,所以在计算时会产生误差。

32.{},new object ,object.creat的区别

字面量和new关键字创建的对象是Object的实例,原型指向Object.prototype,继承内置对象Object

Object.create(arg, pro)创建的对象的原型取决于argargnull,新对象是空对象,没有原型,不继承任何对象;arg为指定对象,新对象的原型指向指定对象,继承指定对象

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值