diff --git a/docs/interview/experiences/0520.md b/docs/interview/experiences/0520.md new file mode 100644 index 0000000..0587037 --- /dev/null +++ b/docs/interview/experiences/0520.md @@ -0,0 +1,293 @@ +--- +title: 20240520积累面试 +order: 37 +group: + order: 11 + title: /interview/experience +nav: + order: 3 + title: 'interview' + path: /interview +--- + +- 熟悉一兩种常见的前端构建工具(Webpack、rollup、vite 等),熟悉 babel 的使用 +- 精通 Web 开发技术,对 JavaScript(含 ES2020)、HTML、CSS、DOM、安全等有较深入的理解,需要有微信小程序开发经验,必须熟悉 typrscript 开发。 +- 5 年及以上开发经验,2 年以上 React 开发经验, +- 熟悉基础的算法实现。链表/hash/回溯/深度/广度/dp 动态规划 + +- 宏任务和微任务 浏览器事件循环 +- esmodule 和 commjs 区别 +- H5 性能优化和小程序性能优化 + +## 宏任务和微任务 浏览器事件循环 + +- 整体的 script 作为第一个宏任务开始执行,执行的时候代码分为同步任务和异步任务 +- 同步任务一次进入执行栈一次执行,然后出栈 +- 异步任务又分为宏任务和微任务,首先说宏任务 +- 宏任务进入到**event Table**中事件表中,并在里面注册回调函数,每当指定的事件完成时,event table 会将这个函数移入到 event queue 中 +- 微任务注册要执行的函数, +- 当主线程内的任务执行完毕,执行栈为空的时候,会优先检查微任务的 event queue 是否有事件回调,如果有事件回调则继续执行,没有则执行下一个宏任务 +- 上述过程会不断重复,这就是 Event Loop,比较完整的事件循环 + +## webRTC 了解多少 + +- 实时通讯技术,允许网络应用建立点对点连接,实现视频流和音频流实时 点对点进行传输,直播业务比较合适 +- 三部分组成,浏览器 API+音视频引擎+网络 I/O 协议还有一个就是信令服务器 +- 交互过程: 1.音视频采集 2.信令交互,阔以使用 Node 搭建信令服务器 3.创建**RTCPeerConnection** 对象,进行媒体协商 + +RTCPeerConnection 是一个由本地计算机到远端的 WebRTC 连接,该接口提供**创建,保持,监控,关闭连接**的方法的实现,可以简单理解为功能强大的 **socket** 连接。通过**new RTCPeerConnection**即可创建一个 RTCPeerConnection 对象,此对象主要负责与各端建立连接(NAT 穿越),接收、发送音视频数据,并保障音视频的服务质量,接下来要说的端到端之间的媒体协商,也是基于 RTCPeerConnection 对象来实现的。 + +### 信令服务是干嘛用的 + +信令可以简单理解为消息,在协调通讯的过程中,为了建立一个 webRTC 的通讯过程,在通信双方彼此连接、传输媒体数据之前,它们要通过**信令服务器**交换一些信息,如加入房间、离开房间及媒体协商等,而这个过程在 webRTC 里面是没有实现的,需要自己搭建信令服务。 + +可以使用 **Socket.io** 来实现 WebRTC 信令服务器,Socket.io 已经内置了房间的概念,所以非常适合用于信令服务器的创建。 + +## esmodule 和 commjs 区别 + +- 值的拷贝和值的引用 + +CommonJS 模块输出的是值的拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值 + +ES6 模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。 + +- 静态编译时输出接口和运行时加载 + +运行时加载: CommonJS 模块就是对象;即在输入时是先加载整个模块,生成一个对象,然后再从这个对象上面读取方法,这种加载称为“运行时加载”。 + +编译时加载: ES6 模块不是对象,而是通过 **export 命令显式指定输出的代码**,import 时采用静态命令的形式。即在 import 时可以指定加载某个输出值,而不是加载整个模块,这种加载称为**编译时加载**。 + +## H5 性能优化和小程序性能优化 + +### H5 性能优化 + +- 请求资源 CDN 和 dns 预解析 +- http2 多路传输 +- 减少不必要的 DOM 访问和操作,动画:请求动画帧 + requestIdleCallback +- 路由懒加载 +- 合理使用 defer+async 异步加载和解析 js 脚本 +- 合理使用 preload/prefetch 预加载关键资源。 +- 减少重绘和回流 + +- 框架层面,引入中间件简化和隔离这些基础设施与业务逻辑之间的细节,只关注业务本身 +- 父组件更新,不波及子组件渲染,没有必要的渲染是对性能的极大浪费。合理使用 react.memo/useMemo/useCallback/shouldComponentUpdate/PureComponent +- 遍历数组的时候,记得加上唯一的标记 key +- 使用 Webpack 等打包工具进行代码分割,按需加载模块。 + +### webpack 性能优化 + +- 使用 externals 优化 cdn 静态资源 +- tree shaking +- 代码分割 +- 图片压缩 +- 多线程构建 +- 按需引入模块 babel-plugin-import + +### 微信小程序性能优化 + +- 使用分包加载:利用微信小程序的分包加载机制,将不常用的页面放在单独的包中。 +- 使用插槽 slot 组件抽离 +- 减少 data 和 setData,避免频繁调用 setData,合并多次 setData 调用,减少界面重绘次数 +- 使用合适的图片格式和压缩图片,利用图片懒加载。 +- 合理使用缓存,利用本地缓存(如 wx.setStorageSync)来存储不常变动的数据,减少网络请求 +- 合理使用生命周期函数:避免在 onLaunch、onShow 等生命周期函数中执行耗时操作。 +- 减少页面的层级和组件嵌套,简化页面结构。 +- 优化第三方库,评估和优化第三方库的使用,避免引入不必要的代码。 + +```js +// 无重复字符的最长子串 +function noDup(str) { + if (str.length == 0) return ''; + let arr = []; + let max = 0; + for (let i = 0; i < str.length; i++) { + let cur = str[i]; + const index = arr.indexOf(cur); + if (index > -1) { + arr.splice(0, index + 1); + } + arr.push(cur); + max = Math.max(max, arr.length); + } + return max; +} + +// 二分法 +function two(arr, target) { + let left = 0; + let rignt = arr.length - 1; + while (left <= right) { + let mid = left + Math.floor(right - left) / 2; + if (arr[mid] === target) { + return mid; + } else if (arr[mid] > target) { + right = mid - 1; + } else if (arr[mid] < target) { + left = mid + 1; + } + } + return -1; +} + +// 链表反转 +function reverse(node) { + let pre = null; + let cur = node; + while (cur) { + let next = cur.next; + cur.next = pre; + pre = cur; + cur = next; + } + return pre; +} +// 是否有环 +function isCycle(head) { + let m = new Set(); + let cur = head; + while (cur) { + if (m.has(cur)) { + return m.get(cur); + } + m.set(cur, cur); + cur = cur.next; + } + return false; +} + +// 盛最多水的容器 +var maxArea = function (height) { + // 长*宽 + let max = 0; + let left = 0; + let right = height.length - 1; + while (left <= right) { + max = Math.max(max, Math.min(height[right], height[left]) * (right - left)); + if (height[right] > height[left]) { + left++; + } else { + right--; + } + } + return max; +}; + +// 有效的括号 +// 给定一个只包括 '(',')','{','}','[',']' 的字符串 s ,判断字符串是否有效。 +function isValid(s) { + let m = new Map([ + [')', '('], + [']', '['], + ['}', '{'], + ]); + let arr = []; + for (let item of s) { + if (m.has(item) && m.get(item) == arr.slice(-1)) { + arr.pop(); + } else { + arr.push(item); + } + } + return arr.length === 0; +} + +// 删除链表一个节点 +var deleteNode = function (head, val) { + let dummy = { + next: head, + val: 0, + }; + let cur = dummy; + while (cur.next) { + if (cur.next.val == val) { + cur.next = cur.next.next; + } + cur = cur.next; + } + return dummy.next; +}; +// 二叉树前中后序 +function inOrder(node) { + if (node) { + return null; + } + let arr = []; + let dfs = (node) => { + if (node) { + // 根左右 + arr.push(node.val); + dfs(node.left); + dfs(node.right); + } + }; + return dfs(node); +} +// BFS 根左右 进右左 => 出 左右根 +function BFSorder(node, res = []) { + let queue = []; + let cur = node; + while (cur) { + if (cur.left) { + cur = cur.left; + queue.push(cur.left); + } else { + cur = queue.pop(); + res.push(cur.val); + cur = cur.right; + } + } + return res; +} + +function preOrder(root, res = []) { + if (!root) return res; + var stack = [root]; + while (stack.length) { + let cur = stack.pop(); + res.push(cur.val); + if (cur.right) { + stack.push(cur.right); + } + if (cur.left) { + stack.push(cur.left); + } + } + return res; +} + +// 层序遍历 +function BFS(node) { + let queue = [node]; + let res = []; + while (queue.length) { + let len = queue.length; + let arr = []; + for (let i = 0; i < len; i++) { + let tree = queue.shift(); + arr.push(tree.val); + if (tree.left) { + queue.push(tree.left); + } + if (tree.right) { + queue.push(tree.right); + } + } + res.push(arr); + } + return res; +} + +var maxSubArray = function (nums) { + let max = 0; + let sum = 0; + for (let n of nums) { + if (sum > 0) { + sum += n; + } else { + sum = n; + } + max = Math.max(max, sum); + } + return max; +}; +``` diff --git a/docs/interview/experiences/practise/202405/0517.js b/docs/interview/experiences/practise/202405/0517.js new file mode 100644 index 0000000..1e55027 --- /dev/null +++ b/docs/interview/experiences/practise/202405/0517.js @@ -0,0 +1,379 @@ +// 还好,不是绝症,天无绝人之路,都是自己的选择,怨不得别人,从现在开始默写到5月29日,一边默写,一边复习基础知识 当时就应该提出 换个地方,而不是坐以待毙,现在有点后悔,当时早点说就好了 +/** + * 1.apply/call + * 2.bind + * 3.new + * 4.compose 洋葱模型实现 + * 5.instanceof 实现 + * 6.reduce/map 实现 + * 7.redux 中的 pipe 和 compose 组合 + * 8.深浅拷贝 + * 9.节流和防抖 + * 10.选择排序/插入排序 + * 11.LRU + * 12.发布订阅模式 + * 13.手写 Promise/Promise.all/race/allSetted + * 14.async/awit + * 15.请求并发限制 + * 16.ajax + * 17.jsonp + * 18.手写虚拟 dom + * 19.对象/数组去重 + * 20.大数相加 + */ + +function removeDup(arr) {} + +function mockVirtualDom() {} + +function mokckJsonp(url, cb) {} + +function mockAjax(url, options) {} + +async function limitRequest(arr, limit, fn) { + let res = []; + let queue = []; + for (let item of arr) { + let p1 = Promise.resolve(item).then((val) => fn(val)); + res.push(p1); + if (arr.length >= limit) { + let p2 = p1.then((val) => { + const index = queue.indexOf(p2); + return queue.splice(index, 1); + }); + queue.push(p2); + if (queue.length >= limit) { + await Promise.race(queue); + } + } + } + return Promise.all(res); +} + +function mockGenerator(fn) { + return (...rest) => { + const G = fn.apply(this, rest); + return step('next'); + function step(key, val) { + return new Promise((resolve, reject) => { + let res = {}; + try { + res = G[key](val); + } catch (e) { + reject(e); + } + const { done, value } = res; + if (done) { + resolve(value); + } else { + return Promise.resolve(value).then( + (data) => { + return step('next', data); + }, + (err) => { + return step('throw', err); + }, + ); + } + }); + } + }; +} +class Promise { + constructor(excutor) { + this.data = undefined; + this.cbs = []; + const resolve = (res) => { + setTimeout(() => { + this.data = res; + this.cbs.forEach((cb) => cb(res)); + }); + }; + excutor(resolve); + } + then(onFulfilled) { + return new Promise((resolve) => { + this.cbs.push(() => { + let res = onFulfilled(this.data); + if (res instanceof Promise) { + res.then(resolve); + } else { + resolve(res); + } + }); + }); + } + static all(arr) { + return new Promise((resolve, reject) => { + let res = []; + for (let [key, item] of Object.e(arr)) { + Promise.resolve(item).then( + (val) => { + if (key + 1 == arr.length) { + resolve(res); + } else { + res[key] = val; + } + }, + (err) => { + reject(err); + }, + ); + } + }); + } + // 最原始的 + static allSettled(arr) { + return new Promise((resolve, reject) => { + let res = []; + for (let [key, item] of Object.e(arr)) { + Promise.resolve(item).then( + (val) => { + if (key + 1 === arr.length) { + resolve(arr); + } else { + res.push({ + value: val, + status: 'fullfilled', + }); + } + }, + (err) => { + if (key + 1 === arr.length) { + resolve(arr); + } else { + res.push({ + reason: err, + status: 'rejected', + }); + } + }, + ); + } + }); + } + static allSettled(arr) { + return this.all( + arr.map((item) => { + Promise.resolve(item).then( + (val) => { + return { + value: val, + status: 'fullfilled', + }; + }, + (err) => { + return { + reason: err, + status: 'rejected', + }; + }, + ); + }), + ); + } +} + +class EventEmitter { + constructor() { + this.events = {}; + } + on(type, fn) { + if (this.events && this.events[type].length) { + this.events[type].push(fn); + } else { + this.events.type = [fn]; + } + } + emit(type, ...rest) { + if (this.events && this.events[type] && this.events[type].length) { + this.events[type].forEach((fn) => fn(...rest)); + } + } + off(type, fn) { + if (this.events[type] && this.events[type].length) { + this.events[type] = this.events[type].filter((rfn) => rfn != fn); + } + } + once(type, fn) { + let infn = (...rest) => { + fn(...rest); + this.off(type, infn); + }; + this.on(type, infn); + } +} + +class LRU { + constructor(limit) { + this.limit = limit; + this.cache = new Map(); + } + get(key) { + if (this.cache.has(key)) { + let val = this.cache.get(key); + this.cache.delete(key); + this.cache.set(key, val); + } + return -1; + } + set(key, val) { + let size = this.cache.size; + if (this.cache.has(key)) { + this.cache.delete(key); + } else if (size >= this.limit) { + let oldKey = this.cache.values().next().value; + this.cache.delete(oldKey); + } + this.cache.set(key, val); + } +} + +// selectSort +function selectSort(arr) { + for (let i = 0; i < arr.length; i++) { + let min = i; + for (let j = i + 1; j < arr.length; j++) { + if (arr[min] > arr[j]) { + min = j; + } + } + let tem = arr[min]; + arr[min] = arr[i]; + arr[i] = tem; + } + return arr; +} +selectSort([11, 22, 111, 2, 11, 44, 3]); +// inserSort +function insertSort(arr) { + for (let i = 1; i < arr.length; i++) { + let j = i; + while (j > 0 && arr[j - 1] > arr[j]) { + let tmp = arr[j]; + arr[j] = arr[j - 1]; + arr[j - 1] = tmp; + j--; + } + } + return arr; +} +insertSort([11, 22, 111, 2, 11, 44, 3]); +// 节流 频率变低 时间戳版 +function throttle(fn, delay) { + let pre = 0; + return (...arg) => { + let now = new Date().getTime(); + if (now - pre > delay) { + fn.apply(this, arg); + pre = now; + } + }; +} + +// 防抖 +function debounce(fn, delay) { + let timer = null; + return (...arg) => { + if (timer) { + clearTimeout(timer); + } + timer = setTimeout(() => { + fn.apply(this, arg); + timer = null; + }, delay); + }; +} + +function deepClone(obj) { + if (typeof obj != 'object') { + return obj; + } + let res = obj instanceof Array ? [] : {}; + for (let key in obj) { + let item = obj[key]; + res[key] = typeof item == 'object' ? deepClone(item) : item; + } + return res; +} + +function composeReduce(fns) { + if (fns.length == 0) { + return (arg) => arg; + } + if (fns.length == 1) { + return fns[1]; + } + return fns.reduce((a, b) => { + return (...arg) => a(b(...arg)); + }); +} + +function pipeReduce(fns) { + if (fns.length == 0) { + return (arg) => arg; + } + if (fns.length == 1) { + return fns[1]; + } + return (...arg) => { + return fns.reduce((a, b) => b(a), arg); + }; +} + +Array.prototype.mockMap = function (fn, context) { + let arr = this || []; + let res = []; + for (let i = 0; i < arr.length; i++) { + res.push(fn.call(context, arr[i], i, arr)); + } + return arr; +}; + +function mockInstanceof(l, r) { + l = Object.getPrototypeOf(l); + while (l) { + if (l === r.prototype) { + return true; + } + l = Object.getPrototypeOf(l); + } + return false; +} + +function compose(middlewares) { + return (ctx, next) => { + return dispatch(0); + function dispatch(i) { + let fn = middlewares[i]; + if (!fn) { + fn = next; + } + if (i >= middlewares.length) { + return Promise.resolve(); + } + try { + return Promise.resolve(fn(ctx, () => dispatch(i + 1))); + } catch (e) { + return Promise.reject(e); + } + } + }; +} + +Function.prototype.apply = function (context, ...param) { + let Obj = new Object(context) || window; + let sys = Symbol(''); + Obj[sys] = this; + let res = Obj[sys](...param); + delete Obj[sys]; + return res; +}; + +Function.prototype.bind = function () {}; + +function mockNew(fn, ...param) { + let obj = Object.create(fn.prototype); + let res = fn.apply(obj, param); + return typeof res == 'object' ? res : obj; +} diff --git a/docs/interview/experiences/practise/202405/0520.js b/docs/interview/experiences/practise/202405/0520.js new file mode 100644 index 0000000..a4517b7 --- /dev/null +++ b/docs/interview/experiences/practise/202405/0520.js @@ -0,0 +1,27 @@ +/** never give up + * 1.选择排序/插入排序 + * 2. + */ + +function swap(arr, i, j) { + let temp = arr[i]; + arr[i] = arr[j]; + arr[j] = temp; +} + +function selectSort(arr) { + for (let i = 0; i < arr.length; i++) { + let min = i; + for (let j = i + 1; j < arr.length; j++) { + if (arr[min] > arr[j]) { + min = j; + } + } + swap(arr, min, i); + } +} +selectSort(); + +function inserSort(arr) { + for (let i = 1; i < arr.length; i++) {} +} diff --git a/docs/interview/experiences/practise/202405/0520.ts b/docs/interview/experiences/practise/202405/0520.ts new file mode 100644 index 0000000..57d1ab0 --- /dev/null +++ b/docs/interview/experiences/practise/202405/0520.ts @@ -0,0 +1,71 @@ +type MockPick = { + [P in K]: T[P]; +}; + +type TT2 = { + name: string; + age: number; + getName: () => void; +}; +type TT3 = MockPick; + +type MockOmit = Pick>; + +type TT4 = Omit; +type TT5 = MockOmit; +// 交集 +type MockExtract = A extends B ? A : never; + +// Exclude 返回 T 中不存在 U 的部分 +type MockExclude = A extends B ? never : A; + +type MockFunction = { + [P in keyof T as T[P] extends K ? P : never]: T[P]; +}; +type TT6 = MockFunction; + +type Equal = ((arg: A) => T extends A ? 1 : 2) extends (arg: B) => T extends B ? 1 : 2 + ? true + : false; + +type TT7 = Equal; + +type MockReturnTypes any> = T extends (...arg: any) => infer R + ? R + : never; + +type MockParameters any> = T extends (...arg: infer P) => any + ? P + : never; + +type MockAwaited> = T extends Promise + ? P extends Promise + ? MockAwaited

+ : P + : T; + +type MockRecord = { + [K in keyof T]: O; +}; + +type MockRequired = { + [P in keyof T]-?: T[P]; +}; + +type Q3 = { + name: 'cpp'; + age: 32; +}; +type Q4 = { name: 'cpp' } | { age: 32 }; +// 索引转联合 +type IndexToUnion = { + [P in keyof T]: { + [K in P]: T[K]; + }; +}[keyof T]; +type Q5 = IndexToUnion; +type Q6 = UnionToIndex; +// 联合转交叉 +type UnionToIndex = (T extends T ? (x: T) => unknown : never) extends (x: infer P) => unknown + ? P + : never; diff --git a/docs/interview/experiences/practise/202405/0521.js b/docs/interview/experiences/practise/202405/0521.js new file mode 100644 index 0000000..13af863 --- /dev/null +++ b/docs/interview/experiences/practise/202405/0521.js @@ -0,0 +1,74 @@ +/** + * 1.数组中的第 K 个最大元素 + * 2.K 个一组链表反转 + * 3.最大子序和 + * 4.最长回文子串 + * 5.搜索旋转排序数组 + * 6.买卖股票的最佳时机 + * 7.岛屿数量 + * 8.合并两个有序数组 + * 9.全排列 I/II + * 10.二叉树的最近公共祖先 + */ +function maxChildSum(arr) {} + +// 最长回文子串 aabbcccbe dp +function maxPalidStr(str) { + if (str.length == 0) return ''; + let len = str.length; + let dp = new Array(len).fill().map(() => new Array(len).fill(false)); + // 对角线设置true + for (let i = 0; i < len; i++) { + dp[i][i] = true; + } + let left = 0; + let maxL = 0; + for (let i = 1; i < len; i++) { + for (let j = 0; j < i; j++) { + if (str[i] == str[j]) { + if (i - i <= 2) { + dp[i][j] = true; + } else { + dp[i][j] = dp[i - 1][j - 1]; + } + } + if (dp[i][j] && i - j + 1 > maxL) { + maxL = i - j + 1; + left = j; + } + } + } + return str.substring(left, left + maxL); +} + +// 搜索旋转排序数组 +function rotateSortArr(arr) {} + +// 买卖股票的最佳时机 +function bestTime(arr) {} + +function islands(grid) { + let num = 0; + let [r, c] = [grid.length, grid[0].length]; + for (let i = 0; i < r; i++) { + for (let j = 0; j < c; j++) { + if (grid[i][j] == '1') { + num += 1; + dfs(i, j, grid); + } + } + } + return num; +} +function dfs(row, column, grid) { + if (!isValid(row, column, grid) || grid[row][column] != '1') return; + grid[row][column] = '2'; + // 上下左右 + dfs(row - 1, column, grid); + dfs(row + 1, column, grid); + dfs(row, column - 1, grid); + dfs(row, column + 1, grid); +} +function isValid(row, column, grid) { + return row >= 0 && row < grid.length && column >= 0 && column < grid[0].length; +} diff --git a/docs/interview/guide/6hotHandwriting.md b/docs/interview/guide/6hotHandwriting.md index 2f40c9c..aeb6558 100644 --- a/docs/interview/guide/6hotHandwriting.md +++ b/docs/interview/guide/6hotHandwriting.md @@ -148,7 +148,7 @@ const pipe = (...funs) => { if (funs.length == 1) return funs(0); return (...arg) => { return funs.reduce((a, b) => { - return a(b); + return b(a); }, arg); }; }; diff --git a/docs/interview/js/35ArrayBuffer.md b/docs/interview/js/35ArrayBuffer.md index 42ec9aa..a37e97e 100644 --- a/docs/interview/js/35ArrayBuffer.md +++ b/docs/interview/js/35ArrayBuffer.md @@ -11,14 +11,126 @@ nav: path: /interview --- -ArrayBuffer/Uint8Array/Blob/File 到底都是些什么东东?今天来学习下 +> 摘自[JS 二进制之 File、Blob、FileReader、ArrayBuffer、Base64](https://juejin.cn/post/7306802651009138688?searchId=20240520101125C0A0B108A4F04C293364) + +- ArrayBuffer/Uint8Array/Blob/File 到底都是些什么东东?今天来学习下 ## Blob -Blob +Blob【Binary large object】即**二进制大对象**,表示原始文件的数据。它表示一个不可变、原始数据的类文件对象。它的数据可以按文本或二进制的格式进行读取,也可以转成  ReadableStream  来用于数据操作。简单来说,Blob 就是一个不可修改的二进制文件! + +### 1.1 Blob 创建 + +```js +new Blob(array, options); +``` + +- array 是一个包含字符串、ArrayBuffer、ArrayBufferView、Blob 等的数组或可迭代对象。多个 BlobParts 会按照它们在数组中的顺序进行连接以形成 Blob。如果省略该参数,则创建一个空的 Blob。 options 是一个对象,可选属性为 + +- type 【较常用】 ,默认值为"",表示放入到 blob 对象中内容的 MIME 类型 + +### 1.2 Blob 切片 + +Blob 对象内置了 slice() 方法用来将 blob 对象分片,其语法如下: + +```js +const instanceOfBlob = new Blob(array, options); +const blob = instanceOfBlob.slice([start [, end [, contentType]]]}; +``` + +其有三个参数: + +- start:设置切片的起点,即切片开始位置。默认值为 0,这意味着切片应该从第一个字节开始; +- end:设置切片的结束点,会对该位置之前的数据进行切片。默认值为 blob.size; +- contentType:设置新 blob 的 MIME 类型。如果省略 type,则默认为 blob 的原始值。 -Blob 是对大数据块的不透明引用或者句柄。名字源于 SQL 数据库,表示“**二进制大数据**”(Binary Large Object)。在 JavaScript 中 Blob 通常表示二进制数据,但是不一定是大量数据。Blob 是不透明的,我们可以对它执行的操作只有获取它的大小,MIME 类型和将他切割成更小的 Blob。——《JavaScript 权威指南》 +来看下面例子 + +```js +const iframe = document.getElementsByTagName('iframe')[0]; + +const blob = new Blob(['Hello World'], { type: 'text/plain' }); + +const subBlob = blob.slice(0, 5); + +iframe.src = URL.createObjectURL(subBlob); +``` + +此时页面会显示"Hello"。 ## File File 继承 Blob,并基于用户的操作系统拓展了 blob,使用户可以通过浏览器安全的访问系统的 File + +> File 对象 其实就是特殊类型的 Blob,即 Blob 的属性和方法同样适用于 File 对象。 + +### JS 中主要有两个地方产生 File 对象: + +- 通过`` 元素上传文件后,返回的 **FileList** 对象 +- 文件拖放操作生成的**DataTransfer** 对象 + +看下两个示例 + +```js +// FileList + +const fileInput = document.getElementById("fileInput"); +fileInput.onchange = (e) => { + console.log(e.target.files); +} +``` + +```js +// 文件拖放 +

; +const dropZone = document.getElementById('drop-zone'); +dropZone.ondragover = (e) => { + e.preventDefault(); +}; +dropZone.ondrop = (e) => { + e.preventDefault(); + const files = e.dataTransfer.files; + console.log(files); +}; +``` + +注意: + +- 两个拖拽事件中都需要添加 e.preventDefault(),用来阻止默认事件,可以阻止浏览器的一些默认行为。比如默认浏览器不允许任何拖拽操作!! + +- **e.dataTransfer.files** 的属性值是一个 FileList 数组。 + +## FileReader + +FileReader **是一个异步 API**,用于读取文件并提取其内容以供进一步使用。 【 FileReader 可以将 Blob 读取为不同的格式!!】 + +## ArrayBuffer + +ArrayBuffer 对象用来表示通用的、固定长度的**原始二进制数据缓冲区**。ArrayBuffer 的内容不能直接操作,只能通过 **DataView 对象或 TypedArrray** 对象来访问。这些对象用于读取和写入缓冲区内容。 + +> 注意: ArrayBuffer 本身就是一个黑盒,不能直接读写所存储的数据,需要借助以下视图对象来读写 + +## Object URL + +Object URL 又称 Blog URL,它是一个用来表示 File Object 或 Blob Object 的 URL。在网页中,我们可能会看到过这种形式的 Blob URL: + +创建一个指向 Blob 或 File 对象的可以用作图像、二进制数据下载链接等的 URL 源,可以在 `< img /> < script /> `中用当作 src 属性的值!! + +```js +const objUrl = URL.createObjectURL(new File([''], 'filename')); +console.log(objUrl); +URL.revokeObjectURL(objUrl); +``` + +## Base64 + +Base64 是一种**基于 64 个可打印字符来表示二进制数据**的表示方法。Base64 编码普遍应用于需要通过被设计为处理文本数据的媒介`< img />`上储存和传输二进制数据而需要编码该二进制数据的场景。这样是为了保证数据的完整并且不用在传输过程中修改这些数据。 + +在 JavaScript 中,有两个函数被分别用来处理解码和编码 base64 字符串: + +- atob():解码,解码一个 Base64 字符串; +- btoa():编码,从一个字符串或者二进制数据编码成一个 Base64 字符串。 + +## 总结 + +https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/042e945267784767bae2ff9852d9c80f~tplv-k3u1fbpfcp-jj-mark:3024:0:0:0:q75.awebp#?w=1512&h=1070&s=146124&e=webp&b=fefcfc diff --git a/docs/interview/js/36webRtc.md b/docs/interview/js/36webRtc.md new file mode 100644 index 0000000..a4414fa --- /dev/null +++ b/docs/interview/js/36webRtc.md @@ -0,0 +1,67 @@ +--- +title: web-RTC +order: 36 +group: + order: 1 + title: js Basic + path: /interview/js +nav: + order: 3 + title: 'interview' + path: /interview +--- + +> 本文转自[WebRTC 这么火 🔥,前端靓仔,请收下这篇入门教程](https://juejin.cn/post/7266417942182608955) + +## webRtc + +WebRTC(Web Real-Time Communications)是一项**实时通讯技术**,它允许网络应用或者站点,在不借助中间媒介的情况下,建立浏览器之间点对点(Peer-to-Peer)的连接,实现视频流和(或)音频流或者其他任意数据的传输。WebRTC 包含的这些标准使用户在无需安装任何插件或者第三方的软件的情况下,创建点对点(Peer-to-Peer)的数据分享和电话会议成为可能。 + +> 手机和手机之间建立点对点连接,实现视频流或者音频流的传输 + +### 实时通信和即时通信的区别 + +- IM 即时通信,就是通过文字聊天、语音消息发送、文件传输等方式通信,考虑的是**可靠性**; +- RTC 实时通信:音视频通话、电话会议,考虑的是**低延时**。 + +### WebRTC 应用场景 + +WebRTC 的能力使其适用于各种实时通信场景: + +- 点对点通讯:WebRTC 支持浏览器之间进行音视频通话,例如语音通话、视频通话等; +- 电话会议:WebRTC 可以支持多人音视频会议,例如腾讯会议、钉钉会议等; +- 屏幕共享:WebRTC 不仅可以传输音视频流,还可以用于实时共享屏幕; +- 直播:WebRTC 可以用于构建实时直播,用户可以通过浏览器观看直播内容。 + +## WebRTC 组成部分 + +WebRTC 主要由三部分组成:浏览器 API、音视频引擎和网络 IO。 + +### 浏览器 API + +用于采集摄像头和麦克风生成媒体流,并处理音视频通信相关的编码、解码、传输过程,可以使用以下 API 在浏览器中创建实时通信应用程序。 + +- getUserMedia: 获取麦克风和摄像头的许可,使得 WebRTC 可以拿到本地媒体流; +- RTCPeerConnection: 建立点对点连接的关键,提供了创建,保持,监控,关闭连接的方法的实现。像媒体协商、收集候选地址都需要它来完成; +- RTCDataChannel: 支持点对点数据传输,可用于传输文件、文本消息等。 + +### 音视频引擎 + +WebRTC 内置了强大的音视频引擎,可以对媒体流进行编解码、回声消除、降噪、防止视频抖动等处理,我们使用者大可不用去关心如何实现 。主要使用的音视频编解码器有: + +- **OPUS**: 一个开源的低延迟音频编解码器,WebRTC 默认使用; +- **G711**: 国际电信联盟 ITU-T 定制出来的一套语音压缩标准,是主流的波形声音编解码器; +- VP8: VP8,VP9,都是 Google 开源的视频编解码器,现在主要用于 WebRTC 视频编码; +- H264: 视频编码领域的通用标准,提供了高效的视频压缩编码,之前 WebRTC 最先支持的是自己家的 VP8,后面也支持了 H264、H265 等。 + +## 网络 I/O + +WebRTC 传输层用的是 UDP 协议,因为音视频传输对及时性要求更高,如果使用 TCP 当传输层协议的话,如果发生丢包的情况下,因为 TCP 的可靠性,就会尝试重连,如果第七次之后仍然超时,则断开 TCP 连接。而如果第七次收到消息,那么传输的延迟就会达到 2 分钟。在延迟高的情况下,想做到正常的实时通讯显然是不可能的,此时 TCP 的**可靠性**反而成了弊端。而 UDP 则正好相反,它只负责有消息就传输,不管有没有收到,这里从底层来看是满足 WebRTC 的需求的,所以 WebRTC 是采用 UDP 来当它的传输层协议的。 + +这里主要用到以下几种协议/技术 + +- RTP/SRTP: 传输音视频数据流时,我们并不直接将音视频数据流交给 UDP 传输,而是先给音视频数据加个 RTP 头,然后再交给 UDP 进行,但是由于浏览器对安全性要求比较高,增加了加密这块的处理,采用 SRTP 协议; +- RTCP:通过 RTCP 可以知道各端的网络质量,这样对方就可以做流控处理; +- **P2P(ICE + STUN + TURN)**: 这是 WebRTC 最核心的技术,利用 ICE、STUN、TURN 等技术,实现了浏览器之间的直接点对点连接,解决了 NAT 穿透问题,实现了高质量的网络传输。 + +除了以上三部分,WebRTC 还需要一个**信令服务**做会话管理,但 WebRTC 规范里没有包含信令协议,需要自行实现。