js中聲明提高、做用域(鏈)、`this`關鍵字和箭頭函數

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.關於做用域

  • 函數做用域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();
    複製代碼
  • 動態做用域

    動態做用域,做用域是基於調用棧的,而不是代碼中的做用域嵌套;

    做用域嵌套,有詞法做用域同樣的特性,查找變量時,老是尋找最近的做用域;

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;
        };
        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

相關文章
相關標籤/搜索