关于前端:腾讯前端常考面试题汇总

5次阅读

共计 16874 个字符,预计需要花费 43 分钟才能阅读完成。

手写 bind、apply、call

// call

Function.prototype.call = function (context, ...args) {
  context = context || window;

  const fnSymbol = Symbol("fn");
  context[fnSymbol] = this;

  context[fnSymbol](...args);
  delete context[fnSymbol];
}
// apply

Function.prototype.apply = function (context, argsArr) {
  context = context || window;

  const fnSymbol = Symbol("fn");
  context[fnSymbol] = this;

  context[fnSymbol](...argsArr);
  delete context[fnSymbol];
}
// bind

Function.prototype.bind = function (context, ...args) {
  context = context || window;
  const fnSymbol = Symbol("fn");
  context[fnSymbol] = this;

  return function (..._args) {args = args.concat(_args);

    context[fnSymbol](...args);
    delete context[fnSymbol];   
  }
}

vue 实现双向数据绑定原理是什么?

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <script src="https://cdn.bootcss.com/vue/2.5.16/vue.js"></script>
    <!-- 引入 vue 文件 -->
    <div id="box">
      <new-input v-bind:name.sync="name"></new-input>
      {{name}}
      <!-- 小胡子语法 -->
      <input type="text" v-model="name" />
    </div>
    <script>
      Vue.component("new-input", {        props: ["name"],        data: function () {          return {            newName: this.name,};        },        template: `<label><input type="text" @keyup="changgeName"        v-model="newName" /> 你的名字:</label>`,        // 模板字符串
        methods: {changgeName: function () {this.$emit("update:name", this.newName);          },        },        watch: {name: function (v) {this.newName = v;},        },        //    监听
      });      new Vue({        el: "#box",        // 挂载实例
        data: {name: "nick",},        // 赋初始值
      });    </script>
  </body>
</html>

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <input type="text" v-mode="msg" />
    <p v-mode="msg"></p>
    <script>
      const data = {msg: "你好",};      const input = document.querySelector("input");      const p = document.querySelector("p");      input.value = data.msg;      p.innerHTML = data.msg;      // 视图变数据跟着变
      input.addEventListener("input", function () {data.msg = input.value;});      // 数据变视图变
      let temp = data.msg;      Object.defineProperty(data, "msg", {        get() {return temp;},        set(value) {          temp = value;          // 视图批改
          input.value = temp;          p.innerHTML = temp;        },      });      data.msg = "小李";    </script>
  </body>
</html>

八股文我不想写了本人百度去

具体阐明 Event loop

家喻户晓 JS 是门非阻塞单线程语言,因为在最后 JS 就是为了和浏览器交互而诞生的。如果 JS 是门多线程的语言话,咱们在多个线程中解决 DOM 就可能会产生问题(一个线程中新加节点,另一个线程中删除节点),当然能够引入读写锁解决这个问题。

JS 在执行的过程中会产生执行环境,这些执行环境会被程序的退出到执行栈中。如果遇到异步的代码,会被挂起并退出到 Task(有多种 task)队列中。一旦执行栈为空,Event Loop 就会从 Task 队列中拿出须要执行的代码并放入执行栈中执行,所以实质上来说 JS 中的异步还是同步行为。

console.log('script start');

setTimeout(function() {console.log('setTimeout');
}, 0);

console.log('script end');

以上代码尽管 setTimeout 延时为 0,其实还是异步。这是因为 HTML5 标准规定这个函数第二个参数不得小于 4 毫秒,有余会主动减少。所以 setTimeout 还是会在 script end 之后打印。

不同的工作源会被调配到不同的 Task 队列中,工作源能够分为 微工作(microtask)和 宏工作(macrotask)。在 ES6 标准中,microtask 称为 jobs,macrotask 称为 task

console.log('script start');

setTimeout(function() {console.log('setTimeout');
}, 0);

new Promise((resolve) => {console.log('Promise')
    resolve()}).then(function() {console.log('promise1');
}).then(function() {console.log('promise2');
});

console.log('script end');
// script start => Promise => script end => promise1 => promise2 => setTimeout

以上代码尽管 setTimeout 写在 Promise 之前,然而因为 Promise 属于微工作而 setTimeout 属于宏工作,所以会有以上的打印。

微工作包含 process.nextTickpromiseObject.observeMutationObserver

宏工作包含 scriptsetTimeoutsetIntervalsetImmediateI/OUI rendering

很多人有个误区,认为微工作快于宏工作,其实是谬误的。因为宏工作中包含了 script,浏览器会先执行一个宏工作,接下来有异步代码的话就先执行微工作。

所以正确的一次 Event loop 程序是这样的

  1. 执行同步代码,这属于宏工作
  2. 执行栈为空,查问是否有微工作须要执行
  3. 执行所有微工作
  4. 必要的话渲染 UI
  5. 而后开始下一轮 Event loop,执行宏工作中的异步代码

通过上述的 Event loop 程序可知,如果宏工作中的异步代码有大量的计算并且须要操作 DOM 的话,为了更快的 界面响应,咱们能够把操作 DOM 放入微工作中。

Node 中的 Event loop

Node 中的 Event loop 和浏览器中的不雷同。

Node 的 Event loop 分为 6 个阶段,它们会依照程序重复运行

┌───────────────────────┐
┌─>│        timers         │
│  └──────────┬────────────┘
│  ┌──────────┴────────────┐
│  │     I/O callbacks     │
│  └──────────┬────────────┘
│  ┌──────────┴────────────┐
│  │     idle, prepare     │
│  └──────────┬────────────┘      ┌───────────────┐
│  ┌──────────┴────────────┐      │   incoming:   │
│  │         poll          │<──connections───     │
│  └──────────┬────────────┘      │   data, etc.  │
│  ┌──────────┴────────────┐      └───────────────┘
│  │        check          │
│  └──────────┬────────────┘
│  ┌──────────┴────────────┐
└──┤    close callbacks    │
   └───────────────────────┘
timer

timers 阶段会执行 setTimeoutsetInterval

一个 timer 指定的工夫并不是精确工夫,而是在达到这个工夫后尽快执行回调,可能会因为零碎正在执行别的事务而提早。

上限的工夫有一个范畴:[1, 2147483647],如果设定的工夫不在这个范畴,将被设置为 1。

I/O

I/O 阶段会执行除了 close 事件,定时器和 setImmediate 的回调

idle, prepare

idle, prepare 阶段外部实现

poll

poll 阶段很重要,这一阶段中,零碎会做两件事件

  1. 执行到点的定时器
  2. 执行 poll 队列中的事件

并且当 poll 中没有定时器的状况下,会发现以下两件事件

  • 如果 poll 队列不为空,会遍历回调队列并同步执行,直到队列为空或者零碎限度
  • 如果 poll 队列为空,会有两件事产生

    • 如果有 setImmediate 须要执行,poll 阶段会进行并且进入到 check 阶段执行 setImmediate
    • 如果没有 setImmediate 须要执行,会期待回调被退出到队列中并立刻执行回调

如果有别的定时器须要被执行,会回到 timer 阶段执行回调。

check

check 阶段执行 setImmediate

close callbacks

close callbacks 阶段执行 close 事件

并且在 Node 中,有些状况下的定时器执行程序是随机的

setTimeout(() => {console.log('setTimeout');
}, 0);
setImmediate(() => {console.log('setImmediate');
})
// 这里可能会输入 setTimeout,setImmediate
// 可能也会相同的输入,这取决于性能
// 因为可能进入 event loop 用了不到 1 毫秒,这时候会执行 setImmediate
// 否则会执行 setTimeout

当然在这种状况下,执行程序是雷同的

var fs = require('fs')

fs.readFile(__filename, () => {setTimeout(() => {console.log('timeout');
    }, 0);
    setImmediate(() => {console.log('immediate');
    });
});
// 因为 readFile 的回调在 poll 中执行
// 发现有 setImmediate,所以会立刻跳到 check 阶段执行回调
// 再去 timer 阶段执行 setTimeout
// 所以以上输入肯定是 setImmediate,setTimeout

下面介绍的都是 macrotask 的执行状况,microtask 会在以上每个阶段实现后立刻执行。

setTimeout(()=>{console.log('timer1')

    Promise.resolve().then(function() {console.log('promise1')
    })
}, 0)

setTimeout(()=>{console.log('timer2')

    Promise.resolve().then(function() {console.log('promise2')
    })
}, 0)

// 以上代码在浏览器和 node 中打印状况是不同的
// 浏览器中打印 timer1, promise1, timer2, promise2
// node 中打印 timer1, timer2, promise1, promise2

Node 中的 process.nextTick 会先于其余 microtask 执行。

setTimeout(() => {console.log("timer1");

  Promise.resolve().then(function() {console.log("promise1");
  });
}, 0);

process.nextTick(() => {console.log("nextTick");
});
// nextTick, timer1, promise1

New 操作符做了什么事件?

1、首先创立了一个新对象
2、设置原型,将对象的原型设置为函数的 prototype 对象
3、让函数的 this 指向这个对象,执行构造函数的代码(为这个新对象增加属性)4、判断函数的返回值类型,如果是值类型,返回创立的对象。如果是援用类型,就返回这个援用类型的对象

代码输入后果

async function async1 () {console.log('async1 start');
  await new Promise(resolve => {console.log('promise1')
  })
  console.log('async1 success');
  return 'async1 end'
}
console.log('srcipt start')
async1().then(res => console.log(res))
console.log('srcipt end')

输入后果如下:

script start
async1 start
promise1
script end

这里须要留神的是在 async1await前面的 Promise 是没有返回值的,也就是它的状态始终是 pending 状态,所以在 await 之后的内容是不会执行的,包含 async1 前面的 .then

对闭包的了解

闭包是指有权拜访另一个函数作用域中变量的函数,创立闭包的最常见的形式就是在一个函数内创立另一个函数,创立的函数能够拜访到以后函数的局部变量。

闭包有两个罕用的用处;

  • 闭包的第一个用处是使咱们在函数内部可能拜访到函数外部的变量。通过应用闭包,能够通过在内部调用闭包函数,从而在内部拜访到函数外部的变量,能够应用这种办法来创立公有变量。
  • 闭包的另一个用处是使曾经运行完结的函数上下文中的变量对象持续留在内存中,因为闭包函数保留了这个变量对象的援用,所以这个变量对象不会被回收。

比方,函数 A 外部有一个函数 B,函数 B 能够拜访到函数 A 中的变量,那么函数 B 就是闭包。

function A() {
  let a = 1
  window.B = function () {console.log(a)
  }
}
A()
B() // 1

在 JS 中,闭包存在的意义就是让咱们能够间接拜访函数外部的变量。经典面试题:循环中应用闭包解决 var 定义函数的问题

for (var i = 1; i <= 5; i++) {setTimeout(function timer() {console.log(i)
  }, i * 1000)
}

首先因为 setTimeout 是个异步函数,所以会先把循环全副执行结束,这时候 i 就是 6 了,所以会输入一堆 6。解决办法有三种:

  • 第一种是应用闭包的形式
for (var i = 1; i <= 5; i++) {;(function(j) {setTimeout(function timer() {console.log(j)    }, j * 1000)  })(i)}

在上述代码中,首先应用了立刻执行函数将 i 传入函数外部,这个时候值就被固定在了参数 j 下面不会扭转,当下次执行 timer 这个闭包的时候,就能够应用内部函数的变量 j,从而达到目标。

  • 第二种就是应用 setTimeout 的第三个参数,这个参数会被当成 timer 函数的参数传入。
for (var i = 1; i <= 5; i++) {
  setTimeout(function timer(j) {console.log(j)
    },
    i * 1000,
    i
  )
}
  • 第三种就是应用 let 定义 i 了来解决问题了,这个也是最为举荐的形式
for (let i = 1; i <= 5; i++) {setTimeout(function timer() {console.log(i)
  }, i * 1000)
}

Unicode、UTF-8、UTF-16、UTF-32 的区别?

(1)Unicode

在说 Unicode 之前须要先理解一下 ASCII 码:ASCII 码(American Standard Code for Information Interchange)称为美国规范信息替换码。

  • 它是基于拉丁字母的一套电脑编码零碎。
  • 它定义了一个用于代表常见字符的字典。
  • 它蕴含了 ”A-Z”(蕴含大小写),数据 ”0-9″ 以及一些常见的符号。
  • 它是专门为英语而设计的,有 128 个编码,对其余语言无能为力

ASCII码能够示意的编码无限,要想示意其余语言的编码,还是要应用 Unicode 来示意,能够说 UnicodeASCII 的超集。

Unicode全称 Unicode Translation Format,又叫做对立码、万国码、繁多码。Unicode 是为了解决传统的字符编码方案的局限而产生的,它为每种语言中的每个字符设定了对立并且惟一的二进制编码,以满足跨语言、跨平台进行文本转换、解决的要求。

Unicode的实现形式(也就是编码方式)有很多种,常见的是 UTF-8UTF-16UTF-32USC-2

(2)UTF-8

UTF-8是应用最宽泛的 Unicode 编码方式,它是一种可变长的编码方式,能够是 1—4 个字节不等,它能够齐全兼容 ASCII 码的 128 个字符。

留神: UTF-8 是一种编码方式,Unicode是一个字符汇合。

UTF-8的编码规定:

  • 对于 单字节 的符号,字节的第一位为 0,前面的 7 位为这个字符的 Unicode 编码,因而对于英文字母,它的 Unicode 编码和 ACSII 编码一样。
  • 对于 n 字节 的符号,第一个字节的前 n 位都是 1,第 n + 1 位设为 0,前面字节的前两位一律设为 10,剩下的没有提及的二进制位,全副为这个符号的 Unicode 码。

来看一下具体的 Unicode 编号范畴与对应的 UTF-8 二进制格局:

编码范畴(编号对应的十进制数) 二进制格局
0x00—0x7F(0-127) 0xxxxxxx
0x80—0x7FF(128-2047) 110xxxxx 10xxxxxx
0x800—0xFFFF(2048-65535) 1110xxxx 10xxxxxx 10xxxxxx
0x10000—0x10FFFF(65536 以上) 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

那该如何通过具体的 Unicode 编码,进行具体的 UTF-8 编码呢?步骤如下:

  • 找到该 Unicode 编码的所在的编号范畴,进而找到与之对应的二进制格局
  • Unicode 编码转换为二进制数(去掉最高位的 0)
  • 将二进制数从右往左一次填入二进制格局的 X 中,如果有 X 未填,就设为 0

来看一个理论的例子:
”字的Unicode 编码是:0x9A6C,整数编号是39532(1)首选确定了该字符在第三个范畴内,它的格局是 1110xxxx 10xxxxxx 10xxxxxx(2)39532 对应的二进制数为1001 1010 0110 1100(3)将二进制数填入 X 中,后果是:11101001 10101001 10101100

(3)UTF-16

1. 立体的概念

在理解 UTF-16 之前,先看一下 立体 的概念:Unicode编码中有很多很多的字符,它并不是一次性定义的,而是分区进行定义的,每个区寄存 65536(216)个字符,这称为一个 立体,目前总共有 17 个立体。

最后面的一个立体称为 根本立体 ,它的码点从0 — 216-1,写成 16 进制就是U+0000 — U+FFFF,那剩下的 16 个立体就是 辅助立体,码点范畴是 U+10000—U+10FFFF

2. UTF-16 概念:

UTF-16也是 Unicode 编码集的一种编码模式,把 Unicode 字符集的形象码位映射为 16 位长的整数(即码元)的序列,用于数据存储或传递。Unicode字符的码位须要 1 个或者 2 个 16 位长的码元来示意,因而 UTF-16 也是用变长字节示意的。

3. UTF-16 编码规定:

  • 编号在 U+0000—U+FFFF 的字符(罕用字符集),间接用两个字节示意。
  • 编号在 U+10000—U+10FFFF 之间的字符,须要用四个字节示意。

4. 编码辨认

那么问题来了,当遇到两个字节时,怎么晓得是把它当做一个字符还是和前面的两个字节一起当做一个字符呢?

UTF-16 编码必定也思考到了这个问题,在根本立体内,从 U+D800 — U+DFFF 是一个空段,也就是说这个区间的码点不对应任何的字符,因而这些空段就能够用来映射辅助立体的字符。

辅助立体共有 220 个字符位,因而示意这些字符至多须要 20 个二进制位。UTF-16 将这 20 个二进制位分成两半,前 10 位映射在 U+D800 — U+DBFF,称为 高位 (H),后 10 位映射在 U+DC00 — U+DFFF,称为 低位(L)。这就相当于,将一个辅助立体的字符拆成了两个根本立体的字符来示意。

因而,当遇到两个字节时,发现它的码点在 U+D800 —U+DBFF之间,就能够晓得,它前面的两个字节的码点应该在 U+DC00 — U+DFFF 之间,这四个字节必须放在一起进行解读。

5. 举例说明

以 “𡠀” 字为例,它的 Unicode 码点为 0x21800,该码点超出了根本立体的范畴,因而须要用四个字节来示意,步骤如下:

  • 首先计算超出局部的后果:0x21800 - 0x10000
  • 将下面的计算结果转为 20 位的二进制数,有余 20 位就在后面补 0,后果为:0001000110 0000000000
  • 将失去的两个 10 位二进制数别离对应到两个区间中
  • U+D800 对应的二进制数为 1101100000000000,将 0001000110 填充在它的后 10 个二进制位,失去 1101100001000110,转成 16 进制数为 0xD846。同理,低位为 0xDC00,所以这个字的UTF-16 编码为 0xD846 0xDC00

(4)UTF-32

UTF-32 就是字符所对应编号的整数二进制模式,每个字符占四个字节,这个是间接进行转换的。该编码方式占用的贮存空间较多,所以应用较少。

比方“”字的 Unicode 编号是:U+9A6C,整数编号是39532,间接转化为二进制:1001 1010 0110 1100,这就是它的 UTF-32 编码。

(5)总结

Unicode、UTF-8、UTF-16、UTF-32 有什么区别?

  • Unicode 是编码字符集(字符集),而 UTF-8UTF-16UTF-32 是字符集编码(编码规定);
  • UTF-16 应用变长码元序列的编码方式,相较于定长码元序列的 UTF-32 算法更简单,甚至比同样是变长码元序列的 UTF-8 也更为简单,因为其引入了独特的 代理对 这样的代理机制;
  • UTF-8须要判断每个字节中的结尾标记信息,所以如果某个字节在传送过程中出错了,就会导致前面的字节也会解析出错;而 UTF-16 不会判断结尾标记,即便错也只会错一个字符,所以容错能力教强;
  • 如果字符内容全副英文或英文与其余文字混合,但英文占绝大部分,那么用 UTF-8 就比 UTF-16 节俭了很多空间;而如果字符内容全副是中文这样相似的字符或者混合字符中中文占绝大多数,那么 UTF-16 就占优势了,能够节俭很多空间;

箭头函数的 this 指向哪⾥?

箭头函数不同于传统 JavaScript 中的函数,箭头函数并没有属于⾃⼰的 this,它所谓的 this 是捕捉其所在高低⽂的 this 值,作为⾃⼰的 this 值,并且因为没有属于⾃⼰的 this,所以是不会被 new 调⽤的,这个所谓的 this 也不会被扭转。

能够⽤ Babel 了解⼀下箭头函数:

// ES6 
const obj = {getArrow() {return () => {console.log(this === obj); 
    }; 
  } 
}

转化后:

// ES5,由 Babel 转译
var obj = {getArrow: function getArrow() { 
     var _this = this; 
     return function () {console.log(_this === obj); 
     }; 
   } 
};

如何进攻 CSRF 攻打?

CSRF 攻打能够应用以下办法来防护:

  • 进行同源检测,服务器依据 http 申请头中 origin 或者 referer 信息来判断申请是否为容许拜访的站点,从而对申请进行过滤。当 origin 或者 referer 信息都不存在的时候,间接阻止申请。这种形式的毛病是有些状况下 referer 能够被伪造,同时还会把搜索引擎的链接也给屏蔽了。所以个别网站会容许搜索引擎的页面申请,然而相应的页面申请这种申请形式也可能被攻击者给利用。(Referer 字段会通知服务器该网页是从哪个页面链接过去的)
  • 应用 CSRF Token 进行验证,服务器向用户返回一个随机数 Token,当网站再次发动申请时,在申请参数中退出服务器端返回的 token,而后服务器对这个 token 进行验证。这种办法解决了应用 cookie 繁多验证形式时,可能会被冒用的问题,然而这种办法存在一个毛病就是,咱们须要给网站中的所有申请都增加上这个 token,操作比拟繁琐。还有一个问题是个别不会只有一台网站服务器,如果申请通过负载平衡转移到了其余的服务器,然而这个服务器的 session 中没有保留这个 token 的话,就没有方法验证了。这种状况能够通过扭转 token 的构建形式来解决。
  • 对 Cookie 进行双重验证,服务器在用户拜访网站页面时,向申请域名注入一个 Cookie,内容为随机字符串,而后当用户再次向服务器发送申请的时候,从 cookie 中取出这个字符串,增加到 URL 参数中,而后服务器通过对 cookie 中的数据和参数中的数据进行比拟,来进行验证。应用这种形式是利用了攻击者只能利用 cookie,然而不能拜访获取 cookie 的特点。并且这种办法比 CSRF Token 的办法更加不便,并且不波及到分布式拜访的问题。这种办法的毛病是如果网站存在 XSS 破绽的,那么这种形式会生效。同时这种形式不能做到子域名的隔离。
  • 在设置 cookie 属性的时候设置 Samesite,限度 cookie 不能作为被第三方应用,从而能够防止被攻击者利用。Samesite 一共有两种模式,一种是严格模式,在严格模式下 cookie 在任何状况下都不可能作为第三方 Cookie 应用,在宽松模式下,cookie 能够被申请是 GET 申请,且会产生页面跳转的申请所应用。

替换元素的概念及计算规定

通过批改某个属性值出现的内容就能够被替换的元素就称为“替换元素”。

替换元素除了内容可替换这一个性以外,还有以下个性:

  • 内容的外观不受页面上的 CSS 的影响:用业余的话讲就是在款式体现在 CSS 作用域之外。如何更改替换元素自身的外观须要相似 appearance 属性,或者浏览器本身裸露的一些款式接口。
  • 有本人的尺寸:在 Web 中,很多替换元素在没有明确尺寸设定的状况下,其默认的尺寸(不包含边框)是 300 像素×150 像素,如
  • 在很多 CSS 属性上有本人的一套体现规定:比拟具备代表性的就是 vertical-align 属性,对于替换元素和非替换元素,vertical-align 属性值的解释是不一样的。比方说 vertical-align 的默认值的 baseline,很简略的属性值,基线之意,被定义为字符 x 的下边缘,而替换元素的基线却被硬生生定义成了元素的下边缘。
  • 所有的替换元素都是内联程度元素:也就是替换元素和替换元素、替换元素和文字都是能够在一行显示的。然而,替换元素默认的 display 值却是不一样的,有的是 inline,有的是 inline-block。

替换元素的尺寸从内而外分为三类:

  • 固有尺寸: 指的是替换内容本来的尺寸。例如,图片、视频作为一个独立文件存在的时候,都是有着本人的宽度和高度的。
  • HTML 尺寸: 只能通过 HTML 原生属性扭转,这些 HTML 原生属性包含的 width 和 height 属性、的 size 属性。
  • CSS 尺寸: 特指能够通过 CSS 的 width 和 height 或者 max-width/min-width 和 max-height/min-height 设置的尺寸,对应盒尺寸中的 content box。

这三层构造的计算规定具体如下:
(1)如果没有 CSS 尺寸和 HTML 尺寸,则应用固有尺寸作为最终的宽高。
(2)如果没有 CSS 尺寸,则应用 HTML 尺寸作为最终的宽高。
(3)如果有 CSS 尺寸,则最终尺寸由 CSS 属性决定。
(4)如果“固有尺寸”含有固有的宽高比例,同时仅设置了宽度或仅设置了高度,则元素仍然依照固有的宽高比例显示。
(5)如果下面的条件都不合乎,则最终宽度体现为 300 像素,高度为 150 像素。
(6)内联替换元素和块级替换元素应用下面同一套尺寸计算规定。

setTimeout、Promise、Async/Await 的区别

(1)setTimeout

console.log('script start')    //1. 打印 script start
setTimeout(function(){console.log('settimeout')    // 4. 打印 settimeout
})    // 2. 调用 setTimeout 函数,并定义其实现后执行的回调函数
console.log('script end')    //3. 打印 script start
// 输入程序:script start->script end->settimeout

(2)Promise

Promise 自身是 同步的立刻执行函数,当在 executor 中执行 resolve 或者 reject 的时候, 此时是异步操作,会先执行 then/catch 等,当主栈实现后,才会去调用 resolve/reject 中寄存的办法执行,打印 p 的时候,是打印的返回后果,一个 Promise 实例。

console.log('script start')
let promise1 = new Promise(function (resolve) {console.log('promise1')
    resolve()
    console.log('promise1 end')
}).then(function () {console.log('promise2')
})
setTimeout(function(){console.log('settimeout')
})
console.log('script end')
// 输入程序: script start->promise1->promise1 end->script end->promise2->settimeout

当 JS 主线程执行到 Promise 对象时:

  • promise1.then() 的回调就是一个 task
  • promise1 是 resolved 或 rejected: 那这个 task 就会放入以后事件循环回合的 microtask queue
  • promise1 是 pending: 这个 task 就会放入 事件循环的将来的某个 (可能下一个) 回合的 microtask queue 中
  • setTimeout 的回调也是个 task,它会被放入 macrotask queue 即便是 0ms 的状况

(3)async/await

async function async1(){console.log('async1 start');
    await async2();
    console.log('async1 end')
}
async function async2(){console.log('async2')
}
console.log('script start');
async1();
console.log('script end')
// 输入程序:script start->async1 start->async2->script end->async1 end

async 函数返回一个 Promise 对象,当函数执行的时候,一旦遇到 await 就会先返回,等到触发的异步操作实现,再执行函数体内前面的语句。能够了解为,是让出了线程,跳出了 async 函数体。

例如:

async function func1() {return 1}
console.log(func1())

func1 的运行后果其实就是一个 Promise 对象。因而也能够应用 then 来解决后续逻辑。

func1().then(res => {console.log(res);  // 30
})

await 的含意为期待,也就是 async 函数须要期待 await 后的函数执行实现并且有了返回后果(Promise 对象)之后,能力继续执行上面的代码。await 通过返回一个 Promise 对象来实现同步的成果。

documentFragment 是什么?用它跟间接操作 DOM 的区别是什么?

MDN 中对 documentFragment 的解释:

DocumentFragment,文档片段接口,一个没有父对象的最小文档对象。它被作为一个轻量版的 Document 应用,就像规范的 document 一样,存储由节点(nodes)组成的文档构造。与 document 相比,最大的区别是 DocumentFragment 不是实在 DOM 树的一部分,它的变动不会触发 DOM 树的从新渲染,且不会导致性能等问题。

当咱们把一个 DocumentFragment 节点插入文档树时,插入的不是 DocumentFragment 本身,而是它的所有子孙节点。在频繁的 DOM 操作时,咱们就能够将 DOM 元素插入 DocumentFragment,之后一次性的将所有的子孙节点插入文档中。和间接操作 DOM 相比,将 DocumentFragment 节点插入 DOM 树时,不会触发页面的重绘,这样就大大提高了页面的性能。

为什么须要浏览器缓存?

对于浏览器的缓存,次要针对的是前端的动态资源,最好的成果就是,在发动申请之后,拉取相应的动态资源,并保留在本地。如果服务器的动态资源没有更新,那么在下次申请的时候,就间接从本地读取即可,如果服务器的动态资源曾经更新,那么咱们再次申请的时候,就到服务器拉取新的资源,并保留在本地。这样就大大的缩小了申请的次数,进步了网站的性能。这就要用到浏览器的缓存策略了。

所谓的 浏览器缓存 指的是浏览器将用户申请过的动态资源,存储到电脑本地磁盘中,当浏览器再次拜访时,就能够间接从本地加载,不须要再去服务端申请了。

应用浏览器缓存,有以下长处:

  • 缩小了服务器的累赘,进步了网站的性能
  • 放慢了客户端网页的加载速度
  • 缩小了多余网络数据传输

分片思维解决大数据量渲染问题

题目形容: 渲染百万条构造简略的大数据时 怎么应用分片思维优化渲染

实现代码如下:

let ul = document.getElementById("container");
// 插入十万条数据
let total = 100000;
// 一次插入 20 条
let once = 20;
// 总页数
let page = total / once;
// 每条记录的索引
let index = 0;
// 循环加载数据
function loop(curTotal, curIndex) {if (curTotal <= 0) {return false;}
  // 每页多少条
  let pageCount = Math.min(curTotal, once);
  window.requestAnimationFrame(function () {for (let i = 0; i < pageCount; i++) {let li = document.createElement("li");
      li.innerText = curIndex + i + ":" + ~~(Math.random() * total);
      ul.appendChild(li);
    }
    loop(curTotal - pageCount, curIndex + pageCount);
  });
}
loop(total, index);

扩大思考:对于大数据量的简略 dom 构造渲染能够用分片思维解决 如果是简单的 dom 构造渲染如何解决?

这时候就须要应用虚构列表了 大家自行百度哈 虚构列表和虚构表格在日常我的项目应用还是很频繁的

异步编程的实现形式?

JavaScript 中的异步机制能够分为以下几种:

  • 回调函数 的形式,应用回调函数的形式有一个毛病是,多个回调函数嵌套的时候会造成回调函数天堂,高低两层的回调函数间的代码耦合度太高,不利于代码的可保护。
  • Promise 的形式,应用 Promise 的形式能够将嵌套的回调函数作为链式调用。然而应用这种办法,有时会造成多个 then 的链式调用,可能会造成代码的语义不够明确。
  • generator 的形式,它能够在函数的执行过程中,将函数的执行权转移进来,在函数内部还能够将执行权转移回来。当遇到异步函数执行的时候,将函数执行权转移进来,当异步函数执行结束时再将执行权给转移回来。因而在 generator 外部对于异步操作的形式,能够以同步的程序来书写。应用这种形式须要思考的问题是何时将函数的控制权转移回来,因而须要有一个主动执行 generator 的机制,比如说 co 模块等形式来实现 generator 的主动执行。
  • async 函数 的形式,async 函数是 generator 和 promise 实现的一个主动执行的语法糖,它外部自带执行器,当函数外部执行到一个 await 语句的时候,如果语句返回一个 promise 对象,那么函数将会期待 promise 对象的状态变为 resolve 后再持续向下执行。因而能够将异步逻辑,转化为同步的程序来书写,并且这个函数能够主动执行。

Vue 的父子组件生命周期钩子函数执行程序?

<!-- 加载渲染过程 -->
    <!-- 父 beforeCreate -> 父 created -> 父 beforeMount -> 子 beforeCreate -> 子 created ->
    子 beforeMount -> 子 mounted -> 父 mounted -->
    <!-- 子组件更新过程 -->
    <!-- 父 beforeUpdate -> 子 beforeUpdate -> 子 updaed -> 父 updated -->
    <!-- 父组件跟新过程 -->
    <!-- 父 beforeUpdate -> 父 updated -->
    <!-- 销毁过程 -->
    <!-- 父 beforeDestroy -> 子 beforeDestroy -> 子 destroyed -> 父 destroyed -->

其余值到字符串的转换规则?

  • Null 和 Undefined 类型,null 转换为 “null”,undefined 转换为 “undefined”,
  • Boolean 类型,true 转换为 “true”,false 转换为 “false”。
  • Number 类型的值间接转换,不过那些极小和极大的数字会应用指数模式。
  • Symbol 类型的值间接转换,然而只容许显式强制类型转换,应用隐式强制类型转换会产生谬误。
  • 对一般对象来说,除非自行定义 toString() 办法,否则会调用 toString()(Object.prototype.toString())来返回外部属性 [[Class]] 的值,如 ”[object Object]”。如果对象有本人的 toString() 办法,字符串化时就会调用该办法并应用其返回值。

vue-router

vue-router 是 vuex.js 官网的路由管理器,它和 vue.js 的外围深度集成,让构建但页面利用变得大海捞针

<router-link> 组件反对用户在具备路由性能的利用中 (点击) 导航。通过 to 属性指定指标地址

<router-view> 组件是一个 functional 组件,渲染门路匹配到的视图组件。<keep-alive> 组件是一个用来缓存组件

router.beforeEach

router.afterEach

to: Route: 行将要进入的指标 路由对象

from: Route: 以后导航正要来到的路由

next: Function: 肯定要调用该办法来 resolve 这个钩子。执行成果依赖 next 办法的调用参数。介绍了路由守卫及用法,在我的项目中路由守卫起到的作用等等

树形构造转成列表

题目形容:

[
    {
        id: 1,
        text: '节点 1',
        parentId: 0,
        children: [
            {
                id:2,
                text: '节点 1_1',
                parentId:1
            }
        ]
    }
]
转成
[
    {
        id: 1,
        text: '节点 1',
        parentId: 0 // 这里用 0 示意为顶级节点
    },
    {
        id: 2,
        text: '节点 1_1',
        parentId: 1 // 通过这个字段来确定子父级
    }
    ...
]

实现代码如下:

function treeToList(data) {let res = [];
  const dfs = (tree) => {tree.forEach((item) => {if (item.children) {dfs(item.children);
        delete item.children;
      }
      res.push(item);
    });
  };
  dfs(data);
  return res;
}
正文完
 0