你会忽略的前端N个小TPS知识点(200题)

3,070 阅读36分钟

Hello,大家好,我是disguiseFish,坚持每天六点起床学习有一段时间啦~收获良多,每天会做些题来储备自己的知识量,在这里把它们分享记录下来,我们一起学习进步吧! 这是一个日更帖哟!

2022.01.06

200.谈谈你对回流和重绘的理解

  • 重绘是当元素的样式啊比如颜色背景颜色改变的时候浏览器会重绘,而当改变元素的位置内容等的时候浏览器会重排,

  • 重排一定会引起重绘,重绘不一定会引起重排,重排的性能更差一些,所以我们在写样式操作dom元素的时候尽量避免重排重绘。浏览器获取特定属性值类似offsetTop,clientTop时候也会引起回流,原生手动操作dom 可用documentFragment 优化。

199.promise 的allSettled方法有使用过吗

allSettled可以传入的数组的promise结果无论是resolve还是reject都可以拿到结果

allSettled可以运用在 我们需要处理多个promise的结果返回,由于all方法存在有一个异常,则直接reject返回这个异常结果,但是其余正常的处理结果就拿不到了,所以有allSelected来解决这个问题,既能够拿到正常的结果返回,也能够拿到异常结果返回。

198.手写实现一个JSON.stringfy方法

image.png

2022.01.05

197. js继承的几种实现方式?

原型链继承 构造函数继承 原型式继承 组合继承 寄生式继承 寄生式组合继承

196. 说下node事件循环和浏览器事件循环机制,它们之间有何区别?

node是独立于Event loop之外的循环机制,严格按照timers pending idle poll check close顺序执行

195.编写一个函数来查找字符串数组中的最长公共前缀。(常考)

如果不存在公共前缀,返回空字符串 ""。

示例 1: 输入:strs = ["flower","flow","flight"] 输出:"fl"

示例 2: 输入:strs = ["dog","racecar","car"] 输出:"" 解释:输入不存在公共前缀。

var longestCommonPrefix = function(strs) { // TODO };

解答:

// 取出字符串列表中最短字符串,每次取其最前面的一个字符,检查其他字符是否前缀为这个字符
// 确认存在则继续添加一个字符,否则直接减少当前的字符并返回  时间复杂度O(n^2)  空间复杂度O(1)
var longestCommonPrefix = function(strs) {
  let ans = ''
  if (strs.length < 1) return ans
  let minStr = strs[0]
  for(let s of strs) {
      minStr = s.length < minStr.length ? s : minStr
  }
  for(let i = 1; i <= minStr.length; i++) {
      ans = minStr.substr(0, i)
      for (let j = 0; j < strs.length; j++) {
          if (strs[j].indexOf(ans) != 0) return ans.substr(0, i - 1)
      }
  }
  return ans
};

// 横向扫描法: 拿出第一个来做比较,求出其字符串交集,后面的字符串匹配这个交集并更新,最后返回 时间复杂度O(mn) 空间复杂度O(1)
var longestCommonPrefix = function(strs) {
  if (strs.length < 1) return ""
  let prefix = strs[0], count = strs.length
  const lcp = function(str1, str2) {
    let index = 0, len = Math.min(str1.length, str2.length)
    while(index < len && str1[index] == str2[index]) {
      index++
    }
    return str1.substr(0, index)
  }
  for(let i = 1; i < count; i ++) {
    prefix = lcp(prefix, strs[i])
    if (!prefix) break
  }
  return prefix
};

// 纵向扫描法 时间复杂度:O(mn) 空间复杂度:O(1)
var longestCommonPrefix = function(strs) {
  if (strs == null || strs.length < 1) return ""
  let len = strs[0].length, count = strs.length
  for (let i = 0; i < len; i++) {
    let char = strs[0].charAt(i)
    for (let j = 1; j < count; j++) {
      if (i == strs[j].length || strs[j].charAt(i) != char) {
        return strs[0].substr(0, i)
      }
    }
  }
  return strs[0]
};

// 分治法 时间复杂度:O(mn) 空间复杂度:O(mlogn)
var longestCommonPrefix = function(strs) {
  if (strs == null || strs.length < 1) return ""
  const _commonPrefix = function(lcpLeft, lcpRight) {
    let minLen = Math.min(lcpLeft.length, lcpRight.length)
    for (let i = 0; i < minLen; i++) {
      if (lcpLeft.charAt(i) != lcpRight.charAt(i)) {
        return lcpLeft.substr(0, i)
      }
    }
    return lcpLeft.substr(0, minLen)
  }
  const _generate = function(start, end) {
    if (start == end) return strs[start]
    else {
      let mid = Math.floor((end - start) / 2) + start
      let lcpLeft = _generate(start, mid)
      let lcpRight = _generate(mid + 1, end)
      return _commonPrefix(lcpLeft, lcpRight)
    }
  }
  return _generate(0, strs.length - 1)
};


// 二分查找 时间复杂度O(mnlogm)  空间复杂度O(1)
var longestCommonPrefix = function(strs) {
  if (strs == null || strs.length < 1) return ""
  const _isCommnPrefix = function (length) {
    let str0 = strs[0].substr(0, length)
    let count = strs.length
    for (let i = 0; i < count; i++) {
      let str = strs[i]
      for (let j = 0; j < length; j++) {
        if (str0.charAt(j) != str.charAt(j)) {
          return false
        }
      }
    }
    return true
  }
  let minLen = Number.MAX_VALUE
  for (let str of strs) {
    minLen = Math.min(minLen, str.length)
  }
  let low = 0, high = minLen
  while (low < high) {
    let mid = Math.floor((high - low + 1) / 2) + low
    if (_isCommnPrefix(mid)) {
      low = mid
    } else {
      high = mid - 1
    }
  }
  return strs[0].substr(0, low)
};

2022.01.04

194. Promise.all以及Promise.race怎么使用

传入promise数组

all是全true则true 否则 false,按传入顺序返回

race是最快结束的状态为返回结果

193.如何捕获到async/await的异常

  • async/await捕获异常得用try catch
  • promise的异常能用promise的catch或者unhandleRejection
  • 补充:非promise的时候内部会用Promise.resolve包装使其变成promise

192.如何实现数组扁平化

const arr = [  1,  [2, 3],
  [4, [5, 6]]
]

// ES10 的 flat 方法
// function flatten (arr) {
//   return arr.flat(Infinity)
// }

// arr.toString()方法
// function flatten (arr) {
//   return arr.toString().split(',').map(item => Number(item))
// }

// JSON.stringify(arr)
// function flatten (arr) {
//   return JSON.stringify(arr).replace(/(\[|\])/g, '').split(',').map(item => Number(item))
// }

// 递归遍历
// function flatten (arr) {
//   let res = []
//   for (let i = 0; i < arr.length; i++) {
//     if (Array.isArray(arr[i])) {
//       res = res.concat(flatten(arr[i]))
//     } else {
//       res = res.concat(arr[i])
//     }
//   }
//   return res
// }

// 利用 reduce 简化递归
function flatten (arr) {
  return arr.reduce((prev, next) => {
    return prev.concat(Array.isArray(next) ? flatten(next) : next)
  }, [])
}

// 利用扩展运算符 [].concat(...arr)
// function flatten (arr) {
//   while (arr.some(item => Array.isArray(item))) {
//     arr = [].concat(...arr)
//   }
//   return arr
// }

// 利用生成器函数
// function * flatten (arr) {
//   for (let i = 0; i < arr.length; i++) {
//     if (Array.isArray(arr[i])) {
//       yield * flatten(arr[i])
//     } else {
//       yield arr[i]
//     }
//   }
// }
// console.log([...flatten(arr)])

摘自:github.com/ronnieXiaoL…

2021.12.31

191.postMessage还可以解决哪些场景的跨域问题(除了页面与嵌套的iframe之外);如何避免接收到其它网站的message

postMessage除了可以解决跨页面通信,嵌套的iframe跨域通信外还可以解决以下问题: image.png

190.【浏览器缓存】浏览器发出请求后什么情况下会考虑协商缓存(列举)

强缓存命中失败,或者cache-control为no-cache

189.【性能优化】如何计算首屏时间;首屏优化可从哪几个方面入手(不用说细节)

  • 资源体积过大:资源压缩,传输压缩,代码拆分,Tree shaking,HTTP/2,缓存
  • 首页内容太多:路由/组件/内容 lazy-loading,预渲染/SSR,inline CSS
  • 加载顺序不合适:prefesh,preload

188.【计算机网络】什么是数字证书;数字证书有什么用处

https不是绝对安全的,而Ca证书是用来给https协议加强安全性的,需要花钱购买;

如果服务器公钥是攻击者伪造的话,我们就需要数字证书了,CA是数字证书认证机构是客户端和服务器双方都可以信赖的第三方立场的服务器向CA申请数字证书,审核通过后CA会向申请者签发认证文件---证书(证书发布机构,证书有效期,公钥,证书所有者,签名......),拿到证书后会给客户端对证书内的内容进行一一校验对比,如果信息一直则是合法的没被冒充

187.【React源码】是否了解React源码;什么是可中断的异步更新以及采用这种方式的好处(可对比React15中的同步更新来说明);Fiber节点是如何被创建并构建Fiber树的

不了解

186.【代码输出(考察点:Promise的resovle、reject)】

var p1 = new Promise(function(resolve, reject){
  resolve(Promise.resolve('resolve'));
});

var p2 = new Promise(function(resolve, reject){
  resolve(Promise.reject('reject'));
});

var p3 = new Promise(function(resolve, reject){
  reject(Promise.resolve('resolve'));
});

p1.then(
  function fulfilled(value){
    console.log('fulfilled: ' + value);
  }, 
  function rejected(err){
    console.log('rejected: ' + err);
  }
);

p2.then(
  function fulfilled(value){
    console.log('fulfilled: ' + value);
  }, 
  function rejected(err){
    console.log('rejected: ' + err);
  }
);

p3.then(
  function fulfilled(value){
    console.log('fulfilled: ' + value);
  }, 
  function rejected(err){
    console.log('rejected: ' + err);
  }
);

答案:

image.png

image.png

2021.12.30

185. es6 的 let 实现原理

丢个链接:juejin.cn/post/700463…

184. LRU 缓存机制 手写

丢个连接看第22题:juejin.cn/post/696871…

183.for...in、 Object.keys() 和 Object.getOwnPropertyNames() 的区别

(enumerable:是否可枚举属性,默认为true,true代表可枚举)

  • for...in:遍历对象,能拿到key值,同步遍历,能遍历到原型链上可枚举的属性
  • Object.keys():对象遍历key的方法,会只返回key值,遍历的是可枚举属性
  • Object.getOwnPropertyNames():返回对象的所有自身属性的属性名(除了symbol属性),返回自身可枚举和不可枚举属性,
// 用这个验证 用三个方式遍历 child,就懂了
var parent = Object.create(Object.prototype, {
    a: {
        value: 1,
        writable: true,
        enumerable: true,
        configurable: true            
    }
});
var child = Object.create(parent, {
    b: {
        value: 2,
        writable: true,
        enumerable: true,
        configurable: true
    },
    c: {
        value: 3,
        writable: true,
        enumerable: false,
        configurable: true
    }
});

182.代码输出题

function Car() {
    this.make = "a";
    return { make: "b" };
}
const myCar = new Car();
console.log(myCar.make);

b

181.代码输出题

class Chameleon {
    static colorChange(newColor) {
        this.newColor = newColor;
    }

    constructor({ newColor = "green" } = {}) {
        this.newColor = newColor;
    }
}
const freddie = new Chameleon({ newColor: "purple" });
freddie.colorChange("orange");

考察 ES6的class中的static方法和普通方法的区别,class中的static方法不会被实例继承,所以构造函数构造出来的实例不能调用static定义的方法,会报错

180.代码输出题

const name = "tom";
const age = 21;

console.log(Number.isNaN(name));
console.log(Number.isNaN(age));

console.log(isNaN(name));
console.log(isNaN(age));

考察的Number.isNaN 和 isNaN 的区别,都是判断是否是NaN,两者的区别是:

  1. Number.isNaN 先判断是不是数字类型,是数组类型的话才判断是不是NaN,又是数字类型又是NaN的话则true,其他情况false;
  2. 而isNaN有问题,只要不能被转成数字的值就返回true,是NaN的话也是true ,其他情况都是false;

所以答案是 false false true false

179.代码输出题

const config = {
    languages: [],
    set language(lang) {
        return this.languages.push(lang);
    },
};
console.log(config.language);

image.png

所以这里输出undefined

image.png

2021.12.28

178. vue-router有什么模式,有什么区别

答案请前往63题

177. 浅谈浏览器缓存

juejin.cn/post/694793…

176. position的值,分别什么作用

image.png

2021.12.27

175. 详细说一下vue2.0响应式数据的原理

丢个链接:juejin.cn/post/696122…

174. webpack 常见有哪些配置,代表什么含义?

丢个链接:juejin.cn/post/684490…

173. webpack 打包速度太慢怎么办

丢个链接:juejin.cn/post/684490…

172. webpack 如何优化前端性能

丢个链接:juejin.cn/post/684490…

171. 详细讲一下MVVM和MVC

丢个链接:juejin.cn/post/696122…

2021.12.24

170.[算法题] [1,0,2,0,3,12]=>[1,2,3,12,0,0]把0往后排,然后非0数字顺序不变,不使用额外空间

169.继承,除了class以外,其他继承方式有哪些,各有什么优缺点

丢个链接:

168.css画三角形有哪几种

方法1

#triangle-up {
    width: 0;
    height: 0;
    border-left: 50px solid transparent;
    border-right: 50px solid transparent;
    border-bottom: 100px solid red;
}

方法2

#triangle-up {
    width: 0;
    height: 0;
    border: 50px solid;
    border-color: transparent transparent red transparent;
}

167.401和403的区别 ,301和307区别

  • 401 Unauthorized 未授权
  • 403 Forbidden 拒绝访问
  • 301 永久重定向
  • 302 临时重定向,但是会在重定向的时候改变 method: 把 POST 改成 GET
  • 307 临时重定向,在重定向时不会改变 method

166.webpack中context.require()

// 使用规则:
require.context(directory, useSubdirectories, regExp)
require.context(要查找的文件路径,是否查找子目录,要匹配文件的正则)

// 举例:
require.context('./components/', true, /\.js$/)

165.代码输出

function foo () {
  console.log(this.a)
}
var obj = { a: 1 }
var a = 2

foo()
foo.call(obj)
foo().call(obj)

输出:

2 1 2 报错

解析:

foo()直接调用,this指向windowconsole.log(this.a),则是window下的a;2
foo.call(obj),call改变this指向指向obj,console.log(this.a)则是obj下的a;1
foo().call(obj),先执行foo() ,原理和第一个一样打印2;返回值不是函数,没有call方法,接着会报错‘Cannot read properties of undefined (reading 'call')’

2021.12.23

164.行内元素,块级元素,行内块元素的区别

行内元素块级元素行内块
display为inlinedisplay为blockdisplay为inline-block
多个元素单行展示单个元素独占一行展示多个元素单行展示
不能设置宽高可以设置宽高有宽高
水平margin和水平padding生效margin padding生效margin padding生效

163.http和https的区别

  • HTTPS协议需要CA证书,费用较高;而HTTP协议不需要;
  • HTTP协议是超文本传输协议,信息是明文传输的,HTTPS则是具有安全性的SSL加密传输协议;
  • 使用不同的连接方式,端口也不同,HTTP协议端口是80,HTTPS协议端口是443;
  • HTTP协议连接很简单,是无状态的;HTTPS协议是有SSL和HTTP协议构建的可进行加密传输、身份认证的网络协议,比HTTP更加安全。

162.new的执行过程,简单自实现一个new

(1)创建了一个新的空对象

(2)设置原型,把构造函数的prototype设置给新对象的__proto__属性

(3)调用构造函数,把this指向新对象,这样就把构造函数的属性和方法赋值给新对象了

(4)会做数据类型判断,并返回这个新对象。

function objectFactory(){
    // 把数组的第一个元素从其中删除,并返回第一个元素的值=>第一个为构造函数后面的为参数
    let constructor = Array.prototype.shift.call(arguments)
    // 限定第一个参数必须为构造函数
    if (typeof constructor !== "function") {
        console.error("第一个参数应为函数")
        return
    }
    // 创建一个空对象
    let newObject = null
    
    // 将构造函数的prototype赋给新对象的__proto__
    newObject = Object.create(constructor.prototype)
    
    // 相当于newObject.constructor(arguments)
    let result = null // 将this指向新对象,执行构造函数,将属性方法赋值给添加到这个新对象里
    result = constructor.apply(newObject, arguments)
    
    // 判断返回的类型-对象或者函数
    let flag = result && (typeof result === "object" || typeof result === "function")
    
    // 如果是函数或者对象则返回结果,否则返回构造函数
    return flag ? result : newObject
}

// 使用方法
objectFactory(构造函数, 初始化参数);
let a = objectFactory(Array, '111', '2222')
console.log(a, 'aa') //  ["111", "2222"] "aa"


2021.12.22

161.代码输出题,最好简述过程

Promise.resolve().then(() => {
  console.log('promise1');
  const timer2 = setTimeout(() => {
    console.log('timer2')
  }, 0)
});
const timer1 = setTimeout(() => {
  console.log('timer1')
  Promise.resolve().then(() => {
    console.log('promise2')
  })
}, 0)
console.log('start');

答案:// start promise1 timer1 promise2 timer2 原因:

代码执行过程如下:

首先,Promise.resolve().then是一个微任务,加入微任务队列
执行timer1,它是一个宏任务,加入宏任务队列
继续执行下面的同步代码,打印出start
这样第一轮宏任务就执行完了,开始执行微任务Promise.resolve().then,打印出promise1
遇到timer2,它是一个宏任务,将其加入宏任务队列,此时宏任务队列有两个任务,分别是timer1、timer2;
这样第一轮微任务就执行完了,开始执行第二轮宏任务,首先执行定时器timer1,打印timer1;
遇到Promise.resolve().then,它是一个微任务,加入微任务队列
开始执行微任务队列中的任务,打印promise2;
最后执行宏任务timer2定时器,打印出timer2;

160.简述XSS和CSRF

xss是跨站脚本攻击,分三种类型:

  • 存储型(攻击者将恶意代码通过请求发送到服务器,服务器把东西存到数据库,请求的时候从数据库中取出来拼接到html上会执行),
  • 反射型(攻击者将恶意代码拼接在url上,浏览器将url的代码取下来拼接在html上也会立即执行),
  • DOM型(攻击者将恶意代码放在url后面,浏览器将url上的恶意代码取下来,通过dom的操作去执行恶意代码);

解决方案:所以我们可以把script标签尖括号进行转义成&符npmb等,这样就不可被执行了;还可以用CSP建立白名单,这个只需要配置,如何拦截是浏览器那边实现的


csrf是跨站请求伪造攻击,通过引诱客户点击第三方网站冒充用户执行一些操作

解决方法:校验该接口是用户正常请求还是通过黑客攻击的方式,可以通过origin去拿到请求连接是否是允许访问的站点发送的请求;还可以CSRF Token验证,双方约定token的值,前后端对token进行解析,对比token是否一致;然后还可以做验证,不能直接调接口,还需要验证码

159.|| 和 && 操作符的返回值

|| 和 && 首先会对第一个操作数执行条件判断,如果其不是布尔值就先强制转换为布尔类型,然后再执行条件判断。|| 和 && 返回它们其中一个操作数的值,而非条件判断的结果。

  • || 来说,如果条件判断结果为 true 就返回第一个操作数的值,如果为 false 就返回第二个操作数的值。如果有一个为真则结果为真,只要有真值则返回第一个出现的真值,如果都是假值则返回最后的假值

  • && 则相反,如果条件判断结果为 true 就返回第二个操作数的值,如果为 false 就返回第一个操作数的值。表示并,如果都真的话,返回后面的值;如果有假的话,返回第一个出现的假值

158.简述CSS的两种盒模型

盒模型都是由四个部分组成的,分别是margin、border、padding和content。

标准盒模型和IE盒模型的区别在于设置width和height时,所对应的范围不同:

  • 标准盒模型的width和height属性的范围只包含了content,
  • IE盒模型的width和height属性的范围包含了border、padding和content。

可以通过修改元素的box-sizing属性来改变元素的盒模型:

  • box-sizeing: content-box表示标准盒模型(默认值)
  • box-sizeing: border-box表示IE盒模型(怪异盒模型)

2021.12.21

157.Vue项目优化,有哪些办法?

  1. 使用webpack-bundle-analyzer,分析包大小
  2. 使用babel插件dynamic-import-node区分开发生产环境导入页面
  3. 脚手架工程,使用NODE_ENV=production,因为内部在生产模式会进行优化
  4. 按需引入第三方组件库
  5. 使用骨架屏提升用户体验
  6. 首页不使用懒加载,一同打包进主文件,会加快渲染
  7. 频繁切换操作多使用v-show而不是v-if
  8. v-for数据都要带上key,key保证唯一,提升diff性能,并且不能为循环的index,因为不能保证数据跟index的唯一关联性
  9. 利用事件冒泡的特性,v-for循环列表如果需要添加事件操作,在父组件上添加事件
  10. 与后端沟通接口尽量使用分页,如果不能分页,使用虚拟列表等方式优化
  11. 生产环境打包使用gzip和关闭sourcemap以减小包大小
  12. 按需加载组件/使用路由懒加载、异步组件
  13. v-if和v-for不能连用
  14. 使用cdn加载第三方模块
  15. 防抖、节流
  16. 第三方模块按需导入
  17. 长列表滚动到可视区域动态加载
  18. 图片懒加载
  19. SEO优化
  20. 预渲染
  21. 服务端渲染SSR
  22. 打包优化
  23. 压缩代码
  24. Tree Shaking/Scope Hoisting
  25. 多线程打包happypack
  26. splitChunks抽离公共文件
  27. sourceMap优化
  28. SPA 页面采用keep-alive缓存组件
  29. 使用缓存(客户端缓存、服务端缓存)优化

等等等........

156. 有 1000 个 dom,需要更新其中的 100 个,如何操作才能减少 dom 的操作?

添加多个dom元素,可以把元素先append进DocumentFragment中去,最后再统一将DocumentFragment添加到页面中

155. (字符最短距离) 给定一个字符串 S 和一个字符 C。返回一个代表字符串 S 中每个字符到字符串 S 中的字符 C 的最短距离的数组。

示例 : 输入: S = "loveleetcode", C = 'e' 输出: [3, 2, 1, 0, 1, 0, 0, 1, 2, 2, 1, 0]

说明:
    - 字符串 S 的长度范围为 [1, 10000]。
    - C 是一个单字符,且保证是字符串 S 里的字符。
    - S 和 C 中的所有字母均为小写字母。

var shortestToChar = function (S, C) {//TODO}

答案:


var generate = function(S, C) {
  const n = S.length
  let left = S[0] === C ? 0 : n,
      right = S.indexOf(C, 1)
  const res = new Array(n)
  for (let i = 0; i < n; ++i) {
    // 当前字符距离左右两边C字符的距离
    res[i] = Math.min(Math.abs(i - left), Math.abs(right - i))
    // 窗口右移
    if (i === right) {
      left = right
      right = S.indexOf(C, left + 1)
    }
  }
  return res
}

定义左右边界,给一个足够长的长度,这样在下面遍历的时候abs(i - left) 一定是一个很大的数,min就一定是拿到right边界

2021.12.20

154.vue 虚拟dom的解析过程?

  • 首先对将要插入到文档中的 DOM 树结构进行分析,使用 js 对象将其表示出来,比如一个元素对象,包含 TagName、props 和 Children 这些属性。然后将这个 js 对象树给保存下来,最后再将 DOM 片段插入到文档中。
  • 当页面的状态发生改变,需要对页面的 DOM 的结构进行调整的时候,首先根据变更的状态,重新构建起一棵对象树,然后将这棵新的对象树和旧的对象树进行比较,记录下两棵树的的差异。
  • 最后将记录的有差异的地方应用到真正的 DOM 树中去,这样视图就更新了。

153.为什么要用虚拟DOM?

(1)保证性能下限,在不进行手动优化的情况下,提供过得去的性能

看一下页面渲染的流程:解析HTML -> 生成DOM -> 生成 CSSOM -> Layout -> Paint -> Compiler

下面对比一下修改DOM时真实DOM操作和Virtual DOM的过程,来看一下它们重排重绘的性能消耗∶

  • 真实DOM∶ 生成HTML字符串+重建所有的DOM元素
  • 虚拟DOM∶ 生成vNode+ DOMDiff+必要的dom更新

Virtual DOM的更新DOM的准备工作耗费更多的时间,也就是JS层面,相比于更多的DOM操作它的消费是极其便宜的。尤雨溪在社区论坛中说道∶ 框架给你的保证是,你不需要手动优化的情况下,依然可以给你提供过得去的性能。

(2)跨平台

Virtual DOM本质上是JavaScript的对象,它可以很方便的跨平台操作,比如服务端渲染、uniapp等。

152.虚拟DOM真的比真实DOM性能好吗?

分情况,简单dom和大量复杂dom,复杂的DOM进行差异比较比那些简单的DOM直接渲染更废性能,虚拟dom存在的意义从来就不是只为了性能,跨平台,跨端,能让你数据驱动,告别手动操作dom,这些都是他的核心价值

  • 首次渲染大量DOM时,由于多了一层虚拟DOM的计算,会比innerHTML插入慢。
  • 正如它能保证性能下限,在真实DOM操作的时候进行针对性的优化时,还是更快的。

2021.12.17

151.性能优化的方法有哪些

答案在143题

150.promise 里面放一个setTimeout的代码块在抛出一个异常,catch能否捕做到?

不可以~要reject且promise.catch才能捕获到

promise一旦创建无法中途取消,如果不设置回调promise内部抛出的错误不会反应到外部

149.代码输出题

function fn1() {console.log(11)}
function fn2() {console.log(22)}
fn1.call(fn2); // 打印 
fn1.call.call.call.call(fn2); // 打印

答案: fn1.call.call之后无论call多少this指向的都是window,相当于window.fn2()

11
22

148.代码输出题

[] == ![] // true 隐式转换相当于比较 0 == 0  
{} == {} // false  两个对象地址不一样 
{} + [] // 0  {}开头的话会被解析成一个空代码块,+[]会被隐式转换成0

2021.12.16

147.什么是浏览器同源策略

同源策略是浏览器自我保护的一种策略。协议域名端口只要有其一不同即为跨域

146.为什么不建议用通配符来初始化css样式

通配符消耗太大,* 不仅仅因为它是缓慢和低效率的方法,而且还会导致一些不必要的元素也重置了外边距和内边距

dom树 +cssom才会是渲染树,cssom是挂在dom节点上的 一一对应,所以会很消耗性能

145.实现一个最简单的vue2.0响应式

const data = {
  name: '张三',
  age: 86
}
function render() {
  document.write(`我的名字是${data.name},年龄是${data.age}`)
}
function $watch('name', ()=> {
  console.log('名字被修改了')
})

答案:

const data = {
  a: {
    b: 343,
  },
  name: "菜鸡",
  age: 18,
}

function walk(data) {
  for (const key in data) {
    let dep = []
    let val = data[key]
    const nativeString = Object.prototype.toString.call(val)
    if (nativeString === "[object Object]") {
      walk(val)
    }
    Object.defineProperty(data, key, {
      get() {
        dep.push(Target)
        return val
      },
      set(newval) {
        if (newval === val) return
        val = newval
        dep.forEach((fn) => {
          fn()
        })
      },
    })
  }
}

walk(data)

// 全局的变量,用来存储 fn
let Target = null
function $watch(exp, fn) {
  Target = fn
  let patharr,
    obj = data
  // 如果exp是一个函数
  if (typeof exp === "function") {
    exp()
    return
  }
  // 如果是 data[a][b]
  if (/\./.test(exp)) {
    patharr = exp.split(".")
    patharr.forEach((p) => {
      obj = obj[p]
      return
    })
  }
  // 触发data 不然怎么存 fn
  data[exp]
}

function render() {
  return document.write(`我的名字是${data.name},年龄是${data.age}`)
}

$watch("a.b", () => {
  console.log("名字被修改了")
})

$watch(render, render)


144.说说强制缓存和协商缓存  1.0到1.1的改进

丢一个很棒的浏览器缓存知识梳理:juejin.cn/post/694793…

1.0使用expires、If-Modified-Since

1.1使用Etag、if-match、If-None-Match

2021.12.15

143. (开放题)工作中,怎么优化自己的应用

实际项目中用过

1.缓存机制( 可以聊浏览器缓存和nginx 相关的gzip压缩及缓存配置(具体为啥这么配置));

2:体验(骨架屏及图片懒加载实现;蒙层引导2种实现方式;防抖节流)

3:项目代码优化(webpack相关的打包配置 按需加载,以及日常用的工具包)

超全总结: image.png

142.为什么dom操作回影响页面性能,结合浏览器渲染原理回答

对dom操作会触发浏览器的重排或者重绘,主要是看布局是否发生变化,浏览器存在渲染队列,可以将多次DOM操作也放在一起,用于优化性能

141.vue-cli如何配置webpack(涉及脚手架原理,可不答)

image.png

2021.12.14

140.Vue的keep-alive是如何实现的,具体缓存的是什么?

      const KeepAliveImpl = {
        __isKeepAlive: true,
        inheritRef: true,
        props: {
          include: [String, RegExp, Array],
          exclude: [String, RegExp, Array],
          max: [String, Number],
        },
        setup(props: KeepAliveProps, { slots }: SetupContext) {
          // 省略其他代码...

          return () => {
            if (!slots.default) {
              return null;
            }
            // 拿到组件的子节点
            const children = slots.default();
            // 取第一个子节点
            let vnode = children[0];
            // 存在多个子节点的时候,keepAlive组件不生效了,直接返回
            if (children.length > 1) {
              current = null;
              return children;
            } else if (
              !isVNode(vnode) ||
              !(vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT)
            ) {
              current = null;
              return vnode;
            }
            // 省略其他代码...

            // 返回第一个子节点
            return vnode;
          };
        },
      };
      

keep-alive 具体是通过cache数组缓存所有组件的vnode实例。当cache内原有组件被使用时会将该组件key从keys数组中删除,然后push到keys数组最后面,方便清除最不常用组件 步骤总结

1.获取keep-alive下第一个子组件的实例对象,通过它去获取这个组件的名字

2.通过当前组件名去匹配原来include和exclude,判断当前组件是否需要缓存,不需要缓存直接返回当前组件的实例vnode

3.需要缓存,判断它当前是否在缓存数组里面,存在的话就将它原来的位置上的key给移除,同时将这个组件的key放到数组最后面

4.不存在的话,将组件key放入数组,然后判断当前key数组是否超过max所设置的范围,超过的话那就削减没使用时间最长的一个组件的key值

5.最后将这个组件的keepAlive设置为true

keep-alive本身的创建过程和patch过程

缓存渲染的时候,会根据vnode.componentInstance(首次渲染 vnode.componentInstance为undefined)和keepAlive属性判断不会执行组件的created、mounted等钩子函数,而是对缓存的组件执行patch过程:直接把缓存的DOM对象直接插入到目标元素中,完成了数据更新情况下的渲染过程

丢个图理解: image.png

2021.12.13

139.代码输出

function side(arr) {
  arr[0] = arr[2];
}
function a(a, b, c = 3) {
  c = 10;
  side(arguments);
  return a + b + c;
}
a(1, 1, 1);

function side(arr) {
  arr[0] = arr[2];
}
function a(a, b, c) {
  c = 10;
  console.log(arguments);
  side(arguments); 
  return a + b + c;
}
a(1, 1, 1);

答案:

12
Arguments(3[1, 1, 10, callee: ƒ, Symbol(Symbol.iterator): ƒ]
21

138.对闭包的看法,为什么要用闭包?说一下闭包的原理以及应用场景

闭包:a函数嵌套b函数,b内引用a内的变量,这样就会形成闭包,使用闭包可以私有化变量,缓存变量,缺点是可能导致内存泄漏,因为内部函数一直引用外部函数的变量,导致引用没有被释放,在垃圾回收机制循环的时候,检测到这个变量在被引用就不会释放掉这部分的内存,所以就会导致内存泄漏;

应用场景:防抖,节流

137.this指向

this是执行上下文中的一个属性,是在创建执行上下文的时候创建的,所以this的指向是在调用函数的时候才确定的

this的指向有7种情况:

  1. 函数调用:fn() ,this 指向全局对
  2. 对象调用:obj.fn(),this 指向obj
  3. 构造器调用:var a = new Object() ,this 指向a
  4. apply/call/bind调用:this指向第一个参数,其中bind分2种情况
  5. 箭头函数:this指向捕获其上下文的this值
  6. class中调用,this也是指向实例
  7. 定时器:定时器内的function下的this通常情况下是指向window,除非用bind,call,箭头函数等特殊方法

136.css伪类与伪元素的区别

答案在36题

2021.12.10

135. 文件指纹是什么?怎么用

文件的唯一标识,通常是文件的md5码;从网络上下载了软件后,想确保此软件没有被人修改过(如添加了木马/病毒/非官方插件),或在下载中被破坏,可以用文件指纹验证(MD5)技术进行确认;在各种应用系统中,如果需要设置账户,那么就会涉及到存储用户账户信息的问题,为了保证所存储账户信息的安全,通常会采用MD5加密的方式来,进行存储。

丢出一个链接:www.cnblogs.com/gsw-mayuan/…

134. 手写一个简单的DOM2JSON,将dom结构转换为json的形式输出

function dom2Json(domtree) {
  let obj = {}
  obj.name = domtree.tagName
  obj.children = []
  domtree.childNodes.forEach(
    child => obj.children.push(dom2Json(child))
    )
  return obj
}

133.::before 和 :after中双冒号和单冒号有什么区别?解释一下这2个的作用

单冒号(:)用于CSS3伪类,双冒号(::)用于CSS3伪元素。

  • ::before 创建一个伪元素,其将成为匹配选中的元素的第一个子元素
  • :after 在被选元素的内容后面插入内容。
伪类伪元素
将特殊的效果添加到特定选择器上在内容元素的前后插入额外的元素或样式
已有元素上添加类别的,不会产生新的元素这些元素实际上并不在文档中生成。它们只在外部显示可见,但不会在文档的源代码中找到它们

132. 制作一个访问量很高的大型网站,你会如何来管理所有CSS文件,js 与图片(开放题)

涉及到人手、分工、同步;

先期团队必须确定好全局样式(globe.css),编码模式(utf-8) 等

编写习惯必须一致(例如都是采用继承式的写法,单样式都写成一行);

标注样式编写人,各模块都及时标注(标注关键样式调用的地方);

页面进行标注(例如 页面 模块 开始和结束);

CSS跟HTML 分文件夹并行存放,命名都得统一(例如style.css)

JS 分文件夹存放 命民以该JS 功能为准英文翻译;

图片采用整合的 images.png png8 格式文件使用 尽量整合在一起使用方便将来的管理

2021.12.09

131.webpack 的 code spliting 是如何动态加载 chunk 的?

产生Chunk的三种途径:

  1. entry入口
  2. 异步加载模块
  3. 代码分割(code spliting) 其中代码分割(code spliting)的实现:
module.exports = {
  entry: {
    main: __dirname + "/app/main.js",
    other: __dirname + "/app/two.js",
  },
  output: {
    path: __dirname + "/public",//打包后的文件存放的地方
    filename: "[name].js", //打包后输出文件的文件名
    chunkFilename: '[name].js',
  },

  optimization: {
    runtimeChunk: "single",
    splitChunks: {
      cacheGroups: {
        commons: {
          chunks: "initial",
          minChunks: 2,
          maxInitialRequests: 5, // The default limit is too small to showcase the effect
          minSize: 0 // This is example is too small to create commons chunks
        },
        vendor: {
          test: /node_modules/,
          chunks: "initial",
          name: "vendor",
          priority: 10,
          enforce: true
        }

      },
    }
  }
}

image.png 丢个参考连接:juejin.cn/post/684490…

130.webpack的runtime 做了哪些事情?

image.png 大致做了以下的事情: image.png

2021.12.08

129.forEach中return有效果吗?如何中断forEach循环

image.png 有效果,但只是不走本次循环,后面的还会走

  • throw error
  • try…catch

推荐使用for

128.介绍一下setTimeout的运行机制

setTimeoutl的运行机制是将指定的代码移出本次执行,等到下一轮Event Loop 时,再检查是否到了指定时间。 如果到了,就执行对应的代码;如果不到,就等到再下一轮Event Loop 时重新判断

127.git pull和git fetch 的区别

  • git pull 是将远程主机的最新内容拉下来后直接合并,即:git pull = git fetch + git merge,这样可能会产生冲突,需要手动解决。

  • git fetch是将远程主机的最新内容拉到本地,用户在检查了以后决定是否合并到工作本机分支中

2021.12.07

126.JS 中继承实现的几种方式?

答案在 98 题

125.computed和watch有何区别?

答案在105 题

124.字面量new出来的对象和 Object.create(null)创建出来的对象有什么区别?

new 是通过构造函数创建一个对象, 创建出来的对象具有构造函数内部的属性和方法, 而Object.create(null)是以null为原型创建对象,内部的属性和方法都没有;

2021.12.06

123.webpack HMR 原理

hmr是热更新那块的webpack complier将对应的文件打包成了bundle.js发送给了bundler server,浏览器就可以通过访问服务器的方式获取bundle.js

如果文件更新的话webpack compiler会重新编译,发送给hmr server,hmr会判断哪些文件哪些资源发生了变化,然后发送给hmr runtime,hmr runtime就会更新代码

webpack-dev-server主要依靠webpack express 和websocket实现的热更新 express启动本地服务,浏览器访问时可以做响应 服务端和客户端使用的websocket实现的长连接 webpack监听源文件的变化,即当开发者保存文件时触发webpack重新编译,每次编译都会生成hash值、已改动模块的json文件、已改动模块代码的js文件,编译完成后通过socket向客户端推送当前编译的hash戳 客户端的websocket监听hash,如果不同就重新请求资源,相同走缓存

122.tree shakking 原理

丢连接:webpack.wuhaolin.cn/

121.tapable

tapable就是webpack的lirbrary,核心原理是订阅发布模式,作用是将各个插件串起来,compiler和compilation都是它的实例;

120.介绍下 compiler 和 compliation

compiler就是包含了webpack所有的配置信息,类似options、loaders、plugins这些,全局唯一,是在webpack启动时实例化的;对象可以访问hooks属性

compilation相对来说生命周期短暂一些,每一次编译都会重新实例化,它包含了当前的模块资源、编译生成资源、变化的文件等,通过它也能读到compiler; 是打包的上下文对象 存放打包后的结果

119.什么是 pitch loader

118.如何实现一个 loader,loader如何调试,loader的this指向谁

117.说下短链

116.vue2 数组为什么只拦截 push pop shift unshift sort splice reverse

只拦截这七个方法,是因为这些方法都会更改原数组

115.vue2 和 vue3 diff区别

114.TCP 队头阻塞

Tcp队头堵塞是http1.1的,因为管道通信,可以一个tcp连接发送多个请求,但是处理只能一个一个处理,所以只要队头的处理时间过长就会导致后续的无法处理,造成队头堵塞

113.react hooks 闭包陷阱

2021.12.03

112. Webpack构建流程简单说一下

  1. 初始化参数: 以 shell webpack.config.js 来获取完整的配置参数
  2. 开始编译: 使用参数初始化一个Complier对象,加载所有的配置,开始执行编译
  3. 确定入口: 根据entry中的配置,找出所有的入口文件
  4. 编译模块: 从入口文件开始,调用所有的loader,再去递归的找依赖
  5. 完成模块编译: 得到每个模块被翻译后的最终内容以及他们之间的依赖关系;依赖关系的生成主要是通过JS解析器生成AST对象,从中找出依赖关系
  6. 输出资源: 根据得到的依赖关系,组装成一个个包含多个module的chunk
  7. 输出完成: 根据配置,确定要输出的文件名以及文件路径

111. 原型链判断输出

Object.prototype.__proto__ 原型链的尽头是什么 
Function.prototype.__proto__ 答案是什么 
构造函数自身的__proto__是什么 
Object.__proto__ 答案是什么 
Object instanceof Function 
Function instanceof Object 
Function.prototype===Function.__proto__

110.new本质

当通过“new 函数()”的语法创建对象时,函数被称为构造函数;new了一个构造函数做了以下四件事:

(1)创建了一个新的空对象

(2)设置原型:将对象的__proto__设置为函数的 prototype 对象。

(3)执行构造函数的代码,让函数的 this 指向这个新对象(为这个新对象添加属性方法)

(4)判断函数的返回值类型,如果是值类型,返回创建的对象。如果是引用类型,就返回这个引用类型的对象。

2021.12.02

109.bind 的实现

//  手写bind
function myBind(context, ...args) {
  context = context || window

  const fn = Symbol()
  context[fn] = this
  const _this = this

  const result = function(...args2) {
    if (this instanceof _this) {
      this[fn] = _this
      return this[fn](...args, ...args2)
    } else {
      return context[fn](...args, ...args2)
    }
  }

  result.prototype = Object.create(this.prototype)
  return result
}

108.如何从浏览器的事件循环的角度来说说如何更高效提升视图渲染和JS执行的效率?

将大量 DOM 的变动放入 micro task 队列中,可以将 DOM 变动更快的呈现给用户。(主要依据的就是,在一次事件循环中,task和 micro task 执行完毕后,通常会执行浏览器的绘制渲染,在流程中 micro task 也更接近绘制渲染,同时如果将大量 DOM 的变动放入到 task 任务中,会导致整体的阻塞)

首先一个事件循环处理程序有1个或者多个 task 队列,这里的 task 队列其实就是 macro task 队列,为什么会有多个 task 队列呢,是因为这些 task 队列中 task 都是有不同的任务源所提供的。比如说操作 DOM 的 task(说白了就是操作 DOM 的 api),就是由 DOM 操作任务源提供;为页面上的元素添加的点击、滑动事件这些用户交互的 task(说白了就是各种 DOM 事件的回调函数,或者说事件处理程序),是由用户交互任务源提供;页面上的 ajax 请求 task,是由网络任务源所提供;还有 history traversal 任务源,也就是我们熟知的

history.back、history.go 这些;还有 setTimeout、setInterval、setImmediate 也是任务源,主要概括起来由以下几种:setTimeout、setInterval、setImmediate、I/O、UI rendering。其实 script 本身也是一个任务源,script 本身作为任务主要是在解析 HTML 文档的时候被解析执行,只有解析和执行了 script 才有后续。

不同于 task 队列,一个事件循环中只有1个 micro task 队列,micro task 队列中的 micro task 主要任务源有process.nextTick、promise、Object.observe、MutationObserve 这些。 事件循环运作的大致过程是:事件循环处理程序会循环检查 task 队列中是否有待执行的 task,如果有 task 就会取出来放到执行栈来执行,当此次循环中 task 执行完毕后,一个 micro task 检查点的程序会检查是否有需要执行的 micro task,如果有则会将需要执行的 micro task 放入到主执行栈中执行,直至将将本次循环中所有需要执行的 micro task 执行完(一次事件循环中,执行的 micro task 是有数量限制的),这个过程是一个不断循环的过程。在执行完 micro task 队列中的任务后,浏览器有可能会渲染更新。

107.事件循环是否会导致渲染:

在一轮事件循环中多次修改同一 DOM,只有最后一次会进行渲染绘制。 渲染更新会在事件循环中的 task 和 micro task 完成后进行,但并不是每轮事件循环都需要更新渲染,这取决于是否修改了 DOM 和浏览器是否有必要在此时将新状态呈现给用户。如果在一帧的时间内(通常是16.7ms,通常情况下浏览器会在1s内进行60次的视图刷新)修改了多处 DOM,浏览器可能将这些变动收集起来,只进行一次绘制。 如果希望在每轮事件循环都呈现 DOM 的变动,可以使用 requestAnimationFrame(raf)。

106.什么是事件循环

协调这些脚本计算运行、操作DOM、网络、渲染的一种处理机制,因为 Js 是单线程,他只有一个主执行栈,事件循环就是为了协调这些任务在什么时机进入主执行栈执行的一种处理程序,因为有些任务可以在当前就执行,有些任务并当前不需要执行或者说必须要等待一段时间才可以执行,比如普通的计算赋值就是当前就可以执行的,而比如像 ajax 请求,DOM 事件的回调,定时器这些,需要特定触发条件或者等待一段时间的才能执行,如何让这些任务在合理的时机内高效的运行,这就是事件循环的作用了。

2021.12.01

105.vue:Computed 和Watch的区别

computedwatch
计算属性,依赖别的值进行计算侦听属性,监听已有的数据发生变化做出相应的逻辑改变
多对一监听一对多监听
有缓存无缓存
不支持异步支持异步

104.vue:如何保持页面当前状态

keep-alive:保持页面状态

103. 浏览器打开一个页面需要启动多少进程,渲染进程包含哪些线程

打开一个页面需要启动多少进程:浏览器进程,CPU进程,第三方插件进程,Renderer进程

默认情况下一个页面一个进程,有两点除外:1. 新建的没有搜索的空白页 共用同一渲染进程;2. b页面从a页面打开,并且属于同一站点 共用同一渲染进程 3. Chrome的默认策略

渲染进程(浏览器内核)包括:GPU渲染线程, Js引擎线程, 事件触发线程, 定时器出发线程 ,异步http请求线程

102. for in 和 for of 的区别,for of怎么才能遍历对象?

for..of适用遍历数/数组对象/字符串/map/set等拥有迭代器对象的集合.但是不能遍历对象,因为没有迭代器对象.与forEach()不同的是,它可以正确响应break、continue和return语句 for-of循环不支持普通对象,但如果你想迭代一个对象的属性,你可以用for-in循环(这也是它的本职工作)或内建的Object.keys()方法:

2021.11.30

101.SSR到底快在哪里?为啥性能比spa好?

SSR就是把数据和网络一起加载过来了一起渲染;传统的是先加载网页再加载数据,浏览器少了编译js的步骤,减少了服务器的请求先加载html,直接把组件给你编译成字符串输出html; 所以更快,缺点就是比较耗资源;

而Spa,页面只有一个dom;没办法SEO;seo不好优化;

100.如何加快页面渲染速度?

  • 高频事件防抖(rAF)
  • 资源体积过大方面:资源压缩,传输压缩,代码拆分,Tree shaking,HTTP/2,缓存
  • 首页内容太多:路由/组件/内容 lazy-loading,预渲染/SSR,Inline CSS
  • 加载顺序不合适:prefetch,preload
  • 减少JS脚本引入
  • 使用webpage格式图片
  • 小图使用svg
  • 通过webpack压缩代码
  • 按需引入
  • Tree shaking
  • http缓存
  • CDN
  • 懒加载
  • 减少重绘回流
  • 压缩静态资源
  • perfomanceAPI
  • dom缓存查询
  • ssr服务器渲染(如果是ssr的话,可以考虑采用bigPige做分块传输;是facebook提出的动态网页加载技术,原理就是把网页分解成一小块一小块,然后分块传输到浏览器端进行渲染,以此提高首屏渲染速度;和 分屏加载区别就是bigPige给你的是html结构;然后分屏加载更像是用户看到了我才给你加载)

99.浏览器的缓存机制?

丢一个满分答案: juejin.cn/post/694793…

98.js有哪些继承方式?

  • 原型链继承
  • 构造函数继承
  • 原型链和构造函数组合继承,借用构造函数的方式来实现类型的属性继承,将子类型的原型设置为超类型的实例来实现方法的继承。
  • 原型式继承,基于已有对象来创建新对象,Object.create()的实现
  • 寄生式继承,创建一个用于封装继承过程的函数,通过传入一个对象,然后复制一个对象的副本,对这个对象进行扩展,最后返回这个对象。
  • 寄生式组合继承,使用超类型的原型的副本作为子类型的原型。

97.前端解决跨域的方式有哪些?

  • cors
  • jsonp
  • hash
  • nginx
  • nodejs 中间件
  • document.domain + iframe
  • window.name+iframe
  • websocket
  • postMessage

2021.11.29

96.一次性加载几万条数据,要求不卡住界面

这道题考察了如何在不卡住页面的情况下渲染数据,也就是说不能一次性将几万条都渲染出来,而应该一次渲染部分 DOM,那么就可以通过 requestAnimationFrame 来每 16 ms 刷新一次。

 <!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <ul>控件</ul>
  <script>
    setTimeout(() => {
      // 插入十万条数据
      const total = 100000
      // 一次插入 20 条,如果觉得性能不好就减少
      const once = 20
      // 渲染数据总共需要几次
      const loopCount = total / once
      let countOfRender = 0
      let ul = document.querySelector("ul");
      function add() {
        // 优化性能,插入不会造成回流
        const fragment = document.createDocumentFragment();
        for (let i = 0; i < once; i++) {
          const li = document.createElement("li");
          li.innerText = Math.floor(Math.random() * total);
          fragment.appendChild(li);
        }
        ul.appendChild(fragment);
        countOfRender += 1;
        loop();
      }
      function loop() {
        if (countOfRender < loopCount) {
          window.requestAnimationFrame(add);
        }
      }
      loop();
    }, 0);
  </script>
</body>
</html>


95.10 个 Ajax 同时发起请求,全部返回展示结果,并且至多允许三次失败,设计思路

考点是失败重试并且对失败次数控制:并发加retry,可以写一个类 里面有方法,肯定不能直接用promise.all

      // 以下是不完整代码,着重于思路 非 Promise 写法
      let successCount = 0;
      let errorCount = 0;
      let datas = [];
      ajax(url, (res) => {
        if (successCount) {
          successCount++;
          if (successCount + errorCount === 10) {
            console.log(datas);
          } else {
            datas.push(res.data);
          }
        } else {
          errorCount++;
          if (errorCount > 3) {
            // 失败次数大于3次就应该报错了
            throw Error("失败三次");
          }
        }
      });
      
      
      
      // Promise 写法
      let errorCount = 0;
      let p = new Promise((resolve, reject) => {
        if (success) {
          resolve(res.data);
        } else {
          errorCount++;
          if (errorCount > 3) {
            // 失败次数大于3次就应该报错了
            reject(error);
          } else {
            resolve(error);
          }
        }
      });
      Promise.all([p]).then((v) => {
        console.log(v);
      });
      

94.什么是AST,有什么作用和使用场景

AST抽象语法树,可以通过该语法树精确操纵代码的中声明语句,赋值语句,运算语句等,可以在代码的分析,优化,变更的操作使用场景出现。主要是对于代码结构的一种描述;

  • 这边以babel兼容es5为例:babel兼容es5主要会分为3步,分别为根据词法分析和语法分析生成ast语法树,然后针对ast进行修改,最终针对修改后的ast生成代码
  • 在vue源码的模板编译中:将元素先转成ast结构再转成render函数最后返回成虚拟dom再替换原来的元素的

93.rem小数问题如何处理

问题场景:遇到最多的问题就是 background-image 的问题,特别是一些需要在不同大小的屏幕时候,经常会因为小数像素导致背景图被裁掉一部分。

如何避免这种问题呢?

  • 使用 iconfont
  • 如需使用 background-image,尽量为背景图设置一定的空白间隙

92.ES6 模块与 CommonJS 模块的差异

commonJs(require/module.exports)ES Module(import/export default)
1.基本引用类型:值复制,不共享 2.引用类型:浅拷贝,共享只可导入,动态读取
运行时加载静态加载,编译时执行
加载的是一个对象(即module.exports属性)该对象只有在脚本运行完才会生成。加载的不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。
异步加载 可缓存同步 不支持动态加载
1.检查是否有该模块的缓存2.如果有使用缓存3.没有则执行该模块代码并缓存1检查该模块是否引入过2.是则暂时该模块为{} 3.否,进入该模块并执行代码,不做缓存
可修改引入的值不可修改外部引入的值,但可调用引入中包含的方法

amd 推崇依赖前置 define定义 exports导出

cmd是依赖后置 require定义 exports导出

umd是整合了CommonJS和ESModule两个模块定义规范的方法

2021.11.26

91.如何触发BFC

  • float不为 none
  • overflow的值不为 visible
  • position 为 absolute 或 fixed
  • display的值为 inline-block 或 table-cell 或 table-caption 或 grid

90.BFC的应用场景

  1. 清除浮动:BFC内部的浮动元素会参与高度计算,因此可用于清除浮动,防止高度塌陷
  2. 避免某元素被浮动元素覆盖:BFC的区域不会与浮动元素的区域重叠
  3. 阻止外边距重叠:属于同一个BFC的两个相邻Box的margin会发生折叠,不同BFC不会发生折叠

89.介绍一下单一职责原则和开放封闭原则

  • 单一职责:任何一个软件模块都应该有且仅有一个被修改的原因。
  • 开闭原则:一个设计良好的计算机系统应该在不需要修改的前提下就可以轻易被扩展。

88. 介绍一下HTTP/2.0新特性

  • 多路复用: 即多个请求都通过一个TCP连接并发地完成
  • 服务端推送: 服务端能够主动把资源推送给客户端
  • 新的二进制格式: HTTP/2采用二进制格式传输数据,相比于HTTP/1.1的文本格式,二进制格式具有更好的解析性和拓展性
  • header压缩: HTTP/2压缩消息头,减少了传输数据的大小(HPACK算法)
  • 使用帧作为最小传输单位
  • 流量控制(只有数据帧会受到流量控制)
  • HTTPS rfc 规范并没有要求 HTTP2 强制使用 TLS,但是目前世界所有浏览器和服务器实现都基于 HTTPS 来实现 HTTP2

87. 层叠上下文:说一说z-index(+),z-index(0),z-index(-),background,block盒子,inline-block盒子,float盒子的层叠顺序

background,z-index(-),block,float,inline-block,z-index(0),z-index(+)

2021.11.25

86.编写一个 sleep(ms) 函数,用于暂停程序,用法示例如下

async function wait() {
   await sleep(2000);
   console.log("2秒后执行");
}

答案:

function sleep(time) {
  return new Promise((resolve,reject)=>{
    let timer = setTimeout(()=>{
      clearTimeout(timer)
      resolve()
    },time)
  })
}

85.执行结果

3.toSting()
3..toString()
3...toString() 

image.png

3.toString 会报错是因为,3是数值也就是 Number 类型,他不是对象因此没有 toString 方法,let num = 3 num.toString() 不会报错是因为,当执行 num.toString 时,是以读取模式访问 num 的,这个时候就会出现我们之前一直说的 “包装类型”,第二行实际上是 Number(3).toString

运算符优先级的问题,点运算符会被优先识别为数字常量的一部分,然后才是对象属性访问符

在 JS 中,3.1,3.,.3 都是合法的数字

3.toString() 会被 JS 引擎解析成 (3.)toString() 报错

3..toString() 会被 JS 引擎解析成 (3.).toString() "3"

3...toString() 会被 JS 引擎解析成 (3.)..toString() 报错

84. 场景题:我有多张图片,如何组成一个gif

开放题~

83. 算法题

leetcode-cn.com/problems/ro…

image.png

2021.11.24

82. void0是为了什么?

void 0可以确保返回值就是undefined,这样写是为了拿到一个安全的undefined,void这个变量代表的是不返回,里面传入什么都不返回,所以拿到的都是undefined;而undefined是个标识符可以作为属性名,并且值有可能被更改,所以没有使用void属性安全

81. 函数构造函数和函数声明有什么区别?

构造函数创建的实例的原型是指向这个构造函数的,这个构造函数的原型指向Function;而字面量创建函数的话,原型直接指向Function;

new 函数Function constructor不会为其创建上下文创建闭包,它们始终在全局范围内创建;

函数声明可以访问外部函数变量(闭包) 函数构造器:

        var a = 100;
        function createFunction() {
        var a = 200;
        return new Function('return a;');
        }
        console.log(createFunction()()); // 100
        

new Function本质上也就一个对象,所以在运行时才能确定a的值,也就是指向window

函数声明:

        var a = 100;
        function createFunction() {
            var a = 200;
            return function func() {
                return a;
            }
        }
        console.log(createFunction()()); // 200

return function 本质上也是定义了一个函数,在编译阶段就会进行处理,保存上层作用域,也就是a=200

80. 手写一个 Object.is

Object.is不会转换被比较的两个值的类型,这点和===更为相似,他们之间也存在一些区别。

1. NaN在===中是不相等的,而在Object.is中是相等的
2. +0和-0在===中是相等的,而在Object.is中是不相等的

image.png

79. null 和 undefined 有什么区别?

  1. null:空对象,一般可以用来给对象设置一个初始值;也代表未声明未赋值;因为是关键字所以不能作为属性名;typeof null='object'

  2. undefined:声明未赋值,可以作为属性名,任何类型和undefined计算都是NaN,和null计算当做0运算

     1.变量被声明了.但没有赋值时,就等于undefined.
     2.调用函数时,应该提供的参数没有提供,该参数等于undefined. 
     3.对象没有赋值的属性,该属性的值为undefined 
     4.函数没有返回值时,默认返回undefined
    

2021.11.23

78.说说Virtual DOM和真实DOM的区别及优缺点

在浏览器中操作真正的DOM会比操作虚拟dom更消耗性能。频繁的操作DOM,会产生一定的性能问题,这就是虚拟Dom的产生原因。

虚拟dom本质就是用一个原生的JS对象去描述一个DOM节点,是对真实DOM的一层抽象。

虚拟dom映射到真实DOM要经历VNode的create、diff、patch等阶段,而且可适配性更高,可以用作跨端(platforms)。

77.说说你对状态管理的理解

它是一个程序里面的状态管理模式,它是集中式存储所有组件的状态的小仓库,并且保持我们存储的状态以一种可以预测的方式发生变化。

丢一个满分链接:juejin.cn/post/692846…

76.防抖和节流的实现与实际应用场景

防抖实际运用场景:input输入框 onChange的时候触发查询接口调用debounce是每次触发时清除上一次的callback,直到最后一次输入完成之后,间隔指定时间后再去调用callback。 例如用户在input框中不断的输入文字,只有在用户停止输入后一段时间才会去调用查询接口;防抖就是防止用户短时间内连续多次触发事件, 可以用定时器控制一定的时间内不能再触发,如果再触发就return出去,等时间到了后再关闭定时器,用户就能再次触发该事件;

节流运用场景:页面滚动onScroll时需要做一些操作的时候。区别于防抖的点在于 防抖可能只会触发一次,而节流在连续的过程中每间隔 指定的时间 就会触发一次; 节流是解决一个需要频繁触发的事件,但防止事件触发太多次,所以我们用定时器设置每当用户触发了这个事件后重新计时

// 防抖
function debounce(fn,wait){
    let timer=null
    return function(){
        let args=arguments
        clearTimeout(timer)
        timer=setTimeout(()=>{
            fn.apply(this,args)
        },wait)
    }
}
function f1(){
    console.log('111')
}
var db=debounce(f1,500)

// 节流
function throttle(fn,wait){
    let timer=null
    return function(){
        let args=arguments
        if(!timer){
            timer=setTimeout(()=>{
                timer=null
                fn.apply(this,args)
            },wait)
        }      
    }
}

function f2(){
    console.log('222')
}
var db=throttle(f2,500)

2021.11.22

75.事件循环代码题

第一段:

new Promise((resolve, reject) => {
  console.log("外部promise");
  resolve();
})
  .then(() => {
    console.log("外部第一个then");
    return new Promise((resolve, reject) => {
      console.log("内部promise");
      resolve();
    })
      .then(() => {
        console.log("内部第一个then");
      })
      .then(() => {
        console.log("内部第二个then");
      });
  })
  .then(() => {
    console.log("外部第二个then");
  });
  

答案:

// 外部promise
// 外部第一个then
// 内部promise
// 内部第一个then
// 内部第二个then
// 外部第二个then

如果promise 有return,就是等这个promise里面的所有then都执行完,返回一个promise才会执行后面的任务。

第二段:

new Promise((resolve, reject) => {
  console.log("外部promise");
  resolve();
})
  .then(() => {
    console.log("外部第一个then");
    new Promise((resolve, reject) => {
      console.log("内部promise");
      resolve();
    })
      .then(() => {
        console.log("内部第一个then");
      })
      .then(() => {
        console.log("内部第二个then");
      });
  })
  .then(() => {
    console.log("外部第二个then");
  });
  
// 外部promise
// 外部第一个then
// 内部promise
// 内部第一个then
// 外部第二个then
// 内部第二个then

如果直接new promise,那就是任务放到事件队列里面,先执行对级的then,再之后后面同级的then

74.JS原型链的理解

对JS原型链的理解,并完成下列判断

Object.prototype.__proto__ === null    // ?    
Object.__proto__ === Function.prototype    //?    
Function.__proto__ === Function.prototype    //?    
Function.prototype.__proto__ === Object.prototype    //?    
Function.prototype === Function.__proto__    // ?

所有的构造函数都可以看作Function的实例对象,Function自己也是Function构造的

Object要Function的prototype去创建,但Function的prototype要Object去创建

丢一张图帮助理解

image.png 答案自己去控制台试试吧~

73.let是否存在变量提升

根据 ES 文档,认为 let 确实存在提升。只不过由于暂时死区的限制,我们不能在 let x 之前使用 let

我们来对比let和var的声明:

1.我们来看看 var 声明的「创建、初始化和赋值」过程

function fn(){
  var x = 1
  var y = 2
}
fn()
  1. 进入 fn,为 fn 创建一个环境。
  2. 找到 fn 中所有用 var 声明的变量,在这个环境中「创建」这些变量(即 x 和 y)。
  3. 将这些变量「初始化」为 undefined。
  4. 开始执行代码
  5. x = 1 将 x 变量「赋值」为 1
  6. y = 2 将 y 变量「赋值」为 2 var 声明会在代码执行之前就将「创建变量,并将其初始化为 undefined」

2.function 声明的「创建、初始化和赋值」过程

fn2()

function fn2(){
  console.log(2)
}
  1. 找到所有用 function 声明的变量,在环境中「创建」这些变量。
  2. 将这些变量「初始化」并「赋值」为 function(){ console.log(2) }。
  3. 开始执行代码 fn2() 也就是说 function 声明会在代码执行之前就「创建、初始化并赋值」

3.let 声明的「创建、初始化和赋值」过程

{
  let x = 1
  x = 2
}

只看 {} 里面的过程:

  1. 找到所有用 let 声明的变量,在环境中「创建」这些变量
  2. 开始执行代码(注意现在还没有初始化)
  3. 执行 x = 1,将 x 「初始化」为 1(这并不是一次赋值,如果代码是 let x,就将 x 初始化为 undefined)
  4. 执行 x = 2,对 x 进行「赋值」
let x = 'global'
{
  console.log(x) // Uncaught ReferenceError: x is not defined
  let x = 1
}

这就解释了为什么在 let x 之前使用 x 会报错: 原因有两个

  1. console.log(x) 中的 x 指的是下面的 x,而不是全局的 x
  2. 执行 log 时 x 还没「初始化」,所以不能使用(也就是所谓的暂时死区)

看到这里,应该明白了 let 到底有没有提升:

  1. let 的「创建」过程被提升了,但是初始化没有提升。
  2. var 的「创建」和「初始化」都被提升了。
  3. function 的「创建」「初始化」和「赋值」都被提升了。

其实 const 和 let 只有一个区别,那就是 const 只有「创建」和「初始化」,没有「赋值」过程。

重要参考:JavaScript variables lifecycle: why let is not hoisted www.jianshu.com/p/0f49c88cf…

2021.11.19

72.强缓存与协商缓存(304状态码是什么)

浏览器缓存的全过程

  1. 浏览器第一次加载资源,服务器返回 200,浏览器从服务器下载资源文件,并缓存资源文件与 response header,以供下次加载时对比使用;
  2. 下一次加载资源时,由于强制缓存优先级较高,先比较当前时间与上一次返回 200 时的时间差,如果没有超过 cache-control 设置的 max-age,则没有过期,并命中强缓存,直接从本地读取资源。如果浏览器不支持HTTP1.1,则使用 expires 头判断是否过期;
  3. 如果资源已过期,则表明强制缓存没有被命中,则开始协商缓存,向服务器发送带有 If-None-Match 和 If-Modified-Since 的请求;
  4. 服务器收到请求后,优先根据 Etag 的值判断被请求的文件有没有做修改,Etag 值一致则没有修改,命中协商缓存,返回 304;如果不一致则有改动,直接返回新的资源文件带上新的 Etag 值并返回 200;
  5. 如果服务器收到的请求没有 Etag 值,则将 If-Modified-Since 和被请求文件的最后修改时间做比对,一致则命中协商缓存,返回 304;不一致则返回新的 last-modified 和文件并返回 200;

cache-control 的属性:no-cache表示不用强制缓存,no-store表示不用协商缓存,max-age表示最大缓存时间

71.diff

丢个满分链接:juejin.cn/post/695343…

70.var、let、const 的区别

  1. 块级作用域:块作用域由 { }包括,let和const具有块级作用域,只能在块作用域里访问,不能跨块访问,也不能跨函数访问,var不存在块级作用域。
  2. 变量提升:都存在变量提升
  3. 给全局添加属性:浏览器的全局对象是window,Node的全局对象是global。var声明的变量为全局变量,并且会将该变量添加为全局对象的属性,但是let和const不会。
  4. 重复声明:var声明变量时,可以重复声明变量,后声明的同名变量会覆盖之前声明的遍历。const和let不允许重复声明变量。
  5. 暂时性死区:在使用let、const命令声明变量之前,该变量都是不可用的。这在语法上,称为暂时性死区。使用var声明的变量不存在暂时性死区。
  6. 初始值设置:在变量声明时,var 和 let 可以不用设置初始值。而const声明变量必须设置初始值。
  7. 指针指向:let和const都是ES6新增的用于创建变量的语法。 let创建的变量是可以更改指针指向(可以重新赋值)。但const声明的变量是不允许改变指针的指向。

69.谈谈事件循环(含浏览器与 Nodejs)

丢个满分链接:juejin.cn/post/684490…

2021.11.18

68. 如何统计白屏时间和首屏时间,以及LCP的计算

白屏时间: 是从用户开始请求页面时开始计算到开始显示内容结束,中间过程包括DNS查询、建立TCP链接、发送首个HTTP请求、返回HTML文档、HTML文档head解析完毕。

因此影响白屏时间的因素:网络、服务端性能、前端页面结构设计。

通常认为浏览器开始渲染body或者解析完head的时间是白屏结束的时间点。所以我们可以在html文档的head中所有的静态资源以及内嵌脚本/样式之前记录一个时间点,在head最底部记录另一个时间点,两者的差值作为白屏时间

最大内容绘制 LCP: 用于记录视窗内最大的元素绘制的时间,该时间会随着页面渲染变化而变化,因为页面中的最大元素在渲染过程中可能会发生改变,另外该指标会在用户第一次交互后停止记录。计算时间都是需要用到PerformanceObserver

白屏时间主要有2个关注点(FP, FCP):

  • FP (first paint)第一次绘制,输入URL开始,到页面开始有变化,只要有任意像素点变化,都算是白屏时间的完结。
  • FCP (first contenful paint) 第一次内容绘制,页面上绘制了第一个元素。

首次内容绘制 FCP:页面上绘制了第一个元素。 区别:FP 指的是绘制像素,比如说页面的背景色是灰色的,那么在显示灰色背景时就记录下了 FP 指标。但是此时 DOM 内容还没开始绘制,可能需要文件下载、解析等过程,只有当 DOM 内容发生变化才会触发,比如说渲染出了一段文字,此时就会记录下 FCP 指标。因此说我们可以把这两个指标认为是和白屏时间相关的指标,所以肯定是最快越好。

function getLCP() {
        // 增加一个性能条目的观察者
    new PerformanceObserver((entryList, observer) => {
        let entries = entryList.getEntries();
        const lastEntry = entries[entries.length - 1];
        console.log('LCP', lastEntry.renderTime || lastEntry.loadTime);
    }).observe({entryTypes: ['largest-contentful-paint']});
}

function getFP() {
    new PerformanceObserver((entryList, observer) => {
        let entries = entryList.getEntries()
        // console.log(entries)
        for (let i = 0; i < entries.length; i++) {
            if (entries[i].name === 'first-paint') {
                console.log('FP', entries[i].startTime)
            }
        }
        const lastEntry = entries[entries.length - 1]
    }).observe({entryTypes: ['paint']})
}

function getFCP() {
    new PerformanceObserver((entryList, observer) => {
        let entries = entryList.getEntries()
        // console.log(entries)
        for (let i = 0; i < entries.length; i++) {
            if (entries[i].name === 'first-contentful-paint') {
                console.log('FP', entries[i].startTime)
            }
        }
        const lastEntry = entries[entries.length - 1]
    }).observe({entryTypes: ['paint']})
}

首屏时间是整个页面渲染完的时间

首屏加载优化

  • 资源体积过大:资源压缩,传输压缩,代码拆分,Tree shaking,HTTP/2,缓存
  • 首页内容太多:路由/组件/内容 lazy-loading,预渲染/SSR,inline CSS
  • 加载顺序不合适:prefesh,preload

67. 说下http3

HTTP3 由于了HTTP1.1 具有 TCP 队头阻塞问题,所以之后出现了HTTP2,HTTP2采用了头部压缩和帧传递的形式,通过二进制进行传输,有效的减少了TCP阻塞,不过在个别情况下,可能会比HTTP1.1还要慢。所以之后就出现了HTTP3。

HTTP3 由谷歌进行开发,采用了UDP进行处理, 由于 UDP 属于不稳定传输,所以在这上面加了一层 QUIC协议,用来提高传输稳定性。

HTTP3 彻底解决了HTTP1.1、HTTP2的TCP阻塞问题,从而大幅提高速度。

66. prefetch 和 preload 的区别

区别

  • preload 提前加载
  • prefetch 预判加载

preload

preload 属于预加载,通过向浏览器声明一个需要提前加载的资源,当资源被使用的时候,立即执行

<link rel="preload" href="/path/to/style.css" as="style">

prefetch

prefetch 跟 preload 不同,它的作用是告诉浏览器未来可能会使用到的某个资源,浏览器就会在闲时去加载对应的资源,若能预测到用户的行为,比如懒加载,点击到其它页面等则相当于提前预加载了需要的资源。

<link rel="prefetch" href="/path/to/style.css" as="style">

意义

当一个资源被 preload 或者 prefetch 获取后,它将被放在内存缓存中等待被使用,如果资源位存在有效的缓存机制(如 cache-control 或 max-age),它将被存储在 HTTP 缓存中可以被不同页面所使用。

正确使用 preload/prefetch 不会造成二次下载,也就说:当页面上使用到这个资源时候 preload 资源还没下载完,这时候不会造成二次下载,会等待第一次下载并执行脚本。

对于 preload 来说,一旦页面关闭了,它就会立即停止 preload 获取资源,而对于 prefetch 资源,即使页面关闭,prefetch 发起的请求仍会进行不会中断。

65. css解析规则是什么样的

CSS 解析规则属于从后往前进行解析的,因为这样可以提高解析速度,如果从前往后解析,可能解析到一部分会发现不对,所以要从后面进行解析。

而且在CSS 解析过程并不会阻塞HTML进行解析,而渲染会,这个是因为浏览器在渲染的时候,需要对CSS规则进行计算,从而来进行渲染。

64.浏览器进程有哪些

  • GPU 进程
  • 插件进程
  • 渲染进程
  • 浏览器进程(主进程)
  • 网络进程
进程功能
渲染进程分为事件处理线程、GUI渲染线程、定时器触发线程、网络请求线程,默认情况下,每个Tab下面都会有一个渲染进程,渲染进程用来负责页面的解析、网络请求、渲染等。
浏览器进程负责页面的前进、后退、刷新、导航、界面显示、用户交互、子进程管理,同时提供存储等功能。
插件进程负责一些第三方插件的执行,因为插件可能导致浏览器崩溃,一般都是运行在沙箱环境内。
GPU 进程使用初衷是为了实现 3D CSS 的效果,只是随后网页、Chrome 的 UI 界面都选择采用 GPU 来绘制,这使得 GPU 成为浏览器普遍的需求。最后,Chrome 在其多进程架构上也引入了 GPU 进程。
网络进程负责页面的网络请求

线程和进程的区别及优缺点: 进程是cpu进行资源分配的最小单位,线程是cpu进行调度的最小单位,每个tab都有一个渲染进程,进程和线程都可以并发,创建进程的开销大,线程的开销小,而程序的许多功能都需要依赖并发来进行,因此线程诞生,线程属于进程,同进程的线程共享进程的资源内存空间,由于线程开销小的优点,大大提高了程序运行效率。

1.flex:1 代表了什么意思

flex: 1的组成:

  • flex-grow(放大比例,默认为0

  • flex-shrink(缩小比例,默认为1

  • flex-basic(flex 元素的内容盒(content-box)的尺寸,默认auto)的缩写;

flex的第三项指的是flex-basis这个属性,这个属性的“0”和“auto”在大多数情况下都会有所不同:

  • 数值被解释为特定宽度,因此0将与指定“width:0”相同;
  • 0%就相当于容器内所有项目原本的width不起作用,然后平分宽度;
  • auto就相当于容器内所有项目原本的width起作用,伸缩空间减去这些width,剩余空间再平分。所以有的情况下auto和0%是相同的

2种,flex-basis为0%,覆盖width,实际占用0, flex-basis为auto,width权限更高,占用width的值

所以在下面的情况下


     .content {
        display: flex;
      }
     .content1{
        flex:1
      }
      
    <div class="content">
        <div class="content1" style="background-color: blue;">1</div>
        <div class="content1" style="background-color: blueviolet;">2</div>
        <div class="content1" style="background-color: chartreuse;">3</div>
    </div>
    

flex: 1 等价于:

    {
        flex: 1 1 0%;
    }
    {
        flex: 1 1 0%;
    }
    {
        flex-grow: 1;
        flex-shrink : 1; 
        flex-basis: 0%;
    }

2.单行文本溢出省略号和一个向下的三角形的css怎么写

单行文本溢出省略号:

{
    overflow: hidden;            // 溢出隐藏 
    text-overflow: ellipsis;      // 溢出用省略号显示 
    white-space: nowrap;         // 规定段落中的文本不进行换行 
}

多行文本溢出省略号:

{
    overflow: hidden;            // 溢出隐藏 
    text-overflow: ellipsis;     // 溢出用省略号显示 
    display:-webkit-box;         // 作为弹性伸缩盒子模型显示。 
    -webkit-box-orient:vertical; // 设置伸缩盒子的子元素排列方式:从上到下垂直排列 
    -webkit-line-clamp:3;        // 显示的行数 
}

向下三角形:

div {
    width: 0;
    height: 0;
    border-top: 50px solid red;
    border-right: 50px solid transparent;
    border-left: 50px solid transparent;
}
// 或者
div {
    width: 0;
    height: 0;
    border: 50px solid;
    border-color: red transparent transparent transparent;
}
// 或者
div {
  width: 0;
  height: 0;
  border: 50px solid transparent;
  border-top-color: red;
}

3.flex布局有哪些常用属性?

以下6个属性设置在容器上:

  • flex-direction属性决定主轴的方向(即项目的排列方向)。
  • flex-wrap属性定义,如果一条轴线排不下,如何换行。
  • flex-flow属性是flex-direction属性和flex-wrap属性的简写形式,默认值为row nowrap。
  • justify-content属性定义了项目在主轴上的对齐方式。
  • align-items属性定义项目在交叉轴上如何对齐。
  • align-content属性定义了多根轴线的对齐方式。如果项目只有一根轴线,该属性不起作用。

以下6个属性设置在子元素上:

  • order属性定义项目的排列顺序。数值越小,排列越靠前,默认为0。
  • flex-grow属性定义项目的放大比例,默认为0,即如果存在剩余空间,也不放大。
  • flex-shrink属性定义了项目的缩小比例,默认为1,即如果空间不足,该项目将缩小。
  • flex-basis属性定义了在分配多余空间之前,项目占据的主轴空间。浏览器根据这个属性,计算主轴是否有多余空间。它的默认值为auto,即项目的本来大小。
  • flex属性是flex-grow,flex-shrink和flex-basis的简写,默认值为0 1 auto。
  • align-self属性允许单个项目有与其他项目不一样的对齐方式,可覆盖align-items属性。默认值为auto,表示继承父元素的align-items属性,如果没有父元素,则等同于stretch。

4.css如何开启硬件加速

  • 动画实现过程中利用transform: translateZ(0),欺骗浏览器开启GPU加速
  • will-change开启gpu加速,就是性能不太好

5.如何在浏览器画出一个正方形并保证这个正方形是浏览器可视区最大的?

(第1种答案)

body {
  margin:0;
  padding:0;
}
.box{
  width: 100vmin;
  height:100vmin;
  background-color: #ccc;
  margin:0 auto
}

换一种方式,比如使用padding-top,我们想一下 padding-top的百分比是相对什么来的?--- 宽度

(第2种答案)

<body>
    <div class="container">

    </div>
    
</body>
<style>

.container {
    background-color: aqua;
    width: 100vmin;
    padding-top:  100vmin;
}

</style>
<body>
    <div class="container">

    </div>
</body>
<style>

.container {
    background-color: aqua;
    width: 100%;
    padding-top:  100vmin;
}

</style>

上面那个写法对吗?

显然是不对的,因为container是用了body的width去做的padding,导致存在滑动效果 所以我们可以在外面再包一层

(第3种答案)

<body>
    <div class="container">
        <div class="child">

        </div>

    </div>
    
</body>
<style>

.container {
    background-color: aqua;
    width: 100vmin;
}
.child{
    width: 100%;
    padding-top: 100%;
}

</style>

(第4种答案)

div::before {
  content:'',
  padding-top:100%;
  display:block;
  width:100vmin;
}

当然你也可以改成after的写法

2021.11.1

6.数据检测的方法有哪些

typeof

// 只能检测简单数据类型还有function;不能判断数组和对象
typeof(11) === 'number'
typeof(function(){}) === 'function'

instanceof

用法:判断实例 instanceof 构造函数

// 原理:判断在其原型链中能否找到该类型的原型

// 简单数据类型检测不出来,会false
console.log(2 instanceof Number); // false 
console.log(true instanceof Boolean); // false

// 可以用来判断复杂数据类型
console.log([] instanceof Array); // true 
console.log([] instanceof Object) // true 
console.log({} instanceof Object); // true 
console.log(function(){} instanceof Function); // true

constructor

// 虽然简单和复杂数据类型都能判断,但是! 构造函数可以被手动改变,所以也不是百分之百准确
console.log((2).constructor === Number); // true 
console.log((true).constructor === Boolean); // true 
console.log(('str').constructor === String); // true 
console.log(([]).constructor === Array); // true 
console.log((function() {}).constructor === Function); // true console.log(({}).constructor === Object); // true

Object.prototype.toString.bind()

var testMethods = Object.prototype.toString; 
console.log(testMethods.call(2)); // [object Number] 
console.log(testMethods.call(true)); // [object Boolean] 
console.log(testMethods.call('str')); // [object String]

// 实现原理:
// 先判断参数是否是null或者undefined,是的话直接返回结果;
// 否则将参数转为对象,取得该对象的 [Symbol.toStringTag] 属性值(可能会遍历原型链)取得 tag, 然后返回 "[object " + tag + "]" 形式的字符串。

7.如何实现bind

// 模拟bind 自己手写bind函数,可以在原型上增加这个方法
Function.prototype.myBind = function (context) {
    // 如果调用bind方法的对象不是函数的话返回出去
    if (typeof(this) !== 'function') {
        throw new TypeError('类型错误')
    }
    // 把对象转成数组,且把参数截出来存入args中
    var args = [...arguments].slice(1)
    var self = this  // 把调用bind的函数存起来
    return function Fn(){
        //  new的话this指向新创建的实例this;普通调用的this指向新bind的第一个参数context
        return self.apply(this instanceof Fn ? this : context, args.concat(...arguments))
    }
}

// 使用
function fn1 (a, b, c){
    console.log('this', this, a, b, c) // this {x: 100} 10 20 30
    return 'this is fn1'
}

const fn2 = fn1.myBind({x:100},10,20,30) 
const res = fn2()
console.log('fn2', fn2, res) // fn2返回一个函数

8.mouseover和mouseenter的区别

二者的本质区别:mouseenter不会冒泡,简单的说,它不会被它本身的子元素的状态影响到.但是mouseover就会被它的子元素影响到,在触发子元素的时候,mouseover会冒泡触发它的父元素.(想要阻止mouseover的冒泡事件就用mouseenter)

共同点:当二者都没有子元素时,二者的行为是一致的,但是二者内部都包含子元素时,行为就不同了.

9.JS垃圾回收机制

垃圾回收机制:Javascript 具有自动垃圾回收机制,会定期对那些不再使用的变量、对象所占用的内存进行释放,原理就是找到不再使用的变量,然后释放掉其占用的内存

浏览器通常使用的垃圾回收方法有两种:标记清除,引用计数

标记清除: 当变量进入执行环境时,就标记这个变量“进入环境”, 变量离开环境时,就会被标记为“离开环境”,被标记为“离开环境”的变量会被内存释放。

引用计数: 跟踪记录每个值被引用的次数。当一个新的引用指向对象时,引用计数器就递增+1,当去掉一个引用时,引用计数就递减-1。当引用计数到0时,该对象就将释放占有的资源。

详解可移步至juejin.cn/post/698158…

2021.11.2

10.组合继承的缺点,寄生继承的缺点

组合继承:由于我们是以父级的实例来作为子类型的原型,所以调用了两次超类的构造函数,造成了子类型的原型中多了很多不必要的属性。导致原型对象被父类实例属性污染,出现属性冗余问题

function Father(name){
  this.name = name
}
Father.prototype.say = function(){
  console.log(`hello大家好,我是${this.name}今年${this.age}岁了`)
}
function Son(name ,age){
  this.age = age
  Father.call(this, name)
}
Son.prototype = new Father // 将父子方法建立关系,为了复用prototype属性

var xrr = new Son('小熔熔', '12')
xrr.say()

寄生继承:没有盗用父类的构造函数,而只能够继承原型中方法,所以没有办法实现函数的复用。

function People(obj){
  function F(){}
  F.prototype = obj
  return new F()
}

function Student(obj){
  var clone = People(obj) // 通过调用函数创建一个新对象
  clone.__proto__.long = '12cm'
  clone.__proto__.getAA =  function () {
        console.log('aaaa')
  }
  clone.say = function(){
    console.log(`hello大家好,我是${this.name}今年${this.age}岁了`)
  }
  return clone
}

var obj = {
  name:'小熔熔',
  age:'12'
}
var xrr = Student(obj)
xrr.say()

解决方案:使用组合寄生继承

11.function构造函数和Class的差别

class事实上是一种特殊的funcion,就像可以定义funcion表达式和funcion声明一样,class语法也有2种形式:class表达式和class声明

  1. Function构造函数:可以实例化函数对象,function声明会变量提升。

  2. Class:ES6中的构造语法——Class,在语法上更贴合面向对象的写法。实现继承更加易读、易理解。更易于写java等后端语言的使用。本质是语法糖,使用prototyp。不会变量提升,需要先声明再使用

    class的body部分包含在花括号{}中
    constructorconstructor中可以通过super关键字,调用父类的constructor方法
    通过static关键字为一个class创建静态方法
    extends关键字用于在class声明或表达式中,创建另一个class的子类。
    
    

12.怎么计算元素ele到浏览器视口顶部的距离

let sum = 0
let ele = xxx; // 要求距离的元素
while(ele.offsetParent) {
	sum += ele.offsetTop; // 统计当前到其有定位元素的距离
	ele = ele.offsetParent; // 将父设为当前继续求距离
}
// 输出到视口顶部距离
console.log(sum)

13.说说prop和attribute的差别

  • property是js原生对象的直接属性,是 dom 元素在 js 中作为对象拥有的属性。
  • attribute:是 dom 元素在文档中作为 html 标签拥有的性,它的值只能够是字符串,attributes是属于property的一个子集

对于 html 的标准属性来说,attribute 和 property 是同步的,是会自动更新的, 但是对于自定义的属性来说,他们是不同步的。

14.事件冒泡和捕获的区别?

捕获阶段 -> 目标阶段 -> 冒泡阶段

比如点击了某个input框,从window 到 body 是捕获阶段(捕获从外向里),到input是目标阶段(到达目标阶段),再到body到window是冒泡阶段(冒泡从里向外);ie规定先让他冒泡,addEventListener 第三个参数就代表的是冒泡还是捕获,默认是false代表默认冒泡

2021.11.3

15. Cookie有哪些属性?

(1)cookie内包含

  1. name:名称

  2. value:值

  3. domain:域名

        有特定场景下会使用到domain,比如顶级域名和子级域名之间的cookie共享和相互修改、删除,会用到domain
    
  4. path:访问此cookie的页面路径

  5. expires/Max-Age:cookie超时时间

  6. size:大小

  7. http:cookie的httpOnly属性

  8. secure

  9. same-site等等

       cookie的httpOnly是安全相关,cors要求cookie传输的时候httpOnly不能为none
    

(2)发送请求的时候会携带cookie吗

axios/ajax默认是发送请求的时候不会带上cookie的,需要在请求头上设置withCredentials: true来解决;

同时后端要配合设置:

  • header 信息 Access-Control-Allow-Credentials: true
  • Access-Control-Allow-Origin不可以为 '*',因为会和 Access-Control-Allow-Credentials: true 冲突,需配置指定的地址
  • 设置httponly,一般值都是httponly,要设置为same-site

(3)关掉页面对cookie有影响吗?

cookie的生命期为浏览器会话期间,关闭浏览器窗口,cookie就消失。

没有设置cookie生命周期的话,cookie 的生命周期是累計的,从创建开始计时,20mins后cookie的生命周期結束,cookie就無效了; 一般保存在内存里,如果设置了过期时间,浏览器就会把cookie保存到硬盘上,关闭后再次打开浏览器,这些cookie依然有效直到超过设定的过期时间。

16. 说明EventTarget.addEventListener() 的第三个参数

第三个参数true为捕获,默认false 是冒泡;

新语法:第三个参数可为 Boolean,也可为 Object;

  • options (可选)

    1. capture 表示listener会在该类型的事件捕获阶段传播到该EventTarget 时触发。
    2. once 表示listener在添加以后最多只调用一次。若是是 true,listener会在其被调用以后自动移除。
    3. passive 表示listener永远不会调用preventDefault()。若是listener仍然调用了这个函数,客户端将会忽略它并抛出一个控制台警告。
target.addEventListener(type, listener, true/false)
target.addEventListener(type, listener, options)

17. Object.freeze() 是做什么用的,有哪些应用场景

1.Object.freeze()是ES5新增的特性,可以冻结一个对象,冻结指的是不能向这个对象添加新的属性,不能修改其已有属性的值,不能删除已有属性,以及不能修改该对象已有属性的可枚举性、可配置性、可写性。防止对象被修改,但不能防止被修改引用。如果你有一个巨大的数组或Object,并且确信数据不会修改,使用Object.freeze()可以让性能大幅提升,多用于展示。

    // 为了不让obj中的键对应的值被修改可以使用;Object.freeze(obj)
    const obj = { a: '1', b: '2' }

2. freeze可以用来实现const; 3. 只是浅冻结:复合类型的对象不能freeze比如 const obj = { arg: [1, 2, 3] },这个arg是冻不了的; 4. 就是要冻它,只能遍历逐层freeze 5. object.freeze这个可以不让vue去添加响应

    // 深层冻结ultil:
    function deepFreeze(obj = {}) {
      Object.freeze(obj);
      (Object.keys(obj) || []).forEach(key => {
        if (typeof obj[key] === 'object') {
          deepFreeze(obj[key])
        }
      })
    }

6.reduce可以实现深层冻结一行搞定

18. 点击移动端浏览器的前进按钮或后退按钮,往返页面不刷新,可以如何解决

往返缓存指浏览器为了在页面间执行前进后退操作时能拥有更流畅体验的一种策略。

该策略具体表现为:当用户前往新页面前将旧页面的DOM状态保存在缓存里,当用户返回旧页面前将旧页面的DOM状态从缓存里取出并加载。大部分移动端浏览器都会部署缓存,可大大节省接口请求的时间和带宽。

解决方案:

1. onunload

// 在新页面监听页面销毁事件
window.addEventListener("onunload", () => {
    // 执行旧页面代码
});

2. beforeRouteEnter 若在Vue中使用了keep-alive,可将接口请求放到beforeRouteEnter()里。

3. 监听pageshow事件 pageshow事件在每次页面加载时都会触发,无论是首次加载还是再次加载都会触发,这就是它与load事件的区别。 pageshow事件暴露的persisted可判断页面是否从缓存里取出。

window.addEventListener("pageshow", e => e.persisted && location.reload());

如果e.persisted依然是false,可以观察performance对象

window.addEventListener('pageshow', () => { 
if (e.persisted || (window.performance && window.performance.navigation.type == 2)) { 
    location.reload() 
} }, false)
performance.navigation.type是一个无符号短整型

TYPE_NAVIGATE (0): 
当前页面是通过点击链接,书签和表单提交,或者脚本操作,或者在url中直接输入地址,type值为0

TYPE_RELOAD (1) 
点击刷新页面按钮或者通过Location.reload()方法显示的页面,type值为1

TYPE_BACK_FORWARD (2) 
页面通过历史记录和前进后退访问时。type值为2

TYPE_RESERVED (255) 
任何其他方式,type值为255

4. 禁用缓存

5. history,通过路由就可以前进后退不刷新,history路由变化就是pushstate 和popstate,通过popState事件还能监听到history,用来在点击按钮后进行操作加强。

2021.11.4

19. new做了什么

(1)创建了一个新的空对象

(2)设置原型:将对象的__proto__设置为函数的 prototype 对象。

(3)执行构造函数的代码,让函数的 this 指向这个新对象(为这个新对象添加属性方法)

(4)判断函数的返回值类型,如果是值类型,返回创建的对象。如果是引用类型,就返回这个引用类型的对象。

function objectFactory(){
    // 把数组的第一个元素从其中删除,并返回第一个元素的值=>第一个为构造函数后面的为参数
    let constructor = Array.prototype.shift.call(arguments)
    // 限定第一个参数必须为构造函数
    if (typeof constructor !== "function") {
        console.error("第一个参数应为函数")
        return
    }
    // 创建一个空对象
    let newObject = null
    
    // 将构造函数的prototype赋给新对象的__proto__
    newObject = Object.create(constructor.prototype)
    
    // 相当于newObject.constructor(arguments)
    let result = null // 将this指向新对象,执行构造函数,将属性方法赋值给添加到这个新对象里
    result = constructor.apply(newObject, arguments)
    
    // 判断返回的类型-对象或者函数
    let flag = result && (typeof result === "object" || typeof result === "function")
    
    // 如果是函数或者对象则返回结果,否则返回构造函数
    return flag ? result : newObject
}

// 使用方法
objectFactory(构造函数, 初始化参数);
let a = objectFactory(Array, '111', '2222')
console.log(a, 'aa') //  ["111", "2222"] "aa"

20.通过原型实现的继承和类继承有什么区别

  • 原型继承:以原型链的方式来实现继承,缺点:在包含有引用类型的数据时,会被所有的实例对象所共享,容易造成修改的混乱;还有就是在创建子类型的时候不能父级传递参。

    function Father(){
          this.FatherName = "father's 构造函数";
     }
    Father.prototype.age = 12;
    function Son(){
          this.SonNname = "Son's 构造函数";
    }
    //Son的原型继承Father创建出来的对象,相当于继承了Father的全部内容,同时全部都存在Son__proto__属性里
    Son.prototype = new Father();
    
    var xrr = new Son();
    console.log(xrr.age);
    
  • 类式继承(构造函数):JS中其实是没有类的概念的,所谓的类也是模拟出来的。通过在子类型的函数中调用父级的构造函数来实现,这一种方法解决了不能向父级传递参数的缺点;缺点:无法实现函数方法的复用(只能复用father里 this.name = name的这种,无法复用father.prototype.a ),并且父级原型定义的方法子类型也没有办法访问到(都没有用到原型继承当然就访问不到原型定义的方法)

    function Father(name){
            this.name = name
    }
    function Son(name, age){
      this.age = age
            Father.call(this, name)
    }
    
    var xrr = new Son('小熔熔', '12')
    console.log(xrr.age, xrr.name)
    
  • class继承:class以及extends其实是寄生组合继承的语法糖,新的class写法只是让对象原型的写法更加清晰,更加面向对象编程的语法而已。子类在构造函数中必须调用super方法,这个super指的是父类的构造函数,从而获取到父类实例,在组合继承里父类需要将this指向子类的实例需要用parent.call(this, ...arguments)。

    // 父类
    class People {
        constructor(name){
            this.name = name
        }
        eat(){
            console.log(`${this.name}是一个妹子`)
        }
    }
    // 子类 继承的话要记得在constructor内super
    class Student extends People {
        constructor(name,number){
            super(name)
            this.number = number
        }
        sayHi(){
            console.log(`姓名${this.name}妹子还对你招了手${this.number}`)
        }
    }
    
    // 再写一个继承People的子类
    class Teacher extends People {
        constructor(name,major) {
            super(name)
            this.major = major
        }
        teacher(){
            console.log(`${this.name}教授${this.major}`)
        }
    }
    
    // 实例
    const xrr = new Student('小熔熔', 100)
    console.log(xrr.name) //小熔熔
    console.log(xrr.Siholll)//undefined
    xrr.sayHi() 
    xrr.eat()
    xrr instanceof Student // true
    xrr instanceof People // true
    
    // 了解this指向后补充一下
    xrr._proto_.sayHi()  // 姓名undfined妹子还对你招了手undfined
    // 这里undefined其实是因为xrr._proto_调用了sayHi()方法,this指向的是xrr._proto_
    
    // xrr.sayHi()  其实相当于xrr._proto_.sayHi.call(xrr) ,但内部不是这样执行的
    

21. window.onload和domcontentloaded的区别

window.addEventListener('load',function(){.......}):页面全部资源加载完才会执行

ducument.addEventListener('DOMContentLoaded',function(){.......}):DOM渲染完可执行;通常用这个

2021.11.5

22.什么是BFC,如何形成BFC

BFC: 块级格式化上下文.通俗的说就是一个完全独立的空间(布局环境),让空间里的子元素不会影响到外面的布局。

如何形成BFC:(float不是none,position是absolute和fixed,overflow不是visible)

  • overflow: hidden
  • display: inline-block
  • position: absolute
  • position: fixed
  • display: table-cell
  • display: flex

解决了什么问题:

  1. 使用Float脱离文档流,高度塌陷
  2. Margin边距重叠
  3. 两栏布局,Float脱离文档后文字环绕问题

23.你知道的浅拷贝有哪些?

  1. object.assign
  2. 扩展运算符方式(...)
  3. concat 拷贝数组
  4. slice/concat 拷贝数组
  5. lodash函数库
  6. for单层循环

24.说说Object.assign和扩展运算符的区别

  • Object.assign()方法接收的第一个参数作为目标对象,后面的所有参数作为源对象。然后把所有的源对象合并到目标对象中。它会修改了一个对象,因此会触发 ES6 setter。

  • 扩展操作符(…)使用它时,数组或对象中的每一个值都会被拷贝到一个新的数组或对象中。它不复制继承的属性或类的属性,但是它会复制ES6的 symbols 属性。

25.你知道的深拷贝有哪些?(让你实现,你会怎么设计)

  1. JSON.parse(JSON.stringify())
缺陷:
    会忽略 undefined
    会忽略 symbol
    不能序列化函数
    无法拷贝不可枚举的属性
    无法拷贝对象的原型链
    拷贝 RegExp 引用类型会变成空对象
    拷贝 Date 引用类型会变成字符串
    对象中含有 NaNInfinity 以及 -InfinityJSON 序列化的结果会变成 null
    不能解决循环引用的对象,即对象成环 (obj[key] = obj)。
    
  1. 自己设计深拷贝函数
自己设计思路:
【基础版】递归实现,通过 for in 遍历传入参数的属性值,如果值是引用类型则再次递归调用该函数,如果是基础数据类型就直接复制。
【考虑的点】
1.递归 (存在问题,不够健壮)
2.需考虑数组 (问题是只考虑了普通的object,没有考虑数组的话会有问题)
3.循环引用(如果递归进入死循环会导致栈内存溢出了)
4.性能优化(历数组和对象都使用了for in这种方式,实际上for in在遍历时效率是非常低的)
5.其他数据类型(只考虑了普通的object和array两种数据类型,实际上所有的引用类型不止这两个,就需要精确判断引用类型)
【存在问题】:
1.简单的递归实现深拷贝的函数并不能复制不可枚举的属性以及 Symbol 类型;
2.这种方法只是针对普通的引用类型的值做递归复制,而对于 ArrayDateRegExpErrorFunction 这样的引用类型并不能正确地拷贝;
3.对象的属性里面成环,即循环引用没有解决。

26.Promise执行过程中可以中断吗?若想中断,怎么对其进行中断。

Promise执行是不可以中断的。但实际需求中会出现一种场景,就是在合适的时候,把pending状态的promise给reject掉。例如把网络请求设置超时时间,一旦超时就中断。
这里用定时器模拟一个网络请求,
function timeoutWrapper(p,timeout =2000){
     const wait = new Prkmise(ersolve,reject){
        setTimeout(()=>{

          reject('请求超时')
          
       },timeout)
    }
    return Promise.race([p,wait])
}

2021.11.8

27.event loop 执行过程

事件循环从宏任务队列开始,一开始宏任务队列中只有一个script(整体代码)任务,遇到任务源时,分发到相应的任务队列中。异步任务可分为task 和 microtask 两类(requestAnimationFrame 既不属于 macrotask, 也不属于 microtask),不同的API注册的异步任务会依次进入自身对应的队列中,然后等待 Event Loop 将它们依次压入执行栈中执行。执行栈在执行完同步任务后,检查执行栈是否为空,如果为空,检查微任务队列是否为空,如果微任务队列不为空,则一次性执行完所有的微任务。如果微任务为空,则去执行下一个宏任务。每次单个的宏任务执行完之后,都会检查微任务队列是否为空,如果不为空,则按照先进先出的方式执行完所有的微任务,然后执行下一个宏任务,以此循环。每次执行宏任务 产生的微任务队列都是新创建的 宏任务队列只有一个。

image.png

image.png

28.数组去重的方式

  1. Set,

  2. filter,

  3. 双层for循环然后splice去重,

  4. indexof去重,

  5. sort,

  6. includes,

  7. hasOwnProperty(利用hasOwnProperty 判断是否存在对象属性)

  8. map,

  9. […new Set(arr)]

  10. Array.from(new Set(arr))

    function unique(arr) {
        var obj = {};
        return arr.filter(function(item, index, arr){
            return obj.hasOwnProperty(typeof item + item) ? false : (obj[typeof item + item] = true)
        })
    }
    var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
    console.log(unique(arr))
    //[1, "true", true, 15, false, undefined, null, NaN, "NaN", 0, "a", {…}]   //所有的都去重了
    
    

29.set和map的区别

set不允许元素重复

属性和方法:

  • size-获取元素数量;
  • add(value)-添加元素,返回set实例本身;
  • delete(value)-删除元素返回一个布尔值;
  • has(value)-返回布尔值,表示该值是否是set实例的元素;
  • clear()-清除所有元素,无返回值。

map的key可以是任何数据类型,Map中的键值是有序的,Map的键值对个数可以从 size 属性获取。

属性和方法:

  • set:设置成员key和value;
  • get :获取成员属性值;
  • has:判断是否值存在;
  • delete:删除;
  • clear:清除所有

set存储单个的值,map存储键值对

30.介绍一下es6的symbol

symbol是es6新增的一种数据类型,symbol可以用来创建唯一值,可以用作对象的属性。

  1. 作为属性名时不能被for...in, for...of遍历,不能被Object.keys()或者object.getOwnPropertyNames()返回
  2. 不能使用new命令
  3. 相同参数的symbol()返回的值是不相等的:例如let a = Symbol(‘a’) let b = Symbol(’a’)a===b 返回false
  4. Symbol 值作为属性名时,该属性是公有属性不是私有属性
  5. Symbol 作为对象的属性名,可以保证属性不重名,可以在类的外部访问。
  6. Symbol 作为对象属性名时不能用.运算符,要用方括号[]

2021.11.9

31.常见的DOM操作有哪些

  • 增:createElement(),appendChild()

  • 删:removeChild()

  • 改: insertBefore(),appendChild()

  • 查:

    getElementById // 按照 id 查询
    getElementsByTagName // 按照标签名查询
    getElementsByClassName // 按照类名查询
    querySelectorAll // 按照 css 选择器查询
    

32.数组有哪些原生方法?

(具体使用可自己查)

slice,splice,pop,push,unshift,shift

toString,reverse,sort,concat,indexOf,lastIndexOf,every,some,filter,forEach,reduce,reduceRight

find,findIndex, includes, fill,join,flat flatmap entries reduceRight

33.重绘和回流

重绘:当页面中某些元素的样式发生变化比如颜色等,但是不会影响其在文档流中的位置时,浏览器就会对元素进行重新绘制,这个过程就是重绘,不会导致浏览器重新渲染页面,不影响性能

重流(回流):渲染树中部分或者全部元素的大小尺寸、位置,结构或者属性发生变化时,浏览器会重新渲染部分或者全部文档的过程,会影响性能

34.协商缓存和强缓存的区别

强制缓存: 优先级较高,先比较当前时间与上一次返回 200 时的时间差,如果没有超过 cache-control 设置的 max-age,则没有过期,并命中强缓存,直接从本地读取资源。如果浏览器不支持HTTP1.1,则使用 expires 头判断是否过期;强制缓存不用走请求,使用强制缓存可以提升性能。

协商缓存: 白话说就是和服务端协商能不能用缓存,需耗时。向服务器发送带有If-None-MatchIf-Modified-Since的请求优先根据Etag的值判断被请求的文件有没有做修改,Etag 值一致则没有修改,命中协商缓存,返回304;如果不一致则有改动,直接返回新的资源文件带上新的 Etag 值并返回 200; 如果服务器收到的请求没有 Etag 值,则将If-Modified-Since和被请求文件的最后修改时间做比对,一致则命中协商缓存,返回304;不一致则返回新的 last-modified 和文件并返回 200;

image.png

35.浏览器的渲染过程

  1. 生成DOM 树:首先解析收到的文档(html),根据文档定义构建一棵 DOM 树,DOM 树是由 DOM 元素及属性节点组成的网页结构框架。
  2. 生成CSSOM 规则树:然后对 CSS 进行解析,生成 CSSOM 规则树,它决定了网页的样式。
  3. 构建成渲染树:将 DOM树 和 CSSOM规则树 构建成 渲染树(Render Tree)。
  4. 布局阶段:浏览器开始计算布局(元素位置信息,大小等等),计算层级,position z-index会导致产生新的层,渲染还会对区域进行分块。接下来图层绘制(画像素,媒体文件解码),最后图层合并,GPU绘制。
  5. 绘制阶段:布局阶段结束后是绘制阶段,遍历渲染树并调用渲染对象的paint方法,将它们的内容显示在屏幕上,绘制使用 UI 基础组件,遇到script标签则暂停渲染,优先加载并执行JS代码,执行完后再继续渲染(js线程和dom渲染线程是共用一个线程的;因为js有可能会改变dom结构有可能改变Render Tree结构),所以 我们要把js放在末尾避免它堵塞加载渲染页面;Css如果没有解析完 rendertree会构建不了,样式放在底部可能会导致重绘,所以css 要放前面尽早加载出来。
  6. 直至把Render Tree渲染显示完成

2021.11.10

36.伪元素和伪类的区别和作用?

伪元素:在一个元素前后加一个元素或者样式,但是在dom节点上不存在这个元素,代表某个元素的子元素,特征看起来有 ::

::before 
::after 
::placeholder 
::selection 
::first-letter 
::first-line

伪类:在css中只有一个:形成,不会产生新的元素,可以在元素选择器上改变元素的状态,特征看起来有 :

:hover 
:link 
:active 
:focus 
:first-child 
:last-child

区别:是否创建了新的元素

37.this 的指向

  1. 箭头函数:往上找到的最近的词法作用域的this指向
  2. 定时器:windows
  3. 对象/函数调用:this指向调用方,
  4. 全局直接调用函数:window(非严格模式),undefined(严格模式)
  5. 构造函数:this指向实例
  6. 显示绑定apply\call\bind:指向第一个参数

38.https 加密过程

https是由http通讯,用SSL/TLS(完全传输层协议)来加密数据包的,https主要是提供对网站服务器的身份认证,保护交换数据的隐私和完整性。

传输加密过程:

(1)客户端先向服务端发起https请求,客户端生成一个随机数发给服务端,传输的时候会带上客户端支持的加密方式套件,还带上协议版本号(随机数 + 加密方式套件 + 协议版本号)

(2)服务端收到请求后存放随机数,确认加密方法,也生成一个随机数伴随着公钥(数字证书)一起传给客户端(加密方法+随机数+公钥)

(3)客户端确认服务器证书后对证书进行递归验证并获取公钥,把收到的公钥生成一个预主密钥,再生成一个用公钥加密后的随机数一起发给服务端,还会发一个用hash加密过的内容(预主密钥+随机数)

(4)服务端拿到预主秘钥后,用私钥解密信息拿到预主密钥

(5)客户端和服务端同时对这三个随机数用同一套加密方式算出一个主密钥,所以这个主密钥只有客户端和服务端知道,中间件是破解不了的

(6)客户端finished,发送共享秘钥key加密过的“finished”信号

(7)服务端finished,发送共享秘钥key加密过的“finished”信号

(8)达成安全通信

image.png

39.柯里化的定义和实现

柯里化是把接受多个参数的函数变成接受单一参数的函数,并返回接受剩下参数返回结果的技术, 本质就是要实现减少代码冗余同时增加可读性,用途之一:参数复用

  • 输入是一个函数,并且这个函数拥有n个参数
  • 输出也是一个函数,并且可以使用fn()()()这种方式调用
  • 参数被柯里化过程中的函数被拆分
let add = this.curry(this.addf)
console.log(add(1)(2)(3)(4)(5, 3), '111')
curry(fn, ...args) {
      console.log(args, 'args,fn一直没有改变')
      return args.length < fn.length ? (...innerArgs) => { console.log(innerArgs, 'innerArgs'); return this.curry(fn, ...args, ...innerArgs) } : fn(...args)
},
addf(a, b, c, d, e) {
      return a + b + c + d + e
},
    

40.什么是 JavaScript 生成器?如何使用生成器?

generator生成器是一种返回迭代器的函数,通过functuion关键字后的星号(*)来表示,函数中会用到新的关键字yield,星号可以紧挨着function关键字,也可以在中间添加一个空格.

特性

  1. 每当执行完一条yield语句后函数就会自动停止执行,直到再次调用next();
  2. yield -- 关键字只可在生成器内部使用,yeild是相当于return的功能,在其他地方使用会导致程序抛出错误;
  3. 可以通过函数表达式来创建生成器,但是不能使用箭头函数
  4. generator并不执行但会返回一个遍历器对象,当执行next()的时候才依次执行内部的函数
  5. 用函数名之前加上*的方式去创建,具有暂停、执行、结束三种状态
  6. Generator.prototype.throw() -- 向生成器抛出错误
  7. Generator.prototype.return()-- 返回给定的值并结束生成器
  8. Generator.prototype.next()-- 返回一个由yeild表达式生成的值
  9. yield* -- Generator函数内部调用另一个Generator函数

2021.11.11

41.你知道哪些攻击类型?

  1. XSS攻击:跨站脚本攻击,是一种代码注入攻击。攻击者把可执行的恶意脚本注入搭配页面中,使之在用户的浏览器上运行,从而盗取用户的信息如 cookie 等。

    解决方案:1. 对需要插入到 HTML 中的代码做好充分的转义;2.使用 CSP,建立一个白名单,告诉浏览器哪些外部资源可以加载和执行,从而防止恶意代码的注入攻击

  2. CSRF攻击:跨站请求伪造攻击,攻击者诱导用户进入一个第三方网站,然后该网站向被攻击网站发送跨站请求,如果用户在被攻击网站中保存了登录状态,那么攻击者就可以利用这个登录状态,绕过后台的用户验证,冒充用户向服务器执行一些操作。比如攻击者可以通过在输入留言框内输入可发送请求的代码,留言放到网站上,这样每个人进入这个网站的时候都会执行这行代码并发送携带自己的cookie账号密码等关键信息的请求,这样攻击者就能拿到用户的信息伪造拿去登录了

    解决方案:1.同源检测,2.使用 CSRF Token 进行验证,3.限制 cookie 不能作为被第三方使用

42.实现一个函数 countFn,接收一个函数作为参数,返回一个新函数,具备调用计数功能

`1.Proxy中函数调用操作的捕捉器是apply,因此我们只需要有一个计数的属性count,然后重写apply中内容,在这里面做count的增量就行了。

这样每次调用 testCount(),都会执行到 apply 里的内容。

至于 count,不论是放在 handler 这个对象中还是放到 countFn 这个函数里面都可以:

function countFn (fn) {
    var count = 0
    let handler = {
        // count: 0,
        apply: function(target, that, args) {
            // console.log(target, that, args);
            // console.log(++this.count);
            console.log(++count);
            target.apply(that, args);
        }
    }
    return new Proxy(fn, handler);
}
function test () {
    console.log('test');
    console.log(this);
}
function test2 () {
    console.log('test2');
}
const testCount = countFn(test);
const test2Count = countFn(test2);
testCount(); // 1 'test'
testCount(); // 2 'test'
testCount(); // 3 'test'

test2Count(); // 1 'test2'
test2Count(); // 2 'test2'
test2Count(); // 3 'test2'

2.闭包

function countFn(fn){
    var count=0
    return function(){
        count++
        fn.call(this,...arguments)
        console.log(count)
    }
}
function fn1(test){
    console.log('test1',test)
}
function fn2(test){
    console.log('test2',test)
}

var test1 =countFn(fn1)
var test2 =countFn(fn2)

test1('111')
test2('111')
test1('222')
test2('222')
test2('333')
test1('333')
//test1 111 1
//test2 111 1
//test1 222 2
//test2 222 2
//test2 333 3
//test1 333 3

3.放在prototype上

Function.prototype.countFn=function(){
    var count=0;
    var fn=this
    return function(){
        count++
        fn.call(this,...arguments)
        console.log(count)
    }
}
var test1= fn1.countFn()
var test2= fn2.countFn()
test1('111')
test2('111')
test1('222')
test2('222')
test2('333')
test1('333')
//test1 111 1
//test2 111 1
//test1 222 2
//test2 222 2
//test2 333 3
//test1 333 3

`

43.Object.create(null) 和直接用字面量{}创建空对象有什么区别和好处吗

Object.create()有两个参数,第一个参数是指定的原型对象,第二个参数是可选参数,给新对象自身添加新属性以及描述器

直接创建{ }的话内部会有很多对象内置写好的原型,里面存放了对象的属性和方法,而Object.create(null)的话是以null为原型创建对象,这样创建出来的对象相对比较干净,没有那么多属性和方法,不会担心自己写的方法会和原型链上的方法名重复,可以节省hasOwnProperty带来的性能损失;如果我们是想自己在一个干净的对象里写属性方法不被原生的原型上的属性和方法影响的话,可以采取Object.create(null)这种方式

44.手写Promise.then如何保证后一个then里的方法在前一个then结束之后再执行?

我们可以将传给 then 的函数和新 promiseresolve 一起 push 到前一个 promisecallbacks 数组中,达到承前启后的效果:

  • 承前:当前一个 promise 完成后,调用其 resolve 变更状态,在这个 resolve 里会依次调用 callbacks 里的回调,这样就执行了 then 里的方法了
  • 启后:上一步中,当 then 里的方法执行完成后,返回一个结果,如果这个结果是个简单的值,就直接调用新 promiseresolve,让其状态变更,这又会依次调用新 promisecallbacks 数组里的方法,循环往复。。如果返回的结果是个 promise,则需要等它完成之后再触发新 promiseresolve,所以可以在其结果的 then 里调用新 promiseresolve
then(onFulfilled, onReject){
    // 保存前一个promise的this
    const self = this; 
    return new MyPromise((resolve, reject) => {
      // 封装前一个promise成功时执行的函数
      let fulfilled = () => {
        try{
          const result = onFulfilled(self.value); // 承前
          return result instanceof MyPromise? result.then(resolve, reject) : resolve(result); //启后
        }catch(err){
          reject(err)
        }
      }
      // 封装前一个promise失败时执行的函数
      let rejected = () => {
        try{
          const result = onReject(self.reason);
          return result instanceof MyPromise? result.then(resolve, reject) : reject(result);
        }catch(err){
          reject(err)
        }
      }
      switch(self.status){
        case PENDING: 
          self.onFulfilledCallbacks.push(fulfilled);
          self.onRejectedCallbacks.push(rejected);
          break;
        case FULFILLED:
          fulfilled();
          break;
        case REJECT:
          rejected();
          break;
      }
    })
   }

注意:

  • 连续多个 then 里的回调方法是同步注册的,但注册到了不同的 callbacks 数组中,因为每次 then 都返回新的 promise 实例(参考上面的例子和图)
  • 注册完成后开始执行构造函数中的异步事件,异步完成之后依次调用 callbacks 数组中提前注册的回调

2021.11.12

45.事件循环代码输出题


async function async1 () { 
    console.log('1'); 
    await async2(); 
    console.log('2'); 
}
async function async2 () { 
    console.log('3'); 
}
console.log('4')
setTimeout(function () {
    console.log('5');
    Promise.resolve().then(function () { 
        console.log('6'); 
    });
}, 0);
setTimeout(function () {
    console.log('7');
    Promise.resolve().then(function () { 
    console.log('8'); 
    });
}, 0);
async1();
new Promise(function (resolve) {
    console.log('9'); 
    resolve();
}).then(function () { 
    console.log('10'); 
});
console.log('11');

答案:4->1->3->9->11->2->10->5->6->7->8(注意:微任务要在当前本轮宏任务执行完成之后再次执行)

46.类型变换代码输出题

var home = {
    address'shanghai'
}
var xrr = Object.create(home);
delete xrr.address
console.log(xrr.address);

答案:shanghai(注意:delete不能删除原型上的属性)

47.作用域相关代码输出题

var x = 1;
if (function f () { }) {
    x += typeof f;
}
console.log(x)

答案:1undefined (注意:typeof运算结果为字符串,函数声明写在运算符中,其为 true,但 放在运算符中的函数声明在执行阶段时找不到的

48.闭包代码输出题

function xrr (n, o) {
    console.log(o); 
    return {
        XRR: function (m) {
            return xrr(m, n);
        }
    }
}
const a = xrr(0); a.XRR(1); a.XRR(2); a.XRR(3);
const b = xrr(0).XRR(1).XRR(2).XRR(3);
const c = xrr(0).XRR(1); c.XRR(2); c.XRR(3);

答案:

undefined  0 0 0 
undefined  0 1 2 
undefined  0 1 1

解析:

1. a 执行过程:const a = xrr(0); a.XRR(1); a.XRR(2); a.XRR(3);

1> const a = xrr(0); 当把0传进去的时候,调用的是外层的函数xrr传入参数0,n为0,但是o没有值,打印为undefined,返回一个未执行的函数赋值给a,其中a的XRR的n为0
2> a.XRR(1) ;执行内层的函数XRR并传入1进去返回执行xrr的结果,此时xrr传入10两个参数,打印第二个参数为0,所以输出为0
3> a.XRR(2) ;执行内层的函数XRR并传入1进去返回执行xrr的结果,此时xrr传入20两个参数,打印第二个参数为0,所以输出为0
4> a.XRR(3) ;执行内层的函数XRR并传入1进去返回执行xrr的结果,此时xrr传入30两个参数,打印第二个参数为0,所以输出为0

所以结果是 undefined 0 0 0

2. b 执行过程:const b = xrr(0).XRR(1).XRR(2).XRR(3);

1> 第一次调用第一层xrr(0) 时,o 为 undefined
2> 第二次调用 .XRR(1) 时 m 为 1,此时 xrr 闭包了外层函数的 n ,也就是第一次调用的 n=0,即 m=1,n=0,并在内部调用第一层的xrr(1,0);所以 o 为 03> 第三次调用 .XRR(2) 时 m 为 2,此时当前的 xrr 函数不是第一次执行的返回对象,而是第二次执行的返回对象。而在第二次第一层 xrr(1,0) 时,n=1,o=0,返回时闭包了第二次的 n,所以在第三次调用第三层的 XRR 函数时,m=2,n=1,即调用第一层 xrr(21) 函数,所以 o 为 14> 第四次调用 .XRR(3) 时 m=3,闭包了第三次的 n ,同理,最终调用第一层 xrr(3,2);所以 o 为 2

所以结果为: undefined 0 1 2

3. c 执行过程:const c = xrr(0).XRR(1); c.XRR(2); c.XRR(3);

1> const c = xrr(0):调用第一层xrr(0) 时,o 为 undefined
2> 第二次调用 .XRR(1) 时 m 为 1,此时 XRR 闭包了外层函数的 n ,也就是第一次调用的 n=0,即 m=1,n=0,并在内部调用第一层的xrr(1,0);所以 o 为 0
3> 第三次调用 .XRR(2) 时 m 为 2,此时xrr闭包的是第二次执行的返回的函数XRR(内层的xrr(m=2(本次传入的2),n=1(私有值1)),所以外层的xrr(n=2,o=1))。所以打印 o 输出为 14> 第四次 .XRR(3) 时同理,但依然时调用第二次的返回值,所以最终调用第一层的 XRR(3,1),所以 o 为 1

所以结果是 undefined 0 1 1

2021.11.15

49.TCP是如何保持可靠的?

  1. 连接管理机制:tcp是面向连接的协议,也就是说收发前必须和对方建立可靠的链接,一个tcp必须经过三四才能建立起来,大大的提高了数据可靠性,使我们在数据收发前就有了交互,为正式传输打下了基础;
  2. 序列号机制,保证数据乱序处理:tcp为了保证报文传输可靠,给每个包一个序号,同时序号也保证了传输到接收端实体的包的安顺序接收,
  3. 确认重传机制:然后接受端实体对已成功收到的字节发回一个相应的确认ack,如果发送端实体在合理往返时延内未收到确认,那么对应的丢失数据将被重传.
  4. 当网络拥塞时,tcp能够减少向网络注入数据的速率和数量,缓解拥塞
  5. 流量控制:处理能力
  6. 拥塞控制:网络环境

50.后端一次性给出几万条数据该怎么渲染 ?

  1. 优先使用虚拟列表(思路就是动态控制显示区域的内容,类似于antd滚动加载无限长列表)
  2. 使用分页
  3. js缓冲器分片处理
  4. web worker(懒加载->页面会崩溃了)
  5. 尽量使用flexbox布局
  6. 使用骨架组件减少布局移动
  7. Windowing提高性能
  8. 时间分片: 1.定时器(setTimeout) 有白屏现象 ---> requestAnimationFrame -> DocumentFragment---->.....

51.前端的一次请求会经过哪些缓存?

强缓,协商缓存,启发缓存,缓存失败,四个阶段

52.promise和async await的区别

promise解决回调地狱的问题,但会有一些逻辑混乱的问题,而async await是在promise的基础上优化了的,可以解决逻辑混乱的问题,以同步的视角去看异步

53.算法题:不同路径(动态规划)

leetcode-cn.com/problems/un… image.png

2021.11.16

54.说一下 js 严格模式下有哪些不同

  1. 严格模式下无法再意外创建全局变量。
  2. 严格模式会使引起静默失败的赋值操作抛出异常
  3. 在严格模式下, 试图删除不可删除的属性时会抛出异常
  4. 严格模式要求函数的参数名唯一
  5. 严格模式禁止八进制数字语法
  6. 严格模式禁用with
  7. 严格模式下eval不再为上层范围引入新变量
  8. 严格模式禁止删除声明变量
  9. 名称eval和arguements不能通过程序语法被绑定或赋值
  10. 严格模式下,参数的值不会随argurments对象的值的改变而变化
  11. 不再支持arguments.callee
  12. 在严格模式下通过this传递给一个函数的值不会被强制转换为一个对象。
  13. 在严格模式中再也不能通过广泛实现的ecmascript扩展“游走于”javascript的栈中
  14. 严格模式下的arguements不会再提供访问与调用这个函数相关的变量的途径
  15. 在严格模式中一部分字符变成了保留的关键字。
  16. 严格模式禁止了不在脚本或者函数层面上的函数声明。

55.说一下递归和迭代的区别是什么,各有什么优缺点?

(一)定义:

递归: 递归常被用来描述以自相似方法重复事物的过程,在数学和开发中,指的是在函数定义中使用函数自身的方法;递归实际上不断地深层调用函数,直到函数有返回才会逐层的返回,递归是用栈机制实现的,每深入一层,都要占去一块栈数据区域,因此,递归涉及到运行时的堆栈开销(参数必须压入堆栈保存,直到该层函数调用返回为止),所以有可能导致堆栈溢出的错误;但是递归编程所体现的思想正是人们追求简洁、将问题交给计算机,以及将大问题分解为相同小问题从而解决大问题的动机。递归,还有个尾调用优化,尾调用优化就是如果本次调用的返回值,是子调用的返回值的话,本次调用就可以直接出栈了,不需要进行嵌套。就可以实现栈深为1的递归调用。递归从字面可以其理解为重复“递推”和“回归”的过程(递推:层层推进,分解问题;回归:层层回归,返回较大问题的解)

迭代: 是重复反馈过程的活动,其目的通常是为了接近并到达所需的目标或结果。每一次对过程的重复被称为一次“迭代”,而每一次迭代得到的结果会被用来作为下一次迭代的初始值。迭代是顺序的,不涉及调用栈操作,前面的代码不会被后面代码影响,递归是涉及到调用栈的,遵循先入栈后结束后入栈先结束原则,前面的函数调用会阻塞,要在后面的调用返回值后才能继续执行,所以迭代的好处就是栈深小,但是代码逻辑不够清晰,递归则是嵌套调用多,栈深比较大,容易爆栈,但代码结构会比较简洁,速度的话还是迭代快。迭代大部分时候需要人为的对问题进行剖析,分析问题的规律所在,将问题转变为一次次的迭代来逼近答案。迭代不像递归那样对堆栈有一定的要求,另外一旦问题剖析完毕,就可以很容易的通过循环加以实现。迭代的效率高,但却不太容易理解,当遇到数据结构的设计时,比如图表、二叉树、网格等问题时,使用就比较困难,而是用递归就能省掉人工思考解法的过程,只需要不断的将问题分解直到返回就可以了。

(二)同异点:

  1. 相同点:递归和迭代都是循环的一种。

  2. 不同点:

    (1)程序结构不同:递归是重复调用函数自身实现循环。迭代是函数内某段代码实现循环。 其中,迭代与普通循环的区别是:迭代时,循环代码中参与运算的变量同时是保存结果的变量,当前保存的结果作为下一次循环计算的初始值。

    (2)算法结束方式不同:递归循环中,遇到满足终止条件的情况时逐层返回来结束。迭代则使用计数器结束循环。 当然很多情况都是多种循环混合采用,这要根据具体需求。

    (3)效率不同:在循环的次数较大的时候,迭代的效率明显高于递归

    (4)运行过程不同,如果是循环迭代的话,这个整个就在主函数的或者在调用函数的栈空间里面,如果是递归的话,它会不断的申请函数调用的栈空间,在计算的过程中,计算一个结果,退一层栈,递归过程,在调用的时候有可能会出现栈的溢出。

    (5)理论上递归和迭代时间复杂度方面是一样的,但实际应用中(函数调用和函数调用堆栈的开销)递归比迭代效率要低。

(三)优缺点

  • 递归的

    优点: 大问题转化为小问题,可以减少代码量,同时代码精简,可读性好。

    缺点: 递归调用浪费了空间,而且递归太深容易造成堆栈的溢出。

  • 迭代的

    优点: 就是代码运行效率好,因为时间只因循环次数增加而增加,而且没有额外的空间开销。 缺点: 就是代码不如递归简洁

56.详细说一下你对 cookie、session、token 的理解

  • cookie由服务器生成,发送给浏览器,浏览器把cookie以kv形式保存到某个目录下的文本文件内,下一次请求同网站时会把该cookie发送给服务器。由于cookie是存在客户端上的,所以浏览器加入了一些限制确保cookie不会被恶意使用,同时不会占据太多磁盘空间,所以每个域的cookie数量是有限的。
  • session 从字面上讲,就是会话。这个就类似于你和一个人交谈,你怎么知道当前和你交谈的是张三而不是李四呢?对方肯定有某种特征(长相等)表明他就是张三。session 也是类似的道理,服务器要知道当前发请求给自己的是谁。为了做这种区分,服务器就要给每个客户端分配不同的“身份标识”,然后客户端每次向服务器发请求的时候,都带上这个“身份标识”,服务器就知道这个请求来自于谁了。至于客户端怎么保存这个“身份标识”,可以有很多种方式,对于浏览器客户端,一般都默认采用cookie的方式。服务器使用 session 把用户的信息临时保存在了服务器上,用户离开网站后session会被销毁。这种用户信息存储方式相对cookie来说更安全,可是session有一个缺陷:如果web服务器做了负载均衡,那么下一个操作请求到了另一台服务器的时候session会丢失。
  • 在web领域基于token的身份验证随处可见。在大多数使用web api的互联网公司中,token是多用户下处理认证的最佳方式。基于token的身份验证是无状态的,我们不将用户信息存在服务器或session中。这种概念解决了在服务端存储信息时的许多问题。

57.说一下 在 map 中和 for 中调用异步函数的区别

区别:

  • map会先把执行同步操作执行完,就返回,之后再一次一次的执行异步任务
  • for是等待异步返回结果后再进入下一次循环

map函数的原理:

  • 循环数组,把数组每一项的值,传给回调函数
  • 将回调函数处理后的结果push到一个新的数组
  • 返回新数组
  • map函数函数是同步执行的,循环每一项时,到给新数组值都是同步操作。

58.实现个函数柯里化

// function curry(func) {
//   //此处补全
// }
// function sum(...args) {
//   return args.reduce((a, b) => a + b)
// }

// let curriedSum = curry(sum);

// alert(curriedSum(1, 2, 3)()); // 6
// alert(curriedSum(1)(2, 3, 4)()); // 10
// alert(curriedSum(1)(2)()); // 3

答案:

function sum (...args) {
  return args.reduce((a, b) => a + b)
}
function currying (fn) {
  let args = []
  return function temp (...newArgs) {
      if (newArgs.length) {
          args = [
              ...args,
              ...newArgs
          ]
          return temp
      } else {
          let val = fn.apply(this, args)
          args = [] 
          return val
      }
  }
}

let curriedSum = currying(sum)

2021.11.17

59.object、map和weakMap的区别

  • object有默认的一些键,键只能是string。
  • map的键则可以是各种类型包括引用类型,map提供了很多api对map中的键值对进行操作比如get,set,delete,has,clear,相比较object更方便,针对频繁删改键值的操作,性能更好,
  • weakmap则类似map,但weakmap是弱引用,不增加引用计数器的值,只要其他地方没有再引用weakmap中的引用类型值,那么垃圾回收就会回收掉那个空间。

60.async原理

async 是generator的语法糖,aync封装了generator 让yeild执行变为手动执行,async会将函数返回值为promise.

61.你对于堆与栈的理解

(一)这些数据可以分为原始数据类型和引用数据类型:

栈:原始数据类型(UndefinedNullBooleanNumberString)
堆:引用数据类型(对象、数组和函数)

(二)两种类型的区别在于存储位置的不同:

  • 原始数据类型直接存储在栈(stack)中的简单数据段,占据空间小、大小固定,属于被频繁使用数据,所以放入栈中存储;栈存的是基本类型和引用类型的指针,存在栈中存的数据会被频繁操作提高性能;栈是由操作系统自动分配释放的,存档函数的参数值和局部变量的值等。操作方式就类似数据结构的栈。堆由人工分配释放,或者运行结束os回收,类似于链表

  • 引用数据类型存储在堆(heap)中的对象,占据空间大、大小不固定。如果存储在栈中,将会影响程序运行的性能;引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。

(三)堆和栈的概念存在于数据结构和操作系统内存中,在数据结构中:

  • 在数据结构中,栈中数据的存取方式为先进后出。堆是一个优先队列,是按优先级来进行排序的,优先级可以按照大小来规定。
  • 在操作系统中,内存被分为栈区和堆区:栈区内存由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。堆区内存一般由开发着分配释放,若开发者不释放,程序结束时可能由垃圾回收机制回收。

62.sort的底层原理

  • V8 引擎 sort 函数只给出了两种排序分别是:根据传入的数组大小判断使用 数组长度小于等于 10 的用插入排序,比10大的数组则使用快速排序
  • 如果还要按引擎区分的话 v8就是上面说的这个,ff用的归并排序,webkit用的C++库的排序

63.前端路由模式及其原理

路由是为了需要路径被改变而页面不会重新请求刷新,路由模式有hash和history还有repleaceState,hash就是利用hashchange不会重新请求这个原理,window.addEventListener('hashchange',function() {//改变视图}),history同理,只不过history模式下路径不会有#

  • push是往history去添加一条记录
  • repleaceState是替换路径
  • replace直接替换,不会添加新的;push和replace区别就是push能返回上一页replace不能