壹题(刷题记录)

1.写Vue/React项目时为什么要在列表组件中写key,其作用是什么?

        vue和react都是采用diff算法来对比新旧虚拟节点,从而更新节点。因此key的作用是为了在diff算法执行时更快的找到对应的节点,提高diff速度。

2.['1', '2', '3'].map(parseInt) what & why ? 

       parseInt(string, radix)解析一个字符串,并返回一个整数。radix表示为解析的字符串基数。2~36之间,如果省略或者为0将以10位基础来解析,如果该参数小于2或者大于36则返回NaN。

此题实际执行代码为:

                ['1', '2', '3'].map((item, index) => {
	                return parseInt(item, index)
                })

即返回结果是[1,NaN,NaN]

3.什么是防抖和节流?有什么区别?如何实现?

        防抖:触发高频事件后n秒内函数只会执行一次,如果是n秒内高频事件再次触发,则重新计算事件。也就是说,在时间n内,碰到新的触发,就清除之前的,重新计时。比如生活中的坐公交,就是一定时间内,如果有人陆续刷卡上车,司机就不会开车。只有别人没刷卡了,司机才开车。

思路:每次执行时都取消之前的定时器

function debounce(fn) {
      let timeout = null; // 创建一个标记用来存放定时器的返回值
      return function () {
        clearTimeout(timeout); // 每当用户输入的时候把前一个 setTimeout clear 掉
        timeout = setTimeout(() => { // 然后又创建一个新的 setTimeout, 这样就能保证输入字符后的 interval 间隔内如果还有字符输入的话,就不会执行 fn 函数
          fn.apply(this, arguments);
        }, 500);
      };
    }
    function sayHi() {
      console.log('防抖成功');
    }

    var inp = document.getElementById('inp');
    inp.addEventListener('input', debounce(sayHi)); // 防抖

        

        节流:触发高频事件后每隔n秒内才会执行一次。所以节流会稀释函数的执行频率。比如人的眨眼睛,就是一定时间内眨一次。这是函数节流最形象的解释。

        思路:每次触发事件时都判断当前是否有等待执行的定时器

function throttle(fn) {
      let canRun = true; // 通过闭包保存一个标记
      return function () {
        if (!canRun) return; // 在函数开头判断标记是否为true,不为true则return
        canRun = false; // 立即设置为false
        setTimeout(() => { // 将外部传入的函数的执行放在setTimeout中
          fn.apply(this, arguments);
          // 最后在setTimeout执行完毕后再把标记设置为true(关键)表示可以执行下一次循环了。当定时器没有执行的时候标记永远是false,在开头被return掉
          canRun = true;
        }, 500);
      };
    }
    function sayHi(e) {
      console.log(e.target.innerWidth, e.target.innerHeight);
    }
    window.addEventListener('resize', throttle(sayHi));

4.Set、Map、WeakSet、WeakMap区别

        Set:类似于类数组,但成员的值都是唯一的,没有重复的值(可用于数组去重),是一种叫作集合的数据结构。

        WeakSet:结构和Set类似,也是不重复的值的集合,但是和Set不同的是,WeakSet的成员只能是对象,而不能是其他类型的值。

        Map类似于对象,键值对的集合,各种类型的值都可以当做键。(可用于数据储存)

5.promise/async await /generator 理解

什么是promise

promise async await generator详解

6.将数组扁平化并去除其中重复数据,最终得到一个升序且不重复的数组

let arr = [1,2,[1,2,3],[3,4,5,6,7,8],[[1],2,3,6,9,0],[[0]]]

//将数组扁平化处理 并去重 升序排列
// console.log('数组扁平化处理====>'+arr.toString())


// toString() 将数字转成字符串,并可将数组转成一个字符串
// split(',') 将字符串','分割成数组
// join(',')  将数组连接成字符串

function floatArr(arr){
    // let result = []
    // let result = arr.toString().split(',')
    let result = arr.join(',').split(',')
    result = new Set(result)
    result = [...result]
    result.sort((a,b)=>{
        return a-b
    })
   console.log('得到数组===》',result) 
}


floatArr(arr)


//arr.flat(Infinity) Infinity 参数depth是指定要提取嵌套数组的结构深度,默认值为1。参数depth也可以传进 Infinity,代表不论多少层都要展开


7.介绍下深度优先遍历和广度优先遍历,如何实现?

        深度优先遍历:先深入探索,走到头再回退寻找其他出路的遍历方式(二叉树的前中后遍历就是深度优先遍历)关键在于回溯

        广度优先遍历:先相邻的几个节点遍历再去寻找距离更远的节点,一层一层的由内而外的遍历。 关键在于重放

数据结构 深度优先遍历和广度优先遍历

8.js异步解决方案发展历史及优缺点

       ① 回调函数(callback)

        setTimeout(() => {
            // callback 函数体
        }, 1000)

        优点:简单、容易理解

        缺点:回调地狱,不利于代码阅读和维护,耦合性高,不能用try catch捕获错误,不能用return

       ② Ajax

       ③ Promise

               优点: 回调函数变成了链式写法,解决了地狱回调

                缺点:对于长链式操作而言,看起来是一堆then堆砌,代码冗余,语义也不清楚,而且还是靠箭头函数才使得代码略短一些。还有传递参数太麻烦。无法取消promise,错误需要通过回调函数来捕获。

       ④ generator

                优点:可控制函数的执行

                缺点:必须靠执行器

       ⑤ async await

               async基于generator做了几点改进:

                      内置执行器:将generator函数和自动执行器进一步包装

                      语义更清楚:async表示函数中有异步操作,await表示等待者紧跟在后边的表达式

                       适用性更广泛:await后面可以跟promise对象和原始类型的值(gererator不支持)

                缺点:await 将异步代码改造成同步代码,如果多个异步操作没有依赖性而使用 await                 会导致性能上的降低

参考:js异步操作

        js异步解决6种方案的发展历程及优缺点

9.如何实现一个New

      new一个函数的过程:

  1.  创建一个空对象
  2.  将空对象的原型指向构造函数的原型对象对象,将构造函数作用域赋给空对象(this指向了这个空对象)
  3. 执行构造函数中的代码(为这个新对象添加属性)
  4. 返回新对象

new关键字进行如下操作:

  1. 创建一个空的简单的js对象({})
  2.  将新对象的隐式原型__proto__链接到构造函数显示原型prototype上
  3. 执行构造函数并将构造函数的作用域指向新对象
  4. 如果该函数没有返回对象,则返回this(实际是返回了一个空对象,new OIbject()返回的就是一个空对象)
function _new(fn, ...arg) {
  var obj = {}; // 对应于上面的步骤 1

  obj.__proto__ = fn.prototype; // 对应于上面的步骤 2

  var res = fn.apply(obj, arg); // 对应于上面的步骤 3
  
  console.log('返回结果',res)

  return Object.prototype.toString.call(res) === '[object Object]' ? res : obj; // 对应于上面的步骤 4
}

const Fun = function(name) {
  this.name = name;
};

console.log(_new(Fun, '小明'));

   有涉及到改变this指向问题 参考文章:彻底弄懂bind,apply,call三者的区别

10.简单讲解一下 http2 的多路复用

HTTP2 复用 TCP 连接,在一个连接里,客户端和浏览器都可以同时发送多个请求或回应,而且不用按照顺序一一对应。

举例来说,在一个TCP连接里面,服务器同时收到了A请求和B请求,于是先回应A请求,结果发现处理过程非常耗时,于是就发送A请求已经处理好的部分, 接着回应B请求,完成后,再发送A请求剩下的部分。

历史原因解释:

1、HTTP/1.0 版本

该版本主要缺点是,每个TCP连接只能发送一个请求。发送数据完毕,连接就关闭,如果还要请求其他资源,就必须再新建一个连接。为了解决这个问题,需要使用 Connection: keep-alive 这个字段。

2、HTTP/1.1 版本

该版本引入了持久连接(persistent connection),即 TCP 连接默认不关闭,可以被多个请求复用,不用声明 Connection: keep-alive。还引入了管道机制(pipelining),即在同一个TCP连接里面,客户端可以同时发送多个请求。这样就进一步改进了HTTP协议的效率。

虽然1.1版允许复用TCP连接,但是同一个TCP连接里面,所有的数据通信是按次序进行的。服务器只有处理完一个回应,才会进行下一个回应。要是前面的回应特别慢,后面就会有许多请求排队等着。这称为"队头堵塞"(Head-of-line blocking)。

HTTP2采用二进制格式传输,取代了HTTP1.x的文本格式,二进制格式解析更高效。
多路复用代替了HTTP1.x的序列和阻塞机制,所有的相同域名请求都通过同一个TCP连接并发完成。在HTTP1.x中,并发多个请求需要多个TCP连接,浏览器为了控制资源会有6-8个TCP连接都限制。

  • 同域名下所有通信都在单个连接上完成,消除了因多个 TCP 连接而带来的延时和内存消耗。
  • 单个连接上可以并行交错的请求和响应,之间互不干扰

11、ES5/ES6 如何实现继承、有什么区别?

        ES5:原型链继承 构造函数继承  

【先创建子类,在实例化父类并添加到子类this中】

        ES6:类继承

【先创建父类,在实例化子集中通过调用super方法访问父级后,在修改this指向】

优点:清晰方便

缺点:不是所有的浏览器都支持class

class关键字只是原型的语法糖,js继承仍然是基于原型实现的

 参考文章: 继承详解

12、介绍下npm模块安装机制,为什么输入npm install就可以自动安装对应的模块

npm安装机制:

  • 发出npm install命令   
  • 查询node_miodules目录中是否已经存在指定的模块 
  • 若存在 不再重新安装
  • 若不存在 npm向registry查询模块压缩包的网址  下载压缩包,存放在根目录下的npm目录里 解压压缩包到当前项目的node_modules目录              

参考文章 : npm实现原理

13、介绍下重绘和回流、以及如何优化

回流:当页面布局发生变化(元素的大小或者位置发生变化),触发了重新布局导致渲染树重新计算布局和渲染。

​如添加或删除可见的DOM元素;
元素的位置发生变化;
元素的尺寸发生变化、
内容发生变化(如文本变化或图片被另一个不同尺寸的图片所代替);
页面一开始渲染的时候(无法避免);

重绘:只改变自身样式,不会影响到其他元素

元素样式的改变(但宽高、大小、位置不变)
eg:   visibility、color、background-color等

回流一定会触发重绘,而重绘不一定会回流

优化:限制回流和重绘范围

14、介绍下观察者模式和发订阅-发布模式的区别,以及各自适用场景

观察者模式中主体和观察者是互相感知的,发布-订阅模式是借助第三方来实现调度的,发布者和订阅者之间互不感知

订阅-发布模式耦合性更低更适用于复杂的场景

15、关于const和let声明的变量不在window上

但ES6规定,var 命令和 function 命令声明的全局变量,依旧是顶层对象的属性,但 let命令、const命令、class命令声明的全局变量,不属于顶层对象的属性。用 let 和 const 声明的全局变量并没有在全局对象中,只是一个块级作用域(Script)中。

let aa = 1;
const bb = 2;

console.log(window.aa); // undefined
console.log(window.bb); // undefined
console.log(aa); // 1
console.log(bb); // 2

16、import和require的区别

  1、import在编译时被加载,所以必须放在文件开头;require在代码运行时被加载(理论上可以运用在代码的任何地方;import性能更好。

 2、import引入对象被修改时,源对象也会被修改,相当于浅拷贝,require引入对象被修改时,源对象不会被修改,可以理解为深拷贝。

 3、import是ES6的一个语法标准,如果要兼容浏览器的话必须转化成ES5语法,require是AMD规范引入方式。目前所有的引擎都还没有实现import,import最终会被转码为require,在webpack打包中,import和require都会变成_webpack_require_。

17、(未完)在 Vue 中,子组件为何不可以修改父组件传递的 Prop,如果修改了,Vue 是如何监控到属性的修改并给出警告的

        为了保证数据的单向流动,便于对数据进行追踪,避免数据混乱

18、实现一个 sleep 函数,比如 sleep(1000) 意味着等待1000毫秒,可从 Promise、Generator、Async/Await 等角度实现

//Promise
const sleep = time => {
  return new Promise(resolve => setTimeout(resolve,time))
}
sleep(1000).then(()=>{
  console.log(1)
})

//Generator
function* sleepGenerator(time) {
  yield new Promise(function(resolve,reject){
    setTimeout(resolve,time);
  })
}
sleepGenerator(1000).next().value.then(()=>{console.log(1)})

//async
function sleep(time) {
  return new Promise(resolve => setTimeout(resolve,time))
}
async function output() {
  let out = await sleep(1000);
  console.log(1);
  return out;
}
output();

//ES5
function sleep(callback,time) {
  if(typeof callback === 'function')
    setTimeout(callback,time)
}

function output(){
  console.log(1);
}
sleep(output,1000);

19、为什么 Vuex 的 mutation 和 Redux 的 reducer 中不能做异步操作 

因为异步操作是成功还是失败不可预测,什么时候进行异步操作也不可预测;当异步操作成功或失败时,如果不 commit(mutation) 或者 dispatch(action),Vuex 和 Redux 就不能捕获到异步的结果从而进行相应的操作

20、输出什么?并阐述原理

var obj = {
    '2': 3,
    '3': 4,
    'length': 2,
    'splice': Array.prototype.splice,
    'push': Array.prototype.push
}
obj.push(1)
obj.push(2)

知识点:

① 数组用有丰富的内建方法供大家使用,而类数组是结构与数组十分相似(类数组相当于一个对象,key是数字,或者数字的字符串形式,并且拥有length属性)但是却没有数组那么丰富的内建方法,通常类数组可能还拥有一些别的属性。

② 类数组 push是根据length来进行开始插入值的length加一  例如:obj.push(1)即:obj[length] = 1

如果没有length时或者length不为整数时 默认是0开始插入值

21、call、apply有什么区别,哪个性能更好

区别在于:入参不一样

apply( ):两个参数,第一个是运行函数的作用域,第二个是参数数组(可以是array的实例,或者arguments对象)。
call( ):参数个数不定,第一个是运行函数的作用域,其余传递给函数的参数逐个列出。
 

call()比apply()性能好一点 (不用结构参数)可忽略不计

22、实现 (5).add(3).minus(2) 功能

Number.prototype.add = function (number) {
    if (typeof number !== 'number') {
        throw new Error('请输入数字~');
    }
    return this + number;
};
Number.prototype.minus = function (number) {
    if (typeof number !== 'number') {
        throw new Error('请输入数字~');
    }
    return this - number;
};
console.log((5).add(3).minus(2));

// 不修改原型链
function MyNumber(){
MyNumber.prototype= new Number().__proto__
MyNumber.prototype.add =  function (number) {
    if (typeof number !== 'number') {
        throw new Error('请输入数字~');
    }
    return this + number;
};
}

23、说出下面输出结果

var a = 1

function fn1(){
    console.log('执行结果',a)
}

function fn2(){
    var a = 2
    fn1()
}

fn2()

输出1    // 函数作用域是定义时的作用域 而不是调用时的作用域

24、Vue 的响应式原理中 Object.defineProperty 有什么缺陷?为什么在 Vue3.0 采用了 Proxy,抛弃了 Object.defineProperty

25、输出以下代码的执行结果并解释为什么

结果:
undefined
{n:2}

首先,a和b同时引用了{n:2}对象,接着执行到a.x = a = {n:2}语句,尽管赋值是从右到左的没错,但是.的优先级比=要高,所以这里首先执行a.x,相当于为a(或者b)所指向的{n:1}对象新增了一个属性x,即此时对象将变为{n:1;x:undefined}。之后按正常情况,从右到左进行赋值,此时执行a ={n:2}的时候,a的引用改变,指向了新对象{n:2},而b依然指向的是旧对象。之后执行a.x = {n:2}的时候,并不会重新解析一遍a,而是沿用最初解析a.x时候的a,也即旧对象,故此时旧对象的x的值为{n:2},旧对象为 {n:1;x:{n:2}},它被b引用着。
后面输出a.x的时候,又要解析a了,此时的a是指向新对象的a,而这个新对象是没有x属性的,故访问时输出undefined;而访问b.x的时候,将输出旧对象的x的值,即{n:2}。

更多答案

26、冒泡排序如何实现,时间复杂度是多少, 还可以如何改进

原理:比较两个相邻的元素,将值大的元素交换到右边

思路:依次比较相邻的两个数,将比较小的数放在前面,比较大的数放在后面。

27、分析比较 opacity: 0、visibility: hidden、display: none 优劣和适用场景

  结构:

        display:none;   会让元素从渲染树中完全消失,渲染的时候不占据任何空间,不能点击

        visiblity:hidden;  渲染元素继续占用空间,只是内容不可见,不能点击

        opcity:0;  渲染元素继续占用空间,只是内容不可见,可以点击

  继承:

        display:none;  非继承 子孙节点消失由于元素从渲染树消失,通过修改子孙节点属性无法显示

        visiblity:hidden; 继承 子孙节点消失由于继承了hidden,通过设置visibility: visible;可以显式。

        opcity:0; 非继承 子孙节点消失由于元素从渲染树消失,通过修改子孙节点属性无法显示

  性能:

        display:none;  造成回流 性能消耗大

        visiblity:hidden;  会造成重绘 消耗较小

        opcity:0;  会造成重绘 消耗较小

28、箭头函数与普通函数(function)的区别是什么?构造函数(function)可以使用 new 生成实例,那么箭头函数可以吗?为什么?

        ①箭头函数 是普通函数的简写,可以更优雅的定义一个函数,和 ②普通函数比有差异:

  1. 函数体内this对象,①是在定义时所在的对象,②使用时所在的对象
  2. ①不可以使用argument对象,在对象函数体内不存在,如果要用,可以用rest参数代替
  3. ①不可以使用yield命令,因此箭头函数不能作用Generator函数
  4. ①不能使用new命令,因为:没有自己的this,无法调用call() apply()   没有prototype属性,new命令在执行时需要将构造函数的prototype赋值给新的对象_proto_     

29、模拟实现一个 Promise.finally

Promise.prototype.finally = function(callback){
    let P = this.constructor //原型的constructor 指向构造函数Function
    return this.then( //finally实际是返回then
        value => P.resolve(callback()).then(()=>value), //返回的也是promise对象
        reason => P.resolve(callback()).then(()=>{throw reason})
    )
}

30、ES6转ES5代码实现思路

AST:抽象语法树。源代码语法结构的一种抽象表达

  1. 解析:解析代码字符串,生成AST;
  2. 转换:按一定的规则转换、修改AST;
  3. 生成:将修改后的AST转换成普通代码;

Webpack:Webpack 启动后会从 Entry 里配置的 Module 开始递归解析 Entry 依赖的所有 Module。 每找到一个 Module, 就会根据配置的 Loader 去找出对应的转换规则,对 Module 进行转换后,再解析出当前 Module 依赖的 Module。 这些模块会以 Entry 为单位进行分组,一个 Entry 和其所有依赖的 Module 被分到一个组也就是一个 Chunk。最后 Webpack 会把所有 Chunk 转换成文件输出。 在整个流程中 Webpack 会在恰当的时机执行 Plugin 里定义的逻辑。

31、普通for循环和forEach性能比较

为什么 forEach 的性能那么差,为什么还要有这个方法呢?难道就是为了方便?

forEach是声明式函数,我们不用关心内部如何去实现的;for循环是命令式函数,我们告诉计算器如何去做

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值