前端基本功-常见概念(一) 点这里
前端基本功-常见概念(二) 点这里
前端基本功-常见概念(三) 点这里javascript
let 是更完美的var,不是全局变量,具备块级函数做用域,大多数状况不会发生变量提高。
const 定义常量值,不可以从新赋值,若是值是一个对象,能够改变对象里边的属性值
var存在的问题html
let、const特性前端
var a = [] for (var i = 0; i < 10; i++) { a[i] = function () { console.log(i) } } a[5]() // 10
在上述代码中,变量i是var声明的,在全局范围类都有效。因此每一次循环,新的i值都会覆盖旧值,致使最后输出都是10
而若是对循环使用let语句的状况,那么每次迭代都是为x建立新的绑定代码以下vue
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篇java
在讨论重排(回流)与重绘以前,咱们要知道:node
HTML
成DOM
,把CSS
解析成CSSOM
,DOM
和CSSOM
合并就产生了Render Tree
。RenderTree
,咱们就知道了全部节点的样式,而后计算他们在页面上的大小和位置,最后把节点绘制到页面上。Render Tree
的计算一般只须要遍历一次就能够完成,但table
及其内部元素除外,他们可能须要屡次计算,一般要花3倍于同等元素的时间,这也是为何要避免使用table
布局的缘由之一。一句话:重排必将引发重绘,重绘不必定会引发重排。
当Render Tree中部分或所有元素的尺寸、结构、或某些属性发生改变时,浏览器从新渲染部分或所有文档的过程称为重排。jquery
会致使重排的操做:git
一些经常使用且会致使重排的属性和方法:github
当页面中元素样式的改变并不影响它在文档流中的位置时(例如:color、background-color、visibility等),浏览器会将新样式赋予给元素并从新绘制它,这个过程称为重绘。面试
回流比重绘的代价要更高。
有时即便仅仅回流一个单一的元素,它的父元素以及任何跟随它的元素也会产生回流。
现代浏览器会对频繁的回流或重绘操做进行优化:
浏览器会维护一个队列,把全部引发回流和重绘的操做放入队列中,若是队列中的任务数量或者时间间隔达到一个阈值的,浏览器就会将队列清空,进行一次批处理,这样能够把屡次回流和重绘变成一次。
当你访问如下属性或方法时,浏览器会马上清空队列:
由于队列中可能会有影响到这些属性或方法返回值的操做,即便你但愿获取的信息与队列中操做引起的改变无关,浏览器也会强行清空队列,确保你拿到的值是最精确的。
CSS
JavaScript
本节参考文章:[浏览器的回流与重绘 (Reflow & Repaint)](https://juejin.im/post/5a9923e9518825558251c96a)
浏览器缓存分为强缓存和协商缓存,优先读取强制缓存。
当客户端请求某个资源时,获取缓存的流程以下:
协商缓存是利用的是【Last-Modified,If-Modified-Since】和【ETag、If-None-Match】这两对Header来管理的
Last-Modified 表示本地文件最后修改日期,浏览器会在request header加上If-Modified-Since(上次返回的Last-Modified的值),询问服务器在该日期后资源是否有更新,有更新的话就会将新的资源发送回来
可是若是在本地打开缓存文件,就会形成 Last-Modified 被修改,因此在 HTTP / 1.1 出现了 ETag
Etag就像一个指纹,资源变化都会致使ETag变化,跟最后修改时间没有关系,ETag能够保证每个资源是惟一的
If-None-Match的header会将上次返回的Etag发送给服务器,询问该资源的Etag是否有更新,有变更就会发送新的资源回来
ETag的优先级比Last-Modified更高
推荐必读:http协商缓存VS强缓存、浏览器缓存知识小结及应用、缓存(二)——浏览器缓存机制:强缓存、协商缓存
JavaScript中的内存分为栈内存和堆内存。栈内存和堆内存区别:栈内存运行效率比堆内存高,空间相对堆内存来讲较小。
区别:
token和cookie举例,token就是说你告诉我你是谁就能够。
cookie 举例:服务员看你的身份证,给你一个编号,之后,进行任何操做,都出示编号后服务员去看查你是谁。token 举例:直接给服务员看本身身份证,服务器不须要去查看你是谁,不须要保存你的会话。
小结:
Token 彻底由应用管理,因此它能够避开同源策略
Token 能够避免 CSRF 攻击
Token 能够是无状态的,能够在多个服务间共享
若是你的用户数据可能须要和第三方共享,或者容许第三方调用 API 接口,用 Token,若是之上本身的那就无所谓了。
本节参考文章:cookie和token的五点区别
推荐必读:先后端常见的几种鉴权方式
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时,
比较
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的有效时间改为昨天。
添加/修改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');
大小:官方建议是5M存储空间
类型:只能操做字符串,在存储以前应该使用JSON.stringfy()方法先进行一步安全转换字符串,取值时再用JSON.parse()方法再转换一次
存储的内容: 数组,图片,json,样式,脚本。。。(只要是能序列化成字符串的内容均可以存储)
区别:sessionStorage将数据临时存储在session中,浏览器关闭,数据随之消失,localStorage将数据存储在本地,理论上来讲数据永远不会消失,除非人为删除
注意:数据是明文存储,毫无隐私性可言,绝对不能用于存储
保存数据
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 );
能够经过监听 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 对象。
共同点:都是保存在浏览器端、且同源的,都受同源策略的制约。
区别:
本节参考文章:缓存(三)——数据存储...
其余阅读:关于Cookie、session和Web Storage
事件指能够被 JavaScript 侦测到的行为。即鼠标点击、页面或图像载入、鼠标悬浮于页面的某个热点之上、在表单中选取输入框、确认表单、键盘按键等操做。
事件一般与函数配合使用,当事件发生时函数才会执行。
事件名称:click/mouseover/blur("不带on")
事件处理程序(事件侦听器):响应某个事件的函数,名称为:onclick/onmouseove/onblur,例如<button onclick="alert('hello')"></button>
冒泡:往上
捕获:向下
事件流指从页面中接收事件的顺序,也可理解为事件在页面中传播的顺序。
DOM2级事件规定的事件流包括三个阶段:
(1)事件捕获阶段(2)处于目标阶段(3)事件冒泡阶段。
当事件发生时,最早获得通知的是window,而后是document,由上至下逐级依次而入,直到真正触发事件的那个元素(目标元素)为止,这个过程就是捕获。
接下来,事件会从目标元素开始起泡,由下至上逐级依次传播,直到window对象为止,这个过程就是冒泡。
因此捕获比冒泡先执行。
但愿注册在DOM元素上的事件处理程序在捕获阶段仍是在冒泡阶段触发,取决于 addEventListener() 方法的第三个参数为 true 仍是 false 。
其中DOM3级事件在DOM2的基础之上添加了更多的事件类型。
描述DOM事件捕获的具体流程
window-->document-->html(document.documentElement)-->body(document.body)...
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; } } }; ```
事件对象是用来记录一些事件发生时的相关信息的对象,但事件对象只有事件发生时才会产生,而且只能是事件处理函数内部访问,在全部事件处理函数运行结束后,事件对象就被销毁!
//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、捕获和冒泡
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
事件委托就是利用事件冒泡,只指定一个事件处理程序,就能够管理某一类型的全部事件。
案例一:
<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事件流
二者都是外部引用 CSS 的方式,可是存在必定的区别:
本节参考文章:前端面试题-url、href、src
document.write只能重绘整个页面
innerHTML能够重绘页面的一部分
isPrototypeOf() 与 instanceof 运算符不一样。
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
在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属性
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浅复制与深复制
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