一般函数的词法环境在函数返回后就被销毁,但是闭包会保存对创建时所在词法环境的引用,即便创建时所在的执行上下文被销毁,但是创建时所在词法环境仍然存在,以达到延长变量生命周期的目的
柯里化函数
柯里化的目的在于避免频繁的调用具有相同参数函数的同时,又能轻松的重用
注意事项:
如果不是某些特定任务需要使用闭包,在其他函数中创建函数是不明智的,因为闭包在处理速度和内存消耗方面对脚本性能具有负面影响
prototype
属性,这个属性是原型对象用来创建新对象实例,而所有被创建的对象都会共享原型对象,因此这些对象便可以访问原型对象的属性
js常被描述为一个基于原型的语言--每个对象拥有一个原型对象
当试图访问一个对象的属性时,它不仅仅在该对象上搜寻,还会搜寻该对象的原型,以及该对象的原型的原型,依次向上搜索,直到找到一个名字匹配的属性或者达到原型链的顶端
原因是每个对象都有 __proto__
属性,此属性指向该对象的构造函数的原型
原型对象也可能拥有原型,并从中继承方法和属性,一层一层,以此类推,这种关系被称为原型链,他解释了为何一个对象会拥有定义在其他对象中的属性和方法
在对象实例和他的构造器之间建立一个链接,_proto_属性,是从构造函数的prototype属性中派生的,之后可以通过上溯原型链,在构造器中找到这些属性和方法
一起对象都是继承自object对象,object对象直接继承根源对象null
一切的函数对象,都是继承自Function对象
object对象直接继承自Function对象
Function对象的_proto_会指向自己的原型对象,最总还是继承自object对象
继承的优点:
继承可以使得子类具有父类别的各种属性和方法,而不需要再次编写相同的代码
再子类别继承父类别的同时,可以重新定义某些属性,并重写某些方法,即覆盖父类别的原有属性和方法,使其获得于父类别不同的功能
实现方式:
原型链继承:是比较常见的一种继承方式之一,其中设计的构造函数、原型和实例,三者之间存在一定的关系,即每一个构造函数中都会有一个原型对象,原型对象有包含一个指向构造函数的指针,而实例则包含一个原型对象的指针
构造函数继承(借助 call)借助call调用Parent函数,父类原型对象中一旦存在父类之前自己定义的方法,那么子类将无法继承这些方法,父类的引用属性不会被共享,优化了原型链继承的弊端,但是它只能继承父类的实例属性和方法,不能继承原型属性或者方法
组合继承:组合继承就是将原型链继承和构造函数继承结合起来
原型式继承:借助Object.create方法实现普通对象的继承:因为它实现的是浅拷贝,多个实例的引用类型属性指向相同的内存,存在篡改的可能
寄生式继承:就是利用浅拷贝的能力进行增强,添加一些其他方法
寄生组合式继承:借助解决普通对象的继承问题的Object.create 方法,在前面几种继承方式的优缺点基础上进行改造,这也是所有继承方式里面相对最优的继承方式
javascript中的事件,可以理解就是在HTML文档或者浏览器中发生的一种交互操作,使得网页具备互动性, 常见的有加载事件、鼠标事件、自定义事件等
事件流都会经历三个阶段:
事件捕获阶段(capture phase)
处于目标阶段(target phase)
事件冒泡阶段(bubbling phase)
事件冒泡是一种从下往上的传播方式,由最具体的元素(触发节点)然后逐渐向上传播到最不具体的那个节点,也就是DOM中最高层的父节点
事件捕获与事件冒泡相反,事件最开始由不太具体的节点最早接受事件, 而最具体的节点(触发节点)最后接受事件
事件模型可以分为三种:
原始事件模型(DOM0级)html代码直接绑定,js代码绑定
特性:
绑定速度快
只支持冒泡,不支持捕获
同一个类型的事件只能绑定一次
标准事件模型(DOM2级)
事件捕获阶段:事件从document一直向下传播到目标元素, 依次检查经过的节点是否绑定了事件监听函数,如果有则执行
事件处理阶段:事件到达目标元素, 触发目标元素的监听函数
事件冒泡阶段:事件从目标元素冒泡到document, 依次检查经过的节点是否绑定了事件监听函数,如果有则执行
特性:
可以在一个DOM元素上绑定多个事件处理器,各自并不会冲突
当第三个参数(useCapture)设置为true就在捕获过程中执行,反之在冒泡过程中执行处理函数
IE事件模型(基本不用
IE事件模型共有两个过程:
事件处理阶段:事件到达目标元素, 触发目标元素的监听函数。
事件冒泡阶段:事件从目标元素冒泡到document, 依次检查经过的节点是否绑定了事件监听函数,如果有则执行
适合事件委托的事件有:click
,mousedown
,mouseup
,keydown
,keyup
,keypress
从上面应用场景中,我们就可以看到使用事件委托存在两大优点:
但是使用事件委托也是存在局限性:
focus
、blur
这些事件没有事件冒泡机制,所以无法进行委托绑定事件
mousemove
、mouseout
这样的事件,虽然有事件冒泡,但是只能不断通过位置去计算定位,对性能消耗高,因此也是不适合于事件委托的
如果把所有事件都用事件代理,可能会出现事件误判,即本不该被触发的事件被绑定上了事件
this
对象指向this
要指向的对象,如果如果没有这个参数或参数为undefined
或null
,则默认指向全局window
apply
是数组,而call
是参数列表,且apply
和call
是一次性传入参数,而bind
可以分为多次传入bind
是返回绑定this之后的函数,apply
、call
则是立即执行event.stopPropagation()方法阻止事件冒泡到父元素,阻止任何父事件处理程序被执行。不让事件向documen上蔓延,但是默认事件任然会执行,当你掉用这个方法的时候,如果点击一个连接,这个连接仍然会被打开。
event.preventDefault()方法
取消事件的默认动作。该方法将通知 Web 浏览器不要执行与事件关联的默认动作(如果存在这样的动作)
form表单如果 type 属性是 "submit",在事件传播的任意阶段可以调用任意的事件句柄,通过调用该方法,可以阻止提交表单。a元素中href连接,如果调用此方法是,连接不会被打开。
return false
这个方法比较暴力,它会同时阻止事件冒泡和默认事件,写上此代码,连接不会被打开,事件也不会传递到上一层的父元素;
可以理解为return false就等于同时调用了event.stopPropagation()和event.preventDefault()
内存泄漏(Memory leak)是在计算机科学中,由于疏忽或错误造成程序未能释放已经不再使用的内存
并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,导致在释放该段内存之前就失去了对该段内存的控制,从而造成了内存的浪费
程序的运行需要内存。只要程序提出要求,操作系统或者运行时就必须供给内存
对于持续运行的服务进程,必须及时释放不再用到的内存。否则,内存占用越来越高,轻则影响系统性能,重则导致进程崩溃
js具有自动垃圾回收机制,执行环境会负责管理代码执行过程中的内存
垃圾收集器会定期的找出那些不再继续使用的变量,然后释放内存
常用的两种实现方式:
标记清除:
JavaScript
最常用的垃圾收回机制当变量进入执行环境是,就标记这个变量为“进入环境“。进入环境的变量所占用的内存就不能释放,当变量离开环境时,则将其标记为“离开环境“
引用计数
语言引擎有一张"引用表",保存了内存里面所有的资源(通常是各种值)的引用次数。如果一个值的引用次数是
0
,就表示这个值不再用到了,因此可以将这块内存释放
ECMAScripts5,即ES5,是ECMAScripts的第五次修订,于2009年完成标准化ECMAScripts6,即ES6,是ECMAScripts的第六次修订,于2025年完成,也称ES2015ES6是继ES5之后的一次改进,相对于ES5更加简洁,提高了开发效率ES6新增的一些特性:
1)let声明变量和const声明常量,两个都有块级作用域ES5中是没有块级作用域的,并且var有变量提升,在let中,使用的变量一定要进行声明
2)箭头函数ES6中的函数定义不再使用关键字function(),而是利用了()=>来进行定义
3)模板字符串模板字符串是增强版的字符串,用反引号(`)标识,可以当作普通字符串使用,也可以用来定义多行字符串
4)解构赋值ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值
5)... 展开运算符可以将数组或对象里面的值展开;还可以将多个值收集为一个变量
匿名函数通常是某一个事件触发后进行触发的。
命名函数可以进行预先的封装,在需要使用的地方通过调用函数名运行。
函数的this关键字在js中表现得略有不同,在严格模式下和非严格模式下也会有一些差别
在绝大多数情况下,函数的调用方式决定了this的值
this关键字是在函数运行时自动生成一个内部对象,只能在函数内部使用,总能调用他的对象
在this的执行过程中,一旦被确定,就不能再进行更改
默认绑定:全局环境中定义person函数,内部使用this关键字
隐式绑定:函数还可以作为某个对象的方法调用,这时this就指这个上级对象
new绑定:通过构建函数new关键字生成一个实例对象,此时this指向这个实例对象
显示绑定:apply()、call()、bind()是函数的一个方法,作用是改变函数的调用对象。它的第一个参数就表示改变后的调用这个函数的对象。因此,这时this指的就是这第一个参数
es6中还提供了箭头函数语法,让我们在代码书写时就能确定this指向
虽然箭头函数的this能够在编译的时候就确定this的指向,但是我们也需要注意一些潜在的坑
箭头函数没有this指向,箭头函数不能作为构造函数
new绑定优先级 > 显示绑定优先级 > 隐式绑定优先级 > 默认绑定优先级
简单来说,执行上下文是一种对js代码执行环境的抽象概念,也就是说只有js代码运行,那么它就一定是运行在执行的上下文中
执行上下文的类型分为三种:
全局执行上下文:只有一个,浏览器中的全局对象就是 window对象,this 指向这个全局对象
函数执行上下文:存在无数个,只有在函数被调用的时候才会被创建,每次调用函数都会创建一个新的执行上下文
Eval 函数执行上下文: 指的是运行在 eval 函数中的代码,很少用而且不建议使用
生命周期:
执行上下文的生命周期包括三个阶段:创建阶段 → 执行阶段 → 回收阶段
创建阶段:
创建阶段做了三件事:
确定 this 的值,也被称为 This Binding
LexicalEnvironment(词法环境) 组件被创建
VariableEnvironment(变量环境) 组件被创建
词法环境
词法环境有两个组成部分:
全局环境:是一个没有外部环境的词法环境,其外部环境引用为null,有一个全局对象,this 的值指向这个全局对象
函数环境:用户在函数中定义的变量被存储在环境记录中,包含了arguments 对象,外部环境的引用可以是全局环境,也可以是包含内部函数的外部函数环境
变量环境
变量环境也是一个词法环境,因此它具有上面定义的词法环境的所有属性
在 ES6 中,词法环境和变量环境的区别在于前者用于存储函数声明和变量( let 和 const )绑定,而后者仅用于存储变量( var )绑定
执行阶段
在这阶段,执行变量赋值、代码执行
如果 Javascript 引擎在源代码中声明的实际位置找不到变量的值,那么将为其分配 undefined 值
回收阶段
执行上下文出栈等待虚拟机回收执行上下文
相等操作符(==)
如果操作数相等,则会返回true,在比较中会先进行类型转换,再确定操作数是否相等
遵循以下规则:
两个都为简单类型,字符串和布尔值都会转换成数值,再比较
简单类型与引用类型比较,对象转化成其原始类型的值,再比较
两个都为引用类型,则比较它们是否指向同一个对象
null 和 undefined 相等
存在 NaN 则返回 false
全等操作符(===)
全等操作符由 3 个等于号( === )表示,只有两个操作数在不转换的前提下相等才返回 true。即类型相同,值也需相同
undefined 和 null 与自身严格相等
区别?
相等操作符(==)会做类型转换,再进行值的比较,全等运算符不会做类型转换
null 和 undefined 比较,相等操作符(==)为true,全等为false
总结:
除了在比较对象属性为null或者undefined的情况下,我们可以使用相等操作符(==),其他情况建议一律使用全等操作符(===)
typeof操作符返回一个字符串,表示未经计算的操作数的类型
需要注意的是,null在typeof之后返回是有问题的结果,不能作为判断null的方法,如果想要在if语句中判断是否是null,直接通过===null就可以了
如果我们想要判断一个变量是否存在,可以使用typeof:(不能使用if(a),若a没有声明,则报错)
instanceof
运算符用于检测构造函数的 prototype
属性是否出现在某个实例对象的原型链上
构造函数通过new可以实例对象,instanceof能判断这个对象是否是之前那个构造函数生成的对象
会顺着原型链去寻找,直到找到相同的原型对象,返回true,否则为false
typeof
会返回一个变量的基本类型,instanceof
返回的是一个布尔值
instanceof
可以准确地判断复杂引用数据类型,但是不能正确判断基础数据类型
而typeof
也存在弊端,它虽然可以判断基础数据类型(null
除外),但是引用数据类型中,除了function
类型以外,其他的也无法判断
以上两种方法都有弊端,并不能满足所有场景的需求
Object.prototype.toString
,调用该方法,统一返回格式“[object Xxx]”
的字符串
在js中,new操作符用于创建一个给定构造函数的实例对象
new通过构造函数Person创建出来的实例可以访问到构造函数中的属性
new通过构造函数Person创建出来的实例可以访问到构造函数原型链中的属性(即实例与构造函数通过原型链连接了起来)
创建一个新的对象obj
将对象与构建函数通过原型链连接起来
将构建函数中的this
绑定到新建的对象obj
上
根据构建函数返回类型作判断,如果是原始值则被忽略,如果是返回对象,需要正常处理
AJAX
全称(Async Javascript and XML),是异步的Javascript和XML,是一种创建交互式网页应用的网页开发技术,可以在不重新加载整个网页的情况下,于服务器交换数据,并且更新部分网页
Ajax
的原理简单来说通过XmlHttpRequest
对象来向服务器发异步请求,从服务器获得数据,然后用JavaScript
来操作DOM
而更新页面
创建 Ajax
的核心对象 XMLHttpRequest
对象
通过 XMLHttpRequest
对象的 open()
方法与服务端建立连接
构建请求所需的数据内容,并通过XMLHttpRequest
对象的 send()
方法发送给服务器端
通过 XMLHttpRequest
对象提供的 onreadystatechange
事件监听服务器端你的通信状态
接受并处理服务端向客户端响应的数据结果
将处理结果更新到 HTML
页面中
method
:表示当前的请求方式,常见的有GET
、POST
url
:服务端地址
async
:布尔值,表示是否异步执行操作,默认为true
user
: 可选的用户名用于认证用途;默认为`null
password
: 可选的密码用于认证用途,默认为`null
正则表达式是一种用来匹配字符串的强有力的武器
他的设计思想是用一种描述性的语言定义的一个规则,凡是符合规则的字符串,我们就认为他匹配了,否则,该字符串就是不合法
构建正则表达式有两种方式:
1.字面量创建,包含在斜杠之间的模式组成
const re = /\d+/g;
2.调用RegExp对象的构造函数
const re = new RegExp("\\d+","g");
const rul = "\\d+"
const re1 = new RegExp(rul,"g");
使用构建函数创建,第一个参数可以是一个变量,遇到特殊字符\
需要使用\\
进行转义
规则 | 描述 |
---|---|
\ | 转义 |
^ | 匹配输入的开始 |
$ | 匹配输入的结束 |
* | 匹配前一个表达式 0 次或多次 |
+ | 匹配前面一个表达式 1 次或者多次。等价于 {1,} |
? | 匹配前面一个表达式 0 次或者 1 次。等价于{0,1} |
. | 默认匹配除换行符之外的任何单个字符 |
x(?=y) | 匹配'x'仅仅当'x'后面跟着'y'。这种叫做先行断言 |
(?<=y)x | 匹配'x'仅当'x'前面是'y'.这种叫做后行断言 |
x(?!y) | 仅仅当'x'后面不跟着'y'时匹配'x',这被称为正向否定查找 |
(?y)x | 仅仅当'x'前面不是'y'时匹配'x',这被称为反向否定查找 |
x|y | 匹配‘x’或者‘y’ |
{n} | n 是一个正整数,匹配了前面一个字符刚好出现了 n 次 |
{n,} | n是一个正整数,匹配前一个字符至少出现了n次 |
{n,m} | n 和 m 都是整数。匹配前面的字符至少n次,最多m次 |
[xyz] | 一个字符集合。匹配方括号中的任意字符 |
[^xyz] | 匹配任何没有包含在方括号中的字符 |
\b | 匹配一个词的边界,例如在字母和空格之间 |
\B | 匹配一个非单词边界 |
\d | 匹配一个数字 |
\D | 匹配一个非数字字符 |
\f | 匹配一个换页符 |
\n | 匹配一个换行符 |
\r | 匹配一个回车符 |
\s | 匹配一个空白字符,包括空格、制表符、换页符和换行符 |
\S | 匹配一个非空白字符 |
\w | 匹配一个单字字符(字母、数字或者下划线) |
\W | 匹配一个非单字字符 |
标志 | 描述 |
---|---|
g |
全局搜索。 |
i |
不区分大小写搜索。 |
m |
多行搜索。 |
s |
允许 . 匹配换行符。 |
u |
使用unicode 码的模式进行匹配。 |
y |
执行“粘性(sticky )”搜索,匹配从目标字符串的当前位置开始。 |
贪婪模式:尽可能多去匹配,
懒惰模式:惰性量词就是在贪婪量词后面加个问号。表示尽可能少的匹配
分组:分组主要是用过()
进行实现,反向引用,巧用$
分组捕获
方法 | 描述 |
---|---|
exec | 一个在字符串中执行查找匹配的RegExp方法,它返回一个数组(未匹配到则返回 null)。 |
test | 一个在字符串中测试是否匹配的RegExp方法,它返回 true 或 false。 |
match | 一个在字符串中执行查找匹配的String方法,它返回一个数组,在未匹配到时会返回 null。 |
matchAll | 一个在字符串中执行查找所有匹配的String方法,它返回一个迭代器(iterator)。 |
search | 一个在字符串中测试匹配的String方法,它返回匹配到的位置索引,或者在失败时返回-1。 |
replace | 一个在字符串中执行查找匹配的String方法,并且使用替换字符串替换掉匹配到的子字符串。 |
split | 一个使用正则表达式或者一个固定字符串分隔一个字符串,并将分隔后的子字符串存储到数组中的 String 方法。 |
验证QQ合法性,校验用户账号的合法性
JavaScript
是一门单线程的语言,意味着同一时间内只能做一件事,但是这并不意味着单线程就是阻塞,而实现单线程非阻塞的方法就是事件循环
在JavaScript
中,所有的任务都可以分为
同步任务:立即执行的任务,同步任务一般会直接进入到主线程中执行
异步任务:异步执行的任务,比如ajax
网络请求,setTimeout
定时函数等
同步任务进入主线程,即主执行栈,异步任务进入任务队列,主线程内的任务执行完毕为空,会去任务队列读取对应的任务,推入主线程执行。上述过程的不断重复就事件循环
宏任务的时间粒度比较大,执行的时间间隔是不能精确控制的,对一些高实时性的需求就不太符合
它的执行机制是:
一个需要异步执行的函数,执行时机是在主函数执行结束之后、当前宏任务结束之前
常见的微任务有:
Promise.then
MutaionObserver
Object.observe(已废弃;Proxy 对象替代)
process.nextTick(Node.js)
async
是异步的意思,await
则可以理解为 async wait
。所以可以理解async
就是用来声明一个异步方法,而 await
是用来等待异步方法执行
正常情况下,await
命令后面是一个 Promise
对象,返回该对象的结果。如果不是 Promise
对象,就直接返回对应的值
不管await
后面跟着的是什么,await
都会阻塞后面的代码
console.log('script start')
直接打印结果,输出 script start
async1()
,执行 async1
函数,先打印 async1 start
,下面遇到await
怎么办?先执行 async2
,打印 async2
,然后阻塞下面代码(即加入微任务列表),跳出去执行同步代码new Promise
这里,直接执行,打印 promise1
,下面遇到 .then()
,它是微任务,放到微任务列表等待执行script end
,现在同步代码执行完了,开始执行微任务,即 await
下面的代码,打印 async1 end
then
的回调,打印 promise2
settimeout
文档对象模型 (DOM) 是 HTML
和 XML
文档的编程接口
它提供了对文档的结构化的表述,并定义了一种方式可以使从程序中对该结构进行访问,从而改变文档的结构,样式和内容
任何 HTML
或XML
文档都可以用 DOM
表示为一个由节点构成的层级结构
日常前端开发,我们都离不开DOM
操作
在以前,我们使用Jquery
,zepto
等库来操作DOM
,之后在vue
,Angular
,React
等框架出现后,我们通过操作数据来控制DOM
(绝大多数时候),越来越少的去直接操作DOM
但这并不代表原生操作不重要。相反,DOM
操作才能有助于我们理解框架深层的内容
下面就来分析DOM
常见的操作,主要分为:
css
选择器,即可选中单个 DOM
元素(首个)DOM
节点的文本内容,还可以直接通过HTML
片段修改DOM
节点内部的子树
,那么,直接使用innerHTML = 'child'
就可以修改DOM
节点的内容,相当于添加了新的DOM
节点removeChild
把自己删掉BOM
(Browser Object Model),浏览器对象模型,提供了独立于内容与浏览器窗口进行交互的对象
其作用就是跟浏览器做一些交互效果,比如如何进行页面的后退,前进,刷新,浏览器的窗口发生变化,滚动条的滚动,以及获取客户的一些信息如:浏览器品牌版本,屏幕分辨率
浏览器的全部内容可以看成DOM
,整个浏览器可以看成BOM
Bom
的核心对象是window
,它表示浏览器的一个实例
在浏览器中,window
对象有双重角色,即是浏览器窗口的一个接口,又是全局对象
因此所有在全局作用域中声明的变量、函数都会变成window
对象的属性和方法
属性名 | 例子 | 说明 |
---|---|---|
hash | "#contents" | utl中#后面的字符,没有则返回空串 |
host | www.wrox.com:80 | 服务器名称和端口号 |
hostname | www.wrox.com | 域名,不带端口号 |
href | http://www.wrox.com:80/WileyCDA/?q=javascript#contents | 完整url |
pathname | "/WileyCDA/" | 服务器下面的文件路径 |
port | 80 | url的端口号,没有则为空 |
protocol | http: | 使用的协议 |
search | ?q=javascript | url的查询字符串,通常为?后面的内容 |
history
对象主要用来操作浏览器URL
的历史记录,可以通过参数向前,向后,或者向指定URL
跳转
history.go()
history.forward()
:向前跳转一个页面history.back()
:向后跳转一个页面history.length
:获取历史记录数在数学与计算机科学中,是指在函数的定义中使用函数自身的方法
在函数内部,可以调用其他函数。如果一个函数在内部调用自身本身,这个函数就是递归函数
一般来说,递归需要有边界条件、递归前进阶段和递归返回阶段。当边界条件不满足时,递归前进;当边界条件满足时,递归返回
尾递归,即在函数尾位置调用自身(或是一个尾调用本身的其他函数等等)。尾递归也是递归的一种特殊情形。尾递归是一种特殊的尾调用,即在尾部直接调用自身的递归函数
尾递归在普通尾调用的基础上,多出了2个特征:
数组求和
使用尾递归优化求斐波那契数列
数组扁平化
数组对象格式化
cookie
cookie是指默写网站为了辨别用户身份而储存在用户本地终端上的数据,是为了解决HTTP无状态导致的问题
cookie每次请求都会被发送,如果不使用HTTPS对它进行加密,保存的信息很容易被窃取,导致安全风险
cookie容易进行cookie的欺骗
sessionStorage
sessionStorage
和localStorage
使用方法基本一致,唯一不同的是生命周期,一旦页面(会话)关闭,sessionStorage
将会删除数据
localStorage
HTML5新的方法,IE8以上兼容
持久化的本地存储,除非手动删除,否则数据会一直存在
存储的信息在同一域中是共享的
大小5M左右
当本页操作(新增、修改、删除)了
localStorage
的时候,本页面不会触发storage
事件,但是别的页面会触发storage
事件
localStorage
本质上是对字符串的读取,如果存储内容多的话会消耗内存空间,会导致页面变卡受同源策略影响
indexedDB
indexedDB
是一种低级API,用于客户端存储大量结构化数据(包括, 文件/ blobs)。该API使用索引来实现对该数据的高性能搜索优点:
- 储存量理论上没有上限
- 所有操作都是异步的,相比
LocalStorage
同步操作性能更高,尤其是数据量较大时- 原生支持储存
JS
的对象- 是个正经的数据库,意味着数据库能干的事它都能干
缺点:
- 操作非常繁琐
- 本身有一定门槛
关于
indexedDB
的使用基本使用步骤如下:
打开数据库并且开始一个事务
创建一个
object store
构建一个请求来执行一些数据库操作,像增加或提取数据等。
通过监听正确类型的
DOM
事件以等待操作完成。在操作结果上进行一些操作(可以在
request
对象中找到)
cookie
localStorage
sessionStorage
indexedDB
函数式编程是一种"编程范式"(programming paradigm),一种编写程序的方法论
主要的编程范式有三种:命令式编程,声明式编程和函数式编程
相比命令式编程,函数式编程更加强调程序执行的结果而非执行的过程,倡导利用若干简单的执行单元让计算结果不断渐进,逐层推导复杂的运算,而非设计一个复杂的执行过程
函数式编程旨在尽可能的提高代码的无状态性和不变性,无副作用的函数就是纯函数
纯函数=无状态+数据不可变
优势:
不依赖外部环境计算,不会产生副作用,提高函数的复用性
可读性更强 ,函数不管是否是纯函数 都会有一个语义化的名称,更便于阅读
可以组装成复杂任务的可能性。符合模块化概念及单一职责原则
函数的参数是一个函数,或者是函数的返回值是一个函数
上面通过高阶函数 forEach
来抽象循环如何做的逻辑,直接关注做了什么
柯里化是把一个多参数函数转化成一个嵌套的一元函数的过程
关于柯里化函数的意义如下:
组合函数,目的是将多个函数组合成一个函数
组合函数与管道函数的意义在于:可以把很多小函数组合起来完成更复杂的逻辑
优点:
更好的管理状态:因为它的宗旨是无状态,或者说更少的状态,能最大化的减少这些未知、优化代码、减少出错情况
更简单的复用:固定输入->固定输出,没有其他外部变量影响,并且无副作用。这样代码复用时,完全不需要考虑它的内部实现和外部影响
更优雅的组合:往大的说,网页是由各个组件组成的。往小的说,一个函数也可能是由多个小函数组成的。更强的复用性,带来更强大的组合性
隐性好处。减少代码量,提高维护性
缺点:
性能:函数式编程相对于指令式编程,性能绝对是一个短板,因为它往往会对一个方法进行过度包装,从而产生上下文切换的性能开销
资源占用:在 JS 中为了实现对象状态的不可变,往往会创建新的对象,因此,它对垃圾回收所产生的压力远远超过其他编程方式
递归陷阱:在函数式编程中,为了实现迭代,通常会采用递归操作
函数缓存,就是将函数运算过的结果进行缓存
本质上就是用空间(缓存存储)换时间(计算过程)
常用于缓存数据计算结果和缓存对象
缓存只是一个临时的数据存储,它保存数据,以便将来对该数据的请求能够更快地得到处理
实现函数缓存主要依靠闭包、柯里化、高阶函数
计算机存储双精度浮点数需要先把十进制数转换为二进制的科学记数法的形式,然后计算机以自己的规则{符号位+(指数位+指数偏移量的二进制)+小数部分}存储二进制的科学记数法
因为存储时有位数限制(64位),并且某些十进制的浮点数在转换为二进制数时会出现无限循环,会造成二进制的舍入操作(0舍1入),当再转换为十进制时就造成了计算误差
解决方案:
凑整
使用第三方库,Math.js BigDecimal.js
我们经常需要判断目标元素是否在视窗之内或者和视窗的距离小于一个值(例如 100 px),从而实现一些常用的功能
offsetTop、scrollTop:offsetTop
,元素的上外边框至包含元素的上内边框之间的像素距离,scrollTop滚动轴与顶部之间像素距离
getBoundingClientRect:返回值是一个 DOMRect
对象,拥有left
, top
, right
, bottom
, x
, y
, width
, 和 height
属性
Intersection Observer:
Intersection Observer
即重叠观察者,从这个命名就可以看出它用于判断两个元素是否重叠,因为不用进行事件的监听,性能方面相比getBoundingClientRect
会好很多
使用步骤主要分为两步:创建观察者和传入被观察者
不管怎样简单的需求,在量级达到一定层级时,会变得异常复杂
文件上传简单,但是当文件变大的时候就会变得复杂
上传大文件,以下几个变量会影响用户体验
分片上传,就是将所要上传的文件,按照一定的大小,将整个文件分隔成多个数据块(Part)来进行分片上传
断点续传指的是在下载或上传时,将下载或上传任务人为的划分为几个部分
一般实现方式有两种:
上拉加载及下拉刷新都依赖于用户交互
上拉加载的本质就是页面触底,或者快要触底时的动作
scrollTop
:滚动视窗的高度距离window
顶部的距离,它会随着往上滚动而不断增加,初始值是0,它是一个变化的值
clientHeight
:它是一个定值,表示屏幕可视区域的高度;
scrollHeight
:页面不能滚动时也是存在的,此时scrollHeight等于clientHeight。scrollHeight表示body
所有元素的总长度(包括body元素自身的padding)
下拉刷新的本质就是页面本身置于顶部时,用户下拉时需要触发的动作
关于下拉刷新的原生实现,主要分成三步:
- 监听原生
touchstart
事件,记录其初始位置的值,e.touches[0].pageY
;- 监听原生
touchmove
事件,记录并计算当前滑动的位置值与初始位置值的差值,大于0
表示向下拉动,并借助CSS3的translateY
属性使元素跟随手势向下滑动对应的差值,同时也应设置一个允许滑动的最大值;- 监听原生
touchend
事件,若此时元素滑动达到最大值,则触发callback
,同时将translateY
重设为0
,元素回到初始位置
使用better-scroll
实现下拉刷新、上拉加载时要注意以下几点:
wrapper
里必须只有一个子元素wrapper
要高DOM
元素是否已经生成,必须要等到DOM
渲染完成后,再new BScroll()
DOM
元素结构有变化后,需要执行刷新 refresh()
finishPullUp()
或者finishPullDown()
,否则将不会执行下次操作better-scroll
,默认会阻止浏览器的原生click
事件,如果滚动内容区要添加点击事件,需要在实例化属性里设置click:true
单点登录(Single Sign On),简称为 SSO,是目前比较流行的企业业务整合的解决方案之一
SSO的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统
SSO 一般都需要一个独立的认证中心(passport),子系统的登录均得通过passport
,子系统本身将不参与登录操作
当一个系统成功登录以后,passport
将会颁发一个令牌给各个子系统,子系统可以拿着令牌会获取各自的受保护资源,为了减少频繁认证,各个子系统在被passport
授权以后,会建立一个局部会话,在一定时间内可以无需再次向passport
发起认证
淘宝、天猫都属于阿里旗下,当用户登录淘宝后,再打开天猫,系统便自动帮用户登录了天猫,这种现象就属于单点登录
用户访问系统1的受保护资源,系统1发现用户未登录,跳转至sso认证中心,并将自己的地址作为参数
sso认证中心发现用户未登录,将用户引导至登录页面
用户输入用户名密码提交登录申请
sso认证中心校验用户信息,创建用户与sso认证中心之间的会话,称为全局会话,同时创建授权令牌
sso认证中心带着令牌跳转会最初的请求地址(系统1)
系统1拿到令牌,去sso认证中心校验令牌是否有效
sso认证中心校验令牌,返回有效,注册系统1
系统1使用该令牌创建与用户的会话,称为局部会话,返回受保护资源
用户访问系统2的受保护资源
系统2发现用户未登录,跳转至sso认证中心,并将自己的地址作为参数
sso认证中心发现用户已登录,跳转回系统2的地址,并附上令牌
系统2拿到令牌,去sso认证中心校验令牌是否有效
sso认证中心校验令牌,返回有效,注册系统2
系统2使用该令牌创建与用户的局部会话,返回受保护资源
用户登录成功之后,会与sso
认证中心及各个子系统建立会话,用户与sso
认证中心建立的会话称为全局会话
用户与各个子系统建立的会话称为局部会话,局部会话建立之后,用户访问子系统受保护资源将不再通过sso
认证中心
全局会话与局部会话有如下约束关系:
Web攻击(WebAttack)是针对用户上网行为或网站服务器等设备进行攻击的行为
恶意植入代码,修改网站权限,获取网站用户隐私
web应用程序的安全性是人格基于web业务的重要组成部分
我们常见的Web攻击方式有
XSS (Cross Site Scripting) 跨站脚本攻击
根据攻击的来源,
XSS
攻击可以分成:
- 存储型
- 反射型
- DOM 型
存储型
存储型 XSS 的攻击步骤:
- 攻击者将恶意代码提交到目标网站的数据库中
- 用户打开目标网站时,网站服务端将恶意代码从数据库取出,拼接在 HTML 中返回给浏览器
- 用户浏览器接收到响应后解析执行,混在其中的恶意代码也被执行
- 恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行为,调用目标网站接口执行攻击者指定的操作
这种攻击常见于带有用户保存数据的网站功能,如论坛发帖、商品评论、用户私信等
反射型 XSS
反射型 XSS 的攻击步骤:
- 攻击者构造出特殊的 URL,其中包含恶意代码
- 用户打开带有恶意代码的 URL 时,网站服务端将恶意代码从 URL 中取出,拼接在 HTML 中返回给浏览器
- 用户浏览器接收到响应后解析执行,混在其中的恶意代码也被执行
- 恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行为,调用目标网站接口执行攻击者指定的操作
反射型 XSS 跟存储型 XSS 的区别是:存储型 XSS 的恶意代码存在数据库里,反射型 XSS 的恶意代码存在 URL 里。
反射型 XSS 漏洞常见于通过 URL 传递参数的功能,如网站搜索、跳转等。
由于需要用户主动打开恶意的 URL 才能生效,攻击者往往会结合多种手段诱导用户点击。
POST 的内容也可以触发反射型 XSS,只不过其触发条件比较苛刻(需要构造表单提交页面,并引导用户点击),所以非常少见
DOM 型 XSS
DOM 型 XSS 的攻击步骤:
- 攻击者构造出特殊的 URL,其中包含恶意代码
- 用户打开带有恶意代码的 URL
- 用户浏览器接收到响应后解析执行,前端 JavaScript 取出 URL 中的恶意代码并执行
- 恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行为,调用目标网站接口执行攻击者指定的操作
DOM 型 XSS 跟前两种 XSS 的区别:DOM 型 XSS 攻击中,取出和执行恶意代码由浏览器端完成,属于前端 JavaScript 自身的安全漏洞,而其他两种 XSS 都属于服务端的安全漏洞
XSS的预防
通过前面介绍,看到
XSS
攻击的两大要素:
- 攻击者提交而恶意代码
- 浏览器执行恶意代码
CSRF(Cross-site request forgery)跨站请求伪造
一个典型的CSRF攻击有着如下的流程:
- 受害者登录a.com,并保留了登录凭证(Cookie)
- 攻击者引诱受害者访问了b.com
- b.com 向 a.com 发送了一个请求:a.com/act=xx。浏览器会默认携带a.com的Cookie
- a.com接收到请求后,对请求进行验证,并确认是受害者的凭证,误以为是受害者自己发送的请求
- a.com以受害者的名义执行了act=xx
- 攻击完成,攻击者在受害者不知情的情况下,冒充受害者,让a.com执行了自己定义的操作
CSRF的特点
- 攻击一般发起在第三方网站,而不是被攻击的网站。被攻击的网站无法防止攻击发生
- 攻击利用受害者在被攻击网站的登录凭证,冒充受害者提交操作;而不是直接窃取数据
- 整个过程攻击者并不能获取到受害者的登录凭证,仅仅是“冒用”
- 跨站请求可以用各种方式:图片URL、超链接、CORS、Form提交等等。部分请求方式可以直接嵌入在第三方论坛、文章中,难以进行追踪
CSRF通常从第三方网站发起,被攻击的网站无法防止攻击发生,只能通过增强自己网站针对CSRF的防护能力来提升安全性
防止
csrf
常用方案如下:
- 阻止不明外域的访问
- 同源检测
- Samesite Cookie
- 提交时要求附加本域才能获取的信息
- CSRF Token
- 双重Cookie验证
SQL注入攻击:Sql 注入攻击,是通过将恶意的 Sql
查询或添加语句插入到应用的输入参数中,再在后台 Sql
服务器上解析执行进行的攻击
流程如下所示:
找出SQL漏洞的注入点
判断数据库的类型以及版本
猜解用户名和密码
利用工具查找Web后台管理入口
入侵和破坏
预防方式如下:
- 严格检查输入变量的类型和格式
- 过滤和转义特殊字符
- 对访问数据库的Web应用程序采用Web应用防火墙
本质上是优化高频率执行代码的一种手段
定义
- 节流: n 秒内只运行一次,若在 n 秒内重复触发,只有一次生效
- 防抖: n 秒后在执行该事件,若在 n 秒内被重复触发,则重新计时
一个经典的比喻:
想象每天上班大厦底下的电梯。把电梯完成一次运送,类比为一次函数的执行和响应
假设电梯有两种运行策略 debounce
和 throttle
,超时设定为15秒,不考虑容量限制
电梯第一个人进来后,15秒后准时运送一次,这是节流
电梯第一个人进来后,等待15秒。如果过程中又有人进来,15秒等待重新计时,直到15秒后开始运送,这是防抖
相同点:
setTimeout
实现不同点:
clearTimeout
和 setTimeout
实现。函数节流,在一段连续操作中,每一段时间只执行一次,频率较高的事件中使用来提高性能防抖在连续的事件,只需触发一次回调的场景有:
resize
。只需窗口调整完成后,计算窗口大小。防止重复渲染。节流在间隔一段时间执行一次回调的场景有: