Event Loop Promise async/await 总结

同步任务 因为 JavaScript 是单线程的,所以同步任务只能在主线程上排队执行,并且只有当前一个任务完成才能执行下一个任务 当 JavaScript 运行时,每个函数的执行都会进入调用栈(Call Stack)中,当前函数执行完毕后将其移出栈,然后执行下一个函数,如果其中有非常耗时的函数执行,那么调用栈就阻塞了,后面的任务只能一直处于等待状态无法执行,且此时 UI 对面任何用户操作都不会响应,看起来就像程序崩溃了一样。 为了解决这个问题,我们有了异步任务。 异步任务 常见的异步: setTimeout() setInterval() Promise.resolve().then() fetch.then() 异步任务在执行之前不会进入调用栈,它进入事件循环队列(Event Loop Queue),当主线程执行完毕后,且异步时机到了,就会将事件循环队列中的任务推入调用栈中执行。 Event Loop Event Loop 的工作非常简单,它只是监控调用栈和回调队列(包含每个事件对应的回调函数,比如 click 的回调函数)。如果调用栈为空,Event Loop 将队列中的第一个任务移除并推送到调用栈。 从 ES6 开始,Event Loop 处理异步也分两种,在Macrotask中处理setTimeout这类的,在Microtask中处理Promise这类的。它会优先处理Microtask中的任务 browser event model Node.js 中的 Event Loop Node.js 中的 Event Loop 分为 6 个阶段,依次是: timers 定时器 这个阶段执行已被setTimeout()和setInterval()调度的回调函数 pending callbacks 待定回调 执行延迟到下一个循环迭代的I/O回调 idle, prepare Node.js 系统内部使用 poll 轮询 检索新的 I/O 事件;执行与 I/O 相关的回调(几乎所有情况下,除了关闭的回调函数,那些由计时器和 setImmediate() 调度的之外),其余情况 node 将在适当的时候在此阻塞(等待) check 检测 setImmediate()回调函数在这里执行 close callback 关闭的回调函数 一些关闭的回调函数,如:sockt.on('close', ...) process.nextTick()的回调会在每个阶段结束之前调用。 ...

March 10, 2022 · 1 min · Zink

什么是继承?

要理解继承,首先需要理解原型链 function Person(name, age, gender){ this.name = name this.age = age this.gender = gender } Person.prototype.hi = function (){ console.log(`Hi, I'm ${this.name}`) } let f = new Person('zoe', 18, 'female') f.__proto__ === Person.prototype // true,ES5写法 Object.getPrototypeOf(f) === Person.prototype // true,ES6写法, __proto__ 是浏览器厂商私建 Person.prototype.__proto__ === Object.prototype // true 首先定义几个基本名词: 实例对象:通过new xxx出来的对象叫做实例对象 构造函数:如上方代码中的Person,一般以大写字母开头命名 - __proto__:ES6 之前,没有明确访问原型的方法,只能通过__proto__来访问,它的作用就是找到实例对象的原型 prototype:原型,是构造函数的一个属性 constructor:构造函数,写过 Java 这类 oop 的同学应该不陌生,这个在 ES5 中不经常接触到,ES6 中的 Class 经常用到, 然后通过上面的代码,我们可以得到这样的公式: 实例对象.__proto__ === 构造函数.prototype 构造函数.prototype.__proto__ === Object.prototype (长继承不适用,这里的继承链是 Person 继承 Object) 其次,Object 是原型链的顶端 Object.prototype.__proto__ === null ok,那么明白了这个,我们就来讨论继承,继承本质上只是用来减少重复代码的一种编程手段,如果我们有多个类,并且都有重复的属性,那么我们可以将重复的属性提取到父类身上,再由父类通过继承给子类。再简化一下:子类构造函数的实例对象也能访问到父类的属性或方法。 function Male(name, age){ this.name = name this.age = age this.gender = 'male' } let m = new Male("m", 24) function Female(name, age){ this.name = name this.age = age this.gender = 'female' } let f = new Female("f", 24) 在上面这个例子中,我们定义了两个构造函数,分别来构造男性和女性,但是 name 和 age 重复了,此时我们可以创建一个父类,来优化掉重复的代码 ...

February 10, 2022 · 2 min · Zink

Call & Apply & Bind实现

call && apply && bind 的作用 call、apply、bind的作用都是改变函数的 this 指向 call:call 的第一个参数就是对函数绑定的 this,为一个对象,后面的参数是函数调用时所需要的参数 apply:apply 只接受两个参数,第一个与 call 相同,第二个参数是为数组,内容是函数调用所需要的参数 bind:bind 与 call作用相同,不过 bind 会返回一个新的函数,第一个参数之后的参数将绑定原函数的参数。 实现 call 先来看看原生call的使用方法 let obj = { a: 1 } function fn(){ console.log(this.a) } fn() // undefined fn.call(obj) // 1 call 是如何将 this 绑定到 obj 上的呢? 我们换一种思路,先看下面的代码 let obj = { a: 1, fn(){ console.log(this.a) } } obj.fn() // 1 这段代码大家都能理解对吧,接下来,让我们打开脑洞。 如果 call 只是将调用函数绑定到了第一个参数身上呢?似乎一切就说得通了 function fn(){ console.log(this.a) } Function.prototype.myCall = function(context){ context = context || window //首先判断是否传入第一个参数,如果没传,那么 this 就默认绑定到 window context.callFn = this let result = context.callFn() delete context.callFn return result } let obj = { a: 1 } fn.myCall(obj) // 1 ok,初步绑定 this 的工作我们已经完成,接下来思考如何将剩下的参数传给调用函数。 ...

September 24, 2019 · 2 min · Zink

Input中如何处理中文输入防抖

防抖 函数防抖和节流,都是控制事件触发频率的方法。具体实现可以看我的这篇文章 input 如何处理中文输入 如果只是单纯的使用 input 事件来监听 input 值的改变,那么每次敲击键盘,输入一个字母都会触发 input 事件,其实我们想要的是获取用户输入的中文再进行后续操作。 我们可以假象一下,输入中文时的字母都是预输入,只有当选择候选词之后,input得到的值才是我们想要的。 现在,向大家介绍两个新的 DOM 事件:compositionstart、compositionend compositionstart:compositionstart 事件触发于一段文字的输入之前 compositionend:当文本段落的组成完成或取消时, compositionend 事件将被触发 <input type="text"> let inputElement = document.querySelector('input') let canRun = true inputElement.addEventListener('compositionstart', function(){ console.log('start') canRun = false }) inputElement.addEventListener('compositionend', function(){ console.log('end') canRun = true }) inputElement.addEventListener('input', debounce(fn)) function debounce(fn) { let timer = null; return function() { clearTimeout(timer); timer = setTimeout(() => { fn.apply(this, arguments) }, 500) }; } function fn(e){ if(canRun){ console.log(e.target.value) } } 当我们切换到中文输入法输入时开始在 input 中输入时,compositionstart 的回调函数被调用,将 canRun 状态切换为 false,半秒后执行 fn 时,canRun 状态为 false,不进行任何操作,当中文输入结束后,compositionend 的回调函数被调用,将 canRun 变为 true,此时 input 的回调函数调用,就可以执行后续的操作了,其实就是利用了 compositionstart 和 compositionend 来切换状态机的状态 ...

September 23, 2019 · 1 min · Zink

这一次,我一定会搞定 This !

迫于 迫于在写 React 事件处理中的 this 时,写到 this 突然卡壳了,一顿冷静分析之后发现自己对于 this 的理解又忘了一些,于是痛定思痛,重新读了 《You-Dont-Know-JS》中关于 this 的文章。 函数的执行环境 首先上一段代码 function fn(){ console.log(this.a) } let obj = { a: 1, fn: fn } var a = 'window' fn() //window obj.fn() //1 首先我们要明确一点 ,this 不是编写代码时就绑定的,而是运行时绑定的。 比如 fn() 在调用时,所处的环境是 global,所以他的 this 就是 window 而 obj.fn() 在调用时,所处的环境是在对象 obj 内,所以他的 this 指向 obj 对象 当一个函数被调用时,会建立一个称为执行环境的活动记录。这个记录包含函数是从何处(调用栈 —— call-stack)被调用的,函数是 如何 被调用的,被传递了什么参数等信息。这个记录的属性之一,就是在函数执行期间将被使用的 this 引用。 所以,this是根据调用点(函数如何被调用)来为每次函数调用建立的绑定 调用点的规则 默认绑定: 首先是独立函数调用。在这种倾向下,this 规则是在没有其他规则适用时的默认规则。 function fn(){ console.log(this) } fn() //window fn() 被独立调用,此时的 this 是默认绑定,所以 this 指向了 window 隐含绑定 这种规则是:调用点是否拥有一个对象环境。 ...

September 16, 2019 · 2 min · Zink