js知识总结

本文详细介绍了JavaScript的基础知识,包括ECMAScript文档对象模型(DOM)和浏览器对象模型(BOM),数组操作方法,高阶函数,闭包的概念及其应用,模块化编程,事件代理与事件流,for...in和for...of的区别,以及异步编程、this指向、作用域和作用域链。此外,还涉及到了大文件上传的断点续传技术

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

JS由那三部分组成?

  1. ECMASript
  2. 文档对象模型(DOM)
  3. 浏览器对象模型(BOM)

操作数组的方法有那些?

  1. 高阶函数:map、filter、forEach、reduce、find、findIndex、every、some、
  2. push、unshift、shift、pop、splice、slice、join、concat、fill、indexOf、includes、reduceRight、reverse、sort、
    会改变原数组的方法:push、unshift、shift、pop、splice、join、reverse、sort

说一下闭包,以及特点。

  1. 如何产生闭包?
    -函数嵌套,内部函数引用了外部函数的(变量或函数)时,就产生了闭包
  2. 闭包的作用?
    使用闭包函数执行完成后,内部函数引用外部函数的数据(变量或函数)在存在内存中
    使函数外部可以操作到函数内部的数据
  3. 闭包的生命周期
    1. 产生: 在内部嵌套函数定义执行(不是真实执行)完成时就产生了(不是在调用)
    2. 死亡: 在内部嵌套函数成为垃圾对象时
function fn1() {
// 程序执行到此处时, 先函数提升然后变量提升(内部函数对象创建) 闭包出现 
// 因为有 var a = undefined
  var a = 2
  function fun() {
    console.log(++a)
  }
  return fun
}
var f = fn1()
f() // 3
f() // 4
// 此时没有死亡
f = null // 此时内部函数无引用指向 死亡
  1. 闭包的应用-定义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. 检测微任务队列,输出promise1promise2
  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 {<fulfilled>: "hello async"}

由以上结果可以看到:async函数返回的是一个 Promise 对象。如果在函数中 return 一个直接量,async会把这个直接量通过Promise.resolve()封装成 Promise 对象。

没有返回值(函数默认的返回值就是undefined)

async function testAsync1() {
  console.log('这是没有返回值的async函数');
}
const result = testAsync1()
console.log(result); // Promise {<fulfilled>: 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)
  }
  fun2() // this: window
}
fun1() // this: window

如何改变普通函数 的 this 指向

  • 使用 _this = this,将this值提前用个变量保存起来。
  • 使用 call、apply、bind
  • new 实例化一个对象,this值指向新创建的实例对象

作用域与作用域链

什么是作用域?

作用域就是变量和函数可访问(起作用)的范围和区域,作用域的目的就是为了隔离变量和函数,保证不同作用域下的同名变量或函数不会冲突。

作用域的分类

  • 静态作用域(词法作用域)。js就是属于词法作用域。
  • 动态作用域

JS 作用域分类

  • 全局作用域

  • 函数作用域

  • 块作用域(可以代替匿名函数自调用的写法)

什么是作用域链?

各个作用域的嵌套关系组成了一条作用域链,当代码在执行时,会创建一条变量对象的作用域链。

例子:

function foo() {
    var a = 1
    function bar() {
        var a = 2
	}
}

以上 bar 函数的 作用域链为: bar -> foo -> window, foo的作用域链为: foo -> window

使用作用域链主要是进行标识符(变量和函数)的查询,标识符(变量和函数)的解析就是沿着作用域链一级一级的访问的过程,而作用域链就是保证对变量和函数的有序访问。


大文件实现断点续传与分片上传

大文件上传如何做断点续传?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值