- 闭包的概念:函数执行完成后,它内部的变量会被释放,但是如果这些变量被其他函数所引用就不会被释放,产生了闭包(个人理解)
函数执行形成一个私有的上下文,此上下文中的私有变量,和上下文外的变量互不干扰,也就是把这些变量保护起来了,我们把函数的这种保护机制称之为闭包。【闭包是一种机制】
- 闭包的优点:
- 可以访问其他函数内部的变量
- 可以保存数据
- 可以封装私有数据和方法
-
闭包的缺点:过度的使用闭包可能会导致占用过多的内存,在 IE 浏览器中可能会导致内存泄露
-
闭包产生的场景:
- 立即执行函数(IIFE)
- 返回一个函数
- 函数作为另一个函数的参数
- 定时器、事件绑定、AJAX 等的回调
- 闭包的应用:
- 早期的模块化(立即执行函数)
- 防抖节流(返回一个函数)
- 科里化函数(返回一个函数)
- 组合函数(返回一个函数)
- 循环事件绑定(事件委托)
- let 是块作用域
- let 不会变量提升
- let 不能重复声明
- let 有暂时性死区(在定义之前不能访问)
补充:const 定义的常量,定义时必须赋值,且该常量的内存地址是不会改变的,但是它的值是可能改变的(比如对象,数组)
- 自执行函数 -> window(非严格模式)
- 普通函数执行 -> window
- .执行 -> . 前面的内容(非严格模式)
- 箭头函数 -> 函数定义时的上下文
- call / apply / bind -> 第一个参数(一旦绑定,this 指向不能被改变)
- 事件绑定 -> 绑定事件的元素(IE 6 ~ 8 attachEvent 绑定的事件指向 window)
作用域分为:全局作用域、函数作用域以及 ES6 提出的块级作用域。
作用域的作用:
- 收集并维护所有声明的标识符(变量和函数)
- 按照指定的规则来查找标识符
- 确定当前代码对标识符的访问权限
多个作用域之间嵌套就形成了作用域链。
词法作用域在查找标识符的时候,会先在本作用域查找,没有找到会向上一级作用域查找,一直到找到或者到全局作用域为止
所有的函数都有一个特殊的属性:原型(prototype)。它是一个指针,指向原型对象,原型对象上的所有属性和方法都可以被所有的实例共享。
成员访问首先会找自己的私有属性,如果没有找到,会向所属类的 prototype 查找,直到被找到或者到 Object.prototype 为止,我们把这种查找机制称为“原型链”
作用域链是指变量访问(查找标识符),原型链是指成员访问
作用域是在函数定义的时候就已经确定了的,函数内的变量是和函数所处的作用域有关 执行上下文就是 this,是在函数执行的时候确定的,函数每次执行都会产生一个新的执行上下文环境,比如函数每次执行的参数可能不一样
- 原型链继承:在构造函数的原型上扩展属性和方法
- 原型式继承:使用
Object.create()
- 拷贝继承
- 构造函数继承:只能继承父类的私有属性和方法,无法继承父类原型的属性和方法
- 组合继承:两次调用了构造函数,且子类实例的原型链不干净,包含了父类的私有属性和方法
- 寄生组合继承
扩展:ES6 的 class 继承是使用的哪种继承方式?
寄生组合继承
因为 JS 是单线程的,如果某些任务特别耗时,没有事件循环,那么其他的任务就一直被堵塞。
宏任务的执行顺序总是和加入宏任务队列的顺序相关,如果此时我们有优先级较高的任务需要执行,只有宏任务是不够的,所以需要引入微任务,提高任务的优先级
- 浏览器
宏任务:
- setTimeout
- setInterVal
- requestFrameAnimation
- I/O
微任务:
- Promise
- MutationObserver
宏任务和微任务的执行顺序:
在一个 script 标签里,代码是自上向下执行的,如果遇到微任务会把这个微任务加入微任务队列,如果遇到宏任务会把宏任务加入宏任务队列。当同步任务执行完成后,会先清理微任务队列,如果微任务里又有微任务,宏任务,也会被加入到微任务,宏任务队列。当所有的微任务执行完之后,再去清理宏任务队列
- node
宏任务:
- setImmediate
- setTimeout
- setInterval
- I/O
微任务:
- Promise
- process.nextTick
宏任务的执行顺序:
- timers 定时器的回调
- pending callbacks 待定回调(比如 I/O 回调)(不包括 timers,setImmediate,close 的 callbacks)
- idle,prepare(node 内部的)
- poll 轮询(node 内部的)
- check 检测(执行 setImmediate 的回调)
- close callbacks 关闭的回调函数
宏任务和微任务的执行顺序:
- Node V10 及以前
- 执行完一个阶段的所有任务
- 执行完 nextTick 队列里面的所有内容
- 然后执行完其他微任务队列的内容
nextTick 优先级比微任务还高,只要一个阶段的任务执行完之后,就会去先清理完 nextTick 队列,再去清理其他的微任务队列
- Node V11 及以后
和浏览器保持一致
setTimeout(() => {
console.log('setTimeout')
}, 1)
setImmediate(() => {
console.log('setImmediate')
})
上面代码输出的顺序不确定,为什么呢?
到清空 timers 回调的时候,如果主栈执行耗时小于 1 毫秒,时间还没到,就会跳过了。
JS 是单线程的,但是浏览器的多线程的,打开一个网页,浏览器会开启不同的线程执行不同的任务
浏览器的线程:
- GUI 渲染线程
- JS 引擎线程
- HTTP 网络请求线程
- 定时器监听线程
- DOM 事件监听线程
- Promise 是基于约定管理异步编程,async/await 是基于 Generator 管理异步编程
- Promise 可以通过 then 的第二个参数 和 catch 捕获异常,async/await 通过 try catch 捕获异常
- for in
- Object.keys()
- Object.values()
- Object.getOwnPropertyNames() 可以遍历不可枚举的属性,但是不能遍历 Symbol 类型的属性
- Reflect.ownKeys()
注意:for of 不能遍历对象
- 普通对象的 key 只能是字符串和 Symbol,map 的 key 可以是任意类型
- 普通对象的遍历需使用 Object.keys() Object.values() Object.entries() 转成数组在遍历,虽然 for ... in 可以遍历,但是有些其他的限制,map 可以直接使用 for of 和 forEach 来遍历
三者都用于网络请求,但是不同维度
- Ajax(Asynchronous Javascript and XML),一种技术统称
- Fetch 一个具体的 API
- Axios 第三方库
Fetch:
- 浏览器的原生 API,用于网络请求
- 和 XMLHttpRequest 一个级别
- Fetch 语法更加简洁、易用,支持 Promise
Axios:
- 最常用的网络请求库
- 内部可用 XMLHttpRequest 和 Fetch 来实现
- for in 遍历可枚举的数据,比如对象、数组、字符串等,得到的是 key
- for of 遍历拥有 Symbol.iterator 属性的数据结构(值),比如数组、字符串、Map、Set 等,得到的 value
遍历 Promise 数组
- 全局变量必须先声明再访问
- 禁止使用 with
- 创建 eval 作用域(eval 有自己单独的作用域)
- 禁止 this 指向 window
- 函数参数不能重名
- for 更快
- forEach 每次都要创建一个函数来调用,函数需要独立的作用域,会有额外的开销