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: