前端存储

什么是前端存储 开门见山。前端存储就是通过前端技术来存储一段信息,然后在同源下的不同页面中都可以获取到已存储信息的一种策略。 前端存储的作用 方便网页的加载,避免了在发送请求收到响应前页面的空白期 可以在非强制性要求实时更新时减少向服务端的请求,加快渲染速度 在网络不佳或无网时仍有离线数据可以查看 有哪些前端存储方案 如图,一共有5种前端存储方案。大致可以分为3类: Cookie WebStorage:LocalStorage、SessionStorage 数据库存储:IndexedDB、WebSQL 先做一个简单对比: 下面我们详细介绍。 Cookie Cookie 的工作流程: Cookie 的构成: 域、路径、失效时间和安全性都是服务器给浏览器的指示,它们不会随着请求发送给服务器,发送给服务器的只有名称与值的键值对。 Cookie 的生命周期: 如果设定了 Cookie 的过期时间,那么 Cookie 会在到期时自动失效 如果没有设定过期时间,那么 Cookie 就是 session 级别的,即浏览器关闭时 Cookie 自动消失 Cookie 的优缺点: 优点: 可以控制过期时间,不会永久有效,有一定的安全保障 可进行扩展,可跨域共享 通过加密与安全传输技术,可以减少 Cookie 被破解的可能性 有较高的兼容性 缺点: 存储大小最多4KB 存储数量根据浏览器或浏览器版本的不同而不同,并且每个域最多20条 请求头上的数据容易被拦截攻击 存储的数据只能是字符串类型 操作 Cookie: // 设置 Cookie // Cookie 值必须是字符串类型,并且不支持分号、逗号以及空格, // 所以有时需要先使用 encodeURIComponent() 进行编码,或者使用 JSON.stringify() 进行序列化 document.cookie = '键1=值1;键2=值2;键n=值n'; // 读取 Cookie // 有时需要使用 decodeURIComponent() 或者 JSON.parse() document.cookie // 修改 Cookie // 如果键不存在,就新增;否则就修改 document.cookie = '已经存储过的键=新值'; // 删除 Cookie document.cookie = '要删除的键=任意值;max-age=0'; WebStorage WebStorage 是 HTML5 新加的,WebStorage(Web 存储) 分为 LocalStorage(本地存储) 和 SessionStorage(会话存储)。 ...

October 1, 2024

跨域

一、什么是跨域? 在前端领域中,跨域是指浏览器允许向服务器发送跨域请求,从而克服Ajax只能同源使用的限制。 什么是同源策略? 同源策略是一种约定,由Netscape公司1995年引入浏览器,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到XSS、CSFR等攻击。所谓同源是指"协议+域名+端口"三者相同,即便两个不同的域名指向同一个ip地址,也非同源。 同源策略限制以下几种行为: Cookie、LocalStorage 和 IndexDB 无法读取 DOM和JS对象无法获得 AJAX 请求不能发送 二、常见的跨域场景 URL 说明 是否允许通信 www.domain.com/a.js www.domain.com/b.js www.domain.com/lab/c.js 同一域名,不同文件或路径 允许 www.domain.com:8000/a.js www.domain.com/b.js 同一域名,不同端口 不允许 http://www.domain.com/a.js https://www.domain.com/b.js 同一域名,不同协议 不允许 http://www.domain.com/a.js http://192.168.4.12/b.js 域名和域名对应相同ip 不允许 http://www.domain.com/a.js http://x.domain.com/b.js http://domain.com/c.js 主域相同,子域不同 不允许 http://www.domain1.com/a.js http://www.domain2.com/b.js 不同域名 不允许 三、9种跨域解决方案 1、JSONP跨域 jsonp的原理就是利用<script>标签没有跨域限制,通过<script>标签src属性,发送带有callback参数的GET请求,服务端将接口返回数据拼凑到callback函数中,返回给浏览器,浏览器解析执行,从而前端拿到callback函数返回的数据。 1)原生JS实现: <script> var script = document.createElement('script'); script.type = 'text/javascript'; // 传参一个回调函数名给后端,方便后端返回时执行这个在前端定义的回调函数 script.src = 'http://www.domain2.com:8080/login?user=admin&callback=handleCallback'; document.head.appendChild(script); // 回调执行函数 function handleCallback(res) { alert(JSON.stringify(res)); } </script> 服务端返回如下(返回时即执行全局函数): ...

October 1, 2024

正则表达式

前言 正则表达式使用单个[字符串]来描述、匹配一系列匹配某个[句法规则]的字符串,通常被用来检索、替换那些符合某个模式(规则)的文本。 创建 1.构造函数创建 new RegExp(‘正则表达式’,‘修饰符’) var reg = new RegExp('hello','igm'); 2.直接直面量创建 /正则表达式/修饰符 var reg = /hello/igm; 字符分类 1.普通字符 字母、数字、下划线、汉字、没有特殊含义的符号(,;!@等) 实际上不是特殊字符的字符都是普通字符 2.特殊字符 \:将特殊字符转义成普通字符 3.模式修饰符 i:ignoreCase,匹配时忽视大小写 m:multiline,多行匹配 g:global,全局匹配 字面量创建正则时,模式修饰符写在一对反斜线后 实例方法 1.exec() 用来匹配字符串中符合正则表达式的字符串 如果匹配到,返回值是一个result数组: [匹配的内容,index: 在str中匹配的起始位置,input: 参数字符串,groups: undefined] 否则返回null var str = 'Hello world javascript hello'; var reg = new RegExp('hello','igm'); var res = reg.exec(str); console.log(res); 输出结果为: [ 'Hello', index: 0, input: 'hello world javascript hello', groups: undefined ] 2.test() 用来测试待检测的字符串中是否有可以匹配到正则表达式的字符串 如果有返回true,否则返回false var reg = /hello/; var str = 'hello world'; console.log(reg.test(str)); //true 3.toSting() toLocaleString() 把正则表达式的内容转化成字面量形式字符串/有本地特色的字符串 var reg = /hello/; var str = 'hello world'; console.log(reg.toString(),typeof reg.toString()); // /hello/ string console.log(reg.toLocaleString(),typeof reg.toLocaleString()); // /hello/ string 4.valueOf() 返回正则表达式本身 ...

October 1, 2024

call、apply和bind

apply 和 call简介 在 javascript 中,call 和 apply 都是为了改变某个函数运行时的上下文(context)而存在的,换句话说,就是为了改变函数体内部 this 的指向。 JavaScript 的一大特点是,函数存在「定义时上下文」和「运行时上下文」以及「上下文是可以改变的」这样的概念。 比如 A 对象有一个方法,而 B 对象因为某种原因,也需要用到同样的方法,那么这时候不用单独为 B 对象扩展一个方法,可以直接借用 A 对象的方法。这样既完成了需求,又减少了内存的占用。 直接来看个例子: function fruits() {} fruits.prototype = { color: "red", say: function() { console.log("My color is " + this.color); }} var apple = new fruits();apple.say(); //My color is red 但是如果我们有一个对象 banana= {color : "yellow"} ,我们不想对它重新定义 say 方法,那么我们可以通过 call 或 apply 用 apple 的 say 方法: banana = { color: "yellow"}apple.say.call(banana); //My color is yellowapple.say.apply(banana); //My color is yellow apply、call 的区别 对于 apply、call 二者而言,作用完全一样,只是接受参数的方式不太一样。例如,有一个函数定义如下: ...

October 1, 2024

事件冒泡、事件捕获、事件委托

DOM事件流(event flow )存在三个阶段:事件捕获阶段、处于目标阶段、事件冒泡阶段。 Dom标准事件流的触发的先后顺序为:先捕获再冒泡。即当触发dom事件时,会先进行事件捕获,捕获到事件源之后通过事件传播进行事件冒泡。 addEventListener的第三个参数 在我们平常用的addEventListener方法中,一般只会用到两个参数,一个是需要绑定的事件,另一个是触发事件后要执行的函数,然而addEventListener还可以传入第三个参数: element.addEventListener(event, function, useCapture); 第三个参数默认值是false,表示在事件冒泡阶段调用事件处理函数;如果参数为true,则表示在事件捕获阶段调用处理函数。如果不写第三个参数则默认在事件冒泡阶段调用事件处理函数。 下面先介绍事件冒泡: 1. 事件冒泡 事件冒泡(dubbed bubbling):当一个元素接收到事件的时候,会把他接收到的事件传给自己的父级,一直到 window (注意这里传递的仅仅是事件,例如click、focus等等这些事件, 并不传递所绑定的事件函数。) 事件源 =>根节点(由内到外)进行事件传播。 举例说明: 给三个盒子依次绑定点击事件,当点击盒子的时候,会依次触发父级元素的点击事件。 click small box click center box click big box 如果父元素没有绑定点击事件则只会触发点击盒子的事件。 click small box 如果子元素(small)的点击事件去掉,当我们点击small的时候会把当前操作的点击事件传递给父元素(因为父元素绑定了点击函数) click small box 有些时候我们不希望产生事件冒泡,所以可以 在子事件中加入e.stopPropagation() 取消冒泡 click small box 2. 事件捕获 事件捕获(event capturing): 当鼠标点击或者触发dom事件时(被触发dom事件的这个元素被叫作事件源),浏览器会从根节点 =>事件源(由外到内)进行事件传播。 事件捕获与事件冒泡是比较类似的,最大的不同在于事件传播的方向。 还是举上面的例子: click small box 3. 事件委托 事件委托也称为事件代理。就是利用事件冒泡,把子元素的事件都绑定到父元素上。如果子元素阻止了事件冒泡,那么委托就无法实现。 原理实现: 不是每个子节点单独设置事件监听器,而是事件监听器设置在其父节点上,然后利用冒泡原理影响设置每个子节点。 应用场景:1000个button需要注册点击事件 如果循环给每个按钮添加点击事件,那么会增加内存损耗,影响性能 此时可以给button的父元素添加点击事件 这时相当于每个按钮都绑定了点击事件 优点: 替代循环绑定事件的操作,减少内存消耗,提高性能。比如: 在table上代理所有td的click事件。 在ul上代理所有li的click事件。 简化了dom节点更新时,相应事件的更新。比如: 不用在新添加的li上绑定click事件。 当删除某个li时,不用移解绑上面的click事件。 缺点: ...

October 1, 2024

DOM操作

1、DOM基本概念 1.1、DOM、DOM节点与DOM树 DOM:(Document Object Model, 文档对象模型)是文档内容(HTML或XML)在编程语言上的抽象模型,它建模了文档的内容和结构,并提供给编程语言一套完整的操纵文档的API。 本文面向HTML,不讨论XML等文档,下文中“文档===HTML”。 DOM节点:简称节点(Node),是DOM模型的组成单元。HTML的基本单元是标签,节点常常与标签对应,但连续的文本内容也是一个文本标签。 DOM树:DOM树是DOM结构的表示形式,DOM把文档的每个节点根据父子关系连接,形成DOM树。 1.2、节点、节点类型和节点类 节点:前面提过,节点是DOM树的组成单元。在JS看来,一个节点就是JS对象。下面用node表示任意的节点。 节点类型:并非所有的节点都是一样的,DOM规定文档中有12种节点类型,分别用常量1 ~ 12(有与之对应的常量名称Node.XXX_NODE)表示,可以通过node.nodeType属性获取节点的类型常量。 现在,有些类型的节点已经弃用了,常见到的只有几种类型的节点,包括: 元素节点: 类型常量为Node.ELEMENT_NODE或1。最常见的一类节点,对应文档中的元素。大部分DOM操作都是在元素节点层次的。 文本节点:类型常量为Node.TEXT_NODE或2。对应文档中的文本,任何文档内容都有对应的文本节点,即使空格和换行符。 空格和换行不会对页面内容产生影响,但它们确实以文本节点的形式存在于DOM树中。 Document节点:类型常量为Node.DOCUMENT_NODE或9。它不对应文档的内容,而是作为文档的入口节点,每个文档都有且仅有一个入口,因为这种独特性,赋予一个特殊的变量名称document。 注释节点:类型常量为Node.COMMENT_NODE或8。它对应文档中的注释标签,文档的注释内容也是可读取和修改的。 节点类:DOM内置许多节点类,类之间存在继承关系,形成一套节点类框架。每个节点对象都属于节点类,拥有该类和其父类的方法与属性,这使得操作节点十分简单。节点类框架的一部分大概如图: 这些节点上有丰富的属性和方法,是继承的结果,可以看到,一个HTML标签元素至少有四层的继承关系。 以<a>标签为例,它属于HTMLAnchorElement类,获得了a.target,a.download等属性,接着继承了HTMLElement类上的title, hidden等属性和click()等方法,又从Element类继承了tagName, className等属性和getAttribute(), setAttribute()等方法,再从Node类继承了nodeType(前面说过的节点类型), appenChild(), removeChild()等方法,最后从EventTarget类中继承了事件相关的属性和方法。 不要混淆节点类型和节点类这两个概念。前者是一个生活中的类别,后者是编程意义上的类。节点对象的nodeType属性表示了它的类型,而节点类是该节点的从属的类。因为Dode是一个抽象类,所以,如果知道了某个节点从属的类,我们就知道它的节点类型。 区分节点与元素节点。我们经常关心元素节点(简称元素),因为这是一类最常使用的节点,但是并非所有节点都是元素。 1.3、探索DOM结构 前面的说法太抽象了,让我们用实际的例子看看文档、DOM树与节点的关系。 Live DOM Viewer是一个可以根据HTML文档实时查看DOM树的网站。你把下面的例子复制过去,或者自己去探索。 一个简单的HTML文档: <!DOCTYPE HTML> <html> <body> A simple text. <ol title="this is a title"> <li>czpcalm</li> <!-- comment --> </ol> </body> </html> 它对应的DOM图(颜色区分了节点类型): 留意这个图,你要注意几点: 总共有4种类型的节点,分别是标签节点(红色),文本节点(灰色),注释节点(黄色)和DOCTYPE节点(紫色)。 文档没有<head>标签却出现了HEAD节点。这是因为HTML必然存在<html>,<head>,<body>标签,不存在时会自动补上。顺便一提,当出现<table>标签时,也一定会有<tbody>标签。 文档中的文本都会形成文本节点的内容,包括空格␣和换行↵。第一,单独的空格和换行都会形成对应的文本节点;第二,有内容的文本节点的值包含前导和后继的空白。 不是说HTML中的空白字符都被忽略吗?怎么这里又说全都是有效的字符? 在从文档解析生成DOM树的过程中,HTML中的任何字符都是有效的;不过,在接下去的页面渲染的过程中,空白内容被忽略。所以从文档到页面的整过过程中,空白确实被忽略了。 理解DOM树中的父子关系对应文档中的包含(嵌套)关系。一个极佳的类比是文件树,把元素看做文件夹,文本看做文件,文件夹中可以存放文件和新的文件夹,然后一层层深入下去,DOM树也是如此。 几个原则可以帮我们快速理解这个DOM树的构建: ...

October 1, 2024

垃圾回收

写在前面 我们知道垃圾回收机制是引擎来做的,JS引擎有很多种(各个浏览器都不同),其垃圾回收机制在一些细节及优化上略有不同,本文我们以一些通用的回收算法作为切入,再由 V8 引擎发展至今对该机制的优化为例(为什么以 V8 为例?因为它市场占有率大 😄 ),一步一步深入来助我们了解垃圾回收机制,因为只有真正了解垃圾回收机制,后面才能理解内存泄漏的问题以及手动预防和优化 JavaScript 是门魅力无限的语言,关于它的 GC(垃圾回收)方面,你了解多少呢?想来大部分人是因为面试才去看一些面试题从而了解的垃圾回收,那在正式开始之前,给大家列几个小问题,大家可以先想一下答案,带着问题及答案再去看文章,最后读完此文如果你的答案可以优化,即有收获 什么是垃圾回收机制? 垃圾是怎样产生的? 为什么要进行垃圾回收? 垃圾回收是怎样进行的? V8 引擎对垃圾回收进行了哪些优化? 当然,我们可不仅仅是为了面试,其目的是一次性彻底搞懂 GC! GC是什么 GC 即 Garbage Collection ,程序工作过程中会产生很多 垃圾,这些垃圾是程序不用的内存或者是之前用过了,以后不会再用的内存空间,而 GC 就是负责回收垃圾的,因为他工作在引擎内部,所以对于我们前端来说,GC 过程是相对比较无感的,这一套引擎执行而对我们又相对无感的操作也就是常说的 垃圾回收机制 了 当然也不是所有语言都有 GC,一般的高级语言里面会自带 GC,比如 Java、Python、JavaScript 等,也有无 GC 的语言,比如 C、C++ 等,那这种就需要我们程序员手动管理内存了,相对比较麻烦 垃圾产生&为何回收 我们知道写代码时创建一个基本类型、对象、函数……都是需要占用内存的,但是我们并不关注这些,因为这是引擎为我们分配的,我们不需要显式手动的去分配内存 但是,你有没有想过,当我们不再需要某个东西时会发生什么?JavaScript 引擎又是如何发现并清理它的呢? 我们举个简单的例子 let test = { name: "isboyjc" }; test = [1,2,3,4,5] 如上所示,我们假设它是一个完整的程序代码 我们知道 JavaScript 的引用数据类型是保存在堆内存中的,然后在栈内存中保存一个对堆内存中实际对象的引用,所以,JavaScript 中对引用数据类型的操作都是操作对象的引用而不是实际的对象。可以简单理解为,栈内存中保存了一个地址,这个地址和堆内存中的实际值是相关的 那上面代码首先我们声明了一个变量 test,它引用了对象 {name: 'isboyjc'},接着我们把这个变量重新赋值了一个数组对象,也就变成了该变量引用了一个数组,那么之前的对象引用关系就没有了,如下图 没有了引用关系,也就是无用的对象,这个时候假如任由它搁置,一个两个还好,多了的话内存也会受不了,所以就需要被清理(回收) 用官方一点的话说,程序的运行需要内存,只要程序提出要求,操作系统或者运行时就必须提供内存,那么对于持续运行的服务进程,必须要及时释放内存,否则,内存占用越来越高,轻则影响系统性能,重则就会导致进程崩溃 垃圾回收策略 在 JavaScript 内存管理中有一个概念叫做 可达性,就是那些以某种方式可访问或者说可用的值,它们被保证存储在内存中,反之不可访问则需回收 至于如何回收,其实就是怎样发现这些不可达的对象(垃圾)它并给予清理的问题, JavaScript 垃圾回收机制的原理说白了也就是定期找出那些不再用到的内存(变量),然后释放其内存 ...

October 1, 2024

模块化

前言 在JavaScript发展初期就是为了实现简单的页面交互逻辑,寥寥数语即可;如今CPU、浏览器性能得到了极大的提升,很多页面逻辑迁移到了客户端(表单验证等),随着web2.0时代的到来,Ajax技术得到广泛应用,jQuery等前端库层出不穷,前端代码日益膨胀,此时在JS方面就会考虑使用模块化规范去管理。 本文内容主要有理解模块化,为什么要模块化,模块化的优缺点以及模块化规范,并且介绍下开发中最流行的CommonJS, AMD, ES6、CMD规范。 本文试图站在小白的角度,用通俗易懂的笔调介绍这些枯燥无味的概念,希望诸君阅读后,对模块化编程有个全新的认识和理解! 一、模块化的理解 1.什么是模块? 将一个复杂的程序依据一定的规则(规范)封装成几个块(文件), 并进行组合在一起 块的内部数据与实现是私有的, 只是向外部暴露一些接口(方法)与外部其它模块通信 2.模块化的进化过程 全局function模式 : 将不同的功能封装成不同的全局函数 编码: 将不同的功能封装成不同的全局函数 问题: 污染全局命名空间, 容易引起命名冲突或数据不安全,而且模块成员之间看不出直接关系 function m1(){ //... } function m2(){ //... } namespace模式 : 简单对象封装 作用: 减少了全局变量,解决命名冲突 问题: 数据不安全(外部可以直接修改模块内部的数据) let myModule = { data: 'www.baidu.com', foo() { console.log(`foo() ${this.data}`) }, bar() { console.log(`bar() ${this.data}`) } } myModule.data = 'other data' //能直接修改模块内部的数据 myModule.foo() // foo() other data 这样的写法会暴露所有模块成员,内部状态可以被外部改写。 IIFE模式:匿名函数自调用(闭包) 作用: 数据是私有的, 外部只能通过暴露的方法操作 编码: 将数据和行为封装到一个函数内部, 通过给window添加属性来向外暴露接口 问题: 如果当前这个模块依赖另一个模块怎么办? // index.html文件 <script type="text/javascript" src="module.js"></script> <script type="text/javascript"> myModule.foo() myModule.bar() console.log(myModule.data) //undefined 不能访问模块内部数据 myModule.data = 'xxxx' //不是修改的模块内部的data myModule.foo() //没有改变 </script> // module.js文件 (function(window) { let data = 'www.baidu.com' //操作数据的函数 function foo() { //用于暴露有函数 console.log(`foo() ${data}`) } function bar() { //用于暴露有函数 console.log(`bar() ${data}`) otherFun() //内部调用 } function otherFun() { //内部私有的函数 console.log('otherFun()') } //暴露行为 window.myModule = { foo, bar } //ES6写法 })(window) 最后得到的结果: ...

October 1, 2024

事件循环

单线程的 JavaScript 同步与异步 JavaScript 在设计之初是作为浏览器脚本语言,主要用于与用户进行页面交互和操纵 DOM。因此,为避免由不可预测的用户操作可能带来的复杂的并发问题,JavaScript 只能设计成单线程的,这也是这门语言的核心特征之一。 JS 的宿主环境通过仅提供一个线程运行 JS 来保证 JS 代码的单线程运行。 单线程意味着 JS 引擎在同一时间只能做一件事情,即同步地执行代码。但在代码执行过程中不可避免地会遇到一些无法立即执行的任务,例如: 计时器到达时间后要执行的任务 — setInterval、setTimeout 网络请求完成后要执行的任务 — XMLHttpRequest、Fetch 监听到用户操作后要执行的任务 — addEventListener … … … 如果让执行 JS 的线程去处理这些任务,就会导致该线程处于长期阻塞的状态,而宿主环境中的 JS 执行线程往往还承担着极其重要的工作。例如,在浏览器中,如果执行 JS 的线程长期阻塞,就会导致浏览器卡死! 为避免上述情况发生,JS 的宿主环境使用了异步的方式来处理这种无法立即执行的任务。 当遇到异步任务时,宿主环境会将其交给其他线程处理,执行 JS 的线程则会立即结束当前任务转而去执行后续代码。 事件循环 事件循环是宿主环境处理 JS 异步操作,让其能够非阻塞式运行的机制。 不同宿主环境对事件循环的实现方式有所不同,不过在核心机制上大同小异。 接下来,本文将详细描述 JavaScript 在浏览器和 Node.js 这两个宿主环境中的事件循环机制。 浏览器事件循环 浅谈浏览器 聊浏览器的事件循环之前,我们先说说浏览器本身。 现代浏览器是一个多进程多线程的应用程序,内部工作极其复杂,其程度直逼操作系统。它拥有数个功能模块,为避免单个模块崩溃牵连其他模块,导致连锁反应,使浏览器彻底崩溃。浏览器在启动时,会开启多个进程,把不同的功能模块放在不同的进程里。 浏览器进程众多,其中比较重要的有: 📚 浏览器进程 浏览器进程是浏览器的主进程,无论打开多少浏览器窗口,它仅有一个。 它主要负责浏览器界面显示、用户交互和进程管理。 这里说的界面和交互,不是视窗内的网站界面。而是指浏览器本身自带的部分,如导航栏、书签栏、刷新按钮等。 刚打开浏览器的时候只有一个浏览器进程,其他进程都是它创建的。 📚 网络进程 网络进程主要负责处理网站的数据请求和响应,通常情况下,它与渲染进程的交互最为密切。每当网站需要进行资源请求,渲染进程就会将任务交给网络进程处理,网络进程取得响应结果后再返回给渲染进程。 网络进程内部会开启多个线程,以实现多网络请求的异步化处理。 📚 渲染进程 渲染进程负责控制和显示视窗部分(网站页面)的所有内容,主要是解析 HTML、CSS、JS 和其他资源,并生成渲染树、执行布局和绘制等操作。 ...

October 1, 2024

异步编程

浏览器中的JavaScript程序是典型的事件驱动型程序,即它们会等待用户触发后才真正的执行,而基于的JavaScript的服务器通常要等待客户端通过网络发送请求,然后才能执行。这种异步编程在JavaScript是很常见的,下面就来介绍几个异步编程的重要特性,它们可以使编写异步代码更容易。 本文将按照异步编程方式的出现时间来归纳整理: 一、什么是异步 下面先来看看同步和异步的概念: 同步: 在执行某段代码时,在没有得到返回结果之前,其他代码暂时是无法执行的,但是一旦执行完成拿到返回值,即可执行其他代码。也就是说,在此段代码执行完未返回结果之前,会阻塞之后的代码执行,这样的情况称为同步。 异步: 当某一代码执行异步过程调用发出后,这段代码不会立刻得到返回结果。而是在异步调用发出之后,一般通过回调函数处理这个调用之后拿到结果。异步调用发出后,不会影响阻塞后面的代码执行,这样的情况称为异步。 下面来看一个例子: // 同步 function syncAdd(a, b) { return a + b; } syncAdd(1, 2) // 立即得到结果:3 // 异步 function asyncAdd(a, b) { setTimeout(function() { console.log(a + b); }, 1000) } asyncAdd(1, 2) // 1s后打印结果:3 这里定义了同步函数 syncAdd 和异步函数 asyncAdd,调用 syncAdd(1, 2) 函数时会等待得到结果之后再执行后面的代码。而调用 asyncAdd(1, 2) 时则会在得到结果之前继续执行,直到 1 秒后得到结果并打印。 我们知道,JavaScript 是单线程的,如果代码同步执行,就可能会造成阻塞;而如果使用异步则不会阻塞,不需要等待异步代码执行的返回结果,可以继续执行该异步任务之后的代码逻辑。因此,在 JavaScript 编程中,会大量使用异步。 那为什么单线程的JavaScript还能实现异步呢,其实也没有什么魔法,只是把一些操作交给了其他线程处理,然后采用了事件循环的机制来处理返回结果。 二、回调函数 在最基本的层面上,JavaScript的异步编程式通过回调实现的。回调的是函数,可以传给其他函数,而其他函数会在满足某个条件时调用这个函数。下面就来看看常见的不同形式的基于回调的异步编程。 1. 定时器 一种最简单的异步操作就是在一定时间之后运行某些代码。如下面代码: setTimeout(asyncAdd(1, 2), 8000) setTimeout()方法的第一个参数是一个函数,第二个参数是以毫秒为单位的时间间隔。asyncAdd()方法可能是一个回调函数,而setTimeout()方法就是注册回调函数的函数。它还代指在什么异步条件下调用回调函数。setTimeout()方法只会调用一次回调函数。 2. 事件监听 给目标 DOM 绑定一个监听函数,用的最多的是 addEventListener: document.getElementById('#myDiv').addEventListener('click', (e) => { console.log('我被点击了') }, false); 通过给 id 为 myDiv 的一个元素绑定了点击事件的监听函数,把任务的执行时机推迟到了点击这个动作发生时。此时,任务的执行顺序与代码的编写顺序无关,只与点击事件有没有被触发有关。 ...

October 1, 2024