說一下對變量提高的理解javascript
說明this的幾種不一樣使用場景java
建立10個a標籤,點擊的時候彈出來相應的序號數組
如何理解做用域瀏覽器
實際開發中閉包的應用緩存
手動實現call apply bind閉包
範圍:一段script或者一個函數app
全局:變量定義、函數聲明 script函數
函數:變量定義、函數聲明、this、arguments (執行以前)性能
函數聲明和函數表達式的區別:this
a(); //報錯 函數表達式 變量聲明 會提早。 var a = function(){} b(); // 不報錯 函數聲明 function b(){}
變量定義時會默認把他的變量聲明提高:(僅限於他的執行上下文,好比一段script和一個函數中)
console.log(a); var a = 0;
其實是
var a; console.log(a); a = 0;
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 就好了。
沒有塊級做用域
if(true){ var name = "test" } console.log(name);
儘可能不要在塊中聲明變量。
只有函數級做用域
自由變量 當前做用域沒有定義的變量 即爲自由變量。
自由變量會去其父級做用域找。是定義時的父級做用域,而不是執行。
var a = 100; function f1(){ var b = 200; function f2(){ var c = 300; console.log(a); //自由變量 console.log(b); //自由變量 console.log(c); } f2(); }; f1();
一個函數中嵌套另一個函數,而且將這個函數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; } })();
閉包缺點:浪費資源!
變量定義和函數聲明
注意函數聲明和函數表達式的區別
變量定義時會默認把他的變量聲明提高:(僅限於他的執行上下文,好比一段script和一個函數中)
console.log(a); var a = 0;
其實是
var a; console.log(a); a = 0;
實現方法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 + '<br>'; 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 + '<br>'; 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封裝在函數內部,禁止隨意修改,防止變量銷燬。
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)) } }