事件
事件冒泡
IE 事件流被称为事件冒泡,这是因为事件被定义为从最具体的元素(文档树中最深的节点)开始触 发,然后向上传播至没有那么具体的元素文档)。
事件捕获
事件捕获的意思是最不具体的节点应该最先收到事件,而最具体的节点应该最后收到事件。事件捕获实际上是为了在事件到达最终目标前拦截事件。
事件流
事件流分为 3 个阶段:事件捕获、到达目标和事件冒泡。事件捕获最先发生, 为提前拦截事件提供了可能。然后,实际的目标元素接收到事件。最后一个阶段是冒泡,最迟要在这个 阶段响应事件。
在 DOM 事件流中,实际的目标(div元素)在捕获阶段不会接收到事件。这是因为捕获阶段在到达img之前就结束了。下一阶段,即会在img元素上触发事件的“到达目标” 阶段,通常在事件处理时被认为是冒泡阶段的一部分。然后,冒泡阶段开始,事件反向传播至文档。
事件委托
在 JavaScript 中,页面中事件处理程序的数量与页面整体性能直接相关。原 因有很多。首先,每个函数都是对象,都占用内存空间,对象越多,性能越差。其次,为指定事件处理 程序所需访问 DOM 的次数会先期造成整个页面交互的延迟。
事件委托利用事件冒泡,可以只使用一个事件 处理程序来管理一种类型的事件。例如,click 事件冒泡到 document。这意味着可以为整个页面指定 一个 onclick 事件处理程序,而不用为每个可点击元素分别指定事件处理程序。
事件委托的优点:
document 对象随时可用,任何时候都可以给它添加事件处理程序(不用等待 DOMContentLoaded 或 load 事件)。这意味着只要页面渲染出可点击的元素,就可以无延迟地起作用。
节省花在设置页面事件处理程序上的时间。只指定一个事件处理程序既可以节省 DOM 引用,也 可以节省时间。
减少整个页面所需的内存,提升整体性能。
事件循环机制
- 所有任务都在主线程上执行,形成一个执行栈。
- 主线程之外,还存在一个”任务队列”(task queue)。只要异步任务有了运行结果,就在”任务队列”之中放置一个事件。
- 一旦”执行栈”中的所有同步任务执行完毕,系统就会读取”任务队列”。那些对应的异步任务,结束等待状态,进入执行栈并开始执行。
- 主线程不断重复上面的第三步。
宏任务与微任务
宏任务(macrotask)::
script(整体代码)、setTimeout、setInterval、UI 渲染、 I/O、postMessage、 MessageChannel、setImmediate
微任务(microtask):
Promise、 MutaionObserver、process.nextTick(Node.js环境)
实例:
1 | console.log(1); |
- 上面的示例中,第一次事件循环,整段代码作为宏任务进入主线程执行。
- 遇到了 setTimeout ,就会等到过了指定的时间后将回调函数放入到宏任务的任务队列中。
- 遇到 Promise,将 then 函数放入到微任务的任务队列中。
- 整个事件循环完成之后,会去检测微任务的任务队列中是否存在任务,存在就执行。
- 第一次的循环结果打印为: 1,3,5,4。
- 接着再到宏任务的任务队列中按顺序取出一个宏任务到栈中让主线程执行,那么在这次循环中的宏任务就是 setTimeout 注册的回调函数,执行完这个回调函数,发现在这次循环中并不存在微任务,就准备进行下一次事件循环。
- 检测到宏任务队列中已经没有了要执行的任务,那么就结束事件循环。
- 最终的结果就是 1,3,5,4,2。
判断数组的六种方法
- ① instanceof 操作符判断
用法:arr instanceof Array
instanceof 主要是用来判断某个实例是否属于某个对象
1 | let arr = []; |
但是 instanceof 会有一个问题,它的问题在于假定只有一个全局执行的环境。如果网页中包含多个框架,那实际上就存在两个以上不同的全局执行环境,从而存在两个以上不同版本的Array构造函数。如果你从一个框架向另一个框架传入一个数组,那么传入的数组与在第二个框架中原生创建的数组分别具有不同的构造函数。
- ②对象构造函数的constructor判断
用法:arr.constructor === Array
Object的每个实例都有构造函数 constructor,用于保存着用于创建当前对象的函数
1 | let arr = []; |
- ③Array 原型链上的 isPrototypeOf
用法:Array.prototype.isPrototypeOf(arr)
Array.prototype 属性表示 Array 构造函数的原型
1 | let arr = []; |
- ④Object.getPrototypeOf
用法:Object.getPrototypeOf(arr) === Array.prototype
Object.getPrototypeOf() 方法返回指定对象的原型
1 | let arr = []; |
- ⑤Object.prototype.toString
用法:Object.prototype.toString.call(arr) === ‘[object Array]’
1 | let arr = []; |
- ⑥Array.isArray
用法:Array.isArray(arr)
ES5中新增了Array.isArray方法,IE8及以下不支持
1 | let arr = []; |
执行上下文和作用域
比较let和var
let是块级作用域,var是全局作用域
1 | for(var i = 1;i<=3;i++){ |
因为var定义的i是全局作用域的,所以访问的一直都是全局作用域中的i
但是let定义的是块级作用域,所以他访问自己块中的i,而访问不到全局作用域
用var模拟出块级作用域
1 | for(var i = 1;i<=3;i++){ |
var虽然没有块级作用域,但是他有函数作用域,每次循环创建一个函数,生成一个函数作用域,这样就和上面的let一样了用自己块中的i
执行上下文
试想当 JavaScript 开始要解释执行代码的时候,最先遇到的就是全局代码,所以初始化的时候首先就会向执行上下文栈压入一个全局执行上下文,我们用 globalContext 表示它,并且只有当整个应用程序结束的时候,ECStack 才会被清空,所以程序结束之前, ECStack 最底部永远有个 globalContext.函数执行上下文只有在函数被调用的时候才会产生函数执行上下文,并压入栈顶.执行上下文对象的个数为n+1,其中n为函数被调用的次数,1为全局上下文
执行环境定义了变量或环境有权访问的其他数据,每个执行环境都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中.每个函数都有自己的执行环境,当执行流进入一个函数时函数的环境就会被推到一个环境栈中,而在函数执行完后,栈将其环境弹出,将控制权返还给之前的执行环境.
作用域
作用域相对于上下文而言,上下文只有在函数被调用时才会产生,而作用域在编写代码时就已经确定了,即在函数定义时就已经产生.作用域共有n+1个,其中n为定义函数的个数,1为全局作用域
作用域链
当代码在一个环境中执行时,会创建变量对象的一个作用域链,用途是保证对执行环境有权访问的变量的有序访问.作用域链的最前端始终都是当前执行代码所在环境的变量对象.作用域链的下一个变量对象来自包含环境的变量对象,一直到全局执行环境.
执行上下文和作用域的区别
区别一
*全局作用域之外,每个函数都会创建自己的作用域,作用域在函数定义时就已经确定了,而不是函数调用时
*全局执行上下文环境是在全局作用域之后,js代码马上执行之前创建
*函数执行上下文环境是在调用函数时,函数体代码执行之前创建
区别二
*作用域是静态的,只要函数定义好了就一直存在,且不会再变化
*上下文环境是动态的,调用函数时创建,函数调用结束时上下文环境就会被释放
闭包
闭包是指有权访问另外一个函数作用域中的变量的函数。
在嵌套函数中, 如果一个内层函数引用外层及更外层的函数当中定义的变量, 那么这个函数拥有包含这些变量数据的闭包. 当该内层函数被返回出去的时候, 其作用域链不会被摧毁.
一个例子
1 | let hd = { |
上面的代码输出undefined,原因是a得到了一个函数,并打印a(),这里函数是在全局作用域中调用的,所以函数中的this就是全局作用域,不会去hd对象中找,所以输出的是undefined.可以改成箭头函数,如下
1 | let hd = { |
这样可以正常输出张三,因为箭头函数的this指向离箭头函数最近的环境.
原型
isPrototypeOf()
b.isPrototypeOf(a)方法检测的是b是否在a的原型链上,即b是否是a原型链上的一份子.
instanceof
b instanceof a 检测的是b这个构造函数的prototype是否在a的原型链上
in与hasOwnProperty()
“name” in person 检测的是”name”属性是否是person中的属性或者person的原型链中的属性.
person.hasOwnProperty(“name”) 只是检测当前对象中是否有这个属性,不会攀至原型链.
深浅拷贝
浅拷贝
Object.assign
Object.assign方法当定义的对象只有基本数据类型时就是深拷贝,当有引用类型时就是浅拷贝,下面举例
1 | //当定义的对象只有基本类型时,该方法就是深拷贝。 |
ES6中扩展运算符
该方法和上述方法一样只能用于深拷贝第一层的值,当拷贝第二层的值时 仍是引用同一个内存地址。
1 | let a = { |
Array.prototype.slice() 和 Array.prototype.concat()
该方法只能用于深拷贝第一层的值,当拷贝第二层的值时仍是引用同一个内存地址。
总结
浅拷贝会在栈中开辟一个新的内存空间,将原对象一级中的“基本数据类型”复制一份到新的内存空间,所以相互不影响。当对象中有“引用类型”时,它只能拷贝“引用类型”在堆内存中的地址,所以赋值后会影响原对象的值。
深拷贝
JSON.parse(JSON.stringify(obj))
1 | //该方法可以实现深拷贝。 但是需要注意 |
使用递归函数遍历
1 | //写一个递归函数 |
伪数组转为数组的方法
假设这里有个伪数组:pagis
1
1 | let arr = [].slice.call(pagis) |
2
1 | let arr = Array.prototype.slice.call(pagis); |
3
1 | var arr1 = [], |
4
1 | var func = Function.prototype.call.bind(Array.prototype.slice); |
5 解构赋值
1 | function args(){ |
6 Array.from
1 | function args(){ |
this指向
普通函数调用
1 | var name = "globle"; |
全局直接调用函数,函数中的this指向window
作为方法调用
1 | var name="XL"; |
换种形式
1 | var personA={ |
构造函数
1 | function Person(name){ |
settimeout
1 |
|
那么如何才能输出"my name is xl"
呢?
1 | var name="XL"; |
匿名函数
1 | var name="XL"; |
箭头函数
es6
里面this
指向固定化,始终指向外部对象,因为箭头函数没有this
,因此它自身不能进行new
实例化,同时也不能使用call, apply, bind
等方法来改变this
的指向
1 | function Timer() { |
1 | var test = { |
防抖和节流
防抖
在一段时间只执行一次,最后一次有效.控制次数
场景:
搜索输入框(Autocomplete),当不再输入后的几百毫秒再去发送请求,减少服务器压力。
注册框(即时判断是否重复用户名),或需要后台校验的文本输入框同理。
不停改变浏览器窗口大小会触发多次 resize 事件,引起浏览器的重排【关联概念(弱)】,消耗性能。
1 | //防抖 |
节流
只在开始执行一次,未执行完成过程中触发的忽略,核心在于开关锁
.总而言之节流
就是在一段时间内不断操作而在你规定的时间内只执行一次的一种提高性能的方法.节流至少在每时间间隔内 保证有规律地执行该功能。控制频率
场景:比如我们滚动鼠标滚轮监听滚动条位置,防止按钮多次点击等。
1 | //节流 |
对于防抖
和节流
一个最主观的判断方法就是:在10s
内你疯狂点击一个按钮,如果使用了防抖则会只执行一次,而你使用了节流则会每隔一段时间执行一次,这个时间可以自己来掌控。
跨域
同源策略
它规定了协议号-域名-端口号
这三者必须都相同
才符合同源策略
,如有有一个不相同
,就会出现跨域问题,不符合同源策略
导致的后果有
- 1、
LocalStorge、SessionStorge、Cookie
等浏览器内存无法跨域访问 - 2、
DOM节点
无法跨域操作 - 3、
Ajax请求
无法跨域请求
跨域发生的时机
请求发出去到后端,后端返回数据,在浏览器接收后端数据时被浏览器的跨域报错拦下来
JSPON
以下这三个标签加载资源路径
是不受束缚的
- 1、script标签:
<script src="加载资源路径"></script>
- 2、link标签:
<link herf="加载资源路径"></link>
- 3、img标签:
<img src="加载资源路径"></img>
JSONP通俗点说就是:利用script的src去发送请求,将一个方法名callback
传给后端,后端拿到这个方法名,将所需数据,通过字符串拼接成新的字符串callback(所需数据)
,并发送到前端,前端接收到这个字符串之后,就会自动执行方法callback(所需数据)
。
缺点:JSONP的缺点就是,需要前后端配合,并且只支持get请求方法