学习周报 - 2024/3/17
网页录屏或截图
MediaDevices.getDisplayMedia() - Web API 接口参考 | MDN
使用MediaDevices.getDisplayMedia()
得到一个MediaStream
对象,然后新建一个MediaRecorder
对象,使用该对象对MediaStream
进行录制。
通过修改对象原型间接获取对象
从一道面试题看闭包“漏洞”,闭包真的“闭”吗?【渡一教育】_哔哩哔哩_bilibili
当只能访问一个对象上的属性,而不能访问其本身时,可以通过在原型上设置一个属性的get,通过这个get获取到该对象:
var o = (function () {
var obj = {
a: 1,
b: 2,
};
return {
get: function (k) {
return obj[k];
},
}
})()
// 通过o.get()方法可以访问obj内部属性,但是无法直接获取obj
Object.defineProperty(Object.prototype, 'returnThis', {
get() {
return this;
},
});
const obj = o.returnThis // 这里就获取到了obj
+
号运算过程
JavaScript 数据类型和数据结构 - JavaScript | MDN
首先会将加号两端的表达式转换为基本数据类型,会依次调用以下方法:
- [
@@toPrimitive](notion://www.notion.so/2024-3-17-145088f9d6ea423483e03f5626de05eb?showMoveTo=true&saveParent=true)()
方法; valueOf()
方法;toString()
方法。
如果中途有报错或已经得到基本数据类型,那就会终止。
一般对象会被转换为字符串"[object Object]”
,而数组则被转换为包含其所有元素的逗号分割的字符串(例如””
,”a,b,c”
)。
如果这样转换后,其中一方是字符串,则进行字符串拼接,否则转换为数字进行计算。
当是字符串++时,则字符串会使用Number()
构造函数进行强制转换,具体的转换规则可参考:
然后就得到了数字,就可以进行数字计算了。
作用域
柯里化
一种函数的转换,即将一个函数的调用形式从f(a, b, c)
转换为的 f(a)(b)(c)
,这样做可以从一个接收多个参数的函数衍生出很多传入很多不同的、部分参数时状态不同的函数(即「部分函数」),在使用上会更加灵活。
lodash中有curry
方法用于js函数的柯里化。
箭头函数和function函数中的this指向
箭头函数的this指向定义时的作用域,而function函数的this则指向运行时的作用域。
而且,如果设置一个对象的某个属性是箭头函数,其内部的this会指向全局对象(Window)。
"use strict";
const obj = {
i: 10,
b: () => console.log(this.i, this),
c() {
console.log(this.i, this);
},
};
obj.b(); // 输出 undefined, Window { /* … */ }(或全局对象)
obj.c(); // 输出 10, Object { /* … */ }
但是如果是一个类体,其内部定义了一个箭头函数属性,则这个箭头函数的this则会指向该类体,但需要注意,之所以会出现这种情况,是因为闭包,和类内部的函数属性的this指向是不同的。
class C {
a = 1;
autoBoundMethod = () => {
console.log(this.a);
};
}
const c = new C();
c.autoBoundMethod(); // 1
const { autoBoundMethod } = c;
autoBoundMethod(); // 1
// 如果这是普通方法,此时应该是 undefined
Vue3中判断一个变量是否是对象
在@vue/core
项目的packages\shared\src\general.ts可以找到isObject方法:
export const isObject = (val: unknown): val is Record<any, any> =>
val !== null && typeof val === 'object'
但是该方法判断数组也是返回true,需要搭配另一个isArray方法:
export const isArray = Array.isArray
洋葱模型
Koa使用的对信息的接收和响应的处理模型,对所有请求会依次从外到内经过中间件进行处理,然后响应的数据也会从内到外反过来再经过中间件的处理:
使用和执行过程如下(官方示例):
代码中每个next
函数其实就是下一个中间件函数,不过它可以接收上一个中间件处理后的ctx
,这个实现的重点就在于如果对中间件函数进行包装,实现在调用next时不需要显性地传递ctx和其下下个中间件,也就是不需要像next(ctx, nextNext)
这样调用。
这里是知乎文章中提供的简化方案,具体方案可以参考官方源码(会多一些类型判断和数据验证):
const middleware= []
let mw1= asyncfunction (ctx, next) {
console.log("next前,第一个中间件")
await next()
console.log("next后,第一个中间件")
}
let mw2= asyncfunction (ctx, next) {
console.log("next前,第二个中间件")
await next()
console.log("next后,第二个中间件")
}
let mw3= asyncfunction (ctx, next) {
console.log("第三个中间件,没有next了")
}
function use(mw) {
middleware.push(mw)
}
use(mw1)
use(mw2)
use(mw3)
let fn=function (ctx) {
return dispatch(0)
function dispatch(i) {
let currentMW= middleware[i]
if(!currentMW) {
return}
return currentMW(ctx, dispatch.bind(null, i+ 1))
}
}
fn()
发布订阅模式和观察者模式
https://vue3js.cn/interview/design/Observer Pattern.html#一、观察者模式
这两种都是前端中常用的通信模式,都是由发布者/被观察者主动传递信息给订阅者/观察者。
区别主要在于:
架构上的区别:观察者模式中,有哪些观察者会被记录在被观察者上,然后被观察者对这些观察者进行一一通知。
而发布订阅模式中,会使用一个事件发布订阅中心记录发布者和订阅者,而不需要发布者记住有哪些订阅者,而只需要通知发布订阅中心自己需要发布什么消息就可以了,然后由发布订阅中心去通知订阅了该类型的所有订阅者。
耦合度:由于上述的结构差异,发布订阅模式是相对于观察者模式更松散的,发布者和订阅者都不知道对方是否存在;
同步异步差异:观察者模式通常是同步的,而发布订阅模式通常是异步的。
Promise.all的原理
【Promise 源码学习】第十一篇 - Promise.all 的实现_源码_Brave_InfoQ写作社区
该方法本质上是返回一个Promise,然后创建一个结果列表,在Promise列表中任一Promise resolve时,将结果放入结果列表对应索引的地方,并且检查是否所有结果都填上了,如果都填上了,就把.all生成的Promise resolve。
而只要有其中一个Promise reject了,就直接把.all生成的Promise reject。
static all (promises) {
return new Promise((resolve, reject)=>{
let result = [];
let times = 0;
// 将成功结果放入数组中对应的位置
const processSuccess = (index, val)=>{
result[index] = val;
if(++times === promises.length){
resolve(result); // 全部执行成功,返回 result
}
}
// 遍历处理集合中的每一个 promise
for(let i = 0;i < promises.length; i++){
let p = promises[i];
if(p && typeof p.then ==='function'){
// 调用这个p的 then 方法
p.then((data)=>{
// 按照执行顺序存放执行结果
processSuccess(i, data)
}, reject);
}else{
// 普通值,直接按照执行顺序放入数组对应位置
processSuccess(i, p)
}
}
})
}
async/await原理
async/await实际上是生成器+Promise的语法糖,Promise可以配合生成器以达到让Promise全部resolve后再执行后续代码。
以下是生成器+Promise实现方式:
// 接收一个generator函数,让它自动执行,并且会考虑到generator函数内部的异步操作
const spawn = (generatorFunc) => {
var generator = generatorFunc();
function continuer(verb, arg) {
try {
var result = generator[verb](arg);
} catch (err) {
return Promise.reject(err);
}
if (result.done) { // 如果generator函数执行完毕,返回最终结果
return result.value;
} else { // 如果generator未执行完毕,继续执行
return Promise.resolve(result.value).then(onFulfilled, onRejected);
}
}
var onFulfilled = continuer.bind(continuer, 'next');
var onRejected = continuer.bind(continuer, 'throw');
return onFulfilled();
};
const myAsync = () => {
spawn(function* () {
const startTime = new Date().getTime();
yield createTimeout(1000);
console.log('myAsync 1', new Date().getTime() - startTime); // 1000
yield createTimeout(1000);
console.log('myAsync 2', new Date().getTime() - startTime); // 2000
});
};
myAsync();
实现的关键在于生成器还未执行完时,返回一个Promise.resolve(result.value)
(考虑到yield的是异步函数),并且在resolve
时再去继续执行生成器的next
函数,或者reject
时执行生成器的throw
函数。