内容简介:let是更完美的var,不是全局变量,具有块级函数作用域,大多数情况不会发生变量提升。const定义常量值,不能够重新赋值,如果值是一个对象,可以改变对象里边的属性值在上述代码中,变量i是var声明的,在全局范围类都有效。所以每一次循环,新的i值都会覆盖旧值,导致最后输出都是10
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就是说你告诉我你是谁就可以。
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级,
其中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
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 前端基本功-示例代码(一)
- 前端基本功-常见概念(一)
- 前端基本功-示例代码 (二)
- 前端基本功-响应式布局(flex)
- 前端基本功(七):javascript中的继承(原型、原型链、继承的实现方式)
- 前端基本功:JavaScript 的七种数据类型与数据类型检测的4种方法
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Where Wizards Stay Up Late
Katie Hafner / Simon & Schuster / 1998-1-21 / USD 16.00
Twenty five years ago, it didn't exist. Today, twenty million people worldwide are surfing the Net. "Where Wizards Stay Up Late" is the exciting story of the pioneers responsible for creating the most......一起来看看 《Where Wizards Stay Up Late》 这本书的介绍吧!