Skip to content

学习周报 - 2024/3/17

网页录屏或截图

MediaDevices.getDisplayMedia() - Web API 接口参考 | MDN

使用MediaDevices.getDisplayMedia()得到一个MediaStream对象,然后新建一个MediaRecorder对象,使用该对象对MediaStream进行录制。

通过修改对象原型间接获取对象

从一道面试题看闭包“漏洞”,闭包真的“闭”吗?【渡一教育】_哔哩哔哩_bilibili

当只能访问一个对象上的属性,而不能访问其本身时,可以通过在原型上设置一个属性的get,通过这个get获取到该对象:

tsx
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 | MDN

JavaScript 数据类型和数据结构 - JavaScript | MDN

首先会将加号两端的表达式转换为基本数据类型,会依次调用以下方法:

  1. [@@toPrimitive](notion://www.notion.so/2024-3-17-145088f9d6ea423483e03f5626de05eb?showMoveTo=true&saveParent=true)()方法;
  2. valueOf()方法;
  3. toString()方法。

如果中途有报错或已经得到基本数据类型,那就会终止。

一般对象会被转换为字符串"[object Object]”,而数组则被转换为包含其所有元素的逗号分割的字符串(例如”””a,b,c”)。

如果这样转换后,其中一方是字符串,则进行字符串拼接,否则转换为数字进行计算。

当是字符串++时,则字符串会使用Number()构造函数进行强制转换,具体的转换规则可参考:

Number - JavaScript | MDN

然后就得到了数字,就可以进行数字计算了。

作用域

柯里化

一种函数的转换,即将一个函数的调用形式从f(a, b, c) 转换为的 f(a)(b)(c),这样做可以从一个接收多个参数的函数衍生出很多传入很多不同的、部分参数时状态不同的函数(即「部分函数」),在使用上会更加灵活。

lodash中有curry方法用于js函数的柯里化。

箭头函数和function函数中的this指向

箭头函数表达式 - JavaScript | MDN

箭头函数的this指向定义时的作用域,而function函数的this则指向运行时的作用域。

而且,如果设置一个对象的某个属性是箭头函数,其内部的this会指向全局对象(Window)。

tsx
"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指向是不同的。

tsx
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方法:

tsx
export const isObject = (val: unknown): val is Record<any, any> =>
  val !== null && typeof val === 'object'

但是该方法判断数组也是返回true,需要搭配另一个isArray方法:

tsx
export const isArray = Array.isArray

洋葱模型

Koa洋葱模型 从理解到实现

Koa使用的对信息的接收和响应的处理模型,对所有请求会依次从外到内经过中间件进行处理,然后响应的数据也会从内到外反过来再经过中间件的处理:

Untitled

使用和执行过程如下(官方示例):

middleware.gif

代码中每个next函数其实就是下一个中间件函数,不过它可以接收上一个中间件处理后的ctx,这个实现的重点就在于如果对中间件函数进行包装,实现在调用next时不需要显性地传递ctx和其下下个中间件,也就是不需要像next(ctx, nextNext)这样调用。

这里是知乎文章中提供的简化方案,具体方案可以参考官方源码(会多一些类型判断和数据验证):

jsx
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#一、观察者模式

这两种都是前端中常用的通信模式,都是由发布者/被观察者主动传递信息给订阅者/观察者。

区别主要在于:

  • 架构上的区别:观察者模式中,有哪些观察者会被记录在被观察者上,然后被观察者对这些观察者进行一一通知。

    Untitled

    而发布订阅模式中,会使用一个事件发布订阅中心记录发布者和订阅者,而不需要发布者记住有哪些订阅者,而只需要通知发布订阅中心自己需要发布什么消息就可以了,然后由发布订阅中心去通知订阅了该类型的所有订阅者。

    Untitled

  • 耦合度:由于上述的结构差异,发布订阅模式是相对于观察者模式更松散的,发布者和订阅者都不知道对方是否存在;

  • 同步异步差异:观察者模式通常是同步的,而发布订阅模式通常是异步的。

Promise.all的原理

【Promise 源码学习】第十一篇 - Promise.all 的实现_源码_Brave_InfoQ写作社区

该方法本质上是返回一个Promise,然后创建一个结果列表,在Promise列表中任一Promise resolve时,将结果放入结果列表对应索引的地方,并且检查是否所有结果都填上了,如果都填上了,就把.all生成的Promise resolve。

而只要有其中一个Promise reject了,就直接把.all生成的Promise reject。

jsx
  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实现方式:

jsx
// 接收一个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函数。