摘要:一前言本文適合有一定開發(fā)基礎(chǔ)的讀者,文章涉及開發(fā)中經(jīng)常遇到的一些令人疑惑的問(wèn)題,理解這些問(wèn)題有助于我們快速提升對(duì)這門語(yǔ)言的理解和應(yīng)用能力。
一:前言
本文適合有一定JS開發(fā)基礎(chǔ)的讀者,文章涉及開發(fā)中經(jīng)常遇到的一些令人疑惑的問(wèn)題,理解這些問(wèn)題有助于我們快速提升對(duì)JS這門語(yǔ)言的理解和應(yīng)用能力。文章只講述具體問(wèn)題中的關(guān)鍵問(wèn)題,不涵蓋全面的知識(shí)點(diǎn)。如想了解具體的知識(shí),可以參考筆者博客的相關(guān)文章。
二:正文 1.丟失的this在實(shí)際應(yīng)用中, this的指向大致分為以下四種:
(1)作為對(duì)象方法的調(diào)用
(2)作為普通函數(shù)調(diào)用
(3)構(gòu)造器調(diào)用
(4)Function.prototype.call或Function.prototype.apply
1-1閱讀下面代碼:
//1.作為對(duì)象方法的調(diào)用this總是指向那個(gè)對(duì)象 window.name = "globalName"; var getName = function(){ return this.name; }; console.log( getName() ); // 輸出:globalName //2.作為普通函數(shù)的調(diào)用:非嚴(yán)格模式下this總是指向window,嚴(yán)格模式下 undefined window.name = "globalName"; var myObject = { name: "sven", getName: function(){ return this.name; } }; var getName = myObject.getName;//關(guān)鍵:這里保留了一個(gè)普通函數(shù)的引用 console.log( getName() ); // globalName
通過(guò)以上兩個(gè)對(duì)比,理解使用方法不同,this指向不同
1-2閱讀下面的代碼:
var getId = function( id ){ return document.getElementById( id ); }; getId( "div1" ); //我們也許思考過(guò)為什么不能用下面這種更簡(jiǎn)單的方式: var getId = document.getElementById; getId( "div1" );
document.getElementById方法需要用到this。這個(gè)this本來(lái)被期望指向document,當(dāng)getElementById被當(dāng)作 document的屬性被調(diào)用時(shí),方法內(nèi)部的this確實(shí)是指向document.
但是當(dāng)使用getId來(lái)引用document.getElementById之后,在調(diào)用getId,此時(shí)就變成了普通函數(shù)調(diào)用,內(nèi)部的this就指向了window。
利用call或者apply更正this指向:
//我們可以嘗試?yán)胊pply 把document 當(dāng)作this 傳入getId 函數(shù),幫助“修正”this:
document.getElementById = (function( func ){ return function(){ return func.apply( document, arguments ); } })( document.getElementById ); var getId = document.getElementById; var div = getId( "div1" ); alert (div.id); // 輸出: div12.實(shí)現(xiàn)手動(dòng)綁定this
2-1:bind方法的兼容寫法
var bind = Function.prototype.bind || function( context ){ var self = this; // 保存原函數(shù) return function(){ // 返回一個(gè)新的函數(shù) return self.apply( context, arguments ); // 執(zhí)行新的函數(shù)的時(shí)候,會(huì)把之前傳入的context當(dāng)作新函數(shù)體內(nèi)的this } };3.閉包
3-1.現(xiàn)在來(lái)看看下面這段代碼:
var func = function(){ var a = 1; return function(){ a++; alert ( a ); } }; var f = func(); f(); // 輸出:2 f(); // 輸出:3 f(); // 輸出:4 f(); // 輸出:5
當(dāng)執(zhí)行f = func()時(shí),f返回了一個(gè)匿名函數(shù)的引用,它可以訪問(wèn)到func()被調(diào)用時(shí)產(chǎn)生的環(huán)境,而局部變量a一直處在這個(gè)環(huán)境里。這個(gè)變量就有了不被銷毀的理由,這里就產(chǎn)生了一個(gè)閉包結(jié)構(gòu)。
3-2常見(jiàn)的閉包的問(wèn)題:
12345
3-3.利用閉包延續(xù)局部變量的壽命
//img 對(duì)象經(jīng)常用于進(jìn)行數(shù)據(jù)上報(bào),如下所示: var report = function( src ){ var img = new Image(); img.src = src; }; report( "http://xxx.com/getUserInfo" ); //丟失數(shù)據(jù)的原因是img是report函數(shù)中的局部變量,當(dāng)函數(shù)調(diào)用之后局部變量就銷毀了,而此時(shí)或許還沒(méi)來(lái)得及發(fā)起http請(qǐng)求 //現(xiàn)在我們把img 變量用閉包封閉起來(lái),便能解決請(qǐng)求丟失的問(wèn)題: var report = (function(){ var imgs = []; return function( src ){ var img = new Image(); imgs.push( img ); img.src = src; } })();
閉包與內(nèi)存管理
閉包會(huì)使一些數(shù)據(jù)無(wú)法被及時(shí)的銷毀,如果將來(lái)需要回收這些變量,我們可以手動(dòng)把這些變量設(shè)置為null。
跟閉包和內(nèi)存泄漏有關(guān)系的地方是,使用閉包的同時(shí)容易形成循環(huán)引用,如果閉包的作用域鏈中保存著一些DOM結(jié)點(diǎn),這時(shí)候就有可能造成內(nèi)存泄漏。
(1)函數(shù)可以作為參數(shù)被傳遞
(2)函數(shù)可以作為返回值輸出
4-1.函數(shù)作為參數(shù)傳遞
Array.prototype.sort方法:
var array = ["10","5","12","3"]; array.sort(); //array:["10","12","3","5"] //如代碼那樣,排序的結(jié)果并不是我們想要的,這與sort函數(shù)的比較規(guī)則有關(guān)系 array.sort(function(a,b){return a-b;}); //array:["3","5","10","12"] 傳入一個(gè)比較的函數(shù),就可以按照數(shù)字大小的規(guī)則進(jìn)行正確的比較了。
4-2.函數(shù)作為返回值輸出
var getSingle = function ( fn ) { var ret; return function () { return ret || ( ret = fn.apply( this, arguments ) ); }; };
4-3.函數(shù)作為參數(shù)被傳遞并且返回另一個(gè)函數(shù)
var getScript = getSingle(function(){ return document.createElement( "script" ); }); var script1 = getScript(); var script2 = getScript(); alert ( script1 === script2 ); // 輸出:true
4-4.高階函數(shù)應(yīng)用
(1)高階函數(shù)實(shí)現(xiàn)AOP
AOP(面向切面編程)的主要作用是把一些跟核心業(yè)務(wù)邏輯模塊無(wú)關(guān)的功能抽離出來(lái),這些業(yè)務(wù)邏輯無(wú)關(guān)的功能包括日志統(tǒng)計(jì)、控制安全、異常處理等。把這些功能抽離出來(lái)之后,再通過(guò)“動(dòng)態(tài)織入”的方式摻入業(yè)務(wù)邏輯模塊中。
下面代碼通過(guò)擴(kuò)展Function.prototype來(lái)實(shí)現(xiàn)把一個(gè)函數(shù)“動(dòng)態(tài)織入”
Function.prototype.before = function( beforefn ){ var __self = this; // 保存原函數(shù)的引用 return function(){ // 返回包含了原函數(shù)和新函數(shù)的"代理"函數(shù) beforefn.apply( this, arguments ); // 執(zhí)行新函數(shù),修正this return __self.apply( this, arguments ); // 執(zhí)行原函數(shù) } }; Function.prototype.after = function( afterfn ){ var __self = this; return function(){ var ret = __self.apply( this, arguments ); afterfn.apply( this, arguments ); return ret; } }; var func = function(){ console.log( 2 ); }; func = func.before(function(){ console.log( 1 ); }).after(function(){ console.log( 3 ); }); func();
(2)柯里化
一個(gè)currying函數(shù)首先會(huì)接受一些參數(shù),接受了這些參數(shù)之后,該函數(shù)不會(huì)立即求值,而是繼續(xù)返回另外一個(gè)函數(shù),剛才傳入的參數(shù)在函數(shù)形成的閉包中被保存了下來(lái)。待到函數(shù)真正需要求值的時(shí)候,之前傳入的所有參數(shù)都會(huì)一次性用于求值。
一個(gè)經(jīng)典的柯里化:
function curry(fn){ var arr1 = Array.prototype.slice.call(arguments,1); return function(){ var arg2 = Array.prototype.slice.call(arguments); var array = arr1.concat(arr2); return fn.apply(null,array); } }
不斷累積的柯里化:
var currying = function( fn ){ var args = [];//外層函數(shù)變量:用來(lái)累積 return function(){ if ( arguments.length === 0 ){ return fn.apply( this, args ); }else{ [].push.apply( args, arguments ); return arguments.callee; } } };
(3)uncurrying
在javascript中,當(dāng)我們調(diào)用對(duì)象的某個(gè)方法時(shí),其實(shí)不用關(guān)心對(duì)象原本是否被設(shè)計(jì)為擁有這個(gè)方法,這是動(dòng)態(tài)類型語(yǔ)言的特點(diǎn),也就是常說(shuō)的鴨子類型思想。
同理,一個(gè)對(duì)象也未必只能使用它自己的方法,其實(shí)可以借用原本不屬于他的方法: call apply
Function.prototype.uncurrying = function () { var self = this; return function() { var obj = Array.prototype.shift.call( arguments ); return self.apply( obj, arguments ); }; }; var push = Array.prototype.push.uncurrying(); var obj = { "length": 1, "0": 1 }; push( obj, 2 );//將2使用push的方法作用到obj上 console.log( obj ); // 輸出:{0: 1, 1: 2, length: 2}5.函數(shù)節(jié)流
函數(shù)節(jié)流也用到了高階函數(shù)的知識(shí),因?yàn)楸容^重要,所以單開了一個(gè)標(biāo)題。
javascript中的函數(shù)在大多數(shù)情況下都是由用戶主動(dòng)調(diào)用觸發(fā)的,除非是函數(shù)本身的實(shí)現(xiàn)不合理。但是在一些少數(shù)情況下,函數(shù)可能被很頻繁的調(diào)用,而造成大的性能問(wèn)題。
(1)函數(shù)被頻繁調(diào)用的場(chǎng)景
1.window.onresize事件 2.mousemove事件 3.上傳進(jìn)度
(2)函數(shù)節(jié)流的原理
解決函數(shù)觸發(fā)頻率太高的問(wèn)題,需要我們按照時(shí)間段來(lái)忽略一些事件請(qǐng)求。
(3)函數(shù)節(jié)流的代碼實(shí)現(xiàn)
詳情可以參考
Underscore.js#throttle
Underscore.js#debounce
簡(jiǎn)單實(shí)現(xiàn):
將即將被執(zhí)行的函數(shù)用steTimeout延時(shí)一段時(shí)間執(zhí)行。如果該次延時(shí)執(zhí)行還沒(méi)有完成,就忽略掉接下來(lái)調(diào)用該函數(shù)的請(qǐng)求。
var throttle = function ( fn, interval ) { var __self = fn, // 保存需要被延遲執(zhí)行的函數(shù)引用 timer, // 定時(shí)器 firstTime = true; // 是否是第一次調(diào)用 return function () { var args = arguments, __me = this; if ( firstTime ) { // 如果是第一次調(diào)用,不需延遲執(zhí)行 __self.apply(__me, args); return firstTime = false; } if ( timer ) { // 如果定時(shí)器還在,說(shuō)明前一次延遲執(zhí)行還沒(méi)有完成 return false; timer = setTimeout(function () { // 延遲一段時(shí)間執(zhí)行 clearTimeout(timer); timer = null; __self.apply(__me, args); }, interval || 500 ); }; }; window.onresize = throttle(function(){ console.log( 1 ); }, 500 );
另一種實(shí)現(xiàn)函數(shù)節(jié)流的方法-分時(shí)函數(shù)
某些函數(shù)確實(shí)是用戶主動(dòng)調(diào)用的,但是因?yàn)橐恍┛陀^的原因,這些函數(shù)會(huì)嚴(yán)重的影響頁(yè)面的性能。
一個(gè)例子就是創(chuàng)建QQ好友列表。如果一個(gè)好友列表用一個(gè)節(jié)點(diǎn)表示,當(dāng)我們?cè)陧?yè)面中渲染這個(gè)列表的時(shí)候,可能要一次性的網(wǎng)頁(yè)面中創(chuàng)建成百上千個(gè)節(jié)點(diǎn)。
var ary = []; for ( var i = 1; i <= 1000; i++ ){ ary.push( i ); // 假設(shè)ary 裝載了1000 個(gè)好友的數(shù)據(jù) }; var renderFriendList = function( data ){ for ( var i = 0, l = data.length; i < l; i++ ){ var div = document.createElement( "div" ); div.innerHTML = i; document.body.appendChild( div ); } }; renderFriendList( ary );
在短時(shí)間內(nèi)網(wǎng)頁(yè)面中大量添加DOM節(jié)點(diǎn)顯然也會(huì)讓瀏覽器吃不消。
這個(gè)問(wèn)題的解決方案之一是下面的timeChunk函數(shù):讓創(chuàng)建節(jié)點(diǎn)的工作分批進(jìn)行
//第一個(gè)參數(shù)是創(chuàng)建節(jié)點(diǎn)時(shí)需要的數(shù)據(jù),第二個(gè)參數(shù)封裝了創(chuàng)建節(jié)點(diǎn)邏輯的函數(shù),第三個(gè)參數(shù)表示每一批創(chuàng)建節(jié)點(diǎn)的數(shù)量。 var timeChunk = function( ary, fn, count ){ var obj, t; var len = ary.length; var start = function(){ for ( var i = 0; i < Math.min( count || 1, ary.length ); i++ ){ var obj = ary.shift(); fn( obj ); } }; return function(){ t = setInterval(function(){ if ( ary.length === 0 ){ // 如果全部節(jié)點(diǎn)都已經(jīng)被創(chuàng)建好 return clearInterval( t ); } start(); }, 200 ); // 分批執(zhí)行的時(shí)間間隔,也可以用參數(shù)的形式傳入 }; }; var ary = []; for ( var i = 1; i <= 1000; i++ ){ ary.push( i ); }; var renderFriendList = timeChunk( ary, function( n ){ var div = document.createElement( "div" ); div.innerHTML = n; document.body.appendChild( div ); }, 8 ); renderFriendList();6.惰性加載函數(shù)
在web開發(fā)中,因?yàn)闉g覽器之間的實(shí)現(xiàn)差異,一些嗅探工作總是不可避免。
var addEvent = function( elem, type, handler ){ if ( window.addEventListener ){ return elem.addEventListener( type, handler, false ); } if ( window.attachEvent ){ return elem.attachEvent( "on" + type, handler ); } };
這個(gè)函數(shù)的缺點(diǎn)是,當(dāng)它每次被調(diào)用的時(shí)候都會(huì)執(zhí)行里面的if條件分支。
下面這個(gè)函數(shù)雖然仍然有一些分支判斷,但是在第一次進(jìn)入條件分支之后,在函數(shù)內(nèi)部就會(huì)重寫這個(gè)函數(shù),重寫之后的函數(shù)就是我們希望的addEvent函數(shù)。
var addEvent = function(ele,type,handler){ if(window.addEventListener){ addEvent = function(ele,type,handler){ elem.addEventListener( type, handler, false ); } } if(window.attachEvent){ addEvent = function(ele,type,handler){ elem.attachEvent( "on" + type, handler ); } } addEvent(ele,type,handler); }三:結(jié)語(yǔ)
文章介紹的都是JS需要掌握的重點(diǎn)又是難點(diǎn)的知識(shí),需要多動(dòng)手實(shí)踐才能理解。有關(guān)相關(guān)知識(shí)的詳細(xì)講解,可以參考筆者的相關(guān)文章。當(dāng)然 ,最好的方式是去谷歌然后自己動(dòng)手實(shí)踐。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/82784.html
摘要:原文鏈接恰當(dāng)?shù)貙W(xué)習(xí)適合第一次編程和非的程序員持續(xù)時(shí)間到周前提無(wú)需編程經(jīng)驗(yàn)繼續(xù)下面的課程。如果你沒(méi)有足夠的時(shí)間在周內(nèi)完成全部的章節(jié),學(xué)習(xí)時(shí)間盡力不要超過(guò)周。你還不是一個(gè)絕地武士,必須持續(xù)使用你最新學(xué)到的知識(shí)和技能,盡可能地經(jīng)常持續(xù)學(xué)習(xí)和提高。 原文鏈接:How to Learn JavaScript Properly 恰當(dāng)?shù)貙W(xué)習(xí) JavaScript (適合第一次編程和非 JavaSc...
摘要:大家好,我叫,江湖人稱吃土小叉,目前擔(dān)任公司的前端負(fù)責(zé)人半年多了,一路上摸爬滾打,歷經(jīng)團(tuán)隊(duì)人員變動(dòng),近日頗有感觸,于是結(jié)合自己近半年的前端負(fù)責(zé)人實(shí)踐經(jīng)驗(yàn),權(quán)當(dāng)作一個(gè)學(xué)習(xí)記錄,整理歸納一下小作坊團(tuán)隊(duì)前端負(fù)責(zé)人的修煉要點(diǎn)大部分只是記錄了關(guān)鍵詞, 大家好,我叫XX,江湖人稱吃土小2叉,目前擔(dān)任公司的前端負(fù)責(zé)人半年多了,一路上摸爬滾打,歷經(jīng)團(tuán)隊(duì)人員變動(dòng),近日頗有感觸,于是結(jié)合自己近半年的前端負(fù)...
摘要:我們常常會(huì)收到一些有趣的問(wèn)題,但大多數(shù)問(wèn)題都是常見(jiàn)問(wèn)題。我創(chuàng)建這個(gè)資源為了幫助學(xué)習(xí)者遇到這些常見(jiàn)的問(wèn)題時(shí)提供一定幫助。這些是表示沒(méi)有任何子節(jié)點(diǎn)的元素的標(biāo)記。不綁定處理程序方法我把這個(gè)留到最后,因?yàn)檫@是一個(gè)大問(wèn)題,一個(gè)很常見(jiàn)的問(wèn)題。 在 jsComplete,我們管理一個(gè)專門用于幫助編程學(xué)習(xí)者 slack 帳戶。我們常常會(huì)收到一些有趣的問(wèn)題,但大多數(shù)問(wèn)題都是常見(jiàn)問(wèn)題。 我創(chuàng)建這個(gè)資源為了...
閱讀 1992·2021-11-24 09:39
閱讀 986·2021-11-11 16:55
閱讀 1441·2021-10-09 09:43
閱讀 1427·2021-10-08 10:17
閱讀 1661·2021-08-25 09:41
閱讀 431·2019-08-30 13:02
閱讀 634·2019-08-29 15:14
閱讀 1011·2019-08-29 13:53