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