console.log(a); // undefined
var a = 1;
複製代碼
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 */
複製代碼
函數做用域javascript
函數做用域內,對外是封閉的,從外層的做用域沒法直接訪問函數內部的做用域html
function bar() {
var testValue = 'inner';
}
console.log(testValue); // 報錯:ReferenceError: testValue is not defined
複製代碼
經過 return 訪問函數內部變量:java
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"
複製代碼
當即執行函數做用域app
這是個很實用的函數,不少庫都用它分離全局做用域,造成一個單獨的函數做用域;它可以自動執行(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>
複製代碼
塊級做用域ui
在 ES6 以前,是沒有塊級做用域的概念的。this
for(var i = 0; i < 5; i++) {
// ...
}
console.log(i) // 5
複製代碼
很明顯,用 var 關鍵字聲明的變量,在 for 循環以後仍然被保存這個做用域裏;es5
這能夠說明: for() { }仍然在,全局做用域裏,並無產生像函數做用域同樣的封閉效果;spa
若是想要實現 塊級做用域 那麼咱們須要用 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();
複製代碼
動態做用域
動態做用域,做用域是基於調用棧的,而不是代碼中的做用域嵌套;
做用域嵌套,有詞法做用域同樣的特性,查找變量時,老是尋找最近的做用域;
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" */
複製代碼
箭頭函數有兩種格式:
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;
};
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(); // 29
//因爲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
複製代碼
例題:
var number = 5;
var obj = {
number: 3,
fn1: (function () {
var number;
this.number *= 2;
number = number * 2;
number = 3;
return function () {
var num = this.number;
this.number *= 2;
console.log(num);
number *= 3;
console.log(number);
}
})()
}
var fn1 = obj.fn1;
fn1.call(null);
obj.fn1();
console.log(window.number);
複製代碼
輸出10 9 3 27 20