记 9 同步异步、asnyc、proxy代理和模块module

128 阅读6分钟

学习一 异步和同步

同步 即按顺序一个接一个执行

异步 是当程序中出现等待时间较长的(需要计算、需要等待反馈、等待延时等等)任务,为防止执行异步任务时程序阻塞(程序干等着什么也不做),异步任务在被创建后不会进入主线程,需要先在任务队列中等待,当主线程任务执行完毕,任务队列才会“通知”主线程执行异步任务‌ image.png

异步任务分为宏任务和微任务

  • macro-task(宏任务):包括整体代码script,setTimeout,setInterval
  • micro-task(微任务):Promise,process.nextTick

( Promise并不是完全的同步,在Promise中是同步任务,执行resolve或者reject回调的时候,此时是异步操作,会先将then/catch等放到异步任务中的微任务队列 )

执行过程:

1.先执行所有同步任务,碰到异步任务放到任务队列中

2.同步任务执行完毕,开始执行当前所有的异步任务

3.先执行任务队列里面所有的微任务

4.然后执行一个宏任务

5.然后再执行所有的微任务

6.再执行一个宏任务,再执行所有的微任务·······依次类推到执行结束。

3-6的这个循环就称为事件循环Event Loop

// 异步请求
const A = new Promise((resolve,reject) => {
	resolve('成功')	
}.then(data => {
	// 异步请求嵌套
	return new Promise((resolve,reject) => {
		resolve('成功2')
	}.then(data => { // then 的第一个参数data
		console.log( data )
	},err =>{ // then 的第二个参数err
		console.log( err )
          //抛出异常 后续任务全部触发失败处理
		throw new Error ( '任务失败’) 
	}
}

学习二 async、await

async (await)是基于promise的一种异步操作的简化功能,async函数完全可以看作多个异步操作,包装成的一个 Promise 对象,而await命令就是内部then命令的语法糖,用同步的书写方式实现了异步的代码。(执行第一步,将执行第一步的结果返回给第二步使用。)

async getFaceResult () {
                try {
                    let location = await this.getLocation(this.phoneNum);
                    if (location.data.success) {
                        let province = location.data.obj.province;
                        let city = location.data.obj.city;
                        let result = await this.getFaceList(province, city);
                        if (result.data.success) {
                            this.faceList = result.data.obj;
                        }
                    }
                } catch(err) {
                    console.log(err);
                }
            }

//先准备一个返回 promise 对象的函数
function asyncTask ( ) {
	return new Promise ( resolve,reject ) => {
          //关键代码执行结果
		const isSuccess = true 
		if ( isSuccess = true ){ 
              resolve ('任务的处理结果data') 
		} else {
	            reject( '失败')
		}
	}
}

//为使用 await 的函数添加 async,增加逻辑可读性
async function main(){ 
    console.log('任务一')
    const data1 = await asyncTask()
    console.log('任务二')
    await asyncTask2()
    console.log('任务三')
    task()
    console.log('任务四')
    await asyncTask3()
}

main()

学习三 proxy代理

proxy是一种网络服务器,充当客户端和目标服务器之间的中介,为客户端提供匿名性、安全性和加速访问等服务。

一、改善网络性能:

proxy可以压缩页面内容,减少传输数据量,同时缓存网页、图像、视频等内容,当用户再次访问该内容时,proxy就可以从缓存中读取,从而避免了重复下载和重复请求,提高了访问速度。proxy还能够进行负载均衡,将用户请求分配到多个服务器上,避免出现某一个服务器负载过高的情况。

二、保障网络安全

proxy可以屏蔽客户端与目标服务器之间的直接联系,保护客户端真实IP地址和身份信息,防止客户端暴露在公网上遭受攻击(实现匿名访问)。proxy还可以对访问进行过滤和检查,拦截恶意代码、病毒和垃圾邮件等,保护网络安全的同时实现安全访问控制,限制用户访问某些不安全或敏感内容,保护重要的企业数据,防止数据泄露。(proxy可以将用户请求转发到国外服务器,从而突破网络限制,实现自由访问。)


const target = {
  message: "Hello, world!",
  foo: function() {
    return "foo";
  }
};

const handler = {
  get: function(target, prop, receiver) {
    if (prop === "message") {
      return "Hello, proxy!";
    }
    // 对于非'message'属性,直接返回原属性
    return Reflect.get(...arguments);
  }

const proxyTest = new Proxy(target, handler);

console.log(proxyTest.message); // 输出 "Hello, proxy!"
console.log(proxyTest.foo());   // 输出 "foo"

在这个示例中,我们创建了一个 Proxy 对象来代理 target 对象。我们通过 handler 对象定义了一个 get 捕获器,它拦截了对 message 属性的访问,并返回了一个不同的字符串。对于其他属性的访问,我们使用 Reflect.get 来转发请求到目标对象。

然后我尝试对foo也进行拦截更改,这是尝试时的错误代码


  // 与get同级
  apply: function(target, thisArg, argumentsList) { 
    // 拦截foo方法的调用 
    if (thisArg === target.foo) {
    return "intercepted foo!"; 
  } 
  // 对于其他函数的调用,直接调用原函数 
  return Reflect.apply(...arguments); }
};

但总是失败。

百度的ai总是说一些囫囵话,做一些莫名其妙的修改,导致我一度以为是判断条件有误,直到最后发现根本没有进入apply方法。

换了个ai,才找到问题

Proxy 对象中,apply 方法只会在代理对象作为函数被调用时触发拦截,而不是在方法调用时触发。且applytarget 参数指向被代理的对象。因此,如果你想要使用 apply 来处理函数调用,你需要确保代理对象本身是一个函数,或者你通过某种方式直接调用了代理对象。

我们无法直接从 apply 的参数中获取被调用函数的名称,但get可以,这些调用会通过 get 陷阱来获取方法的引用,然后直接调用这些方法。例如,如果 proxy 是一个代理一个对象的 Proxy 对象,那么 proxy.foo() 会触发 get 陷阱来获取 foo 方法的引用,然后直接调用 foo 方法。

关于代理对象(Proxy object)和被代理对象(Target object)的 代码示例

const targetObject = {
    data: 42,
    method: function() {
        return 'Called method on target object';
    }
};

const handlerObject = {
    get: function(target, propKey, receiver) {
        console.log(`Property ${propKey} has been accessed.`);
        return Reflect.get(...arguments);
    },
    apply: function(target, thisArg, argumentsList) {
        console.log('Method has been called with arguments:', argumentsList);
    }
};

const proxyObject = new Proxy(targetObject, handlerObject);

console.log(proxyObject.data); // 通过代理对象访问属性,触发 get 陷阱
proxyObject.method(); // 通过代理对象调用方法,触发 apply 陷阱

在这个示例中:

  • targetObject 是被代理对象,它包含了一些属性和方法。

proxy是一个构造函数(类)new Proxy(obj, handler),用于定义基本操作的自定义行为(如属性查找、删除、赋值、枚举、函数调用等)

  • handlerObject 是一个包含 get 和 apply 陷阱的对象,用于自定义属性访问和方法调用的行为。handler (target,property,receiver)(第一个参数是代理对象,第二个参数是属性名,第三个对象是基于当前实例,做一些proxy对象的操作)

  • proxyObject 是代理对象,它是通过 new Proxy(targetObject, handlerObject) 创建的,它包装了 targetObject 并在访问属性或调用方法时触发 handlerObject 中定义的陷阱。(第一个参数是代理对象,第二个参数是要进行的操作)

在 apply 中处理函数调用的正确操作

const target = function () {};

const handler = {
    apply: function (target, thisArg, argumentsList) {
        console.log(`Function is being called with arguments`, argumentsList);
        const result = target.apply(thisArg, argumentsList);
        console.log(`Function has been called`);
        return result;
    }
};

const proxyTest = new Proxy(target, handler);

proxyTest.foo = function () {
    return "foo";
};

proxyTest.bar = function () {
    return "bar";
};

proxyTest("foo"); // 触发 apply 陷阱,并输出 "foo"
proxyTest("bar"); // 触发 apply 陷阱,并输出 "bar"

最后 ai 还给出了解决办法

办法一 返回一个函数包装器

即通过创建一个新的函数来“包装”原始函数,然后在新函数中调用原始函数。

包装函数在调用原始函数时会调用原始对象上的方法,并修改其行为,执行一些额外的操作,比如添加额外的日志、修改参数或返回值等。



const handler = {
    get: function (target, prop, receiver) {
        // 拦截message属性的访问
        if (prop === "message") {
            console.log(prop, 1);
            return "Hello, proxy!";
        } else if (prop === "foo") { 
            console.log(prop, 2); 
            // 返回一个函数包装器来拦截foo的调用
            const origFoo = target[prop];
            return function (...args) {
                console.log('foo is being called');
                // 调用原始的foo函数并获取其返回值
                let result = origFoo.apply(this, args);
                // 返回一个更改后的值
                return result = "intercepted foo: next";
            };
        }
        // 对于其他属性,正常返回它们的值
        return Reflect.get(...arguments);
    }
};

const proxyTest = new Proxy(target, handler);
console.log(proxyTest.foo()); 

foo 属性被访问时,get 陷阱会返回一个新的包装函数。包装函数首先调用原始的 foo 方法,获取其返回值,然后你可以在这个基础上添加额外的逻辑或修改返回值。这种方法允许你保留原始方法的行为,同时添加额外的功能。


// 如果有多个函数要被代理,先检查属性是否是函数,如果是,则返回一个函数包装器
        if (typeof target[prop] === 'function') {
            // 获取原始的函数
            const origMethod = target[prop];
            // 返回一个包装器函数
            return function (...args) {
                // 在原始函数执行前添加的逻辑
                console.log(`${prop} is being called`);
                // 调用原始的函数并获取其返回值
                const result = origMethod.apply(this, args);
                // 在原始函数执行后添加的逻辑
                console.log(`${prop} has been called`);
                // 返回原始函数的返回值,也可以修改它或返回一个新的值
                return result;
            };
        }
        console.log(proxyTest.foo()); 
        console.log(proxyTest.bar()); 

办法二 实际上是创建了一个全新的函数

要拦截对 foo 方法的访问,你需要在 get 中对 foo 方法进行特殊处理。即在 get 中返回一个函数,这个函数会在 foo 被访问时执行。这样,当你尝试访问 proxyTest.foo() 时,实际上是在调用 get 返回的函数,而不是原始的 foo 方法(除非你在新函数中显式调用它)。


const target = {
    message: "Hello, world!",
    foo: function () {
        return "foo";
    }
};

const handler = {
    get: function (target, prop, receiver) {
        // 拦截message属性的访问
        if (prop === "message") {
            return "Hello, proxy!";
        }
        // 拦截foo属性的访问,并返回一个自定义函数
        if (prop === "foo") {
            return function () {
                return "foo proxy";
            };
        }
        // 对于其他属性,正常返回它们的值
        return Reflect.get(target, prop, receiver);
    }
};

const proxyTest = new Proxy(target, handler);

console.log(proxyTest.message); 
console.log(proxyTest.foo());  

学习四 模块 module

两种模块范式 EMS(浏览器) CommonJS(Node.js)

首先在根模块(index.js)引入的script标签内标记 type = 'module'

EMS应用方法

一 export default

  • 模块文件中 export default { } 导出
  • 应用文件内 import xxx 导入 (xxx是新名字)

二 export

  • 模块文件中 export 导出
  • 应用文件内 import { xxx } 导入 (xxx是原名,如果希望改名,应该写 xxx as yyy , yyy是新名)

CommonJS应用方法

  • 模块文件中 module.exports = { } 导出,或 exports.xxx = ''
  • 应用文件内 const xxx = require( ../文件路径) 导入 (xxx是新名字)