1.關於宣告提前
- js中,允許變數使用在宣告之前,不過此時為undefined
console.log(a); // undefined
var a = 1;
複製程式碼
- 變數不管在哪裡宣告,都會在任意程式碼執行前處理。在es5 strict mode,賦值給未宣告的變數將報錯。
- 顯式宣告:帶有關鍵字 var 的宣告,作用域就是當前執行上下文,即某個函式,或者全域性作用域(宣告在函式外,即變數會掛在在window物件上)
- 隱式宣告:如果一個變數沒有使用var宣告,window便擁有了該屬性,因此這個變數的作用域不屬於某一個函式體,而是window物件。
function varscope(){
foo = "I'm in function"; //直接賦值 沒有宣告
console.log(foo);//I'm in function
}
varscope();
console.log(window.foo); //I'm in function
複製程式碼
- 關於宣告提前的例子
function testOrder(arg) {
console.log(arg); // arg是形參,不會被重新定義
console.log(a); // 因為函式宣告比變數宣告優先順序高,所以這裡a是函式
var arg = 'hello'; // var arg;變數宣告被忽略, arg = 'hello'被執行
var a = 10; // var a;被忽視; a = 10被執行,a變成number
function a() {
console.log('fun');
} // 被提升到作用域頂部
console.log(a); // 輸出10
console.log(arg); // 輸出hello
};
testOrder('hi');
/* 輸出:
hi
function a() {
console.log('fun');
}
10
hello
*/
複製程式碼
2.關於作用域
-
函式作用域
函式作用域內,對外是封閉的,從外層的作用域無法直接訪問函式內部的作用域
function bar() { var testValue = 'inner'; } console.log(testValue); // 報錯:ReferenceError: testValue is not defined 複製程式碼
通過 return 訪問函式內部變數:
function bar(value) { var testValue = 'inner'; return testValue + value; } console.log(bar('fun'));// "innerfun" 複製程式碼
通過 閉包 訪問函式內部變數:
function bar(value) { var testValue = 'inner'; var rusult = testValue + value; function innser() { return rusult; }; return innser(); } console.log(bar('fun')); // "innerfun" 複製程式碼
-
立即執行函式作用域
這是個很實用的函式,很多庫都用它分離全域性作用域,形成一個單獨的函式作用域;它能夠自動執行
(function() { //... })()
裡面包裹的內容,能夠很好地消除全域性變數的影響;<script type="text/javascript"> (function() { var testValue = 123; var testFunc = function () { console.log('just test'); }; })(); console.log(window.testValue); // undefined console.log(window.testFunc); // undefined </script> 複製程式碼
-
塊級作用域
在 ES6 之前,是沒有塊級作用域的概念的。
for(var i = 0; i < 5; i++) { // ... } console.log(i) // 5 複製程式碼
很明顯,用 var 關鍵字宣告的變數,在 for 迴圈之後仍然被儲存這個作用域裡;
這可以說明: for() { }仍然在,全域性作用域裡,並沒有產生像函式作用域一樣的封閉效果;
如果想要實現 塊級作用域 那麼我們需要用 let 關鍵字宣告
for(let i = 0; i < 5; i++) { // ... } console.log(i) // 報錯:ReferenceError: i is not defined 複製程式碼
在 for 迴圈執行完畢之後 i 變數就被釋放了,它已經消失了!!!
同樣能形成塊級作用域的還有 const 關鍵字:
if (true) { const a = 'inner'; } console.log(a); // 報錯:ReferenceError: a is not defined 複製程式碼
let 和 const 關鍵字,建立塊級作用域的條件是必須有一個 { } 包裹:
-
詞法作用域
當我們要使用宣告的變數時:JS引擎總會從最近的一個域,向外層域查詢
testValue = 'outer'; function afun() { var testValue = 'middle'; console.log(testValue);// "middle" function innerFun() { var testValue = 'inner'; console.log(testValue);// "inner" } return innerFun(); } afun(); console.log(testValue); // "outer" 複製程式碼
當 JS 引擎查詢變數時,發現全域性的 testValue 離得更近一些,則取全域性的testValue的值即 outer
var testValue = 'outer'; function foo() { console.log(testValue);// "outer" } function bar() { var testValue = 'inner'; foo(); } bar(); 複製程式碼
-
動態作用域
動態作用域,作用域是基於呼叫棧的,而不是程式碼中的作用域巢狀;
作用域巢狀,有詞法作用域一樣的特性,查詢變數時,總是尋找最近的作用域;
3.關於this
關鍵字
在一個函式中,this總是指向當前函式的所有者物件,this總是在執行時才能確定其具體的指向, 也才能知道它的呼叫物件。
window.name = "window";
function f(){
console.log(this.name);
}
f();//window
var obj = {name:'obj'};
f.call(obj); //obj
複製程式碼
在執行f()時,此時f()的呼叫者是window物件,因此輸出”window”
f.call(obj) 是把f()放在obj物件上執行,相當於obj.f(),此時f中的this就是obj,所以輸出的是”obj”
對比以下兩段程式碼:
var foo = "window";
var obj = {
foo : "obj",
getFoo : function(){
return function(){
return this.foo;
};
}
};
var f = obj.getFoo();
f(); //輸出'window'
/* 分析
執行var f = obj.getFoo()返回的是一個匿名函式,相當於:
var f = function(){
return this.foo;
}
f() 相當於window.f(), 因此f中的this指向的是window物件,this.foo相當於window.foo, 所以f()返回"window"
*/
複製程式碼
var foo = "window";
var obj = {
foo : "obj",
getFoo : function(){
var that = this;
return function(){
return that.foo;
};
}
};
var f = obj.getFoo();
f(); //輸出'obj'
/* 分析
執行var f = obj.getFoo() 同樣返回匿名函式,即:
var f = function(){
return that.foo;
}
唯一不同的是f中的this變成了that, 要知道that是哪個物件之前,先確定f的作用域鏈:f->getFoo->window 並在該鏈條上查詢that,此時可以發現that指代的是getFoo中的this, getFoo中的this指向其執行時的呼叫者,從var f = obj.getFoo() 可知此時this指向的是obj物件,因此that.foo 就相當於obj.foo,所以f()返回"obj"
*/
複製程式碼
4.關於箭頭函式
箭頭函式有兩種格式:
var fn = x => x * x; //只包含一個表示式,連{ ... }和return都省略掉了
x => { //還有一種可以包含多條語句,這時候就不能省略{ ... }和return:
if (x > 0) {
return x * x;
}
else {
return - x * x;
}
}
複製程式碼
箭頭函式看上去是匿名函式的一種簡寫,但實際上,箭頭函式和匿名函式有個明顯的區別:箭頭函式內部的this是詞法作用域,由上下文確定。
對比以下兩個例子
var obj = {
birth: 1990,
getAge: function () {
var b = this.birth; // 1990
var fn = function () {
return new Date().getFullYear() - this.birth; // this指向window或undefined
};
return fn();
}
};
複製程式碼
//箭頭函式完全修復了this的指向,this總是指向詞法作用域,也就是外層呼叫者obj:
var obj = {
birth: 1990,
getAge: function () {
var b = this.birth; // 1990
var fn = () => new Date().getFullYear() - this.birth; // this指向obj物件
return fn();
}
};
obj.getAge(); // 25
//由於this在箭頭函式中已經按照詞法作用域繫結了,所以,用call()或者apply()呼叫箭頭函式時,無法對this進行繫結,即傳入的第一個引數被忽略:
var obj = {
birth: 1990,
getAge: function (year) {
var b = this.birth; // 1990
var fn = (y) => y - this.birth; // this.birth仍是1990
return fn.call({birth:2000}, year);
}
};
obj.getAge(2015); // 25
複製程式碼
箭頭函式與this結合例子
var name = 'window'
var person1 = {
name: 'person1',
show1: function () {
console.log(this.name)
},
show2: () => console.log(this.name),
show3: function () {
return function () {
console.log(this.name)
}
},
show4: function () {
return () => console.log(this.name)
}
}
var person2 = { name: 'person2' }
person1.show1() //person1
person1.show1.call(person2) //person2
person1.show2() //window
person1.show2.call(person2) //window
person1.show3()() //window
/*person1.show3是一個高階函式,它返回了一個函式,分步走的話,應該是這樣:
var func = person3.show()
func()
從而導致最終呼叫函式的執行環境是window,但並不是window物件呼叫了它。所以說,this總是指向呼叫該函式的物件,這句話還得補充一句:在全域性函式中,this等於window。
*/
person1.show3().call(person2)//person2 通過person2呼叫了最終的列印方法
person1.show3.call(person2)()//window 先通過person2呼叫了person1的高階函式,然後再在全域性環境中執行了該列印方法。
person1.show4()() //person1 箭頭函式體內的this物件,就是定義時所在的物件,而不是使用時所在的物件
person1.show4().call(person2) //person1 箭頭函式體內的this物件,就是定義時所在的物件,而不是使用時所在的物件,用person2去呼叫這個箭頭函式,它指向的還是person1。
person1.show4.call(person2)() //person2 箭頭函式的this指向的是誰呼叫箭頭函式的外層function,箭頭函式的this就是指向該物件,如果箭頭函式沒有外層函式,則指向window
複製程式碼