内容简介: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种方法
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
颠覆式创新:移动互联网时代的生存法则
李善友 / 机械工业出版社 / 2014-12-1 / 69
为什么把每件事情都做对了,仍有可能错失城池?为什么无人可敌的领先企业,却在一夜之间虎落平阳? 短短三年间诺基亚陨落,摩托罗拉区区29亿美元出售给联想,芯片业霸主英特尔在移动芯片领域份额几乎为零,风光无限的巨头转眼成为被颠覆的恐龙,默默无闻的小公司一战成名迅速崛起,令人瞠目结舌的现象几乎都被“颠覆式创新”法则所解释。颠覆式创新教你在新的商业竞争中“换操作系统”而不是“打补丁”,小公司用破坏性思......一起来看看 《颠覆式创新:移动互联网时代的生存法则》 这本书的介绍吧!