}
return fun
}
var f = fn1()
f() // 3
f() // 4
// 此时没有死亡
f = null // 此时内部函数无引用指向 死亡
4. 闭包的应用-定义js模块
`- 具有特定功能的js模块`
`- 将所有的数据和方法都封装到一个函数的内部(私有的)`
`- 只向外部暴露一个包含n个方法的对象或函数`
`- 模块的使用者,只需要通过模块导出的对象调用方法来实现对应的功能。`
/*方法一*/
function my_module() {
var msg = ‘songRuiXue’
function doSomething() {
console.log(‘doSomething’ + msg.toUpperCase()) // msg: 全部大写
}
function doOtherthing() {
console.log(‘doOtherthing’ + msg.toLowerCase()) // msg: 全部小写
}
return {
doSomething: doSomething,
doOtherthing: doOtherthing
}
}
/*方法二*/
(function(window) {
var msg = ‘songRuiXue’
function doSomething() {
console.log(‘doSomething’ + msg.toUpperCase()) // msg: 全部大写
}
function doOtherthing() {
console.log(‘doOtherthing’ + msg.toLowerCase()) // msg: 全部小写
}
window.obj = {
doSomething: doSomething,
doOtherthing: doOtherthing
}
})(window)
调用
/*方法一*/
var obj = my_module()
obj.doSomething()
obj.doOtherthing()
/*方法二*/
obj.doSomething()
obj.doOtherthing()
## 事件代理(事件委托)?
事件流(event flow)过程:事件捕获 -> 目标阶段 -> 事件冒泡
`阻止冒泡:event.stopPropagation() (停止传播)`
`阻止默认行为:event.preventDefault() return false`
return false 不仅阻止了事件往上冒泡,而且阻止了事件本身(默认事件)。event.stopPropagation()则只阻止事件往上冒泡,不阻止事件本身。
[事件冒泡、事件捕获及事件代理]( )
## 说一下for…in 和 for…of的区别?
for…of遍历获取的是对象的键值,for…in遍历获取的是对象的键名
for…of只遍历当前对象不会去遍历原型链,for…in会遍历对象的整个原型链,性能差。
对于数组的遍历,for…of只返回数组下标对应的属性值。for…in会返回数组中所有可枚举的属性(包括原型链上可枚举的属性)。
总结:for…in主要用来遍历对象,不适合遍历数组。 for…of循环可以用来遍历数组、类数组对象、字符串、Set、Map以及Generator对象
## 给 a b c 三个请求,希望 c 在 a b 获取结果之后再请求。
### 1. Promise.all
### 2. 使用数组实现
const fs = require(‘fs’)
const arr = []
function fn (data) {
arr.push(data)
if (arr.length === 2) {
console.log(arr)
// 在此时可以执行 c 的逻辑
}
}
fs.readFile(‘./a.text’, ‘utf-8’, (err, data) => {
fn(data)
})
fs.readFile(‘./b.text’, ‘utf-8’, (err, data) => {
fn(data)
})
## 数据类型
### 基本数据类型(栈中存储的是值)
* String: 任意的字符串
* Number: 任意的数字
* Boolean: true / false
* undefined: undefined
* null: null
* Symbol: 不能当做构造函数使用,不能使用 new 关键字,可以用来表示对象的唯一键值。
* BigInt: 不能当做构造函数使用,不能使用 new 关键子,可以表示任意大的整数。
### 引用数据类型(栈中存储的是地址值,实际对象存储在堆)
* Object: 任意的对象
* Array: 一种特殊的对象(内部数据是有序的)
* Function: 一种特殊的对象,可以执行。
### 判断数据类型
* typeof:只能判断出基本数据类型,不能区分引用类型。
* instanceof:只能判断出引用类型,不能区分基本类型。
原理:判断实例对象的\_\_proto\_\_属性是否和构造函数的prototype属性指向同一个原型对象。如果没找到,则会在原型链上继续查找。
* ===: 全等于 / 注意尽量不用使用 == 因为会做数据类型转化。
* Object.prototype.toString:最佳的方法。
## js中的数据结构
* 数组:数组是一组有序的值的集合,可以通过索引访问。js数组可以包含不同的数据类型,并且长度是动态的。
* 对象:对象用于存储键值对集合,并且内部是无序的,值可以是任意值。对象的键只能是字符串或符号。
* Map:类似于对象,Map是一种键值对的数据结构,但提供了更多的功能。Map的键可以是任何类型(对象或原始值),而对象的键只能是字符串或符号。
* Set:类似于数组,Set是一种只包含唯一值的数据结构。内部的值都是唯一的。Set提供了添加、删除和检查元素是否存在的方法。
* weakMap和weakSet
* stack(栈):先进后出
* 队列:先进先出
* 树
## 事件循环 (Event Loop)
### javascript为什么是单线程?
js 作为主要运行在浏览器的脚本语言,主要的用处就是操作dom。
如果js同时有两个线程,同时对同一个dom进行操作,这时浏览器应该听哪一个线程的,如何判断优先级?
为了避免这种问题,js的单线程不会变化。
### 事件循环
事件循环就是通过异步执行任务的方法来解决js单线程的问题的。
1. 一开始整个脚本作为一个宏任务执行。
2. 执行过程中如果遇到同步任务直接执行,遇到宏任务加入宏任务队列,微任务加入微任务队列。
3. 当前宏任务执行完毕后,回去微任务队列检查是否有微任务,如果有则依次执行,直到全部执行完毕。
4. 执行完本轮的宏任务,检查宏任务队列并回到第2步继续执行此循环,直到宏任务与微任务队列都为空。
### 宏任务与微任务
js引擎把所有的任务分为两类,一类叫宏任务(macroTask),一类叫微任务(mincoTask)
#### 宏任务:
script(整体代码)、setTimeout、setInterval、I/O、UI渲染、postMessage、MessageChannel
#### 微任务:
new Promise().then()
### 经典题目1
console.log(‘script start’)
setTimeout(() => {
console.log(‘setTimeout’)
}, 0)
Promise.resolve().then(function() {
console.log(‘promise1’)
}).then(function() {
console.log(‘promise2’)
})
console.log(‘script end’)
1. 整体script作为第一个宏任务进入主线程,输出`script start`
2. 遇到setTimeout,加入宏任务队列
3. 遇到Promise,将其then回调函数加入微任务队列;第二个then回调函数也加入微任务队列
4. 遇到同步任务,输出`script end`
5. 检测微任务队列,输出`promise1`、`promise2`
6. 进入下一轮循环,执行setTimeout中的代码,输出`setTimeout`
最后的执行结果:
script start
script end
promise1
promise2
setTimeout
### 经典题目2
async function async1() {
console.log(‘async1 start’);
await asnyc2()
console.log(‘async1 end’);
}
async function asnyc2() {
console.log(‘asnyc2’);
}
console.log(‘script start’);
setTimeout(() => {
console.log(‘setTimeout’);
}, 0)
async1()
new Promise(function(resolve) {
console.log(‘promise1’);
resolve()
}).then(function() {
console.log(‘promise2’);
})
console.log(‘script end’);
1. 整体script代码作为一个宏任务进入主线程中执行,输出 `script start`
2. 遇到setTimeout将其加入宏任务队列
3. 执行async1()函数,输出 `async1 start`
4. 遇到await,执行asnyc2()函数,输出 `asnyc2` 并让出主线程。跳出async1()。并等待asnyc2()函数的结果。
5. 在主线程中执行console.log(‘promise1’); 输出 `promise1`
6. 遇到then()函数将其加入到微任务队列,代码向下执行并输出 `script end`。第一次宏任务执行完毕。
7. 跳回async1()函数,await等待的asnyc2()函数返回了结果。代码向下执行,输出 `async1 end`。
8. 检查微任务队列,输出 `promise2`
9. 检查宏任务队列,输出 `setTimeout`
最后的执行结果:
script start
async1 start
asnyc2
promise1
script end
async1 end
promise2
setTimeout
## async / await 的执行顺序详解
**注意:针对浏览器,nodejs因为版本问题会有差异(如:10.16.0与20.0.0版本差异很大,可以自己试一下)主要以浏览器与node20.0.0版本进行分析**
>
> JavaScript中的 async / await 是AsyncFunction特性中的关键字,从字面意思上来解释的话:async 是 “异步“的简写,而await可以认为是 async wait 的简写。可以理解为:async用户申明一个function是异步的,而await用于等待一个异步方法执行完成。
>
>
>
async / await:
* async / await:是一种编写异步代码的新方法,之前异步代码的方案是primise和回调函数。
* async / await:是建立在Promise基础上的。
* async / await:和Promise一样也是异步非阻塞的。
* async / await:让异步代码看起来表现起来更加的像是同步代码。
* async / await:await 只能出现在 async 函数中。
### async 怎么处理返回值
#### 存在返回值
async function testAsync() {
return ‘hello async’
}
const result = testAsync()
console.log(result); // Promise {: “hello async”}
由以上结果可以看到:`async函数返回的是一个 Promise 对象。如果在函数中 return 一个直接量,async会把这个直接量通过Promise.resolve()封装成 Promise 对象。`
#### 没有返回值(函数默认的返回值就是undefined)
async function testAsync1() {
console.log(‘这是没有返回值的async函数’);
}
const result = testAsync1()
console.log(result); // Promise {: undefined}
### 在没有await的情况下 async 函数的执行过程
没有await的情况下,执行async函数,它会立即执行,返回一个Promise对象,并且不会阻塞后面的语句。这和普通返回Promise对象的函数没有区别。
### await 函数等待的机制
`await从字面意思上来说就是等待。await是在等待一个表达式,这个表达式是一个Promise对象或者其他值。如:await 1、 await fun()`。
**重要:**
>
> 大部分人认为await会一直等待之后的表达式执行完之后才会继续执行后面的代码,实际上`await是一个让出线程的标志`。**await 后面的函数会先执行一遍,然后就会跳出整个async函数,执行后面js栈中的代码。等到本轮事件循环执行完之后又跳回async函数中等待await后面表达式的返回值,如果返回值不是Promise则继续执行async函数后面的代码,否则将返回的Promise放入Promise队列。**
>
>
> **让出线程,等待结果,此时执行主线程其他的同步任务,执行栈为空后,再次进入线程。**
>
>
>
### async / await 执行顺序
#### 经典题目1 `await是一个让出线程的标志`
function testSometing() {
console.log(“执行testSometing”);
return “testSometing”;
}
async function testAsync() {
console.log(“执行testAsync”);
return Promise.resolve(“hello async”);
}
async function test() {
console.log(“test start…”);
const v1 = await testSometing(); // 关键点1
console.log(v1);
const v2 = await testAsync();
console.log(v2);
}
test();
var promise = new Promise((resolve) => {
console.log(“promise start…”);
resolve(“promise”);
}); // 关键点2
promise.then((val) => console.log(val));
console.log(“test end…”);
##### 输出结果:
test start…
执行testSometing
promise start…
test end…
testSometing
执行testAsync
promise
hello async
##### 执行过程
1. 整体script代码作为一个宏任务进入主线程,代码自上而下,执行同步代码,输出 `test start...`
2. 遇到 await,await后面的函数会先执行一遍,输出 `执行testSometing`,然后让出主线程。`跳出test函数`
3. 此时主线程继续向下执行,遇到 new Promise(),输出 `promise start..`
4. 遇到 promise.then(),将其添加到微任务队列。
5. 遇到同步代码,输出 `test end...`。此时第一次宏任务执行完毕。
6. 跳回test函数,等待之前的await后面的表达式的返回值。由于testSometing函数为同步函数,所以直接执行,输出 `testSometing`。
7. 继续执行,遇到 await,await后面的函数会先执行一遍,输出 `执行testAsync`,然后让出主线程。`跳出test函数`
8. 主线程代码执行完毕,此时读取微任务队列,发现promise.then()包含的回调函数,执行并输出 `promise`
9. 清空微任务队列后,跳回 `test函数` 等待之前的await后面的表达式的返回值。该表达式返回一个Promise, await取到结果并输出 `hello async`。
##### 总结
testSometing 与 testAsync 不论 是否被async关键字声明与否,执行结果均是以上结果。执行顺序受 await 关键字和函数返回值的影响。
## JavaScript 中的 this
### this 是什么?
* `只存在于函数中`
* `任何函数的本质都是通过对象来调用的,如果没有直接指定那this就是window`
* `所有的函数内部都有一个变量 this,箭头函数除外`
* `值是调用函数的当前对象`
### 如何确定 this 的值?
**this 永远指向最后调用它的那个对象**
* `fn(): window`
* `p.fn(): p`
* `var p = new Person(): p`
* `fn.call(obj): obj`
* `箭头函数没有this,它的this指向上层作用域中的this`
* `严格模式下直接调用` fn() `this 为 undefined`
#### 题目1:
function Person (color) {
console.log(this)
this.getColor = function () {
console.log(this)
}
}
Person(‘red’) // this: window
var p = new Person(“yello”) // this: p
var obj = {}
p.getColor.call(obj, “pink”) // this: obj
var test = p.getColor
test() // this: window
function fun1() {
console.log(this)
function fun2() {
console.log(this)