前端基本功 - 常见概念(一) 点这里前端基本功 - 常见概念(二) 点这里前端基本功 - 常见概念(三) 点这里
1.let、const/var
let 是更完美的 var,不是全局变量,具有块级函数作用域,大多数情况不会发生变量提升。const 定义常量值,不能够重新赋值,如果值是一个对象,可以改变对象里边的属性值
var 存在的问题
var 有作用域问题(会污染全局作用域)
var 可已重复声明
var 会变量提升预解释
var 不能定义常量
let、const 特性
let、const 不可以重复声明
let、const 不会声明到全局作用域上
let、const 不会预解释变量
const 做常量声明(一般常量名用大写)
let 声明的变量具有块级作用域
let 声明的变量不能通过 window. 变量名进行访问
形如 for(let x..)的循环是每次迭代都为 x 创建新的绑定
下面是 var 带来的不合理场景
var a = []
for (var i = 0; i < 10; i++) {
a[i] = function () {
console.log(i)
}
}
a[5]() // 10
在上述代码中,变量 i 是 var 声明的,在全局范围类都有效。所以每一次循环,新的 i 值都会覆盖旧值,导致最后输出都是 10 而如果对循环使用 let 语句的情况,那么每次迭代都是为 x 创建新的绑定代码如下
var a = []
for (let i = 0; i < 10; i++) {
a[i] = function () {
console.log(i)
}
}
a[5]() // 5
重温一下闭包和立即函数两种方法
闭包的方法
function showNum(i) {
return function () {
console.log(i)
}
}
var a = []
for (var i = 0; i < 5; i++) {
a[i] = showNum(i)
}
立即函数的方法
var a = []
for (var i = 0; i < 5; i++) {
a[i] = (function (i) {
return function () {
console.log(i)
}
})(i)
}
a[2]()
本节参考文章:前端面试之 ES6 篇
2. 重排 / 重绘
在讨论重排 (回流) 与重绘之前,我们要知道:
浏览器使用流式布局模型 (Flow Based Layout)。
浏览器会把 HTML 成 DOM,把 CSS 解析成 CSSOM,DOM 和 CSSOM 合并就产生了 Render Tree。
有了 RenderTree,我们就知道了所有节点的样式,然后计算他们在页面上的大小和位置,最后把节点绘制到页面上。
由于浏览器使用流式布局,对 Render Tree 的计算通常只需要遍历一次就可以完成,但 table 及其内部元素除外,他们可能需要多次计算,通常要花 3 倍于同等元素的时间,这也是为什么要避免使用 table 布局的原因之一。
一句话:重排必将引起重绘,重绘不一定会引起重排。
重排 (Reflow)
当 Render Tree 中部分或全部元素的尺寸、结构、或某些属性发生改变时,浏览器重新渲染部分或全部文档的过程称为重排。
会导致重排的操作:
页面首次渲染
浏览器窗口大小发生改变
元素尺寸或位置发生改变
元素内容变化(文字数量或图片大小等等)
元素字体大小变化
添加或者删除可见的 DOM 元素
激活 CSS 伪类(例如::hover)
查询某些属性或调用某些方法
一些常用且会导致重排的属性和方法:
clientWidth、clientHeight、clientTop、clientLeft
offsetWidth、offsetHeight、offsetTop、offsetLeft
scrollWidth、scrollHeight、scrollTop、scrollLeft
scrollIntoView()、scrollIntoViewIfNeeded()
getComputedStyle()
getBoundingClientRect()
scrollTo()
重绘 (Repaint)
当页面中元素样式的改变并不影响它在文档流中的位置时(例如:color、background-color、visibility 等),浏览器会将新样式赋予给元素并重新绘制它,这个过程称为重绘。
性能影响
回流比重绘的代价要更高。
有时即使仅仅回流一个单一的元素,它的父元素以及任何跟随它的元素也会产生回流。
现代浏览器会对频繁的回流或重绘操作进行优化:
浏览器会维护一个队列,把所有引起回流和重绘的操作放入队列中,如果队列中的任务数量或者时间间隔达到一个阈值的,浏览器就会将队列清空,进行一次批处理,这样可以把多次回流和重绘变成一次。
当你访问以下属性或方法时,浏览器会立刻清空队列:
clientWidth、clientHeight、clientTop、clientLeft
offsetWidth、offsetHeight、offsetTop、offsetLeft
scrollWidth、scrollHeight、scrollTop、scrollLeft
width、height
getComputedStyle()
getBoundingClientRect()
因为队列中可能会有影响到这些属性或方法返回值的操作,即使你希望获取的信息与队列中操作引发的改变无关,浏览器也会强行清空队列,确保你拿到的值是最精确的。
如何避免
CSS
避免使用 table 布局。
尽可能在 DOM 树的最末端改变 class。
避免设置多层内联样式。
将动画效果应用到 position 属性为 absolute 或 fixed 的元素上。
避免使用 CSS 表达式(例如:calc())。
JavaScript
避免频繁操作样式,最好一次性重写 style 属性,或者将样式列表定义为 class 并一次性更改 class 属性。
避免频繁操作 DOM,创建一个 documentFragment,在它上面应用所有 DOM 操作,最后再把它添加到文档中。
也可以先为元素设置 display: none,操作结束后再把它显示出来。因为在 display 属性为 none 的元素上进行的 DOM 操作不会引发回流和重绘。
避免频繁读取会引发回流 / 重绘的属性,如果确实需要多次使用,就用一个变量缓存起来。
对具有复杂动画的元素使用绝对定位,使它脱离文档流,否则会引起父元素及后续元素频繁回流。
本节参考文章:[浏览器的回流与重绘 (Reflow & Repaint)](https://juejin.im/post/5a9923e9518825558251c96a)
3. 函数节流(throttle)与函数去抖(debounce)
Debounce:一部电梯停在某一个楼层,当有一个人进来后,20 秒后自动关门,这 20 秒的等待期间,又一个人按了电梯进来,这 20 秒又重新计算,直到电梯关门那一刻才算是响应了事件。
Throttle:好比一台自动的饮料机,按拿铁按钮,在出饮料的过程中,不管按多少这个按钮,都不会连续出饮料,中间按钮的响应会被忽略,必须要等这一杯的容量全部出完之后,再按拿铁按钮才会出下一杯。
4. 强缓存 / 协商缓存
浏览器缓存分为强缓存和协商缓存,优先读取强制缓存。当客户端请求某个资源时,获取缓存的流程如下:
先根据这个资源的一些 http header 判断它是否命中强缓存,如果命中,则直接从本地获取缓存资源,不会发请求到服务器;
当强缓存没有命中时,客户端会发送请求到服务器,服务器通过另一些 request header 验证这个资源是否命中协商缓存,称为 http 再验证,如果命中,服务器将请求返回,但不返回资源,而是告诉客户端直接从缓存中获取,客户端收到返回后就会从缓存中获取资源;
强缓存和协商缓存共同之处在于,如果命中缓存,服务器都不会返回资源;
区别是,强缓存不对发送请求到服务器,但协商缓存会。
当协商缓存也没命中时,服务器就会将资源发送回客户端。
当 ctrl+f5 强制刷新网页时,直接从服务器加载,跳过强缓存和协商缓存;
当 f5 刷新网页时,跳过强缓存,但是会检查协商缓存;
强缓存
Expires(该字段是 http1.0 时的规范,值为一个绝对时间的 GMT 格式的时间字符串,代表缓存资源的过期时间)
Cache-Control:max-age(该字段是 http1.1 的规范,强缓存利用其 max-age 值来判断缓存资源的最大生命周期,它的值单位为秒)
协商缓存
协商缓存是利用的是【Last-Modified,If-Modified-Since】和【ETag、If-None-Match】这两对 Header 来管理的
Last-Modified,If-Modified-Since
Last-Modified 表示本地文件最后修改日期,浏览器会在 request header 加上 If-Modified-Since(上次返回的 Last-Modified 的值),询问服务器在该日期后资源是否有更新,有更新的话就会将新的资源发送回来
但是如果在本地打开缓存文件,就会造成 Last-Modified 被修改,所以在 HTTP / 1.1 出现了 ETag
ETag、If-None-Match
Etag 就像一个指纹,资源变化都会导致 ETag 变化,跟最后修改时间没有关系,ETag 可以保证每一个资源是唯一的
If-None-Match 的 header 会将上次返回的 Etag 发送给服务器,询问该资源的 Etag 是否有更新,有变动就会发送新的资源回来
ETag 的优先级比 Last-Modified 更高
一些文件也许会周期性的更改,但是他的内容并不改变(仅仅改变的修改时间),这个时候我们并不希望客户端认为这个文件被修改了,而重新 GET;
某些文件修改非常频繁,比如在秒以下的时间内进行修改,(比方说 1s 内修改了 N 次),If-Modified-Since 能检查到的粒度是 s 级的,这种修改无法判断(或者说 UNIX 记录 MTIME 只能精确到秒);
某些服务器不能精确的得到文件的最后修改时间。
推荐必读:http 协商缓存 VS 强缓存、浏览器缓存知识小结及应用、缓存(二)——浏览器缓存机制:强缓存、协商缓存
5. 原始类型 / 引用类型
JavaScript 中的内存分为栈内存和堆内存。栈内存和堆内存区别:栈内存运行效率比堆内存高,空间相对堆内存来说较小。
区别:
值类型属于不可变类型, 由于具有固定长度大小, 其地址和具体内容都存在与栈内存中
而引用类型属于可变类型, 一个对象可以赋予多个属性及值,属性值又可以为一个新的引用对象。其地址存在栈内存,其具体内容存在堆内存中。
6.cookie/token
token 和 cookie 一样都是首次登陆时,由服务器下发,都是当交互时进行验证的功能,作用都是为无状态的 HTTP 提供的持久机制。
token 存在哪儿都行,localstorage 或者 cookie。
token 和 cookie 举例,token 就是说你告诉我你是谁就可以。
cookie 举例:服务员看你的身份证,给你一个编号,以后,进行任何操作,都出示编号后服务员去看查你是谁。token 举例:直接给服务员看自己身份证,服务器不需要去查看你是谁,不需要保存你的会话。
当用户 logout 的时候,cookie 和服务器的 session 都会注销;但是当 logout 时候 token 只是注销浏览器信息,不查库。
token 优势在于,token 由于服务器端不存储会话,所以可扩展性强,token 还可用于 APP 中。
小结:
Token 完全由应用管理,所以它可以避开同源策略 Token 可以避免 CSRF 攻击 Token 可以是无状态的,可以在多个服务间共享
如果你的用户数据可能需要和第三方共享,或者允许第三方调用 API 接口,用 Token,如果之上自己的那就无所谓了。
本节参考文章:cookie 和 token 的五点区别
推荐必读:前后端常见的几种鉴权方式
7.cookie/sessionStorage/localStorage
1.cookie
cookie 分为 cookie 机制和 session 机制(请大神判断正确性)Session: 是在服务端保存的一个数据结构,用来跟踪用户的状态,这个数据可以保存在集群、数据库、文件中,通过在服务器端记录信息确定用户身份 Cookie: 是客户端保存用户信息的一种机制,用来记录用户的一些信息,也是实现 Session 的一种方式,通过在客户端记录信息确定用户身份
如果说 Cookie 机制是通过检查客户身上的“通行证”来确定客户身份的话,那么 Session 机制就是通过检查服务器上的“客户明细表”来确认客户身份。Session 相当于程序在服务器上建立的一份客户档案,客户来访的时候只需要查询客户档案表就可以了。
cookie 机制 cookie 可以通过设置 domain 属性值,可以不同二级域名下共享 cookie,而 Storage 不可以,比如 http://image.baidu.com 的 cookie http://map.baidu.com 是可以访问的,前提是 Cookie 的 domain 设置为.http://baidu.com,而 Storage 是不可以的
session 机制
当程序需要为某个客户端的请求创建一个 session 时,
服务器首先检查这个客户端的请求里是否已包含了一个 session 标识 — 称为 session id,
如果已包含则说明以前已经为此客户端创建过 session,服务器就按照 session id 把这个 session 检索出来使用(检索不到,会新建一个),
如果客户端请求不包含 session id,则为此客户端创建一个 session 并且生成一个与此 session 相关联的 session id,session id 的值应该是一个既不会重复,又不容易被找到规律以仿造的字符串,这个 session id 将被在本次响应中返回给客户端保存。
比较
session 在服务器端,cookie 在客户端(浏览器)
session 保存在服务器,客户端不知道其中的信息;反之,cookie 保存在客户端,服务器能够知道其中的信息
session 会在一定时间内保存在服务器上,当访问增多,会比较占用你服务器的性能,考虑到减轻服务器性能方面,应当使用 cookie
session 中保存的是对象,cookie 中保存的是字符串
cookie 不是很安全,别人可以分析存放在本地的 cookie 并进行 cookie 欺骗,考虑到安全应当使用 session。用户验证这种场合一般会用 session
用户验证这种场合一般会用 session,因此,维持一个会话的核心就是客户端的唯一标识,即 session id
session 的运行依赖 session id,而 session id 是存在 cookie 中的,也就是说,如果浏览器禁用了 cookie,同时 session 也会失效(但是可以通过其它方式实现,比如在 url 中传递 session_id)
session 不能区分路径,同一个用户在访问一个网站期间,所有的 session 在任何一个地方都可以访问到,而 cookie 中如果设置了路径参数,那么同一个网站中不同路径下的 cookie 互相是访问不到的
JavaScript 原生的用法。
Cookie 以名 / 值对形式存储例如 username=John Doe,这里的数据是 string 类型,如要是其他格式注意进行格式转换。
JavaScript 可以使用 document.cookie 属性来创建、读取、及删除 cookie。JavaScript 中,创建 cookie 如下所示:
document.cookie=”username=John Doe”;
您还可以为 cookie 添加一个过期时间(以 UTC 或 GMT 时间)。默认情况下,cookie 在浏览器关闭时删除:
document.cookie=”username=John Doe; expires=Thu, 18 Dec 2013 12:00:00 GMT”;
您可以使用 path 参数告诉浏览器 cookie 的路径。默认情况下,cookie 属于当前页面。
document.cookie=”username=John Doe; expires=Thu, 18 Dec 2013 12:00:00 GMT; path=/”;
设置 cookie
function setCookie(cname,cvalue,exdays){
var SetTime = new Date(); // 设置过期时间
SetTime.setTime(SetTime.getTime()+(exdays*24*60*60*1000)); // 设置过期时间
var expires = “expires=”+SetTime.toGMTString(); // 设置过期时间
document.cookie = cname + “=” + cvalue + “; ” + expires; // 创建一个 cookie
}
读取 cookie
function getCookie(c_name){
if (document.cookie.length>0) {
c_start=document.cookie.indexOf(c_name + “=”)
if (c_start!=-1){
c_start=c_start + c_name.length+1
c_end=document.cookie.indexOf(“;”,c_start)
if (c_end==-1) c_end=document.cookie.length
return unescape(document.cookie.substring(c_start,c_end))
}
}
return “”
}
删除 cookie
将 cookie 的有效时间改成昨天。
使用 jquery.cookies.2.2.0.min.js 插件
添加 / 修改 cookie 并设定过期时间:
$.cookies.set(‘cookie_id’, ‘cookie_value’, { hoursToLive: 10});
这里设置的是过期时间是 10 小时, 还可以这样设置过期时间:
expireDate = new Date();
expireDate.setTime(expireDate.getTime() + (10 * 60 * 60 * 1000) );
$.cookies.set(‘cookie_id’, ‘cookie_value’, {expiresAt:expireDate});
获取 cookie
$.cookies.get(‘cookie_id’);
删除 cookie
$.cookies.del(‘cookie_id’);
2.Storage:localStorage、sessionStorage
大小:官方建议是 5M 存储空间类型:只能操作字符串,在存储之前应该使用 JSON.stringfy()方法先进行一步安全转换字符串,取值时再用 JSON.parse()方法再转换一次存储的内容:数组,图片,json,样式,脚本。。。(只要是能序列化成字符串的内容都可以存储)区别:sessionStorage 将数据临时存储在 session 中,浏览器关闭,数据随之消失,localStorage 将数据存储在本地,理论上来说数据永远不会消失,除非人为删除注意:数据是明文存储,毫无隐私性可言,绝对不能用于存储
基础操作 API
保存数据
localStorage.setItem(key, value);
sessionStorage.setItem(keyName,value); // 将 value 存储到 key 字段中
// 或者
sessionStorage.keyName=’value’;
读取数据
localStorage.getItem(key);
sessionStorage.getItem(keyName); // 获取指定 key 的本地存储的值
// 或者
var keyName=sessionStorage.key;
删除单个数据
localStorage.removeItem(key);
sessionStorage.removeItem(key);
删除全部数据
localStorage.clear();
sessionStorage.clear();
获取索引的 key
localStorage.key(index);
sessionStorage.key(index);
监听 storage 事件
可以通过监听 window 对象的 storage 事件并指定其事件处理函数,当页面中对 localStorage 或 sessionStorage 进行修改时,则会触发对应的处理函数
window.addEventListener(‘storage’,function(e){
console.log(‘key=’+e.key+’,oldValue=’+e.oldValue+’,newValue=’+e.newValue);
})
localstorage 是浏览器多个标签共用的存储空间,可以用来实现多标签之间的通信(ps:session 是会话级的存储空间,每个标签页都是单独的
触发事件的时间对象(e 参数值)有几个属性:key : 键值。oldValue : 被修改前的值。newValue : 被修改后的值。url : 页面 url。storageArea : 被修改的 storage 对象。
3. 对比
共同点:都是保存在浏览器端、且同源的,都受同源策略的制约。
区别:
cookie 数据始终在同源的 http 请求中携带(即使不需要),即 cookie 在浏览器和服务器间来回传递,而 sessionStorage 和 localStorage 不会自动把数据发送给服务器,仅在本地保存。cookie 数据还有路径(path)的概念,可以限制 cookie 只属于某个路径下
存储大小限制也不同,cookie 数据不能超过 4K,同时因为每次 http 请求都会携带 cookie、所以 cookie 只适合保存很小的数据,如会话标识。sessionStorage 和 localStorage 虽然也有存储大小的限制,但比 cookie 大得多,可以达到 5M 或更大
数据有效期不同,sessionStorage:仅在当前浏览器窗口关闭之前有效;localStorage:始终有效,窗口或浏览器关闭也一直保存,因此用作持久数据;cookie:只在设置的 cookie 过期时间之前有效,即使窗口关闭或浏览器关闭
作用域不同,sessionStorage 不在不同的浏览器窗口中共享,即使是同一个页面;localstorage 在所有同源窗口中都是共享的;cookie 也是在所有同源窗口中都是共享的
本节参考文章:缓存(三)——数据存储 …
其他阅读:关于 Cookie、session 和 Web Storage
8.js 事件
1. 事件
事件指可以被 JavaScript 侦测到的行为。即鼠标点击、页面或图像载入、鼠标悬浮于页面的某个热点之上、在表单中选取输入框、确认表单、键盘按键等操作。事件通常与函数配合使用,当事件发生时函数才会执行。
事件名称:click/mouseover/blur(“ 不带 on”)事件处理程序(事件侦听器):响应某个事件的函数,名称为:onclick/onmouseove/onblur,例如 <button onclick=”alert(‘hello’)”></button>
2.DOM 事件模型:冒泡和捕获
冒泡:往上捕获:向下
3. 事件流
事件流指从页面中接收事件的顺序, 也可理解为事件在页面中传播的顺序。
DOM2 级事件规定的事件流包括三个阶段:(1)事件捕获阶段(2)处于目标阶段(3)事件冒泡阶段。
当事件发生时,最先得到通知的是 window,然后是 document,由上至下逐级依次而入,直到真正触发事件的那个元素 (目标元素) 为止,这个过程就是捕获。接下来,事件会从目标元素开始起泡,由下至上逐级依次传播,直到 window 对象为止,这个过程就是冒泡。所以捕获比冒泡先执行。
希望注册在 DOM 元素上的事件处理程序在捕获阶段还是在冒泡阶段触发,取决于 addEventListener() 方法的第三个参数为 true 还是 false。
其中 DOM3 级事件在 DOM2 的基础之上添加了更多的事件类型。
描述 DOM 事件捕获的具体流程
window–>document–>html(document.documentElement)–>body(document.body)…
4.DOM 级别 /DOM 事件
DOM 级别一共可以分为 4 个级别:DOM0 级,DOM1 级,DOM2 级和 DOM3 级,而 DOM 事件分为 3 个级别:DOM0 级事件处理,DOM2 级事件处理和 DOM3 级事件处理。
其中 1 级 DOM 标准中并没有定义事件相关的内容,所以没有所谓的 1 级 DOM 事件模型。
DOM0:element.onclick = function(){}DOM2:element.addEventlistenter(‘click’,function(){},flase)DOM3:element.addEventlistenter(‘keyup’,function(){},flase)
HTML 事件处理程序
<button onclick=”alert(‘hello’)”></button>
<button onclick=”doSomething()”></button>
<button onclick=”try{doSomething();}catch(err){}”></button>
DOM0 级事件处理程序
btn.onclick=function(){
alert(“hello”);
}
btn.onclick = null;// 来删除指定的事件处理程序。
如果我们尝试给事件添加两个事件,如:
<button id=”btn”> 点击 </button>
<script>
var btn=document.getElementById(“btn”);
btn.onclick=function(){
alert(“hello”);
}
btn.onclick=function(){
alert(“hello again”);
}
</script>
输出,hello again,很明显,第一个事件函数被第二个事件函数给覆盖掉了,所以,DOM0 级事件处理程序不能添加多个,也不能控制事件流到底是捕获还是冒泡。
DOM2 级事件处理程序
addEventListener() — 添加事件侦听器
函数均有 3 个参数,第一个参数是要处理的事件名(不带 on 前缀的才是事件名) 第二个参数是作为事件处理程序的函数 第三个参数是一个 boolean 值,默认 false 表示使用冒泡机制,true 表示捕获机制。
<button id=”btn”> 点击 </button>
<script>
var btn=document.getElementById(“btn”);
btn.addEventListener(‘click’,hello,false);
btn.addEventListener(‘click’,helloagain,false);
function hello(){
alert(“hello”);
}
function helloagain(){
alert(“hello again”);
}
</script>
removeEventListener() // 删除事件侦听器
可以绑定多个事件处理程序,但是注意,如果定义了一摸一样时监听方法,是会发生覆盖的,即同样的事件和事件流机制下相同方法只会触发一次,事件触发的顺序是添加的顺序
“`
// 为了兼容 IE 浏览器和标准的浏览器,我们需要编写通用的方法来处理:
var EventUtil = {
addHandler: function (element, type, handler) {
if (element.addEventListener) {
element.addEventListener(type, handler, false);
} else if (element.attachEvent) {
element.attachEvent(“on” + type, handler);
} else {
element[“on” + type] = handler;
}
},
removeHandler: function (element, type, handler) {
if (element.removeEventListener()) {
element.removeEventListener(type, handler, false);
} else if (element.detachEvent) {
element.detachEvent(“on” + type, handler);
} else {
element[“on” + type] = null;
}
}
};
“`
5. 事件对象
事件对象是用来记录一些事件发生时的相关信息的对象,但事件对象只有事件发生时才会产生,并且只能是事件处理函数内部访问,在所有事件处理函数运行结束后,事件对象就被销毁!
//currentTarget、eventPhase 一个例子:
<button id=”btn”> 点击 </button>
<script>
var btn=document.getElementById(“btn”);
btn.ddEventListener(‘click’, doCurrent, true);
// 判断事件的属性
function doCurrent(event) {
// 获取当前事件触发的 div
var target = event.currentTarget;
// 通过判断事件的 event.eventPhase 属性返回事件传播的当前阶段
//1:捕获阶段、2:正常事件派发和 3:起泡阶段。
// 得到当前阶段和 id 值并输出
var msg = (event.eventPhase == 1 ? ‘ 捕获阶段:’ : ‘ 冒泡阶段:’)+ target.attributes[“id”].value;;
console.log(msg);
}
</script>
当然,事件对象也存在一定的兼容性问题,在 IE8 及以前本版之中,通过设置属性注册事件处理程序时,调用的时候并未传递事件对象,需要通过全局对象 window.event 来获取。解决方法如下:
function getEvent(event) {
event = event || window.event;
}
在 IE 浏览器上面是 event 事件是没有 preventDefault()这个属性的,所以在 IE 上,我们需要设置的属性是 returnValue
window.event.returnValue=false
stopPropagation()也是,所以需要设置 cancelBubble,cancelBubble 是 IE 事件对象的一个属性,设置这个属性为 true 能阻止事件进一步传播。
event.cancelBubble=true
常见属性
解析
event.preventDefault()
阻止默认行为
event.stopPropagation()
阻止冒泡。使用了 stopPropagation()之后,事件就不能进一步传播了,同时如果是同一个 div 上有捕获和冒泡两种事件监听,在捕获阶段传播阻止后冒泡阶段事件监听也不会触发。
event.stopImmediatePropagation()
使用了 stopImmediatePropagation()之后,绑定的后续事件监听都会忽略。
event.currentTarget
当前绑定的事件
event.target
事件代理时 点击的元素
关于捕获和冒泡:理解 addEventListener、捕获和冒泡
6. 自定义事件
jq
// 添加一个适当的事件监听器
$(‘#foo’).on(‘custom’, function(event, param1, param2) {
alert(param1 + “\n” + param2);
});
$(‘#foo’).trigger(‘custom’, [‘Custom’, ‘Event’]);
原生 Event:
var eve = new Event(‘custome’)
element.addEventListenter(‘custome’,function(){
console.log(‘custome’)
})
element.dispatchEvent(eve)
原生 CustomEvent
// 添加一个适当的事件监听器
obj.addEventListener(“custom”, function(e) {
console.log(JSON.stringify(e.detail));
})
// 创建并分发事件
var event = new CustomEvent(“custom”, {“detail”:{“Custom”:true}});
obj.dispatchEvent(event)
本节参考文章:一个能拖动,能调整大小,能更新 bind 值的 vue 指令 -vuedragx
7. 事件委托(代理)
事件委托就是利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。
案例一:
<button id=”btnAdd”> 添加 </button>
<ul id=”ulList”>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
<script>
var btnAdd = document.getElementById(‘btnAdd’);
var ulList = document.getElementById(‘ulList’);
var list = document.getElementsByTagName(‘li’);
var num = 3;
btnAdd.onclick = function () {
num++;
var li = document.createElement(‘li’);
li.innerHTML = num;
ulList.appendChild(li)
}
for (i = 0; i < list.length; i++) {
list[i].onclick = function(){
alert(this.innerHTML);
}
}
</script>
// 例子说明,我们为 ul 添加新的 li,
// 其中对 li 标签元素绑定了 click 事件,
// 但是发现,后增加的元素没有办法触发我们的 click 事件。
解决方法:事件委托
<button id=”btnAdd”> 添加 </button>
<ul id=”ulList”>
<li class=”class-1″>1</li>
<li class=”class-1″>2</li>
<li class=”class-1″>3</li>
</ul>
<script>
var btnAdd = document.getElementById(‘btnAdd’);
var ulList = document.getElementById(‘ulList’);
var num = 3;
ulList.onclick = function(event){
var event = event || window.event;
var target = event.target || event.srcElement;
// if (target.matches(‘li.class-1’))
//#ulList 元素下的 li 元素(并且它的 class 为 class-1)
if(target.nodeName.toLowerCase() == ‘li’){
alert(target.innerHTML);
}
}
btnAdd.onclick = function () {
num++;
var li = document.createElement(‘li’);
li.innerHTML = num;
ulList.appendChild(li);
}
</script>
案例二:
点击“添加”按钮添加一个按钮,点击添加的按钮移除这个按钮
<div class=”wrap” id=”wrap”>
<div class=”btn” data-type=”btn” data-feat=”add”> 添加 </div>
<div class=”btn” data-type=”btn” data-feat=”delete”> 绘画 </div>
<div class=”btn” data-type=”btn” data-feat=”delete”> 散步 </div>
<div class=”btn” data-type=”btn” data-feat=”delete”> 静坐 </div>
</div>
document.getElementById(‘wrap’).addEventListener(‘click’, function(e){
var target = e.target;
while(target !== this){
var type = target.dataset.type;
if(type == ‘btn’){
var feat = target.dataset.feat;
switch(feat){
case ‘add’:
this.innerHTML += ‘<div class=”btn” data-type=”btn” data-feat=”delete”> 静坐 </div>’
return;
case ‘delete’:
target.parentNode.removeChild(target);
return;
}
}
target = target.parentNode;
}
}, false);
适合用事件委托的事件:click,mousedown,mouseup,keydown,keyup,keypress。
推荐阅读:JavaScript 事件委托详解
本节参考文章:前端小知识 –JavaScript 事件流
9.link / @import
两者都是外部引用 CSS 的方式,但是存在一定的区别:
link 是 XHTML 标签,除了能够加载 CSS,还可以定义 RSS 等其他事务;而 @import 属于 CSS 范畴,只可以加载 CSS。
link 引用 CSS 时,在页面载入时同时加载;@import 需要页面完全载入以后再加载。
link 是 XHTML 标签,无兼容问题;@import 则是在 CSS2.1 提出的,低版本的浏览器不支持。
link 方式的样式的权重 高于 @import 的权重.
link 支持使用 Javascript 控制 DOM 改变样式;而 @import 不支持。
本节参考文章:前端面试题 -url、href、src
10. 异步编程的实现方式
1. 回调函数优点:简单、容易理解缺点:不利于维护,代码耦合高
2. 事件监听(采用时间驱动模式,取决于某个事件是否发生):优点:容易理解,可以绑定多个事件,每个事件可以指定多个回调函数缺点:事件驱动型,流程不够清晰
3. 发布 / 订阅 (观察者模式) 类似于事件监听,但是可以通过‘消息中心’,了解现在有多少发布者,多少订阅者
4.Promise 对象优点:可以利用 then 方法,进行链式写法;可以书写错误时的回调函数;缺点:编写和理解,相对比较难
5.Generator 函数优点:函数体内外的数据交换、错误处理机制缺点:流程管理不方便
6.async 函数优点:内置执行器、更好的语义、更广的适用性、返回的是 Promise、结构清晰。缺点:错误处理机制
11.documen.write/ innerHTML 的区别
document.write 只能重绘整个页面 innerHTML 可以重绘页面的一部分
12.isPrototypeOf()/instanceof
isPrototypeOf() 与 instanceof 运算符不同。
在表达式 “object instanceof AFunction” 中,object 的原型链是针对 AFunction.prototype 进行检查的,而不是针对 AFunction 本身。
isPrototypeOf() 方法允许你检查一个对象是否存在于另一个对象的原型链上。
function Foo() {}
function Bar() {}
function Baz() {}
Bar.prototype = Object.create(Foo.prototype);
Baz.prototype = Object.create(Bar.prototype);
var baz = new Baz();
//isPrototypeOf
console.log(Baz.prototype.isPrototypeOf(baz)); // true
console.log(Bar.prototype.isPrototypeOf(baz)); // true
console.log(Foo.prototype.isPrototypeOf(baz)); // true
console.log(Object.prototype.isPrototypeOf(baz)); // true
if (Foo.prototype.isPrototypeOf(baz)) {
// do something safe
}
//instanceof
console.log(baz instanceof Baz); // true
console.log(baz instanceof Bar); // true
console.log(baz instanceof Foo); // true
console.log(baz instanceof Object); // true
var obj1 = {
name: ‘esw’
}
var obj2 = Object.create(obj1)
// isPrototypeOf()方法
Object.prototype.isPrototypeOf(obj1) // true
obj1.isPrototypeOf(obj2) // true
Object.prototype.isPrototypeOf(obj2) // true
13.constructor、__proto__与 prototype
在 javascript 中我们每创建一个对象,该对象都会获得一个__proto__属性(该属性是个对象),该属性指向创建该对象的构造函数的原型即 prototype,同时__proto__对象有一个 constructor 属性指向该构造函数。这里我们需要注意的是只有函数才有 prototype,每个对象(函数也是对象)都有__proto__,Object 本身是个构造函数。举例来说:
var obj = new Object()
// 也可以使用对象字面量创建,但使用 Object.create()情况会不一样
// Object 本身是个构造函数
Object instanceof Function // true
obj.__proto__ === Object.prototype // true
obj.__proto__.constructor === Object // true
// 我们一般习惯这样写
obj.constructor === Object // true
当我们访问 obj.constructor 的时候,obj 本身是没有 constructor 属性的,但属性访问会沿着__proto__向上查找,即在 obj.__proto__里面寻找 constructor 属性,如果找到了就返回值,如果未找到则继续向上查找直到 obj.__proto__.__proto__…(__proto__) === null 为止,没有找到则返回 undefined。这样由__proto__构成的一条查找属性的线称为‘原型链’。
本节参考文章:重新认识 javascript 对象(三)——原型及原型链、一篇文章带你进一步了解 object 属性
14. 浅拷贝 / 深拷贝
1. 浅拷贝只能复制值类型的属性。对于引用类型的属性,复制前后的两个对象指向同一内存地址,操作其中一个对象的引用类型属性,另一个对象也会相应发生改变;也就是说只有改变值类型的属性两个对象才不会相互影响。2. 深拷贝不仅可以复制值类型的属性,还可以复制引用类型的属性,无论两个对象怎么改变都不会相互影响。
浅复制
var obj = {
a : 1,
b: {
c: 2
}
}
// 浅复制
function lowerClone(obj){
var newObj=obj.constructor === Array ? [] : {};
for(var i in obj){
newObj[i]=obj[i]
}
return newObj;
}
var objClone = lowerClone(obj)
objClone.a // 1
obj.a // 1
objClone.a = 100
// 改变复制对象的值类型属性,值类型属性的值不变
obj.a // 1
objClone.b.c = 200
// 改变复制对象的引用类型的属性,引用类型的属性值改变
obj.b.c //200
深复制
var obj = {
a : 1,
b: {
c: 2
}
}
function deepClone(obj){
if(typeof obj != ‘object’){
return obj;
}
var newObj=obj.constructor === Array ? [] : {};
for(var i in obj){
newObj[i]=deepClone(obj[i]);
}
return newObj;
}
var objClone = deepClone(obj)
objClone.a // 1
obj.a // 1
objClone.a = 100
// 改变复制对象的值类型属性,值类型属性的值不变
obj.a // 1
objClone.b.c = 200
// 改变复制对象的引用类型的属性,引用类型的属性值不变
obj.b.c // 2
本节参考文章:javascript 浅复制与深复制
15.apply/call/bind
apply、call、bind 三者都是用来改变函数的 this 对象的指向的;apply、call、bind 三者第一个参数都是 this 要指向的对象,也就是想指定的上下文;apply、call、bind 三者都可以利用后续参数传参;bind 是返回对应函数,便于稍后调用;apply、call 则是立即调用。call apply 的区别是他们指定参数的方式不同。
案例
function fn(a,b){
console.log(this);
console.log(a);
console.log(b);
}
// bind(this,args…)
bf = fn.bind(“Bind this”,10); // 没有任何输出,也就是说没有执行这个函数
bf(); // “Bind this”,10,undefined
bf(20);//“Bind this”,10,20
// 原函数不受影响
fn(1,2); //window,1,2
bf2 = fn.bind(“Bind this”,1,2);
bf2(); // “Bind this”,1,2
// call(this,args…)
fn.call(“Call this”,1) // “Call this”,1,undefined
fn.call(“Call this”,1,2) // “Call this”,1,2
// apply(this,[args])
fn.apply(“Apply this”,[1]) // “Apply this”,1,undefined
fn.apply(“Apply this”,[1,2]) // “Apply this”,1,2