5中基本数据类型:null、undefined、string、number、booleanjavascript
《JavaScript高级程序设计》这本书里面,介绍了不少关于setTimeout函数的神奇使用,今天来介绍下第一个——使用setTimeout代替setInterval进行间歇调用。css
“在开发环境下,不多使用间歇调用(setInterval),缘由是后一个间歇调用极可能在前一个间歇调用结束前启动”。html
这话怎么理解呢?前端
首先咱们来看一下通常状况下的setInterval函数的使用,以及如何使用setTimeout代替setIntervaljava
var executeTimes = 0; var intervalTime = 500; var intervalId = null; // 放开下面的注释运行setInterval的Demo intervalId = setInterval(intervalFun,intervalTime); // 放开下面的注释运行setTimeout的Demo // setTimeout(timeOutFun,intervalTime); function intervalFun(){ executeTimes++; console.log("doIntervalFun——"+executeTimes); if(executeTimes==5){ clearInterval(intervalId); } } function timeOutFun(){ executeTimes++; console.log("doTimeOutFun——"+executeTimes); if(executeTimes<5){ setTimeout(arguments.callee,intervalTime); } }
代码比较简单,咱们只是在setTimeout的方法里面又调用了一次setTimeout,就能够达到间歇调用的目的。node
重点来了,为何做者建议咱们使用setTimeout代替setInterval呢?setTimeout式的间歇调用和传统的setInterval间歇调用有什么区别呢?jquery
var executeTimes = 0; var intervalTime = 500; var intervalId = null; var oriTime = new Date().getTime(); // 放开下面的注释运行setInterval的Demo // intervalId = setInterval(intervalFun,intervalTime); // 放开下面的注释运行setTimeout的Demo setTimeout(timeOutFun,intervalTime); function intervalFun(){ executeTimes++; var nowExecuteTimes = executeTimes; var timeDiff = new Date().getTime() - oriTime; console.log("doIntervalFun——"+nowExecuteTimes+", after " + timeDiff + "ms"); var delayParam = 0; sleep(1000); console.log("doIntervalFun——"+nowExecuteTimes+" finish !"); if(executeTimes==5){ clearInterval(intervalId); } } function timeOutFun(){ executeTimes++; var nowExecuteTimes = executeTimes; var timeDiff = new Date().getTime() - oriTime; console.log("doTimeOutFun——"+nowExecuteTimes+", after " + timeDiff + "ms"); var delayParam = 0; sleep(1000); console.log("doTimeOutFun——"+nowExecuteTimes+" finish !"); if(executeTimes<5){ setTimeout(arguments.callee,intervalTime); } } function sleep(sleepTime){ var start=new Date().getTime(); while(true){ if(new Date().getTime()-start>sleepTime){ break; } } }
(这里使用大牛提供的sleep函数来模拟函数运行的时间)css3
执行setInterval的Demo方法,看控制台web
doIntervalFun——1, after 500ms VM2854:19 doIntervalFun——1 finish ! VM2854:16 doIntervalFun——2, after 1503ms VM2854:19 doIntervalFun——2 finish ! VM2854:16 doIntervalFun——3, after 2507ms VM2854:19 doIntervalFun——3 finish ! VM2854:16 doIntervalFun——4, after 3510ms VM2854:19 doIntervalFun——4 finish ! VM2854:16 doIntervalFun——5, after 4512ms VM2854:19 doIntervalFun——5 finish !
能够发现,fun2和fun1开始的间歇接近1000ms,恰好就是fun1的执行时间,也就意味着fun1执行完后fun2立刻就执行了,和咱们间歇调用的初衷背道而驰。ajax
咱们注释掉setInterval的Demo方法,放开setTimeout的Demo方法,运行,查看控制台
doTimeOutFun——1, after 500ms VM2621:32 doTimeOutFun——1 finish ! VM2621:29 doTimeOutFun——2, after 2001ms VM2621:32 doTimeOutFun——2 finish ! VM2621:29 doTimeOutFun——3, after 3503ms VM2621:32 doTimeOutFun——3 finish ! VM2621:29 doTimeOutFun——4, after 5004ms VM2621:32 doTimeOutFun——4 finish ! VM2621:29 doTimeOutFun——5, after 6505ms VM2621:32 doTimeOutFun——5 finish !
这下终于正常了,fun1和fun2相差了1500ms = 1000 + 500,fun2在fun1执行完的500ms后执行。
在js高级教程中的定义是:有权访问另外一个函数做用域中的变量的函数。通俗地讲,若是一个函数执行完之后,这个函数中还存在一部分在内存当中,没有被垃圾回收机制回收,这个函数就称为闭包。
若是咱们写一个函数,里面有一个name值,咱们能够容许任何人访问这个name属性,可是只有少部分人,能够修改这个name属性,咱们就能够使用闭包,能够在setName值中,写哪些人具备修改的权限。
var person = function(){ //变量做用域为函数内部,外部没法访问,不会与外部变量发生重名冲突 var name = "FE"; return { //管理私有变量 getName : function(){ return name; }, setName : function(newName){ name = newName; } } };
假如说咱们执行一个计算量很大函数,返回一个值,而这个值在其余函数中还有应用,这种状况下使用闭包,能够将该数据保存在内存中,供其余的函数使用(这是在其余博客中看到的,具体不是很清楚,若是有兴趣,能够本身查阅相关文献)。
形成内存消耗过大,若是处理不当,会形成内存泄漏
浏览器的一些事件,如:resize,scroll,keydown,keyup,keypress,mousemove等。这些事件触发频率太过频繁,绑定在这些事件上的回调函数会不停的被调用。这样浏览器的目的是为了保证信息的一致性,而对于咱们来讲就是一种资源的浪费了。
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>test</title> </head> <style> #mydiv{ border-bottom: 1px solid; width: 100%; height: 300px; background-color: #999999; } .mouseMoveDisplay{ border-top: 1px solid rosybrown; width: 100%; height: 200px; background-color: #bbbbbb; } </style> <body> <div id="mydiv"></div> <table class="mouseMoveDisplay"> <tr> <th> 一共移动了<span class="mouseMove all">0</span>次 </th> </tr> <tr> <th> 防抖移动了<span class="mouseMove debounce">0</span>次 </th> </tr> <tr> <th> 节流移动了<span class="mouseMove throttle">0</span>次 </th> </tr> </table> <script> //防抖模式 function debounceFunction(fn,delay){ var delay=delay||200; var timer; return function(){ var th=this; var args=arguments; if (timer) { clearTimeout(timer); } timer=setTimeout(function () { timer=null; fn.apply(th,args); }, delay); }; } //事件节流 function throttleFunction(fn,interval){ var last; var timer; var interval=interval||200; return function(){ var th=this; var args=arguments; var now=+new Date(); if(last&&now-last<interval){ clearTimeout(timer); timer=setTimeout(function(){ last=now; fn.apply(th,args); },interval); }else{ last=now; fn.apply(th,args); } } } var mydiv=document.getElementById("mydiv"); //全部的移动次数 var allCount = 0;//全部的次数 var all = document.getElementsByClassName('all')[0]; mydiv.addEventListener("mousemove",function () { allCount++; all.innerHTML = allCount; }) //防抖的移动次数 var debounceCount = 0;//全部的次数 var debounce = document.getElementsByClassName('debounce')[0]; mydiv.addEventListener("mousemove",debounceFunction(function () { debounceCount++; debounce.innerHTML = debounceCount; })) //节流的移动次数 var throttleCount = 0;//全部的次数 var throttle = document.getElementsByClassName('throttle')[0]; mydiv.addEventListener("mousemove",throttleFunction(function () { throttleCount++; throttle.innerHTML = throttleCount; })) </script> </body> </html>
大多数状况下,咱们都要对数组进行遍历,而后常常用到的两个方法就是forEach和map方法。
先来讲说它们的共同点
==map方法==
1.map方法返回一个新的数组,数组中的元素为原始数组调用函数处理后的值。
2.map方法不会对空数组进行检测,map方法不会改变原始数组。
3.浏览器支持:chrome、Safari1.5+、opera都支持,IE9+,
array.map(function(item,index,arr){},thisValue) var arr = [0,2,4,6,8]; var str = arr.map(function(item,index,arr){ console.log(this); //window console.log("原数组arr:",arr); //注意这里执行5次 return item/2; },this); console.log(str);//[0,1,2,3,4]
若arr为空数组,则map方法返回的也是一个空数组。
==forEach方法==
1.forEach方法用来调用数组的每一个元素,将元素传给回调函数
2.forEach对于空数组是不会调用回调函数的。
Array.forEach(function(item,index,arr){},this) var arr = [0,2,4,6,8]; var sum = 0; var str = arr.forEach(function(item,index,arr){ sum += item; console.log("sum的值为:",sum); //0 2 6 12 20 console.log(this); //window },this) console.log(sum);//20 console.log(str); //undefined
不管arr是否是空数组,forEach返回的都是undefined。这个方法只是将数组中的每一项做为callback的参数执行一次。
遍历数组一般使用for循环,ES5的话也能够使用forEach,ES5具备遍历数组功能的还有map、filter、some、every、reduce、reduceRight等,只不过他们的返回结果不同。可是使用foreach遍历数组的话,使用break不能中断循环,使用return也不能返回到外层函数。
Array.prototype.method=function(){ console.log(this.length); } var myArray=[1,2,4,5,6,7] myArray.name="数组" for (var index in myArray) { console.log(myArray[index]); }
1.index索引为字符串型数字,不能直接进行几何运算
2.遍历顺序有可能不是按照实际数组的内部顺序
3.使用for in会遍历数组全部的可枚举属性,包括原型。例如上栗的原型方法method和name属性
因此for in更适合遍历对象,不要使用for in遍历数组。
那么除了使用for循环,如何更简单的正确的遍历数组达到咱们的指望呢(即不遍历method和name),ES6中的for of更胜一筹.
Array.prototype.method=function(){ console.log(this.length); } var myArray=[1,2,4,5,6,7] myArray.name="数组"; for (var value of myArray) { console.log(value); }
记住,for in遍历的是数组的索引(即键名),而for of遍历的是数组元素值。
for of遍历的只是数组内的元素,而不包括数组的原型属性method和索引name
遍历对象 一般用for in来遍历对象的键名
Object.prototype.method=function(){ console.log(this); } var myObject={ a:1, b:2, c:3 } for (var key in myObject) { console.log(key); }
for in 能够遍历到myObject的原型方法method,若是不想遍历原型方法和属性的话,能够在循环内部判断一下,==hasOwnPropery==方法能够判断某属性是不是该对象的实例属性
for (var key in myObject) { if(myObject.hasOwnProperty(key)){ console.log(key); } }
一样能够经过ES5的Object.keys(myObject)获取对象的实例属性组成的数组,不包括原型方法和属性。
Object.prototype.method=function(){ console.log(this); } var myObject={ a:1, b:2, c:3 } Object.keys(myObject).forEach(function(key,index){ console.log(key,myObject[key]) })
class EventEmitter { constructor() { this.events = {}; } on(eventName, fn) { let fnList = this.events[eventName] || []; fnList.push(fn) if (eventName) { this.events[eventName] = fnList; } } emit(eventName, ...agr) { let funcs = this.events[eventName]; if (funcs && funcs.length) { for (let j = 0; j < funcs.length; j++) { funcs[j](...agr); } } } off(eventName, fn) { let funcs = this.events[eventName]; if (fn) { this.events[eventName].splice(fn, 1); } else { delete this.events[eventName] } } }
第一个就是做用域的问题,var不是针对一个块级做用域,而是针对一个函数做用域。举个例子:
function runTowerExperiment(tower, startTime) { var t = startTime; tower.on("tick", function () { ... code that uses t ... }); ... more code ... }
这样是没什么问题的,由于回调函数中能够访问到变量t,可是若是咱们在回调函数中再次命名了变量t呢?
function runTowerExperiment(tower, startTime) { var t = startTime; tower.on("tick", function () { ... code that uses t ... if (bowlingBall.altitude() <= 0) { var t = readTachymeter(); ... } }); ... more code ... }
后者就会将前者覆盖。
第二个就是循环的问题。
看下面例子:
var messages = ["Meow!", "I'm a talking cat!", "Callbacks are fun!"]; for (var i = 0; i < messages.length; i++) { setTimeout(function () { document.write(messages[i]); },i*1500); }
输出结果是:undefined
由于for循环后,i置为3,因此访问不到其值。
为了解决这些问题,ES6提出了let语法。let能够在{},if,for里声明,其用法同var,可是做用域限定在块级。可是javascript中不是没有块级做用域吗?这个咱们等会讲。还有一点很重要的就是let定义的变量==不存在变量提高==。
变量提高
这里简单提一下什么叫作变量提高。
var v='Hello World'; (function(){ alert(v); var v='I love you'; })()
上面的代码输出结果为:undefined。
为何会这样呢?这就是由于变量提高,变量提高就是把变量的声明提高到函数顶部,好比:
(function(){ var a='One'; var b='Two'; var c='Three'; })()
实际上就是:
(function(){ var a,b,c; a='One'; b='Two'; c='Three'; })()
因此咱们刚才的例子其实是:
var v='Hello World'; (function(){ var v; alert(v); v='I love you'; })()
因此就会返回undefined啦。
这也是var的一个问题,而咱们使用let就不会出现这个问题。由于它会报语法错误:
{ console.log( a ); // undefined console.log( b ); // ReferenceError! var a; let b; }
再来看看let的块级做用域。
function getVal(boo) { if (boo) { var val = 'red' // ... return val } else { // 这里能够访问 val return null } // 这里也能够访问 val }
而使用let后:
function getVal(boo) { if (boo) { let val = 'red' // ... return val } else { // 这里访问不到 val return null } // 这里也访问不到 val }
一样的在for循环中:
function func(arr) { for (var i = 0; i < arr.length; i++) { // i ... } // 这里访问获得i }
使用let后:
function func(arr) { for (let i = 0; i < arr.length; i++) { // i ... } // 这里访问不到i }
也就是说,==let只能在花括号内部起做用==。
再来讲说const,==const表明一个值的常量索引==。
const aa = 11; alert(aa) //11 aa = 22; alert(aa) //11
可是常量的值在垃圾回收前永远不能改变,因此须要谨慎使用。
还有一条须要注意的就是和其余语言同样,==常量的声明必须赋予初值==。即便咱们想要一个undefined的常量,也须要声明:
const a = undefined;
块级做用域
最后提一下刚才说到的块级做用域。
在以前,javascript是没有块级做用域的,咱们都是经过()来模拟块级做用域。
(function(){ //这里是块级做用域 })();
可是在ES6中,{}就能够直接代码块级做用域。因此{}内的内容是不能够在{}外访问获得的。
咱们能够看看以下代码:
if (true) { function foo() { document.write( "1" ); } } else { function foo() { document.write( "2" ); } } foo(); // 2
在咱们所认识的javascript里,这段代码的输出结果为2。这个叫作函数声明提高,不只仅提高了函数名,也提高了函数的定义。若是你基础不扎实的话,能够看看这篇文章:深刻理解javascript之IIFE
可是在ES6里,这段代码或抛出ReferenceErroe错误。由于{}的块级做用域,致使外面访问不到foo(),也就是说函数声明和let定义变量同样,都被限制在块级做用域中了。
从promise、process.nextTick、setTimeout出发,谈谈Event Loop中的Job queue
简要介绍:谈谈promise.resove,setTimeout,setImmediate,process.nextTick在EvenLoop队列中的执行顺序
event loop都不陌生,是指主线程从“==任务队列==”中循环读取任务,好比
例1:
setTimeout(function(){console.log(1)},0); console.log(2) //输出2,1
在上述的例子中,咱们明白首先执行主线程中的同步任务,当主线程任务执行完毕后,再从event loop中读取任务,所以先输出2,再输出1。
event loop读取任务的前后顺序,取决于任务队列(Job queue)中对于不一样任务读取规则的限定。好比下面一个例子:
例2:
setTimeout(function () { console.log(3); }, 0); Promise.resolve().then(function () { console.log(2); }); console.log(1); //输出为 1 2 3
先输出1,没有问题,由于是同步任务在主线程中优先执行,这里的问题是setTimeout和Promise.then任务的执行优先级是如何定义的。
在Job queue中的队列分为两种类型:==macro-task和microTask==。咱们举例来看执行顺序的规定,咱们设
macro-task队列包含任务: a1, a2 , a3
micro-task队列包含任务: b1, b2 , b3
执行顺序为,首先执行marco-task队列开头的任务,也就是 a1 任务,执行完毕后,在执行micro-task队列里的全部任务,也就是依次执行b1, b2 , b3,执行完后清空micro-task中的任务,接着执行marco-task中的第二个任务,依次循环。
了解完了macro-task和micro-task两种队列的执行顺序以后,咱们接着来看,真实场景下这两种类型的队列里真正包含的任务(咱们以node V8引擎为例),在node V8中,这两种类型的真实任务顺序以下所示:
script(主程序代码),setTimeout, setInterval, setImmediate, I/O, UI rendering
process.nextTick, Promises, Object.observe, MutationObserver
由此咱们获得的执行顺序应该为:
==script(主程序代码)—>process.nextTick—>Promises…——>setTimeout——>setInterval——>setImmediate——> I/O——>UI rendering==
在ES6中macro-task队列又称为ScriptJobs,而micro-task又称PromiseJobs
3 . 真实环境中执行顺序的举例
(1) setTimeout和promise
例3:
setTimeout(function () { console.log(3); }, 0); Promise.resolve().then(function () { console.log(2); }); console.log(1);
咱们先以第1小节的例子为例,这里遵循的顺序为:
script(主程序代码)——>promise——>setTimeout 对应的输出依次为:1 ——>2————>3
(2) process.nextTick和promise、setTimeout
例子4:
setTimeout(function(){console.log(1)},0); new Promise(function(resolve,reject){ console.log(2); resolve(); }).then(function(){console.log(3) }).then(function(){console.log(4)}); process.nextTick(function(){console.log(5)}); console.log(6); //输出2,6,5,3,4,1
这个例子就比较复杂了,这里要注意的一点在定义promise的时候,promise构造部分是同步执行的,这样问题就迎刃而解了。
首先分析Job queue的执行顺序:
==script(主程序代码)——>process.nextTick——>promise——>setTimeout==
I) 主体部分: 定义promise的构造部分是同步的,
所以先输出2 ,主体部分再输出6(同步状况下,就是严格按照定义的前后顺序)
II)process.nextTick: 输出5
III)promise: 这里的promise部分,严格的说实际上是promise.then部分,输出的是3,4
IV) setTimeout : 最后输出1
综合的执行顺序就是: 2——>6——>5——>3——>4——>1
(3)更复杂的例子
setTimeout(function(){console.log(1)},0); new Promise(function(resolve,reject){ console.log(2); setTimeout(function(){resolve()},0) }).then(function(){console.log(3) }).then(function(){console.log(4)}); process.nextTick(function(){console.log(5)}); console.log(6); //输出的是 2 6 5 1 3 4
种状况跟咱们(2)中的例子,区别在于promise的构造中,没有同步的resolve,所以promise.then在当前的执行队列中是不存在的,只有promise从pending转移到resolve,才会有then方法,而这个resolve是在一个setTimout时间中完成的,所以3,4最后输出。
代码实现
既然懒加载的原理是基于判断元素是否出如今窗口可视范围内,首先咱们写一个函数判断元素是否出如今可视范围内:
function isVisible($node){ var winH = $(window).height(), scrollTop = $(window).scrollTop(), offSetTop = $(window).offSet().top; if (offSetTop < winH + scrollTop) { return true; } else { return false; } }
再添加上浏览器的事件监听函数,让浏览器每次滚动就检查元素是否出如今窗口可视范围内:
$(window).on("scroll", function{ if (isVisible($node)){ console.log(true); } })
咱们已经很接近了,如今咱们要作的是,让元素只在第一次被检查到时打印true,以后就再也不打印了
var hasShowed = false; $(window).on("sroll",function{ if (hasShowed) { return; } else { if (isVisible($node)) { hasShowed = !hasShowed; console.log(true); } } })
咦,咱们好像已经实现了懒加载。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> <style> img{ display: block; max-height: 300px; } </style> </head> <body> <div class="container"> <h1>懒加载页面</h1> <img src="1.png" data-src='1.jpg' alt=""> <img src="1.png" data-src='2.jpg' alt=""> <img src="1.png" data-src='3.jpg' alt=""> <img src="1.png" data-src='4.jpg' alt=""> <img src="1.png" data-src='5.jpg' alt=""> <img src="1.png" data-src='6.jpg' alt=""> <img src="1.png" data-src='7.jpg' alt=""> <img src="1.png" data-src='8.jpg' alt=""> <img src="1.png" data-src='9.jpg' alt=""> </div> </body> </html> <script> var scrollTop = window.scrollY; var imgs = Array.from(document.querySelectorAll('img')); lazyLoad(); window.onscroll = () => { scrollTop = window.scrollY; lazyLoad(); } function lazyLoad(){ imgs.forEach((item,index)=>{ if( item.offsetTop < window.innerHeight + scrollTop ){ console.log(item.offsetTop) item.setAttribute('src',item.dataset.src) } }) } </script>
这里有坑请注意!!! 并且这个问题很差百度 - . -
若是复制上面的代码,首次加载进页面发现全部图片均已经加载完毕,没有实现懒加载的效果
由于函数调用时img.onload没有完成,img元素没有高度!!!
解决办法是在外层套一个window.onload
window.onload = function(){ lazyLoad(); }
推荐!!!高频滚动模式下, 每隔一段时间才会实现渲染~~
实现原理是 加入一个开关变量, 控制每隔固定的一段时间,函数才可能被触发~
window.onload = function(){ var scrollTop = window.scrollY; var imgs = Array.from(document.querySelectorAll('img')); lazyLoad(); //函数节流模式 var canRun = true; window.onscroll = () => { if( !canRun ){ return } canRun = false; setTimeout(()=>{ scrollTop = window.scrollY; lazyLoad(); canRun = true; },1000) } function lazyLoad(){ imgs.forEach((item,index)=>{ if( item.offsetTop < window.innerHeight + scrollTop ){ console.log(item.offsetTop) item.setAttribute('src',item.dataset.src) } }) } }
为了逻辑清晰 , 打包成函数调用:
window.onload = function(){ var scrollTop = window.scrollY; var imgs = Array.from(document.querySelectorAll('img')); lazyLoad(); let canRun = true;//开关变量用于函数节流 window.addEventListener('scroll',throttle(lazyLoad,500)); //定义懒加载函数 , 从上到下懒加载 , 从下到上也是懒加载 function lazyLoad(){ imgs.forEach((item,index)=>{ if( scrollTop===0 && item.dataset.src !== '' && item.offsetTop < window.innerHeight + scrollTop ){ alert() item.setAttribute('src',item.dataset.src) item.setAttribute('data-src','') }else if( item.dataset.src !== '' && item.offsetTop < window.innerHeight + scrollTop && item.offsetTop > scrollTop ){ item.setAttribute('src',item.dataset.src) item.setAttribute('data-src','') } }) } //定义函数节流函数 function throttle(fun,delay){ return function(){ // fun(); if( !canRun ){ return } console.log('!!!') canRun = false; setTimeout(()=>{ scrollTop = window.scrollY; fun(imgs); canRun = true },delay) } } }
原理是设置clearTimeout和setTimeout,的dalayTime控制一个事件若是频繁触发,将只会执行最近的一次… 能够用在用户注册时候的手机号码验证和邮箱验证。只有等用户输入完毕后,前端才须要检查格式是否正确,若是不正确,再弹出提示语。如下仍是以页面元素滚动监听的例子
效果:一直滑动的时候,将不会有图片加载, 停下后300ms会加载
window.onload = function(){ var scrollTop = window.scrollY; var imgs = Array.from(document.querySelectorAll('img')); lazyLoad(); //函数防抖模式 var timer = null; window.onscroll = () => { clearTimeout(timer); timer = setTimeout(()=>{ scrollTop = window.scrollY; lazyLoad(); },300) } function lazyLoad(){ imgs.forEach((item,index)=>{ if( item.offsetTop < window.innerHeight + scrollTop ){ console.log(item.offsetTop) item.setAttribute('src',item.dataset.src) } }) } }
完美懒加载
注意点: 在滚动条下拉状态下刷新页面, 页面实现更新渲染以后会立马触发滚动条事件,回到上一次页面的停留点,可是并非从scrollTop为0的位置出发~
window.onload = function(){ var scrollTop = window.scrollY; var imgs = Array.from(document.querySelectorAll('img')); lazyLoad(); // 采用了节流函数 window.addEventListener('scroll',throttle(lazyLoad,500,1000)); function throttle(fun, delay, time) { var timeout, startTime = new Date(); return function() { var context = this, args = arguments, curTime = new Date(); clearTimeout(timeout); // 若是达到了规定的触发时间间隔,触发 handler console.log(curTime - startTime) if (curTime - startTime >= time) { fun(); startTime = curTime; // 没达到触发间隔,从新设定定时器 } else { timeout = setTimeout(fun, delay); } }; }; // 实际想绑定在 scroll 事件上的 handler // 须要访问到imgs , scroll function lazyLoad(){ scrollTop = window.scrollY; imgs.forEach((item,index)=>{ if( scrollTop===0 && item.dataset.src !== '' && item.offsetTop < window.innerHeight + scrollTop ){ // alert() item.setAttribute('src',item.dataset.src) item.setAttribute('data-src','') }else if( item.dataset.src !== '' && item.offsetTop < window.innerHeight + scrollTop && item.offsetTop > scrollTop ){ item.setAttribute('src',item.dataset.src) item.setAttribute('data-src','') } }) } }
ECMAScript是松散类型的,一次须要一种手段来检测给定变量的数据类型,typeof操做符(注意不是函数哈!)就是负责提供这方面信息的,
typeof variable
示例:
console.log(typeof 'hello'); // "string" console.log(typeof null); // "object" console.log(typeof (new Object())); // "object" console.log(typeof(function(){})); // "function"
typeof主要用于检测基本数据类型:数值、字符串、布尔值、undefined, 由于typeof用于检测引用类型值时,==对于任何Object对象实例(包括null),typeof都返回"object"值,没办法区分是那种对象,对实际编码用处不大。==
在检测基本数据类型时typeof是很是得力的助手,但在检测引用类型的值时,这个操做符的用处不大,一般,咱们并非想知道某个值是对象,而是想知道它是什么类型的对象。此时咱们能够使用ECMAScript提供的instanceof操做符。
result = variable instanceof constructor
返回布尔类型值:
示例:
function Person(){} function Animal(){} var person1 = new Person(); var animal1 = new Animal(); console.log(person1 instanceof Person); // true console.log(animal1 instanceof Person); // false console.log(animal1 instanceof Object); // true console.log(1 instanceof Person); //false var oStr = new String("hello world"); console.log(typeof(oStr)); // object console.log(oStr instanceof String); console.log(oStr instanceof Object); // 判断 foo 是不是 Foo 类的实例 function Foo(){} var foo = new Foo(); console.log(foo instanceof Foo); // instanceof 在继承中关系中的用法 console.log('instanceof 在继承中关系中的用法'); function Aoo(){} function Foo(){} Foo.prototype = new Aoo(); var fo = new Foo(); console.log(fo instanceof Foo); console.log(fo instanceof Aoo)
根据规定,全部引用类型的值都是Object的实例。所以,在检测一个引用类型值和Object构造函数时,instanceof操做符会始终返回true。若是使用instanceof 操做符检测基本类型值时,该操做符会始终返回false,由于基本类型不是对象。
console.log(Object.prototype.toString.call(null)); // [object Null] undefined console.log(Object.prototype.toString.call([1,2,3])); //[object Array] undefined console.log(Object.prototype.toString.call({})); // [object Object]
参考js高级教程
push、pop、shift、unshift、splice、join、reverse、sort、slice、map every some fliter forEach、reduce.....
做为最经常使用的类型,JavaScript中的数组仍是和其余语言中有很大的区别的。
主要体如今两点:
首先来介绍建立数组的两种方法
var arr1 = new Array(); var arr2 = new Array(3); var arr3 = new Array('jerry');
能够看到这种方式创建数组,arr1是一个空数组,arr2是一个长度为3的数组,arr3是一个包含‘jerry’一个元素的数组。同时经过这种方式建立的数组,new操做符能够省略。
var a = []; var arr = ['tom','jack']
数组的长度是可动态调整,致使咱们直接就能够设置它的长度
var a = [123,423]; a.length = 10; a[9]='123'; console.log(a[8])//undefined a[10] = '123' console.log(a.length)//10
从上面的代码中咱们能够看出:
pop和push很简单,也很容易理解。pop就是从数组的末尾删除一个元素并返回。push是在数组的末尾添加一个元素。
var arr = [1,3,4]; arr.pop(); console.log(arr);//[1,3] arr.push(5); console.log(arr);//[1,3,5]
shift和unshift是和栈方法是相对的,它俩是从数组的头部进行操做。shift是从头部删除一个元素,unshift是从同步加入一个元素。
var arr = [1,3,4]; arr.shift(); console.log(arr);//[3,4] arr.unshift(5); console.log(arr);//[5,3,4]
reverse是对数组进行翻转。
var arr = [1,3,4]; arr.reverse(); console.log(arr);//[4,3,1]
sort是对数组进行排序。
var arr = [1,3,5,4]; arr.sort(); console.log(arr);//[1,3,4,5];
sort默认的对数组进行升序排序。sort能够接收一个自定义的比较函数,自定义排序规则。
sort方法会调用每一个元素的toString()方法,从而经过字符串进行比较大小。即便是数值,依然要变换成字符串,从而就会带来一些问题。好比
var arr = [1,3,15,4]; arr.sort() console.log(arr);//[1,15,3,4];
转换为字符串以后,‘15’是排在‘3’,‘4’的前面的。这就带来了问题,因此在进行数值数组的排序,必须进行自定义排序规则。
var arr = [1,3,15,4]; function compare(v1,v2){ if(v1 > v2) return 1; if(v1 < v2) return -1; return 0; } arr.sort(compare) console.log(arr);//[1,3,4,15]
splice方法能够说是数组中功能最强大的方法,集多项功能于一身。主要的用途就是用来向数组的中部插入元素。
splice方法主要有三种用法。
splice的返回值为删除的元素组成的数组。若是删除的元素为空,返回空数组。
splice(index,count),index表示删除的位置,count表示删除的项数。
var arr = [1,3,4]; console.log(arr.splice(2,1));//[4] //删除元素 console.log(arr);[1,3];
splice(index,0,element,....)
index 表示要插入的位置,0表明删除0个元素,element要插入的元素,若是要插入多个元素,能够继续添加。
var arr = [1,3,4]; console.log(arr.splice(2,0,'tom'));//[ ] console.log(arr);//[1,3,'tom',4]
若是index的值大于数组自己的长度,那么就在最后位置添加。且数组的长度只会加1.
var arr = [1,3,4]; console.log(arr.splice(5,0,'tom'));//[ ] console.log(arr);//[1,3,4,'tom'] console.log(arr.length);//4
若是index的值为负数,那么就从(arr.length+index)位置开始插入,若是(arr.length+index)的值小于0,那么就从数组的开始位置进行插入。
var arr = [1,3,4,4,7,6]; console.log(arr.splice(-1,0,'tom'));//[ ] console.log(arr);//[1,3,4,4,7,'tom',6] console.log(arr.length);//7 console.log(arr.splice(-7,0,'tom'));//[ ] console.log(arr);//['tom',1,3,4,4,7,'tom',6] console.log(arr.length);//8 console.log(arr.splice(-10,0,'jack'));//[ ] console.log(arr);//['jack','tom',1,3,4,4,7,'tom',6] console.log(arr.length);//9
splice(index,count,element,....).index表明替换开始的位置,count > 0,element表示要替换成的元素。其实替换过程包含两个过程:1.删除. 2插入.也就是上面的两个过程的融合。
var arr = [1,3,4]; console.log(arr.splice(1,1,'tom'));//[3] console.log(arr);//[1,'tom',4]
若是index大于数组的长度,或者小于0,处理的结果同上面插入元素处理的方式同样。
join方法主要是用来将数组的元素经过规定的方式链接成字符串。
var arr = [1,3,4,5]; console.log(arr.join(','))//1,3,4,5 console.log(arr.join('+'))//1+3+4+5 console.log(arr.join('?'))//1?3?4?5 console.log(arr)//[1,3,4,5]
slice和concat方法。
slice方法主要用来返回指定位置的数组的子数组。slice(start,end)。end省略,返回的是从开始位置到数组的末尾。end不省略,返回的是从start到end之间的子数组,包括start位置但不包括end位置的数组。
var arr = [1,3,4,5]; console.log(arr.slice(1));//[3,4,5] console.log(arr.slice(1,2));//[3]
若是slice方法的参数中有一个负数,则用数组长度加上该数来肯定相应的位置。例如在一个长度为5的数组上调用slice(-2,-1)与调用slice(3,4)获得的结果相同。若是结束位置小于起始位置,则返回空数组。
concat 方法,主要是链接多个数组。
var arr = [1,3,4,5]; var testArr = [1,23,4]; console.log(arr.concat(testArr));//[1,3,4,5,1,23,4] console.log(arr.concat('tom'));//[1,3,4,5,'tom']
ES5新增长的迭代方法主要包括以下几种
map
every
some
fliter
forEach
这几个方法有一下共同点,都接收两个参数,一个是要在数组上每一项运行的函数,一个是运行该函数做用域的对象,改变this的指向(可选)。其中函数须要传入三个参数,一个是每一个元素的值,每一个元素的index,数组自己。
function(value,index,array) { }
下面一个一个的来介绍
map返回数组中每个数组元素通过传入的函数处理后组成的新数组
var arr = [1,3,4]; var newArr = arr.map(function(value,index,array){ return value*2; }) console.log(newArr);//[2,6,8] console.log(arr);//[1,3,4]
some和every比较相像。some是对每个数组中的元素运行传入的函数,若是有一个返回true,那么就返回true;every是对每个数组中的元素运行传入的函数,若是全部的都返回true,那么就返回true。
var arr = [1,3,4]; var result1 = arr.some(function(value,index,array){ return value > 2; }) var result2 = arr.every(function(value,index,array){ return value > 2; }) console.log(result1);// true console.log(result2);// false
从名字能够看出,这是一个过滤的方法,返回的一个数组,这个数组是知足传入的参数的函数的元素所组成的。
var arr = [1,3,4]; var result = arr.filter(function(value,index,array){ return value > 2; }) console.log(result);// [3,4]
forEach主要用来遍历,遍历数组中每个元素,对其进行操做。该方法没有返回值。
var arr = [1,3,4]; arr.forEach(function(value,index,array){ console.log('arr['+index+']='+value); }) // 结果 arr[0]=1 arr[1]=3 arr[2]=4
reduce和reduceRight.这两个方法接收两个参数,一个是每项都运行的函数,一个是缩小基础的初始值(可选)。reduce和reduceRight返回的是一个值。其中每项都运行的函数包含四个参数,
funciton(prev,cur,index,array){ }
下面经过一个例子就能够说明这个函数是干吗的。
var arr = [1,3,4]; var result = arr.reduce(function(prev,cur,index,array){ return prev+cur; },10); console.log(result)//18 var result1 = arr.reduce(function(prev,cur,index,array){ return prev+cur; }); console.log(result1)//8
array.reduce(function(total, currentValue, currentIndex, arr), initialValue)
参数 描述 function(total,currentValue, index,arr) 必需。用于执行每一个数组元素的函数。 total 必需。初始值, 或者计算结束后的返回值。 currentValue 必需。当前元素 currentIndex 可选。当前元素的索引 arr 可选。当前元素所属的数组对象。 initialValue 可选。传递给函数的初始值
var arr = [11,22,33,44,55,66]; var arr1 = arr.reduce(function(total, currentValue, currentIndex, arr){ return total+"-"+currentValue; },"00"); console.log(arr1);//00-11-22-33-44-55-66
var arr = [11,22,33,44,55,66]; var arr1 = arr.reduceRight(function(total, currentValue, currentIndex, arr){ return total+"-"+currentValue; },"00"); console.log(arr1);//00-66-55-44-33-22-11
reduceRight和reduce同样,无非他开始的位置是从数组的后面。
这两个主要是用来判断元素在数组中的位置,未找到返回-1,接收两个参数,indexOf(searchElement[, fromIndex]),lastIndexOf(searchElement[, fromIndex])。fromIndex可选。其中formIndex也能够指定字符串。
var arr = [1,3,4,4,1,5,1]; var value = arr.indexOf(1) console.log(value)//0 value = arr.indexOf(1,4) console.log(value)//4 value = arr.indexOf(1,5) console.log(value)//6 value = arr.lastIndexOf(1) console.log(value)//6 value = arr.lastIndexOf(1,3) console.log(value)//0
这三个方法是全部对象都具备的方法。
toString()返回的是一个字符串,toLocaleString同它相似。valueOf()返回的是一个数组
var arr= [1,3,4] console.log(arr.toString());//1,3,4 console.log(arr.valueOf());//[1,3,4] console.log(arr.toLocaleString());//1,3,4
能够复写toString(),toLocaleString()返回不一样的结果。
(举个栗子哈 ==caller给你打电话的人 == 谁给你打电话了 谁调用了你 很显然是下面a函数的执行 只有在打电话的时候你才能知道打电话的人是谁 因此对于函数来讲 只有caller在函数执行的时候才存在)
var callerTest = function() { console.log(callerTest.caller) ; } ; function a() { callerTest() ; } a() ;//输出function a() {callerTest();} callerTest() ;//输出null
callee是arguments对象的一个成员 表示对函数对象自己的引用 它有==个length属性(表明形参的长度)==
var c = function(x,y) { console.log(arguments.length,arguments.callee.length,arguments.callee) } ; c(1,2,3) ;//输出3 2 function(x,y) {console.log(arguments.length,arguments.callee.length,arguments.callee)}
一、建立一个新对象;[var o = new Object();]
二、将构造函数的做用域赋给新对象(所以this指向了这个新对象);
三、执行构造函数中的代码(为这个新对象添加属性);
四、返回新对象。
误区:咱们常常说get请求参数的大小存在限制,而post请求的参数大小是无限制的。
实际上HTTP 协议从未规定 GET/POST 的请求长度限制是多少。对get请求参数的限制是来源与浏览器或web服务器,浏览器或web服务器限制了url的长度。为了明确这个概念,咱们必须再次强调下面几点:
HTTP 协议 未规定 GET 和POST的长度限制
GET的最大长度显示是由于 浏览器和 web服务器限制了 URI的长度
不一样的浏览器和WEB服务器,限制的最大长度不同
要支持IE,则最大长度为2083byte,若只支持Chrome,则最大长度 8182byte
补充补充一个get和post在缓存方面的区别:
HTTP 定义了与服务器交互的不一样方法,最经常使用的有4种,Get、Post、Put、Delete,若是我换一下顺序就好记了,Put(增),Delete(删),Post(改),Get(查),即增删改查,下面简单叙述一下:
1) GET请求的数据是放在HTTP包头中的,也就是URL以后,一般是像下面这样定义格式的,(而Post是把提交的数据放在HTTP正文中的)。
login.action?name=hyddd&password=idontknow&verify=%E4%BD%E5%A5%BD
2)GET提交的数据比较少,最多1024B,由于GET数据是附在URL以后的,而URL则会受到不一样环境的限制的,好比说IE对其限制为2K+35,而POST能够传送更多的数据(理论上是没有限制的,但通常也会受不一样的环境,如浏览器、操做系统、服务器处理能力等限制,IIS4可支持80KB,IIS5可支持100KB)。
3)Post的安全性要比Get高,由于Get时,参数数据是明文传输的,并且使用GET的话,还可能形成Cross-site request forgery攻击。而POST数据则能够加密的,但GET的速度可能会快些。
因此综上几点,总结成下表:
操做方式 | 数据位置 | 明文密文 | 数据安全 | 长度限制 | 应用场景 |
---|---|---|---|---|---|
GET | http包头 | 明文 | 不安全 | 长度较小 | 查询数据 |
POST | http正文 | 可明可密 | 安全 | 支持较大的数据传输 | 修改数据 |
经典的js问题 实现点击li可以弹出当前li索引与innerHTML的函数
按照咱们日常的想法,代码应该是这样写的:
var myul = document.getElementsByTagName("ul")[0]; var list = myul.getElementsByTagName("li"); function foo(){ for(var i = 0, len = list.length; i < len; i++){ list[i].onclick = function(){ alert(i + "----" + this.innerHTML); } } } foo();
可是不巧的是产生的结果是这样的:
索引index为何老是4呢,这是js中没有块级做用域致使的。这里有三种解决思路
<script type="text/javascript"> var myul = document.getElementsByTagName("ul")[0]; var list = myul.getElementsByTagName("li"); function foo(){ for(var i = 0, len = list.length; i < len; i++){ var that = list[i]; list[i].onclick = (function(k){ var info = that.innerHTML; return function(){ alert(k + "----" + info); }; })(i); } } foo(); </script>
2.使用ES6中的新特性let来声明变量
用let来声明的变量将具备块级做用域,很明显能够达到要求,不过须要注意的是得加个'use strict'(使用严格模式)才会生效
<script type="text/javascript"> var myul = document.getElementsByTagName("ul")[0]; var list = myul.getElementsByTagName("li"); function foo(){'use strict' for(let i = 0, len = list.length; i < len; i++){ list[i].onclick = function(){ alert(i + "----" + this.innerHTML); } } } foo(); </script>
3.事件委托
<script type="text/javascript"> var myul = document.querySelector('ul'); var list = document.querySelectorAll('ul li'); myul.addEventListener('click', function(ev){ var ev = ev || window.event; var target = ev.target || ev.srcElemnt; for(var i = 0, len = list.length; i < len; i++){ if(list[i] == target){ alert(i + "----" + target.innerHTML); } } }); </script>
4.引入jquery,使用其中的on或delegate进行事件绑定(它们都有事件代理的特性)
<script type="text/javascript" src="jquery-1.8.2.min.js"></script>
<script type="text/javascript"> $("ul").delegate("li", "click", function(){ var index = $(this).index(); var info = $(this).html(); alert(index + "----" + info); }); </script> <script type="text/javascript"> $("ul").on("click", "li", function(){ var index = $(this).index(); var info = $(this).html(); alert(index + "----" + info); }); </script>
JS中添加事件 兼容各类环境
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] = handler; } } }
首先必需要说的是,this的指向在函数定义的时候是肯定不了的,只有函数执行的时候才能肯定this到底指向谁,实际上this的最终指向的是==那个调用它的对象==(这句话有些问题,后面会解释为何会有问题,虽然网上大部分的文章都是这样说的,虽然在不少状况下那样去理解不会出什么问题,可是实际上那样理解是不许确的,因此在你理解this的时候会有种琢磨不透的感受),那么接下来我会深刻的探讨这个问题。
例子1:
function a(){ var user = "追梦子"; console.log(this.user); //undefined console.log(this); //Window } a();
按照咱们上面说的this最终指向的是调用它的对象,这里的函数a实际是被Window对象所点出来的,下面的代码就能够证实。
function a(){ var user = "追梦子"; console.log(this.user); //undefined console.log(this); //Window } window.a();
和上面代码同样吧,其实alert也是window的一个属性,也是window点出来的。
例子2:
var o = { user:"追梦子", fn:function(){ console.log(this.user); //追梦子 } } o.fn();
这里的this指向的是对象o,由于你调用这个fn是经过o.fn()执行的,那天然指向就是对象o,这里再次强调一点,this的指向在函数建立的时候是决定不了的,在调用的时候才能决定,谁调用的就指向谁,必定要搞清楚这个。
其实例子1和例子2说的并不够准确,下面这个例子就能够推翻上面的理论。
若是要完全的搞懂this必须看接下来的几个例子
例子3:
var o = { user:"追梦子", fn:function(){ console.log(this.user); //追梦子 } } window.o.fn();
这段代码和上面的那段代码几乎是同样的,可是这里的this为何不是指向window,若是按照上面的理论,最终this指向的是调用它的对象,这里先说个而外话,window是js中的全局对象,咱们建立的变量其实是给window添加属性,因此这里能够用window点o对象。
这里先不解释为何上面的那段代码this为何没有指向window,咱们再来看一段代码。
var o = { a:10, b:{ a:12, fn:function(){ console.log(this.a); //12 } } } o.b.fn();
这里一样也是对象o点出来的,可是一样this并无执行它,那你确定会说我一开始说的那些不就都是错误的吗?其实也不是,只是一开始说的不许确,接下来我将补充一句话,我相信你就能够完全的理解this的指向的问题。
var o = { a:10, b:{ // a:12, fn:function(){ console.log(this.a); //undefined } } } o.b.fn();
尽管对象b中没有属性a,这个this指向的也是对象b,由于this只会指向它的上一级对象,无论这个对象中有没有this要的东西。
还有一种比较特殊的状况,例子4:
var o = { a:10, b:{ a:12, fn:function(){ console.log(this.a); //undefined console.log(this); //window } } } var j = o.b.fn; j();
这里this指向的是window,是否是有些蒙了?实际上是由于你没有理解一句话,这句话一样相当重要。
this永远指向的是最后调用它的对象,也就是看它执行的时候是谁调用的,例子4中虽然函数fn是被对象b所引用,可是在将fn赋值给变量j的时候并无执行因此最终指向的是window,这和例子3是不同的,例子3是直接执行了fn。
this讲来说去其实就是那么一回事,只不过在不一样的状况下指向的会有些不一样,上面的总结每一个地方都有些小错误,也不能说是错误,而是在不一样环境下状况就会有不一样,因此我也没有办法一次解释清楚,只能你慢慢地的去体会。
构造函数版this:
function Fn(){ this.user = "追梦子"; } var a = new Fn(); console.log(a.user); //追梦子
这里之因此对象a能够点出函数Fn里面的user是由于new关键字能够改变this的指向,将这个this指向对象a,为何我说a是对象,由于用了new关键字就是建立一个对象实例,理解这句话能够想一想咱们的例子3,咱们这里用变量a建立了一个Fn的实例(至关于复制了一份Fn到对象a里面),此时仅仅只是建立,并无执行,而调用这个函数Fn的是对象a,那么this指向的天然是对象a,那么为何对象a中会有user,由于你已经复制了一份Fn函数到对象a中,用了new关键字就等同于复制了一份。
除了上面的这些之外,咱们还能够自行改变this的指向,关于自行改变this的指向请看JavaScript中call,apply,bind方法的总结这篇文章,详细的说明了咱们如何手动更改this的指向。
更新一个小问题当this碰到return时
function fn() { this.user = '追梦子'; return {}; } var a = new fn; console.log(a.user); //undefined
再看一个
function fn() { this.user = '追梦子'; return function(){}; } var a = new fn; console.log(a.user); //undefined
再来
function fn() { this.user = '追梦子'; return 1; } var a = new fn; console.log(a.user); //追梦子
function fn() { this.user = '追梦子'; return undefined; } var a = new fn; console.log(a.user); //追梦子
什么意思呢?
==若是返回值是一个对象,那么this指向的就是那个返回的对象,若是返回值不是一个对象那么this仍是指向函数的实例。==
function fn() { this.user = '追梦子'; return undefined; } var a = new fn; console.log(a); //fn {user: "追梦子"}
还有一点就是虽然null也是对象,可是在这里this仍是指向那个函数的实例,由于null比较特殊。
function fn() { this.user = '追梦子'; return null; } var a = new fn; console.log(a.user); //追梦子
知识点补充:
1.在严格版中的默认的this再也不是window,而是undefined。
2.new操做符会改变函数this的指向问题,虽然咱们上面讲解过了,可是并无深刻的讨论这个问题,网上也不多说,因此在这里有必要说一下。
function fn(){ this.num = 1; } var a = new fn(); console.log(a.num); //1
为何this会指向a?首先new关键字会建立一个空的对象,而后会自动调用一个函数apply方法,将this指向这个空对象,这样的话函数内部的this就会被这个空的对象替代。
2017-09-15 11:49:14
注意: 当你new一个空对象的时候,js内部的实现并不必定是用的apply方法来改变this指向的,这里我只是打个比方而已.
if (this === 动态的可改变的) return true;
第一次握手:创建链接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SENT状态,等待服务器确认;SYN:同步序列编号(Synchronize Sequence Numbers)。
第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时本身也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP链接成功)状态,完成三次握手。
时间复杂度:O(n^2),indexOf自己也消耗了O(n)的复杂度, 空间复杂度:O(n) IE8如下不支持indexOf
Array.prototype.removeRepeat1 = function() { var res =[this[0]]; for(var i=1; i<this.length;i++){ //从第二项开始遍历 if(this.indexOf(this[i])==i){ res.push(this[i]); } } return res; };
Array.prototype.removeRepeat2 = function() { var res =[]; for(var i=0; i<this.length;i++){ if(res.indexOf(this[i])==-1){ res.push(this[i]); } } return res; };
相似于,利用对象的属性不能相同的特色进行去重 时间复杂度:O(n),空间复杂度:O(n)
Array.prototype.removeRepeat3 = function() { var h= {}; //哈希表 var res = []; for(var i=0; i<this.length;i++){ if(!h[this[i]]){ //若是hash表中没有当前项 h[this[i]]=true; //存入hash表 res.push(this[i]); } } return res; };
时间复杂度:O(n), 空间复杂度:O(n) Set兼容性很差,IE11如下不支持
Array.prototype.removeRepeat4 = function(){ var result = new Set(); for(var i=0; i<this.length; i++){ result.add(this[i]); } return result; } //Set的方法二: Array.from(array)把Set转化为数组 Array.prototype.removeRepeat41 = function(){ return Array.from(new Set(this));; }
Array.prototype.removeRepeat5 = function() { this.sort(); var res=[this[0]]; for(var i = 1; i< this.length; i++){ if(this[i]!=this[i-1]){ res.push(this[i]); } } return res; }
call apply bind
第一个传的参数都是对象,不能传入构造函数,构造函数的typeof是function
传null
或undefined
时,将是JS执行环境的全局变量。浏览器中是window,其它环境(如node)则是global
语法:call(thisObj,Object)
定义:调用一个对象的一个方法,以另外一个对象替换当前对象。
说明:call 方法能够用来代替另外一个对象调用一个方法。call 方法可将一个函数的对象上下文从初始的上下文改变为由 thisObj 指定的新对象。
若是没有提供 thisObj 参数,那么 Global 对象被用做 thisObj。
语法:apply(thisObj,[argArray])
定义:应用某一对象的一个方法,用另外一个对象替换当前对象。
说明:若是 argArray 不是一个有效的数组或者不是 arguments 对象,那么将致使一个 TypeError。
若是没有提供 argArray 和 thisObj 任何一个参数,那么 Global 对象将被用做 thisObj, 而且没法被传递任何参数。
调用函数时,改变当前传入的对象为函数中this指针的引用当第一个参数thisObj传入null/undefined的时候将执行js全局对象浏览器中是window,其余环境是global。
call, apply方法区别是,从第二个参数起, call方法参数将依次传递给借用的方法做参数, 而apply直接将这些参数放到一个数组中再传递, 最后借用方法的参数列表是同样的.
bind相同点和call apply相同
而bind是返回一个新函数,这个函数的上下文,为传入的对象。须要再次调用才能时候用.
那么 apply、call、bind 三者相比较,之间又有什么异同呢?什么时候使用 apply、call,什么时候使用 bind 呢。简单的一个栗子:
ar obj = { x: 81, }; var foo = { getX: function() { return this.x; } } console.log(foo.getX.bind(obj)()); //81 console.log(foo.getX.call(obj)); //81 console.log(foo.getX.apply(obj)); //81
三个输出的都是81,可是注意看使用 bind() 方法的,他后面多了对括号。
也就是说,区别是,当你但愿改变上下文环境以后并不是当即执行,而是回调执行的时候,使用 bind() 方法。而 apply/call 则会当即执行函数。
==再总结一下==:
JavaScript有两种数据类型,基础数据类型和引用数据类型。基础数据类型都是按值访问的,咱们能够直接操做保存在变量中的实际的值。而引用类型如Array,咱们不能直接操做对象的堆内存空间。引用类型的值都是按引用访问的,即保存在变量对象中的一个地址,该地址与堆内存的实际值相关联。
var a = 25; var b = a; b = 10; console.log(a);//25 console.log(b);//10
//浅拷贝
var obj1 = { a: 10, b: 20, c: 30 }; var obj2 = obj1; obj2.b = 40; console.log(obj1);// { a: 10, b: 40, c: 30 } console.log(obj2);// { a: 10, b: 40, c: 30 }
//深拷贝
var obj1 = { a: 10, b: 20, c: 30 }; var obj2 = { a: obj1.a, b: obj1.b, c: obj1.c }; obj2.b = 40; console.log(obj1);// { a: 10, b: 20, c: 30 } console.log(obj2);// { a: 10, b: 40, c: 30 }
var json1 = {"a":"name","arr1":[1,2,3]} function copy(obj1) { var obj2 = {}; for (var i in obj1) { obj2[i] = obj1[i]; } return obj2; } var json2 = copy(json1); json1.arr1.push(4); alert(json1.arr1); //1234 alert(json2.arr1) //1234
let foo = { a: 1, b: 2, c: { d: 1, } } let bar = {}; Object.assign(bar, foo); foo.a++; foo.a === 2 //true bar.a === 1 //true foo.c.d++; foo.c.d === 2 //true bar.c.d === 1 //false bar.c.d === 2 //true
Object.assign()是一种能够对==非嵌套对象==进行深拷贝的方法,若是对象中出现嵌套状况,那么其对被嵌套对象的行为就成了普通的浅拷贝。
用JSON.stringify把对象转成字符串,再用JSON.parse把字符串转成新的对象。
var obj1 = { body: { a: 10 } }; var obj2 = JSON.parse(JSON.stringify(obj1)); obj2.body.a = 20; console.log(obj1); // { body: { a: 10 } } console.log(obj2); // { body: { a: 20 } } console.log(obj1 === obj2); // false console.log(obj1.body === obj2.body); // false
但这种方法的缺陷是会==破坏原型链==,而且没法拷贝属性值为function的属性
采用递归的方法去复制拷贝对象
var json1={ "name":"shauna", "age":18, "arr1":[1,2,3,4,5], "string":'got7', "arr2":[1,2,3,4,5], "arr3":[{"name1":"shauna"},{"job":"web"}] }; var json2={}; function copy(obj1,obj2){ var obj2=obj2||{}; for(var name in obj1){ if(typeof obj1[name] === "object"){ obj2[name]= (obj1[name].constructor===Array)?[]:{}; copy(obj1[name],obj2[name]); }else{ obj2[name]=obj1[name]; } } return obj2; } json2=copy(json1,json2) json1.arr1.push(6); alert(json1.arr1); //123456 alert(json2.arr1); //12345
Function.prototype.mybind = function(context) { var self = this; var args = [];//保存bind函数调用时传递的参数 for(var i = 1, len = arguments.length; i< len;i ++) { args.push(arguments[i]); } //bind()方法返回值是一个函数 return function() { //哇,新建立的函数传进来的参数能够在这里拿到哎!! var bindArgs = Array.prototype.slice.call(arguments); self.apply(context, args.concat(bindArgs)) } }
var xmlhttp; if (window.XMLHttpRequest){ // IE7+, Firefox, Chrome, Opera, Safari 浏览器执行代码 xmlhttp=new XMLHttpRequest(); } else{ // IE6, IE5 浏览器执行代码 xmlhttp=new ActiveXObject("Microsoft.XMLHTTP"); } xmlhttp.open("GET","/try/ajax/ajax_info.txt",true); xmlhttp.send(); xmlhttp.onreadystatechange=function() { if (xmlhttp.readyState==4 && xmlhttp.status==200) { document.getElementById("myDiv").innerHTML=xmlhttp.responseText; } }
优势
缺点
不少朋友不知道为何要跨域 其实跨域请求存在的缘由:因为浏览器的同源策略,即属于不一样域的页面之间不能相互访问各自的页面内容。
那么什么是==同源策略==呢?
简单说来就是同协议,同域名,同端口。
1.域名不一样 www.yangwei.com 和www.wuyu.com 即为不一样的域名)
2.二级域名相同,子域名不一样(www.wuhan.yangwei.com www.shenzheng.yangwei.com 为子域不一样)
3.端口不一样,协议不一样 ( http://www.yangwei.com 和https://www.yangwei.com属于跨...:8888和www.yangwei.con:8080)
一.==imge.src==,==script.src==,==style.href== 不受同源策略的影响能够加载其余域的资源,能够用这个特性,向服务器发送数据。最经常使用的就是使用image.src 向服务器发送前端的错误信息。image.src 和style.href 是没法获取服务器的数据返回的,script.src 服务器端配合能够获得数据返回。
二possMessage,window.name,document.domain 是两个窗口直接相互传递数据。
(1)possMessage 是HTML5中新增的,使用限制是 必须得到窗口的window 引用。IE8+支持,firefox,chrome,safair,opera支持
(2)window.name ,在一个页面中打开另外一个页面时,window.name 是共享的,因此能够经过window.name 来传递数据,window.name的限制大小是2M,这个全部浏览器都支持,且没有什么限制。
3) document.domain 将两个页面的document.domain 设置成相同,document.domain 只能设置成父级域名,既能够访问,使用限制:这顶级域名必须相同,document.domain + iframe跨域
此方案仅限主域相同,子域不一样的跨域应用场景。
1.)父窗口:(www.domain.com/a.html)
<iframe id="iframe" src="http://child.domain.com/b.html"></iframe> <script> document.domain = 'domain.com'; var user = 'admin'; </script>
2.)子窗口:(child.domain.com/b.html)
<script> document.domain = 'domain.com'; // 获取父窗口中变量 alert('get js data from parent ---> ' + window.parent.user); </script>
CORS 是w3c标准的方式,经过在web服务器端设置:响应头Access-Control-Alow-Origin 来指定哪些域能够访问本域的数据,ie8&9(XDomainRequest),10+,chrom4 ,firefox3.5,safair4,opera12支持这种方式。
服务器代理,同源策略只存在浏览器端,经过服务器转发请求能够达到跨域请求的目的,劣势:增长服务器的负担,且访问速度慢。
前端代码--script.html:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Insert title here</title> <script> // var getvalue = function (data) { // alert(JSON.stringify(data)); // }; // var url = "http://127.0.0.1:3000/cors?callback=getvalue"; // var script = document.createElement('script'); // script.setAttribute('src', url); // document.getElementsByTagName('head')[0].appendChild(script); var xhr = new XMLHttpRequest(); // IE8/9需用window.XDomainRequest兼容 // 前端设置是否带cookie xhr.withCredentials = false; xhr.open('get', 'http://127.0.0.1:3000/cors.js?callback=getvalue', true); xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); xhr.send(); xhr.onreadystatechange = function() { if (xhr.readyState == 4 && xhr.status == 200) { alert(xhr.responseText); } }; </script> <!--<script src="http://127.0.0.1:3000"></script>--> </head> <body> ffffffffffffffffffffffffffff </body> </html>
后端代码--demo2.js:
var express = require('express'); var app = express(); var http = require('http'); var fs = require('fs'); var url = require('url'); app.get('/', function (req, res) { res.sendfile(__dirname+"/index.html"); }) app.get('/cors.js', function(req, res) { var pathname = url.parse(req.url).pathname; console.log("req for " + pathname + " received."); fs.readFile(pathname.substr(1), function (err, data) { if (err) { console.log(err); // HTTP 状态码: 404 : NOT FOUND // Content Type: text/plain res.writeHead(404, {'Content-Type': 'text/html'}); }else{ res.header("Access-Control-Allow-Origin", "*"); //设置请求来源不受限制 res.header("Access-Control-Allow-Headers", "X-Requested-With"); res.header("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS"); //请求方式 res.header("X-Powered-By", ' 3.2.1') res.header("Content-Type", "application/json;charset=utf-8"); var data = { name: ' - server 3001 cors process', id: ' - server 3001 cors process' } console.log(data); // "getvalue(data)" res.send("getvalue({ name: '5'})"); } // 发送响应数据 res.end(); }); }) var server = app.listen(3000, function () { var host = server.address().address var port = server.address().port console.log("应用实例,访问地址为 http://%s:%s", host, port) }) // 控制台会输出如下信息 console.log('Server running at http://127.0.0.1:3000/');
script.src 不受同源策略的限制,因此能够动态的建立script标签,将要请求数据的域写在src 中参数中附带回调的方法,服务器端返回回调函数的字符串,并带参数。
如 script.src="http://www.yangwei.com/?id=001&callback=getInfoCallback,服务器端返回 getInfoCallBack("name:yangwei;age:18") 这段代码会直接执行,在前面定义好getInfoCallBack函数,既能够得到数据并解析。 这种是最长见的方式。
前端代码--script.html
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Insert title here</title> <script> var getvalue = function (data) { alert(JSON.stringify(data)); }; var url = "http://127.0.0.1:3000/person.js?callback=getvalue"; var script = document.createElement('script'); script.setAttribute('src', url); document.getElementsByTagName('head')[0].appendChild(script); </script> <!--<script src="http://127.0.0.1:3000"></script>--> </head> <body> ffffffffffffffffffffffffffff </body> </html>
后端代码--demo2.js
var express = require('express'); var app = express(); var http = require('http'); var fs = require('fs'); var url = require('url'); app.get('/', function (req, res) { res.sendfile(__dirname+"/index.html"); }) app.get('/person.js',function (req,res) { var pathname = url.parse(req.url).pathname; // 输出请求的文件名 console.log("req for " + pathname + " received."); // fs.readFile(pathname.substr(1), function (err, data) { // if (err) { // console.log(err); // // HTTP 状态码: 404 : NOT FOUND // // Content Type: text/plain // res.writeHead(404, {'Content-Type': 'text/html'}); // }else{ // // HTTP 状态码: 200 : OK // // Content Type: text/plain // res.writeHead(200, {'Content-Type': 'text/html'}); // // // 响应文件内容 // res.write(data.toString()); // } // // 发送响应数据 // res.end(); // }); res.send("getvalue({ name: '5'})") }) app.get('/message.js',function (req,res) { var pathname = url.parse(req.url).pathname; // 输出请求的文件名 console.log("req for " + pathname + " received."); fs.readFile(pathname.substr(1), function (err, data) { if (err) { console.log(err); // HTTP 状态码: 404 : NOT FOUND // Content Type: text/plain res.writeHead(404, {'Content-Type': 'text/html'}); }else{ // HTTP 状态码: 200 : OK // Content Type: text/plain res.writeHead(200, {'Content-Type': 'text/html'}); // 响应文件内容 res.write(data.toString()); } // 发送响应数据 res.end(); }); }) app.get('/cors.js', function(req, res) { var pathname = url.parse(req.url).pathname; console.log("req for " + pathname + " received."); fs.readFile(pathname.substr(1), function (err, data) { if (err) { console.log(err); // HTTP 状态码: 404 : NOT FOUND // Content Type: text/plain res.writeHead(404, {'Content-Type': 'text/html'}); }else{ res.header("Access-Control-Allow-Origin", "*"); //设置请求来源不受限制 res.header("Access-Control-Allow-Headers", "X-Requested-With"); res.header("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS"); //请求方式 res.header("X-Powered-By", ' 3.2.1') res.header("Content-Type", "application/json;charset=utf-8"); var data = { name: ' - server 3001 cors process', id: ' - server 3001 cors process' } console.log(data); // "getvalue(data)" res.send("getvalue({ name: '5'})"); } // 发送响应数据 res.end(); }); }) var server = app.listen(3000, function () { var host = server.address().address var port = server.address().port console.log("应用实例,访问地址为 http://%s:%s", host, port) }) // 控制台会输出如下信息 console.log('Server running at http://127.0.0.1:3000/');
person = { name:'FE', age:23 }
使用同一个接口建立不少对象,会产生大量重复的代码
function creatPerson(name,age,job){ var o = new Object(); o.name = name; o.age = age; o.job = job; o.sayName=function(){ alert(this.name); } return o; } var person1 = creatPerson('FE',20,'teacher');
虽然解决了建立类似对象的问题,可是没有解决对象识别的问题(即怎样知道一个对象的类型)
function Person(name,age,job){ this.name = name; this.age = age; this.job = job; this.sayName=function(){ alert(this.name); } } var person1 = Person('FE',20,'teacher');
建立自定义函数意味着未来能够将它的实例标识为一种特定的数据类型。可是每一个方法都要在实例上从新建立一遍。
function Person(){}; Person.prototype.name = "FE"; Person.prototype.age = 20; Person.prototype.sayName = function(){ alert(this.name); }; var person1 = new Person(); person1.sayName(); //'FE'
可让全部的实例共享它所包含的属性和方法。原型中的属性和方法是共享的,可是实例通常要有单独的属性和方法,通常不多单独使用原型模式。
function Person(name,age,job){ this.name = name; this.age = age; this.job = job; this.friends=['aa','ss','dd']; } Person.prototype.sayName = function(){ alert(this.name); } var person1 = new Person('FE',20,'teacher');
构造函数模式定义实例的属性,原型模式定义公共的属性和方法
利用原型让一个引用类型继承另一个引用类型的属性和方法
function SuperType(){ this.property = 'true'; } SuperType.prototype.getSuperValue = function(){ return this.property; } function SubType(){ this.subProperty = 'false'; } SubType.prototype = new SuperType(); SubType.prototype.getSubValue = function(){ return this.subProperty; } var instance = new SubType(); alert(instance.getSuperValue());//true
简单明了,容易实现,在父类新增原型属性和方法,子类都能访问到。
包含引用类型值的函数,全部的实例都指向同一个引用地址,修改一个,其余都会改变。不能像超类型的构造函数传递参数
在子类型构造函数的内部调用超类型的构造函数
function SuperType(){ this.colors = ['red','yellow']; } function SubType(){ SuperType.call(this); } var instance1 = new SubType(); instance1.colors.push('black'); var instance2 = new SubType(); instance2.colors.push('white'); alert(instance1.colors);//'red','yellow','black' alert(instance2.colors);//'red','yellow','white'
简单明了,直接继承了超类型构造函数的属性和方法
方法都在构造函数中定义,所以函数复用就无从谈起了,并且超类型中的原型的属性和方法,对子类型也是不可见的,结果全部的类型只能使用构造函数模式。
使用原型链实现多原型属性和方法的继承,使用构造函数实现实例的继承
function SuperType(name){ this.name = name; this.colors = ['red','black']; } SuperType.prototype.sayName = function() { alert(this.name); } function SubType(name,age){ SuperType.call(this,name); this.age = age; } SubType.protptype = new SuperType(); SubType.protptype.sayAge = function(){ alert(this.age); }
解决了构造函数和原型继承中的两个问题
不管何时,都会调用两次超类型的构造函数
ES6 Promise 用法讲解
Promise是一个构造函数,本身身上有==all、reject、resolve==这几个眼熟的方法,原型上有==then、catch==等一样很眼熟的方法。
那就new一个
var p = new Promise(function(resolve, reject){ //作一些异步操做 setTimeout(function(){ console.log('执行完成'); resolve('随便什么数据'); }, 2000); });
Promise的构造函数接收一个参数,是函数,而且传入两个参数:resolve,reject,分别表示异步操做执行成功后的回调函数和异步操做执行失败后的回调函数。其实这里用“成功”和“失败”来描述并不许确,按照标准来说,resolve是将Promise的状态置为fulfilled,reject是将Promise的状态置为rejected。不过在咱们开始阶段能够先这么理解,后面再细究概念。
在上面的代码中,咱们执行了一个异步操做,也就是setTimeout,2秒后,输出“执行完成”,而且调用resolve方法。
运行代码,会在2秒后输出“执行完成”。注意!我只是new了一个对象,并无调用它,咱们传进去的函数就已经执行了,这是须要注意的一个细节。因此咱们用Promise的时候通常是包在一个函数中,在须要的时候去运行这个函数,如:
function runAsync(){ var p = new Promise(function(resolve, reject){ //作一些异步操做 setTimeout(function(){ console.log('执行完成'); resolve('随便什么数据'); }, 2000); }); return p; } runAsync()
这时候你应该有两个疑问:1.包装这么一个函数有毛线用?2.resolve('随便什么数据');这是干毛的?
咱们继续来说。在咱们包装好的函数最后,会return出Promise对象,也就是说,执行这个函数咱们获得了一个Promise对象。还记得Promise对象上有then、catch方法吧?这就是强大之处了,看下面的代码:
runAsync().then(function(data){ console.log(data); //后面能够用传过来的数据作些其余操做 //...... });
在runAsync()的返回上直接调用then方法,then接收一个参数,是函数,而且会拿到咱们在runAsync中调用resolve时传的的参数。运行这段代码,会在2秒后输出“执行完成”,紧接着输出“随便什么数据”。
这时候你应该有所领悟了,原来then里面的函数就跟咱们平时的回调函数一个意思,可以在runAsync这个异步任务执行完成以后被执行。这就是Promise的做用了,简单来说,就是能把原来的回调写法分离出来,在异步操做执行完后,用链式调用的方式执行回调函数。
你可能会不屑一顾,那么牛逼轰轰的Promise就这点能耐?我把回调函数封装一下,给runAsync传进去不也同样吗,就像这样:
function runAsync(callback){ setTimeout(function(){ console.log('执行完成'); callback('随便什么数据'); }, 2000); } runAsync(function(data){ console.log(data); });
效果也是同样的,还费劲用Promise干吗。那么问题来了,有多层回调该怎么办?若是callback也是一个异步操做,并且执行完后也须要有相应的回调函数,该怎么办呢?总不能再定义一个callback2,而后给callback传进去吧。而Promise的优点在于,能够在then方法中继续写Promise对象并返回,而后继续调用then来进行回调操做。
链式操做的用法
因此,从表面上看,Promise只是可以简化层层回调的写法,而实质上,Promise的精髓是“状态”,用维护状态、传递状态的方式来使得回调函数可以及时调用,它比传递callback函数要简单、灵活的多。因此使用Promise的正确场景是这样的:
runAsync1() .then(function(data){ console.log(data); return runAsync2(); }) .then(function(data){ console.log(data); return runAsync3(); }) .then(function(data){ console.log(data); });
这样可以按顺序,每隔两秒输出每一个异步回调中的内容,在runAsync2中传给resolve的数据,能在接下来的then方法中拿到。运行结果以下:
猜猜runAsync一、runAsync二、runAsync3这三个函数都是如何定义的?没错,就是下面这样
function runAsync1(){ var p = new Promise(function(resolve, reject){ //作一些异步操做 setTimeout(function(){ console.log('异步任务1执行完成'); resolve('随便什么数据1'); }, 1000); }); return p; } function runAsync2(){ var p = new Promise(function(resolve, reject){ //作一些异步操做 setTimeout(function(){ console.log('异步任务2执行完成'); resolve('随便什么数据2'); }, 2000); }); return p; } function runAsync3(){ var p = new Promise(function(resolve, reject){ //作一些异步操做 setTimeout(function(){ console.log('异步任务3执行完成'); resolve('随便什么数据3'); }, 2000); }); return p; }
在then方法中,你也能够直接return数据而不是Promise对象,在后面的then中就能够接收到数据了,好比咱们把上面的代码修改为这样:
runAsync1() .then(function(data){ console.log(data); return runAsync2(); }) .then(function(data){ console.log(data); return '直接返回数据'; //这里直接返回数据 }) .then(function(data){ console.log(data); });
那么输出就变成了这样:
reject的用法
到这里,你应该对“Promise是什么玩意”有了最基本的了解。那么咱们接着来看看ES6的Promise还有哪些功能。咱们光用了resolve,还没用reject呢,它是作什么的呢?事实上,咱们前面的例子都是只有“执行成功”的回调,尚未“失败”的状况,reject的做用就是把Promise的状态置为rejected,这样咱们在then中就能捕捉到,而后执行“失败”状况的回调。看下面的代码。
function getNumber(){ var p = new Promise(function(resolve, reject){ //作一些异步操做 setTimeout(function(){ var num = Math.ceil(Math.random()*10); //生成1-10的随机数 if(num<=5){ resolve(num); } else{ reject('数字太大了'); } }, 2000); }); return p; } getNumber() .then( function(data){ console.log('resolved'); console.log(data); }, function(reason, data){ console.log('rejected'); console.log(reason); } );
getNumber函数用来异步获取一个数字,2秒后执行完成,若是数字小于等于5,咱们认为是“成功”了,调用resolve修改Promise的状态。不然咱们认为是“失败”了,调用reject并传递一个参数,做为失败的缘由。
运行getNumber而且在then中传了两个参数,then方法能够接受两个参数,第一个对应resolve的回调,第二个对应reject的回调。因此咱们可以分别拿到他们传过来的数据。屡次运行这段代码,你会随机获得下面两种结果:
或者
catch的用法
咱们知道Promise对象除了then方法,还有一个catch方法,它是作什么用的呢?其实它和then的第二个参数同样,用来指定reject的回调,用法是这样:
getNumber() .then(function(data){ console.log('resolved'); console.log(data); }) .catch(function(reason){ console.log('rejected'); console.log(reason); });
效果和写在then的第二个参数里面同样。不过它还有另一个做用:在执行resolve的回调(也就是上面then中的第一个参数)时,若是抛出异常了(代码出错了),那么并不会报错卡死js,而是会进到这个catch方法中。请看下面的代码:
getNumber() .then(function(data){ console.log('resolved'); console.log(data); console.log(somedata); //此处的somedata未定义 }) .catch(function(reason){ console.log('rejected'); console.log(reason); });
在resolve的回调中,咱们console.log(somedata);而somedata这个变量是没有被定义的。若是咱们不用Promise,代码运行到这里就直接在控制台报错了,不往下运行了。可是在这里,会获得这样的结果:
也就是说进到catch方法里面去了,并且把错误缘由传到了reason参数中。即使是有错误的代码也不会报错了,这与咱们的try/catch语句有相同的功能。
all的用法
Promise的all方法提供了并行执行异步操做的能力,而且在全部异步操做执行完后才执行回调。咱们仍旧使用上面定义好的runAsync一、runAsync二、runAsync3这三个函数,看下面的例子:
Promise .all([runAsync1(), runAsync2(), runAsync3()]) .then(function(results){ console.log(results); });
用Promise.all来执行,all接收一个数组参数,里面的值最终都算返回Promise对象。这样,三个异步操做的并行执行的,等到它们都执行完后才会进到then里面。那么,三个异步操做返回的数据哪里去了呢?都在then里面呢,all会把全部异步操做的结果放进一个数组中传给then,就是上面的results。因此上面代码的输出结果就是:
有了all,你就能够并行执行多个异步操做,而且在一个回调中处理全部的返回数据,是否是很酷?有一个场景是很适合用这个的,一些游戏类的素材比较多的应用,打开网页时,预先加载须要用到的各类资源如图片、flash以及各类静态文件。全部的都加载完后,咱们再进行页面的初始化。
race的用法
all方法的效果其实是「谁跑的慢,以谁为准执行回调」,那么相对的就有另外一个方法「谁跑的快,以谁为准执行回调」,这就是race方法,这个词原本就是赛跑的意思。race的用法与all同样,咱们把上面runAsync1的延时改成1秒来看一下:
Promise .race([runAsync1(), runAsync2(), runAsync3()]) .then(function(results){ console.log(results); });
这三个异步操做一样是并行执行的。结果你应该能够猜到,1秒后runAsync1已经执行完了,此时then里面的就执行了。结果是这样的:
你猜对了吗?不彻底,是吧。在then里面的回调开始执行时,runAsync2()和runAsync3()并无中止,仍旧再执行。因而再过1秒后,输出了他们结束的标志。
这个race有什么用呢?使用场景仍是不少的,好比咱们能够用race给某个异步请求设置超时时间,而且在超时后执行相应的操做,代码以下:
//请求某个图片资源 function requestImg(){ var p = new Promise(function(resolve, reject){ var img = new Image(); img.onload = function(){ resolve(img); } img.src = 'xxxxxx'; }); return p; } //延时函数,用于给请求计时 function timeout(){ var p = new Promise(function(resolve, reject){ setTimeout(function(){ reject('图片请求超时'); }, 5000); }); return p; } Promise .race([requestImg(), timeout()]) .then(function(results){ console.log(results); }) .catch(function(reason){ console.log(reason); });
requestImg函数会异步请求一张图片,我把地址写为"xxxxxx",因此确定是没法成功请求到的。timeout函数是一个延时5秒的异步操做。咱们把这两个返回Promise对象的函数放进race,因而他俩就会赛跑,若是5秒以内图片请求成功了,那么遍进入then方法,执行正常的流程。若是5秒钟图片还未成功返回,那么timeout就跑赢了,则进入catch,报出“图片请求超时”的信息。运行结果以下:
语义化的HTML就是正确的标签作正确的事情,可以便于开发者阅读和写出更优雅的代码的同时让网络爬虫很好地解析。
一、有利于SEO,有利于搜索引擎爬虫更好的理解咱们的网页,从而获取更多的有效信息,提高网页的权重。
二、在没有CSS的时候可以清晰的看出网页的结构,加强可读性。
三、便于团队开发和维护,语义化的HTML可让开发者更容易的看明白,从而提升团队的效率和协调能力。
四、支持多终端设备的浏览器渲染。
在作前端开发的时候要记住:HTML 告诉咱们一块内容是什么(或其意义),而不是它长的什么样子,它的样子应该由CSS来决定。(结构与样式分离!)
写语义化的 HTML 结构其实很简单,首先掌握 HTML 中各个标签的语义,在看到内容的时候想一想用什么标签能更好的描述它,是什么就用什么标签。
<h1>~<h6> ,做为标题使用,而且依据重要性递减,<h1> 是最高的等级。 <p>段落标记,知道了 <p> 做为段落,你就不会再使用 <br /> 来换行了,并且不须要 <br /> 来区分段落与段落。<p> 中的文字会自动换行,并且换行的效果优于 <br />。 段落与段落之间的空隙也能够利用 CSS 来控制,很容易并且清晰的区分出段落与段落。 <ul>、<ol>、<li>,<ul> 无序列表,这个被你们普遍的使用,<ol> 有序列表也挺经常使用。在 web 标准化过程当中,<ul> 还被更多的用于导航条,原本导航条就是个列表,这样作是彻底正确的, 并且当你的浏览器不支持 CSS 的时候,导航连接仍然很好使,只是美观方面差了一点而已。 <dl>、<dt>、<dd>,<dl> 就是“定义列表”。好比说词典里面的词的解释、定义就能够用这种列表。 <em>、<strong>,<em> 是用做强调,<strong> 是用做重点强调。 <q>也不只仅只是为文字增长双引号,而是表明这些文字是引用来的。 <table>、<td>、<th>、<caption>, (X)HTML中的表格再也不是用来布局。
补充:网络爬虫,SEO等概念
——搜索引擎优化,这是一种利用搜索引擎的搜索规则,采起优化策略或程序,提升网站在搜索结果中的排名。
又称网络蜘蛛、网络机器人,是一种搜索引擎用于自动抓取网页资源的程序或者说叫机器人。从某一个网址为起点,去访问,而后把网页存回到数据库中,如此不断循环,通常认为搜索引擎爬虫都是靠连接爬行的,因此管他叫爬虫。这个只有开发搜索引擎才会用到。对于网站而言,只要有连接指向咱们的网页,爬虫就会自动提取咱们的网页。
3.生成绝对定位的元素,相对于 static 定位之外的第一个父元素进行定位。元素的位置经过 "left", "top", "right" 以及 "bottom" 属性进行规定。4.子元素:position:absolute也能够==相对于父元素==position:absolute
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>未知宽高元素水平垂直居中</title> </head> <style> .parent1{ display: table; height:300px; width: 300px; background-color: #FD0C70; } .parent1 .child{ display: table-cell; vertical-align: middle; text-align: center; color: #fff; font-size: 16px; } </style> <body> <div class="parent1"> <div class="child">hello world-1</div> </div> </body> </html>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>未知宽高元素水平垂直居中</title> </head> <style> .parent2{ height:300px; width: 300px; text-align: center; background: #FD0C70; } .parent2 span{ display: inline-block;; width: 0; height: 100%; vertical-align: middle; zoom: 1;/*BFC*/ *display: inline; } .parent2 .child{ display: inline-block; color: #fff; zoom: 1;/*BFC*/ *display: inline; } </style> <body> <div class="parent1"> <div class="child">hello world-1</div> </div> <div class="parent2"> <span></span> <div class="child">hello world-2</div> </div> </body> </html>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>未知宽高元素水平垂直居中</title> </head> <style> .parent3{ position: relative; height:300px; width: 300px; background: #FD0C70; } .parent3 .child{ position: absolute; top: 50%; left: 50%; color: #fff; transform: translate(-50%, -50%); } </style> <body> <div class="parent3"> <div class="child">hello world-3</div> </div> </body> </html>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>未知宽高元素水平垂直居中</title> </head> <style> .parent4{ display: flex; justify-content: center; align-items: center; width: 300px; height:300px; background: #FD0C70; } .parent4 .child{ color:#fff; } </style> <body>div> <div class="parent4"> <div class="child">hello world-4</div> </div> </body> </html>
margin 折叠--margin值合并
代码
<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title>测试</title> </head> <style> *{ margin: 0; padding: 0; } #father{ width: 2000px; height: 2000px; background: #0016d9; overflow: hidden; } #first-child{ margin-top: 20px; background: chocolate; width: 60px; height: 60px; } #second-child{ background: chartreuse; width: 60px; height: 60px; margin-bottom: 20px; } #three-child{ margin-top:40px; background: fuchsia; width: 60px; height: 60px; display: inline-block; } </style> <body> <div id="father"> <div id="first-child">box1</div> <div id="second-child">box2</div> <div id="three-child">box3</div> </div> </body> </html>
父层div加: padding-top: 1px,或者 border-top:1px ;
复制代码
<style type="text/css"> .div1{background:#000080;border:1px solid red;} .div2{background:#800080;border:1px solid red;height:100px;margin-top:10px} .left{float:left;width:20%;height:200px;background:#DDD} .right{float:right;width:30%;height:80px;background:#DDD} /*清除浮动代码*/ .clearfloat:after{display:block;clear:both;content:"";visibility:hidden;height:0} .clearfloat{zoom:1} </style> <div class="div1 clearfloat"> <div class="left">Left</div> <div class="right">Right</div> </div> <div class="div2"> div2 </div>
<style type="text/css"> .div1{background:#000080;border:1px solid red} .div2{background:#800080;border:1px solid red;height:100px;margin-top:10px} .left{float:left;width:20%;height:200px;background:#DDD} .right{float:right;width:30%;height:80px;background:#DDD} /*清除浮动代码*/ .clearfloat{clear:both} </style> <div class="div1"> <div class="left">Left</div> <div class="right">Right</div> <div class="clearfloat"></div> </div> <div class="div2"> div2 </div>
<style type="text/css"> .div1{background:#000080;border:1px solid red;/*解决代码*/height:200px;} .div2{background:#800080;border:1px solid red;height:100px;margin-top:10px} .left{float:left;width:20%;height:200px;background:#DDD} .right{float:right;width:30%;height:80px;background:#DDD} </style> <div class="div1"> <div class="left">Left</div> <div class="right">Right</div> </div> <div class="div2"> div2 </div>
<style type="text/css"> .div1{background:#000080;border:1px solid red;/*解决代码*/width:98%;overflow:hidden} .div2{background:#800080;border:1px solid red;height:100px;margin-top:10px;width:98%} .left{float:left;width:20%;height:200px;background:#DDD} .right{float:right;width:30%;height:80px;background:#DDD} </style> <div class="div1"> <div class="left">Left</div> <div class="right">Right</div> </div> <div class="div2"> div2 </div>
<style type="text/css"> .div1{background:#000080;border:1px solid red;/*解决代码*/width:98%;overflow:auto} .div2{background:#800080;border:1px solid red;height:100px;margin-top:10px;width:98%} .left{float:left;width:20%;height:200px;background:#DDD} .right{float:right;width:30%;height:80px;background:#DDD} </style> <div class="div1"> <div class="left">Left</div> <div class="right">Right</div> </div> <div class="div2"> div2 </div>
<style type="text/css"> .div1{background:#000080;border:1px solid red;/*解决代码*/width:98%;margin-bottom:10px;float:left} .div2{background:#800080;border:1px solid red;height:100px;width:98%;/*解决代码*/clear:both} .left{float:left;width:20%;height:200px;background:#DDD} .right{float:right;width:30%;height:80px;background:#DDD} </style> <div class="div1"> <div class="left">Left</div> <div class="right">Right</div> </div> <div class="div2"> div2 </div>
<style type="text/css"> .div1{background:#000080;border:1px solid red;/*解决代码*/width:98%;display:table;margin-bottom:10px;} .div2{background:#800080;border:1px solid red;height:100px;width:98%;} .left{float:left;width:20%;height:200px;background:#DDD} .right{float:right;width:30%;height:80px;background:#DDD} </style> <div class="div1"> <div class="left">Left</div> <div class="right">Right</div> </div> <div class="div2"> div2 </div>
<style type="text/css"> .div1{background:#000080;border:1px solid red;margin-bottom:10px;zoom:1} .div2{background:#800080;border:1px solid red;height:100px} .left{float:left;width:20%;height:200px;background:#DDD} .right{float:right;width:30%;height:80px;background:#DDD} .clearfloat{clear:both} </style> <div class="div1"> <div class="left">Left</div> <div class="right">Right</div> <br class="clearfloat" /> </div> <div class="div2"> div2 </div>
box-sizing:content-box状况下,元素的宽度=width+pading+border;
解释:box-sizing:content-box,至关于你从网上买东西,content-box为你买的实际要用的东西,假设为A。
快递员配送快递的时候,实际上你收到的快递是带有包装的A。
类比一下,content-box是A,box-sizing是收快递的你,赋值是快递员配送,
最后你手里收到的东西就是A+包装盒,也就是content+border+padding;
/ width 和 height 属性包括内容,内边距和边框,但不包括外边距 /
状况下,元素的宽度=width,pading,border都包含在width里面
解释:box-sizing:border-box;至关于你从网上买东西,border-box是带有包装的你买的东西,假设为B。
快递员配送快递的时候,实际上你收到的快递就是B。
类比一下,border-box是B,box-sizing是收快递的你,赋值是快递员配送,
最后你手里收到的东西就是B;
!important > 行内样式>ID选择器 > 类选择器 > 标签 > 通配符 > 继承 > 浏览器默认属性 类选择器和属性选择器优先级相同,谁在后面谁的优先级较高 注意:通用选择器(*),子选择器(>)和相邻同胞选择器(+),他们的权值是0,因此两个通配符选择器和一个通配符选择器的权值是相同的
line-height是能够继承的,因此子元素就能够不用重复定义line-height了。咱们通常也会在后面带上单位(如:line-height:22px; 或是line-height:1.4em;),但line-height会给人带来麻烦的地方也就是这个继承和后面加的单位。
有的时候,咱们为了实现单行文字的垂直居中,会给line-height一个和height相同的固定的值;有的时候,咱们为了调整特定的某段文字 的行间距,一般会考虑使用百分比或者相对尺寸的em。
或许是习惯,因而咱们都习惯了line-height是要有单位的。
这些状况下,咱们都不须要考虑 line-height的继承,也不会发现任何问题,固然后咱们在使用到line-height继承的时候,就会发现问题的所在。
例以下面的代码:
一、样式
<style type="text/css"> .test{line-height:1.4em; font-size:12px;} span{font-size:30px; font-weight:bold;} </style>
二、HTML结构
<div class="test"> <span> 白培铭先生于1960年出生于中国台湾。<br/> 毕业于中国台湾省清华大学核物理系,<br/> </span> 以后留学于美国加州大学伯克利分校和密西根大学,得到双硕士学位。<br/> 在工做以后,凭着对营销领域的浓厚兴趣,他又考入密执安大学深造<br/> </div>
看过例子后,你会发现,只要有单位的line-height继承,都发生了重叠的现象。那到底这是什么缘由致使的呢?
css中单位em和rem的区别
在css中单位长度用的最多的是px、em、rem,这三个的区别是:
em
rem
块级格式上下文Block Formatting Context(简称BFC ),这是Web页面的一种布局方式,通俗点说,也是指页面上一个渲染区域,里面的元素按文档流中的顺序垂直排列,而且发生垂直方向上的margin折叠,同时这个区域内的元素布局不会对外面的元素有任何影响,反过来,也是同样。
当元素知足一下任何一个条件是都会产生一个BFC: