摘要:作用域沒有塊級作用域盡量不要在塊中聲明變量。只有函數級作用域作用域鏈自由變量當前作用域沒有定義的變量即為自由變量。自由變量會去其父級作用域找。
1. 題目
說一下對變量提升的理解
說明this的幾種不同使用場景
創建10個a標簽,點擊的時候彈出來相應的序號
如何理解作用域
實際開發中閉包的應用
手動實現call apply bind
2. 知識點 2.1 執行上下文范圍:一段script或者一個函數
全局:變量定義、函數聲明 script
函數:變量定義、函數聲明、this、arguments (執行之前)
函數聲明和函數表達式的區別:
a(); //報錯 函數表達式 變量聲明 會提前。 var a = function(){} b(); // 不報錯 函數聲明 function b(){}
變量定義時會默認把他的變量聲明提升:(僅限于他的執行上下文,比如一段script和一個函數中)
console.log(a); var a = 0;
實際上是
var a; console.log(a); a = 0;2.2 this
this要在執行時才能確認,定義時無法確認。
var a = { name:"a", fn:function(){ console.log(this.name); } } a.fn(); // a a.fn.apply({name:"b"}); // b a.fn.call({name:"b"}); var fn1 = a.fn(); fn1(); // undefined
this的使用場景
構造函數中(指向構造的對象)
function Fun(name){ this.name = name; } var f = new Fun("a"); console.log(f.name);
對象屬性中(指向該對象)
普通函數中(指向window)
call apply bind
都是用來改變一個函數的this指向,用法略有不同。
call:后面的參數為調用函數的參數列表
function greet(name) { console.log(this.animal,name); } var obj = { animal: "cats" }; greet.call(obj,"貓咪");
apply:第二個參數為調用函數的參數數組
function greet(name) { console.log(this.animal,name); } var obj = { animal: "cats" }; greet.apply(obj,["貓咪"]);
bind:當綁定函數被調用時,bind傳入的參數會被插入到目標函數的參數列表的開始位置,傳遞給綁定函數的參數會跟在它們后面。
var fun = function (name1,name2){ console.log(this); console.log(name); }.bind({a:1},"name1"); fun("name2");
arguments中的this:
var length = 10; function fn(){ alert(this.length) } var obj = { length: 5, method: function(fn) { arguments[0]() } }
obj.method(fn)//輸出1
這里沒有輸出5,也沒有輸出10,反而輸出了1,有趣。這里arguments是javascript的一個內置對象(可以參見mdn:arguments - JavaScript),是一個類數組(就是長的比較像數組,但是欠缺一些數組的方法,可以用slice.call轉換,具體參見上面的鏈接),其存儲的是函數的參數。也就是說,這里arguments[0]指代的就是你method函數的第一個參數:fn,所以arguments[0]()的意思就是:fn()。
不過這里有個疑問,為何這里沒有輸出5呢?我method里面用this,不應該指向obj么,至少也會輸出10呀,這個1是鬧哪樣?
實際上,這個1就是arguments.length,也就是本函數參數的個數。為啥這里的this指向了arguments呢?因為在Javascript里,數組只不過使用數字做屬性名的方法,也就是說:arguments[0]()的意思,和arguments.0()的意思差不多(當然這么寫是不允許的),你更可以這么理解:
arguments = { 0: fn, //也就是 functon() {alert(this.length)} 1: 第二個參數, //沒有 2: 第三個參數, //沒有 ..., length: 1 //只有一個參數 }
所以這里alert出來的結果是1。
如果要輸出5應該咋寫呢?直接 method: fn 就行了。
2.3 作用域沒有塊級作用域
if(true){ var name = "test" } console.log(name);
盡量不要在塊中聲明變量。
只有函數級作用域
2.4 作用域鏈自由變量 當前作用域沒有定義的變量 即為自由變量。
自由變量會去其父級作用域找。是定義時的父級作用域,而不是執行。
var a = 100; function f1(){ var b = 200; function f2(){ var c = 300; console.log(a); //自由變量 console.log(b); //自由變量 console.log(c); } f2(); }; f1();2.5 閉包
一個函數中嵌套另外一個函數,并且將這個函數return出去,然后將這個return出來的函數保存到了一個變量中,那么就創建了一個閉包。
閉包的兩個使用場景
1.函數作為返回值
function fun(){ var a = 0; return function(){ console.log(a); //自由變量,去定義時的父級作用域找 } } var f1 = fun(); a = 1000; f1();
2.函數作為參數
function fun(){ var a = 0; return function(){ console.log(a); //自由變量,去定義時的父級作用域找 } } function fun2(f2){ a = 10000 f2(); } var f1 = fun(); fun2(f1);
具體解釋看 高級-閉包中的說明
閉包的兩個作用:
能夠讀取其他函數內部變量的函數
可以讓函數內部的變量一直保存在內存中
實際應用場景1:
閉包可以將一些不希望暴露在全局的變量封裝成“私有變量”。
假如有一個計算乘積的函數,mult函數接收一些number類型的參數,并返回乘積結果。為了提高函數性能,我們增加緩存機制,將之前計算過的結果緩存起來,下次遇到同樣的參數,就可以直接返回結果,而不需要參與運算。這里,存放緩存結果的變量不需要暴露給外界,并且需要在函數運行結束后,仍然保存,所以可以采用閉包。
上代碼:
function calculate(param){ var cache = {}; return function(){ if(!cache.parame){ return cache.param; }else{ //緩存計算.... //cache.param = result //下次訪問直接取 } } }
實際應用場景2
延續局部變量的壽命
img 對象經常用于進行數據上報,如下所示:
var report = function( src ){ var img = new Image(); img.src = src; }; report( "http://xxx.com/getUserInfo" );
但是通過查詢后臺的記錄我們得知,因為一些低版本瀏覽器的實現存在 bug,在這些瀏覽器
下使用 report 函數進行數據上報會丟失 30%左右的數據,也就是說, report 函數并不是每一次
都成功發起了 HTTP 請求。
丟失數據的原因是 img 是 report 函數中的局部變量,當 report 函數的
調用結束后, img 局部變量隨即被銷毀,而此時或許還沒來得及發出 HTTP 請求,所以此次請求
就會丟失掉。
現在我們把 img 變量用閉包封閉起來,便能解決請求丟失的問題:
var report = (function(){ var imgs = []; return function( src ){ var img = new Image(); imgs.push( img ); img.src = src; } })();
閉包缺點:浪費資源!
3. 題目解答 3.1 說一下對變量提升的理解變量定義和函數聲明
注意函數聲明和函數表達式的區別
變量定義時會默認把他的變量聲明提升:(僅限于他的執行上下文,比如一段script和一個函數中)
console.log(a); var a = 0;
實際上是
var a; console.log(a); a = 0;3.2 說明this的幾種不同使用場景
構造函數中(指向構造的對象)
對象屬性中(指向該對象)
普通函數中(指向window)
call apply bind
3.3 創建10個a標簽,點擊的時候彈出來相應的序號實現方法1:用let聲明i
var body = document.body; console.log(body); for (let i = 0; i < 10; i++) { let obj = document.createElement("i"); obj.innerHTML = i + "
"; body.appendChild(obj); obj.addEventListener("click",function(){ alert(i); }) }
實現方法2 包裝作用域
var body = document.body; console.log(body); for (var i = 0; i < 10; i++) { (function (i) { var obj = document.createElement("i"); obj.innerHTML = i + "3.4 實際開發中閉包的應用
"; body.appendChild(obj); obj.addEventListener("click", function () { alert(i); }) })(i) }
能夠讀取其他函數內部變量的函數
可以讓函數內部的變量一直保存在內存中
封裝變量,權限收斂
應用1
var report = (function(){ var imgs = []; return function( src ){ var img = new Image(); imgs.push( img ); img.src = src; } })();
用于防止變量銷毀。
應用2
function isFirstLoad() { var arr = []; return function (str) { if (arr.indexOf(str) >= 0) { console.log(false); } else { arr.push(str); console.log(true); } } } var fun = isFirstLoad(); fun(10); fun(10);
將arr封裝在函數內部,禁止隨意修改,防止變量銷毀。
3.5 手動實現call apply bindcontext 為可選參數,如果不傳的話默認上下文為 window;
context 創建一個 Symbol 屬性,調用后即刪除,不會影響context
Function.prototype.myCall = function (context) { if (typeof this !== "function") { return undefined; // 用于防止 Function.prototype.myCall() 直接調用 } context = context || window; const fn = Symbol(); context[fn] = this; const args = [...arguments].slice(1); const result = context[fn](...args); delete context[fn]; return result; }
apply實現類似call,參數為數組
Function.prototype.myApply = function (context) { if (typeof this !== "function") { return undefined; // 用于防止 Function.prototype.myCall() 直接調用 } context = context || window; const fn = Symbol(); context[fn] = this; let result; if (arguments[1] instanceof Array) { result = context[fn](...arguments[1]); } else { result = context[fn](); } delete context[fn]; return result; }
1.判斷是否為構造函數調用
2.注意參數要插入到目標函數的開始位置
Function.prototype.myBind = function (context) { if (typeof this !== "function") { throw new TypeError("Error") } const _this = this const args = [...arguments].slice(1) return function F() { // 判斷是否用于構造函數 if (this instanceof F) { return new _this(...args, ...arguments) } return _this.apply(context, args.concat(...arguments)) } }
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/101742.html
摘要:閉包面試題解由于作用域鏈機制的影響,閉包只能取得內部函數的最后一個值,這引起的一個副作用就是如果內部函數在一個循環中,那么變量的值始終為最后一個值。 (關注福利,關注本公眾號回復[資料]領取優質前端視頻,包括Vue、React、Node源碼和實戰、面試指導) 本周正式開始前端進階的第二期,本周的主題是作用域閉包,今天是第8天。 本計劃一共28期,每期重點攻克一個面試重難點,如果你還不了...
摘要:使用上一篇文章的例子來說明下自由變量進階期深入淺出圖解作用域鏈和閉包訪問外部的今天是今天是其中既不是參數,也不是局部變量,所以是自由變量。 (關注福利,關注本公眾號回復[資料]領取優質前端視頻,包括Vue、React、Node源碼和實戰、面試指導) 本周正式開始前端進階的第二期,本周的主題是作用域閉包,今天是第7天。 本計劃一共28期,每期重點攻克一個面試重難點,如果你還不了解本進階計...
摘要:寫在前面對于一個前端開發者,應該沒有不知道作用域的。欺騙詞法作用域有兩個機制可以欺騙詞法作用域和。關于你不知道的的第一部分作用域和閉包已經結束了,但是,更新不會就此止住未完待續 這是《你不知道的JavaScript》的第一部分。 本系列持續更新中,Github 地址請查閱這里。 寫在前面 對于一個前端開發者,應該沒有不知道作用域的。它是一個既簡單有復雜的概念,簡單到每行代碼都有它的影子...
摘要:注意由于閉包會額外的附帶函數的作用域內部匿名函數攜帶外部函數的作用域,因此,閉包會比其它函數多占用些內存空間,過度的使用可能會導致內存占用的增加。 作用域和作用域鏈是javascript中非常重要的特性,對于他們的理解直接關系到對于整個javascript體系的理解,而閉包又是對作用域的延伸,也是在實際開發中經常使用的一個特性,實際上,不僅僅是javascript,在很多語言中都...
摘要:本篇收錄了一些面試中經常會遇到的經典面試題,并且都給出了我在網上收集的答案。網頁的行為層負責回答內容應該如何對事件做出反應這一問題。 本篇收錄了一些面試中經常會遇到的經典面試題,并且都給出了我在網上收集的答案。眼看新的一年馬上就要開始了,相信很多的前端開發者會有一些跳槽的悸動,通過對本篇知識的整理以及經驗的總結,希望能幫到更多的前端面試者。(如有錯誤或更好的答案,歡迎指正,水平有限,望...
閱讀 3392·2021-09-22 15:17
閱讀 2740·2021-09-02 15:15
閱讀 1750·2019-08-30 15:54
閱讀 2001·2019-08-30 14:02
閱讀 2529·2019-08-29 16:58
閱讀 2988·2019-08-29 16:08
閱讀 1330·2019-08-26 12:24
閱讀 1653·2019-08-26 10:41