函式宣告與表示式
我們先來看一個栗子
foo(); // 正常執行,因為foo在程式碼執行前已經被建立
function foo() {}
複製程式碼
方法會在執行前被解析,因此它存在於當前上下文的任意一個地方, 即使在函式定義體的上面被呼叫也是對的。
函式賦值表示式
foo; // 'undefined'
foo(); // 出錯:TypeError
var foo = function() {};
複製程式碼
由於 var 定義了一個宣告語句,對變數 foo 的解析是在程式碼執行之前,因此 foo 變數已經被定義過了。 但是由於賦值語句只在執行時執行,因此在相應程式碼執行之前, foo 的值預設為 undefined。
如果內部函式和外部函式的變數名重名
JavaScript的函式在查詢變數時從自身函式定義開始,從“內”向“外”查詢。如果內部函式定義了與外部函式重名的變數,則內部函式的變數將“遮蔽”外部函式的變數。
this 的工作原理(劃重點!)
this在js中一共有五種不同的情況
1.全域性範圍內:當在全部範圍內使用 this,它將會指向全域性物件。
2.函式呼叫:這裡 this 也會指向全域性物件。
foo();
複製程式碼
3.方法呼叫:這個例子中,this 指向 test 物件。
test.foo();
複製程式碼
4.呼叫建構函式:如果函式傾向於和 new 關鍵詞一塊使用,則我們稱這個函式是 建構函式。 在函式內部,this 指向新建立的物件。
new foo();
複製程式碼
5.顯式的設定this:
function foo(a, b, c) {}
var bar = {};
foo.apply(bar, [1, 2, 3]); // 陣列將會被擴充套件,如下所示
foo.call(bar, 1, 2, 3); // 傳遞到foo的引數是:a = 1, b = 2, c = 3
複製程式碼
當使用 call 或者 apply 方法時,函式內的 this 將會被 顯式設定為函式呼叫的第一個引數,在foo 函式內 this 被設定成了 bar。
補充!!:
1.作為函式直接呼叫,非嚴格模式下,this指向window,嚴格模式下,this指向undefined;
2.作為某物件的方法呼叫,this通常指向呼叫的物件。
3.使用apply、call、bind 可以繫結this的指向。
4.在建構函式中,this指向新建立的物件
5.箭頭函式沒有單獨的this值,this在箭頭函式建立時確定,它與宣告所在的上下文相同。
如果對一個函式進行多次 bind,那麼上下文會是什麼呢?
let a = {}
let fn = function () { console.log(this) }
fn.bind().bind(a)() // => ?
複製程式碼
不管我們給函式 bind 幾次,fn 中的 this 永遠由第一次 bind 決定,所以結果永遠是 window。
為了方便記憶這裡給出一張圖
瞭解了這些小知識,下面我們再來看一個栗子
Foo.method = function() {
function test() {
// this 將會被設定為全域性物件
}
test();
}
複製程式碼
我們以為test中的this會指向Foo物件,其實不是。那麼我們怎麼要解決這個問題呢?通常是在函式外部寫一個var that = this,that只是我們隨意起的名字,不過這個名字被廣泛的用來指向外部的 this 物件。
Foo.method = function() {
var that = this;
function test() {
// 使用 that 來指向 Foo 物件
}
test();
}
複製程式碼
為了在 test 中獲取對 Foo 物件的引用,我們需要在 method 函式內部建立一個區域性變數指向 Foo 物件。
除了這樣解決,在es6的新特性裡新增了箭頭函式,它能很好的解決這個問題。另外箭頭函式還用簡化程式碼量的特點。
什麼是箭頭函式
() => console.log('Hello')
複製程式碼
特點:
1.不繫結this(箭頭函式不會建立自己的this,它只會從自己的作用域鏈的上一層繼承this)
2.不繫結arguments(在箭頭函式中我們是不能直接使用arguments物件的,得不到結果)
3.更簡化的程式碼語法
注意:即使有這麼多好處,但同時最好不要在定義物件方法、定義原型方法、定義建構函式、定義事件回撥函式中使用箭頭函式。
閉包
當內部函式被儲存到外部時,將會生成閉包。生成閉包後,內部函式依舊可以訪問其所在的外部函式的變數。
閉包問題的解決方法:立即執行函式、let
詳細解釋:
當函式執行時,會建立一個稱為執行期上下文的內部物件(AO),執行期上下文定義了一個函式執行時的環境。
函式還會獲得它所在作用域的作用域鏈,是儲存函式能夠訪問的所有執行期上下文物件的集合,即這個函式中能夠訪問到的東西都是沿著作用域鏈向上查詢直到全域性作用域。
函式每次執行時對應的執行期上下文都是獨一無二的,當函式執行完畢,函式都會失去對這個作用域鏈的引用,JS的垃圾回收機制是採用引用計數策略,如果一塊記憶體不再被引用了那麼這塊記憶體就會被釋放。
但是,當閉包存在時,即內部函式保留了對外部變數的引用時,這個作用域鏈就不會被銷燬,此時內部函式依舊可以訪問其所在的外部函式的變數,這就是閉包。
先看兩個例子,兩個例子都列印5個5
for (var i = 0; i < 5; i++) {
setTimeout(function timer() {
console.log(i)
}, i * 100)
}
function test() {
var a = [];
for (var i = 0; i < 5; i++) {
a[i] = function () {
console.log(i);
}
}
return a;
}
var myArr = test();
for(var j=0;j<5;j++)
{
myArr[j]();
}
複製程式碼
解決方法: 使用立即執行函式
for (var i = 0; i < 5; i++) {
;(function(i) {
setTimeout(function timer() {
console.log(i)
}, i * 100)
})(i)
}
function test(){
var arr=[];
for(i=0;i<10;i++)
{
(function(j){
arr[j]=function(){
console.log(j);
}
})(i)
}
return arr;
}
var myArr=test();
for(j=0;j<10;j++)
{
myArr[j]();
}
複製程式碼
閉包-封裝私有變數
function Counter() {
let count = 0;
this.plus = function () {
return ++count;
}
this.minus = function () {
return --count;
}
this.getCount = function () {
return count;
}
}
const counter = new Counter();
counter.puls();
counter.puls();
console.log(counter.getCount())
複製程式碼
待補充,持續更新中...
注:參考部分引自JavaScript 祕密花園