1. 程式人生 > 其它 >函式-變數作用域與解構賦值

函式-變數作用域與解構賦值

變數提升

js的函式定義有個特點,它會先掃描整個函式體的語句,把所有申明的變數"提升"到函式頂部:

function log(){
    var x = 'hello,' + y;
    console.log(x);         // hello, undefined
    var y = 'world'
}
log();

雖然是strict模式,但語句 var x = 'Hello, ' + y並不報錯,原因是變數y在稍後聲明瞭。但是console.log 顯示hello, undefined, 說明變數y的值為undefined, 這正是因為js引擎自動提升了變數y的宣告,但是不會提升變數y的賦值。

對於上述foo()函式, js引擎看到的程式碼相當於:

function foo() {
   var y;         // 提升變數y的宣告,此時y為undefined
   var x = 'Hello, ' + y;
   console.log(x);
   y = 'world'   
}

全域性作用域

不在任何函式內定義的變數就具有全域性作用域,實際上,javaScript預設有一個全域性物件window, 全域性作用域的變數實際上被繫結到window的一個屬性:

var xz = 'xiaohu'
console.log(xz);        // xiaozhu
console.log(window.xz)  // xiaozhu

函式定義有兩種方式,以變數方式 var foo = function(){} 定義的函式實際上也是一個全域性變數,因此,頂層函式的定義也被視為一個全域性變數,並繫結到window物件:

javascript 實際上只有一個全域性作用域。任何變數(函式也視為變數), 如果沒有在當前函式作用域中找到,就會繼續往上查詢,最後如果再全域性作用域中也沒找到,則報ReferenceError錯誤。

名字空間

全域性變數會繫結到window上,不同的js檔案如果使用了相同的全域性變數,或者定義了相同名字的頂層函式,都會造成命名衝突。

減少衝突的一個方法是把自己的所有變數和函式全部繫結到一個全域性變數中,例如:

// 唯一的全域性變數xz 
var xz = {};
// 其他變數
xz.name = 'xiaozhu';
xz.version = 1.0;

// 其他函式
xz.foo = function(){
  return 'foo'
}

區域性作用域

由於js的變數作用域實際上是函式內部,我們在for迴圈等語句塊中無法定義具有區域性作用域的變數。

function foo(){
    for(var i = 0; i < 100; i++){
        //
    }
    var b = i + 100;    //  仍可以引用變數i
    console.log(b);     // 200
}
foo()

為了解決塊級作用域,es6引入了新的關鍵字 let,let 替代var 可以宣告一個塊級作用域的變數:

function foo() {
   var sum = 0;
   for(let i=0; i < 100; i++){
      sum += i;
   }  

    // SyntaxError:
    i += 1;
}

解構賦值

'use strict';
 // 解構賦值
let [xl,yl,zl]  = ['Hello','Javascript','ES6']
console.log(`xl = ${xl} , yl = ${yl} , zl = ${zl}`);    // xl = Hello , yl = Javascript , zl = ES6

對陣列進行解構賦值時,多個變數要用[...] 括起來。

如果本身有巢狀,則

let [x,[y,z]] = ['Hello', ['Javascript', 'ES6']];    // hello Javascript ES6

如果需要從一個物件中取出若干屬性,也可以使用解構賦值,便於快去獲取物件的指定屬性:

var person = {
    name: 'xiaoming',
    age: 20,
    gender: 'male',
    passport: 'G-12345678',
    school: 'No.4'
}
var {name,age, school} = person;
console.log(`name: ${name} ,age: ${age}, school: ${school}`);   // name: xiaoming ,age: 20, school: No.4

對一個物件進行解構賦值時,同樣可以直接對巢狀的屬性進行賦值,只要保證對應的層次是一致的:

 var person1 = {
            name: 'xiaoming',
            age: 20,
            gender: 'male',
            passport: 'G-12345678',
            school: 'No.4',
            address: {
                city: 'Beijing',
                street: 'No.1 Road',
                zipcode: '10001'
            }
        }
 var {name ,address: {city, zip}} = person1;
 name;         // xiaoming
 city;        //  beijing
 zip;         //  undefined ,  因為屬性名是zipcode而不是zip
// 注意: address 不是變數,而是為了讓city和zip獲得巢狀的address物件的屬性
address; //
Uncaught ReferenceError: address is not defined


使用結構賦值對物件屬性進行賦值時,如果對應的豎向不遜在,變數將被賦值為undefined, 這和引用一個不存在的屬性獲得undefined是一致的。 如果要使用的變數名和屬性名不一致,可以是使用下面的語法獲取:

var person = {
    name: '小明',
    age: 20,
    gender: 'male',
    passport: 'G-12345678',
    school: 'No.4 middle school'
};

// 把passport屬性賦值給變數id:
let {name, passport:id} = person;
name; // '小明'
id; // 'G-12345678'
// 注意: passport不是變數,而是為了讓變數id獲得passport屬性:
passport; // Uncaught ReferenceError: passport is not defined

解構賦值還可以使用預設值,這樣就避免了不存在的屬性返回undefined的問題:

let person2 = {
    name: 'xiaozhu',
    age: 20,
    sex: '女',
    // single: false
}
var {name, single= true} = person2;
console.log(name);    // xiaozhu
console.log(single);   // true

變數宣告,再次賦值。正確的寫法;(js引擎把{ 開頭的語句當作了塊處理,於是 = 不再合法)

// 變數已經宣告
let i,o;
({i,o} = {i:'xiaozhu', o:'l', name: 'xx'})
console.log(i);   // xiaozhu
console.log(o);   // l

使用場景

解構賦值現在很多時間大大簡化程式碼, 例如交換兩個變數 x 和 y的值, 可以這麼寫:

var x = 1, y = 2;
[x,y] = [y,x]

快速獲取當前頁面的域名和路徑:

var {hostname: domain,  pathname: path} = location;