首頁 > 軟體

一篇文章告訴你JavaScript的作用域和函數該這樣理解

2022-02-11 19:07:06

一、作用域

【解釋】: 規定了變數能夠被存取的“範圍”,離開了這個“範圍”變數便不能被存取。

【分類】:

區域性作用域全域性作用域

1.1 區域性作用域

【分類】:

數作用域塊作用域

1、函數作用域

【解釋】: 在函數內部宣告的變數只能在函數內部被存取,外部無法直接存取。

【範例】:

<script>
  // 宣告 counter 函數
  function counter(x, y) {
    // 函數內部宣告的變數
    let s = x + y;
    console.log(s); // 18
  }
  // 呼叫 counter 函數
  counter(10, 8);
  // 存取變數 s
  console.log(s); // 報錯 外部無法存取函數內部的變數
</script>

【總結】:

  • 函數內部宣告的變數,在函數外部無法被存取
  • 函數的引數也是函數內部的區域性變數
  • 不同函數內部宣告的變數無法互相存取
  • 函數執行完畢後,函數內部的變數實際被清空了

2、塊作用域

【解釋】: 在 JavaScript 中使用 {} 包裹的程式碼稱為程式碼塊,程式碼塊內部宣告的變數外部將【有可能】無法被存取。

【範例】:

<script>
  {
    // age 只能在該程式碼塊中被存取
    let age = 18;
    console.log(age); // 正常
  }
  // 超出了 age 的作用域
  console.log(age); // 報錯
  let flag = true;
  if(flag) {
    // str 只能在該程式碼塊中被存取
    let str = 'hello world!';
    console.log(str); // 正常
  }
  // 超出了 age 的作用域
  console.log(str); // 報錯
  for(let t = 1; t <= 6; t++) {
    // t 只能在該程式碼塊中被存取
    console.log(t); // 正常
  }
  // 超出了 t 的作用域
  console.log(t); // 報錯
</script>

【常數值】: JavaScript 中除了變數外還有常數,常數與變數本質的區別是 【常數必須要有值且不允許被重新賦值】,常數值為物件時其屬性和方法允許重新賦值。

【範例】:

<script>
  // 必須要有值
  const version = '1.0';
  // 不能重新賦值
  // version = '1.1';
  // 常數值為物件型別
  const user = {
    name: '小明',
    age: 18
  }
  // 不能重新賦值
  user = {};
  // 屬性和方法允許被修改
  user.name = '小小明';
  user.gender = '男';
</script>

【總結之let、var、const】:

let 宣告的變數會產生塊作用域,var 不會產生塊作用域

const宣告的常數也會產生塊作用域

不同程式碼塊之間的變數無法互相存取

推薦使用 let 或 const

開發中 let 和 const 經常不加區分的使用,如果擔心某個值會不小被修改時,則只能使用 const 宣告成常數。

關鍵字塊級作用域變數提升初始值更改值通過window呼叫
let× √-×
const× √××
var×-

1.2 全域性作用域

【解釋】: <script> 標籤和 js 檔案的【最外層】就是所謂的全域性作用域,在此宣告的變數在函數內部也可以被存取。

【範例】:

<script>
  // 此處是全域性
  function sayHi() {
    // 此處為區域性
  }
  // 此處為全域性
</script>
<script>
  // 此處是全域性
  function sayHi() {
    // 此處為區域性
  }
  // 此處為全域性
</script>

全域性作用域中宣告的變數,任何其它作用域都可以被存取

<script>
    // 全域性變數 name
    let name = '小明';
  	// 函數作用域中存取全域性
    function sayHi() {
      // 此處為區域性
      console.log('你好' + name);
    }
    // 全域性變數 flag 和 x
    let flag = true;
    let x = 10;
  	// 塊作用域中存取全域性
    if(flag) {
      let y = 5;
      console.log(x + y); // x 是全域性的
    }
</script>

【總結】:

  • 為 window 物件動態新增的屬性預設也是全域性的,不推薦!
  • 函數中未使用任何關鍵字宣告的變數為全域性變數,不推薦!!!
  • 儘可能少的宣告全域性變數,防止全域性變數被汙染

1.3 作用域鏈

【解釋】: 函數內部允許建立新的函數,f 函數內部建立的新函數 g,會產生新的函數作用域,由此可知作用域產生了巢狀的關係。作用域鏈本質上是底層的變數查詢機制,在函數被執行時,會優先查詢當前函數作用域中查詢變數,如果當前作用域查詢不到則會依次逐級查詢父級作用域直到全域性作用域

【範例】:

<script>
  // 全域性作用域
  let a = 1;
  let b = 2;
  // 區域性作用域
  function f() {
    let c;
    // 區域性作用域
    function g() {
      let d = 'yo';
    }
  }
</script>
<script>
  // 全域性作用域
  let a = 1;
  let b = 2;
  // 區域性作用域
  function f() {
    let c;
    // let a = 10;
    console.log(a); // 1 或 10
    console.log(d); // 報錯
    // 區域性作用域
    function g() {
      let d = 'yo';
      // let b = 20;
      console.log(b); // 2 或 20
    }
    // 呼叫 g 函數
    g()
  }
  console.log(c); // 報錯
  console.log(d); // 報錯
  f();
</script>

【總結】:

  • 巢狀關係的作用域串聯起來形成了作用域鏈
  • 相同作用域鏈中按著從小到大的規則查詢變數
  • 子作用域能夠存取父作用域,父級作用域無法存取子級作用域(就近原則)

1.4、閉包

【解釋】: 閉包是一種比較特殊和函數,使用閉包能夠存取函數作用域中的變數。

【好處】: 可以把一個變數使用範圍延伸

【範例】:

<script>
  function foo() {
    let i = 0;
    // 函數內部分函數
    function bar() {
			console.log(++i);
    }
    // 將函數做為返回值
    return bar;
  }
  // fn 即為閉包函數
  let fn = foo();
  fn(); // 1
</script>

【總結】:

  • 閉包本質仍是函數,只不是從函數內部返回的
  • 閉包能夠建立外部可存取的隔離作用域,避免全域性變數汙染
  • 過度使用閉包可能造成記憶體漏失‘
  • 回撥函數也能存取函數內部的區域性變數。

1.5 變數提升

【解釋】: 允許在變數宣告之前即被存取

【範例】:

<script>
  // 存取變數 str
  console.log(str + 'world!');
  // 宣告變數 str
  var str = 'hello ';
</script>
let和var都有提升,但是let定義的變數沒有賦值之前是不可以使用、var可以使用是undefined

【總結】:

  • 變數在未宣告即被存取時會報語法錯誤
  • 變數在宣告之前即被存取,變數的值為 `undefinedlet
  • 宣告的變數不存在變數提升,推薦使用let`【也有人認為具有提升但是不賦值不能使用】
  • 變數提升出現在相同作用域當中
  • 實際開發中推薦先宣告再存取變數

二、函數

2.1、函數提升

【解釋】: 函數在宣告之前即可被呼叫

【範例】:

<script>
  // 呼叫函數
  foo();
  // 宣告函數
  function foo() {
    console.log('宣告之前即被呼叫...');
  }
  // 不存在提升現象
  bar();
  var bar = function () {
    console.log('函數表示式不存在提升現象...');
  }
</script>

【總結】:

  • 函數提升能夠使函數的宣告呼叫更靈活
  • 函數表示式不存在提升的現象
  • 函數提升出現在相同作用域當中

2.2、函數引數

1、預設引數

【範例】:

<script>
  // 設定引數預設值
  function sayHi(name="小明", age=18) {
    document.write(`<p>我叫${name},我今年${age}歲了。</p>`);
  }
  // 呼叫函數
  sayHi();
  sayHi('小紅');
  sayHi('小剛', 21);
</script>

【總結】:

  • 宣告函數時為形參賦值即為引數的預設值
  • 如果引數未自定義預設值時,引數的預設值為 undefined
  • 呼叫函數時沒有傳入對應實參時,引數的預設值被當做實參傳入

2、動態引數

【解釋】: arguments` 是函數內部內建的偽陣列變數,它包含了呼叫函數時傳入的所有實參。

【範例】:

<script>
  // 求生函數,計算所有引數的和
  function sum() {
    // console.log(arguments);
    let s = 0;
    for(let i = 0; i < arguments.length; i++) {
      s += arguments[i];
    }
    console.log(s);
  }
  // 呼叫求和函數
  sum(5, 10); // 兩個引數
  sum(1, 2, 4); // 兩個引數
</script>

【注意】:

  • arguments是一個偽陣列
  • arguments的作用是動態獲取函數的實參

3、剩餘引數

【語法及範例】:

<script>
  function config(baseURL, ...other) {
    console.log(baseURL);
    // other 是真陣列,動態獲取實參
    console.log(other);
  }
  // 呼叫函數
  config('http://baidu.com', 'get', 'json');
</script>

2.3、箭頭函數

【解釋】: 箭頭函數是一種宣告函數的簡潔語法,它與普通函數並無本質的區別,差異性更多體現在語法格式上。

【範例】:

<script>
  // 箭頭函數
  let foo = () => {
    console.log('^_^ 長相奇怪的函數...');
  }
  // 呼叫函數
  foo();
  // 更簡潔的語法
  let form = document.querySelector('form');
  form.addEventListener('click', ev => ev.preventDefault());
</script>

【總結】:

  • 箭頭函數屬於表示式函數,因此不存在函數提升
  • 箭頭函數只有一個引數時可以省略圓括號 `()
  • 箭頭函數函數體只有一行程式碼時可以省略花括號 {},並自動做為返回值被返回
  • 箭頭函數中沒有 arguments,只能使用 ... 動態獲取實參
  • 涉及到this的使用,不建議用箭頭函數

總結

本篇文章就到這裡了,希望能夠給你帶來幫助,也希望您能夠多多關注it145.com的更多內容!     


IT145.com E-mail:sddin#qq.com