js基礎之變量做用域和es6解構賦值

變量做用域與解構賦值


在JavaScript中,用var申明的變量其實是有做用域的。javascript

若是一個變量在函數體內部申明,則該變量的做用域爲整個函數體,在函數體外不可引用該變量:php

'use strict';

function foo() {
    var x = 1;
    x = x + 1;
}

x = x + 2; // ReferenceError! 沒法在函數體外引用變量x

若是兩個不一樣的函數各自申明瞭同一個變量,那麼該變量只在各自的函數體內起做用。換句話說,不一樣函數內部的同名變量互相獨立,互不影響:java

'use strict';

function foo() {
    var x = 1;
    x = x + 1;
}

function bar() {
    var x = 'A';
    x = x + 'B';
}

因爲JavaScript的函數能夠嵌套,此時,內部函數能夠訪問外部函數定義的變量,反過來則不行:數組

'use strict';

function foo() {
    var x = 1;
    function bar() {
        var y = x + 1; // bar能夠訪問foo的變量x!
    }
    var z = y + 1; // ReferenceError! foo不能夠訪問bar的變量y!
}

若是內部函數和外部函數的變量名重名:瀏覽器

JavaScript的函數在查找變量時從自身函數定義開始,從「內」向「外」查找。若是內部函數定義了與外部函數重名的變量,則內部函數的變量將「屏蔽」外部函數的變量。ruby

變量提高

JavaScript的函數定義有個特色,它會先掃描整個函數體的語句,把全部申明的變量「提高」到函數頂部:app

'use strict';

function foo() {
    var x = 'Hello, ' + y;
    alert(x);
    var y = 'Bob';
}

foo();

雖然是strict模式,但語句var x = 'Hello, ' + y;並不報錯,緣由是變量y在稍後申明瞭。可是alert顯示Hello, undefined,說明變量y的值爲undefined。這正是由於JavaScript引擎自動提高了變量y的聲明,但不會提高變量y的賦值。dom

對於上述foo()函數,JavaScript引擎看到的代碼至關於:函數

function foo() {
    var y; // 提高變量y的申明
    var x = 'Hello, ' + y;
    alert(x);
    y = 'Bob';
}

因爲JavaScript的這一怪異的「特性」,咱們在函數內部定義變量時,請嚴格遵照「在函數內部首先申明全部變量」這一規則。最多見的作法是用一個var申明函數內部用到的全部變量:ui

function foo() {
    var
        x = 1, // x初始化爲1
        y = x + 1, // y初始化爲2
        z, i; // z和i爲undefined
    // 其餘語句:
    for (i=0; i<100; i++) {
        ...
    }
}

全局做用域

不在任何函數內定義的變量就具備全局做用域。實際上,JavaScript默認有一個全局對象window,全局做用域的變量實際上被綁定到window的一個屬性:

'use strict';

var course = 'Learn JavaScript';
alert(course); // 'Learn JavaScript'
alert(window.course); // 'Learn JavaScript'

所以,直接訪問全局變量course和訪問window.course是徹底同樣的。

你可能猜到了,因爲函數定義有兩種方式,以變量方式var foo = function () {}定義的函數實際上也是一個全局變量,所以,頂層函數的定義也被視爲一個全局變量,並綁定到window對象:

'use strict';

function foo() {
    alert('foo');
}

foo(); // 直接調用foo()
window.foo(); // 經過window.foo()調用

進一步大膽地猜想,咱們每次直接調用的alert()函數其實也是window的一個變量:

'use strict';

window.alert('調用window.alert()');
// 把alert保存到另外一個變量:
var old_alert = window.alert;
// 給alert賦一個新函數:
window.alert = function () {

這說明JavaScript實際上只有一個全局做用域。任何變量(函數也視爲變量),若是沒有在當前函數做用域中找到,就會繼續往上查找,最後若是在全局做用域中也沒有找到,則報ReferenceError錯誤。

名字空間

全局變量會綁定到window上,不一樣的JavaScript文件若是使用了相同的全局變量,或者定義了相同名字的頂層函數,都會形成命名衝突,而且很難被發現。

減小衝突的一個方法是把本身的全部變量和函數所有綁定到一個全局變量中。例如:

// 惟一的全局變量MYAPP:
var MYAPP = {};

// 其餘變量:
MYAPP.name = 'myapp';
MYAPP.version = 1.0;

// 其餘函數:
MYAPP.foo = function () {
    return 'foo';
};

把本身的代碼所有放入惟一的名字空間MYAPP中,會大大減小全局變量衝突的可能。

許多著名的JavaScript庫都是這麼幹的:jQuery,YUI,underscore等等。

局部做用域

因爲JavaScript的變量做用域其實是函數內部,咱們在for循環等語句塊中是沒法定義具備局部做用域的變量的:

'use strict';

function foo() {
    for (var i=0; i<100; i++) {
        //
    }
    i += 100; // 仍然能夠引用變量i
}

爲了解決塊級做用域,ES6引入了新的關鍵字let,用let替代var能夠申明一個塊級做用域的變量:

'use strict';

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

常量

因爲varlet申明的是變量,若是要申明一個常量,在ES6以前是不行的,咱們一般用所有大寫的變量來表示「這是一個常量,不要修改它的值」:

var PI = 3.14;

ES6標準引入了新的關鍵字const來定義常量,constlet都具備塊級做用域:

'use strict';

const PI = 3.14;
PI = 3; // 某些瀏覽器不報錯,可是無效果!
PI; // 3.14

解構賦值

從ES6開始,JavaScript引入瞭解構賦值,能夠同時對一組變量進行賦值。

什麼是解構賦值?咱們先看看傳統的作法,如何把一個數組的元素分別賦值給幾個變量:

var array = ['hello', 'JavaScript', 'ES6'];
var x = array[0];
var y = array[1];
var z = array[2];

如今,在ES6中,可使用解構賦值,直接對多個變量同時賦值:

'use strict';

// 若是瀏覽器支持解構賦值就不會報錯:
var [x, y, z] = ['hello', 'JavaScript', 'ES6'];

注意,對數組元素進行解構賦值時,多個變量要用[...]括起來。

若是數組自己還有嵌套,也能夠經過下面的形式進行解構賦值,注意嵌套層次和位置要保持一致:

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

解構賦值還能夠忽略某些元素:

let [, , z] = ['hello', 'JavaScript', 'ES6']; // 忽略前兩個元素,只對z賦值第三個元素
z; // 'ES6'

若是須要從一個對象中取出若干屬性,也可使用解構賦值,便於快速獲取對象的指定屬性:

'use strict';

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

對一個對象進行解構賦值時,一樣能夠直接對嵌套的對象屬性進行賦值,只要保證對應的層次是一致的:

var person = {
    name: '小明',
    age: 20,
    gender: 'male',
    passport: 'G-12345678',
    school: 'No.4 middle school',
    address: {
        city: 'Beijing',
        street: 'No.1 Road',
        zipcode: '100001'
    }
};
var {name, address: {city, zip}} = person;
name; // '小明'
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, passort:id} = person;
name; // '小明'
id; // 'G-12345678'
// 注意: passport不是變量,而是爲了讓變量id得到passport屬性:
passport; // Uncaught ReferenceError: passport is not defined

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

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

// 若是person對象沒有single屬性,默認賦值爲true:
var {name, single=true} = person;
name; // '小明'
single; // true

有些時候,若是變量已經被聲明瞭,再次賦值的時候,正確的寫法也會報語法錯誤:

// 聲明變量:
var x, y;
// 解構賦值:
{x, y} = { name: '小明', x: 100, y: 200};
// 語法錯誤: Uncaught SyntaxError: Unexpected token =

這是由於JavaScript引擎把{開頭的語句看成了塊處理,因而=再也不合法。解決方法是用小括號括起來:

({x, y} = { name: '小明', x: 100, y: 200});

使用場景

解構賦值在不少時候能夠大大簡化代碼。例如,交換兩個變量xy的值,能夠這麼寫,再也不須要臨時變量:

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

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

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

若是一個函數接收一個對象做爲參數,那麼,可使用解構直接把對象的屬性綁定到變量中。例如,下面的函數能夠快速建立一個Date對象:

function buildDate({year, month, day, hour=0, minute=0, second=0}) {
    return new Date(year + '-' + month + '-' + day + ' ' + hour + ':' + minute + ':' + second);
}

它的方便之處在於傳入的對象只須要yearmonthday這三個屬性:

buildDate({ year: 2017, month: 1, day: 1 });
// Sun Jan 01 2017 00:00:00 GMT+0800 (CST)

也能夠傳入hourminutesecond屬性:

buildDate({ year: 2017, month: 1, day: 1, hour: 20, minute: 15 });
// Sun Jan 01 2017 20:15:00 GMT+0800 (CST)

使用解構賦值能夠減小代碼量,可是,須要在支持ES6解構賦值特性的現代瀏覽器中才能正常運行。目前支持解構賦值的瀏覽器包括Chrome,Firefox,Edge等

變量做用域與解構賦值

閱讀: 159085

在JavaScript中,用var申明的變量其實是有做用域的。

若是一個變量在函數體內部申明,則該變量的做用域爲整個函數體,在函數體外不可引用該變量:

'use strict';

function foo() {
    var x = 1;
    x = x + 1;
}

x = x + 2; // ReferenceError! 沒法在函數體外引用變量x

若是兩個不一樣的函數各自申明瞭同一個變量,那麼該變量只在各自的函數體內起做用。換句話說,不一樣函數內部的同名變量互相獨立,互不影響:

'use strict';

function foo() {
    var x = 1;
    x = x + 1;
}

function bar() {
    var x = 'A';
    x = x + 'B';
}

因爲JavaScript的函數能夠嵌套,此時,內部函數能夠訪問外部函數定義的變量,反過來則不行:

'use strict';

function foo() {
    var x = 1;
    function bar() {
        var y = x + 1; // bar能夠訪問foo的變量x!
    }
    var z = y + 1; // ReferenceError! foo不能夠訪問bar的變量y!
}

若是內部函數和外部函數的變量名重名

JavaScript的函數在查找變量時從自身函數定義開始,從「內」向「外」查找。若是內部函數定義了與外部函數重名的變量,則內部函數的變量將「屏蔽」外部函數的變量。

變量提高

JavaScript的函數定義有個特色,它會先掃描整個函數體的語句,把全部申明的變量「提高」到函數頂部:

'use strict';

function foo() {
    var x = 'Hello, ' + y;
    alert(x);
    var y = 'Bob';
}

foo();

雖然是strict模式,但語句var x = 'Hello, ' + y;並不報錯,緣由是變量y在稍後申明瞭。可是alert顯示Hello, undefined,說明變量y的值爲undefined。這正是由於JavaScript引擎自動提高了變量y的聲明,但不會提高變量y的賦值。

對於上述foo()函數,JavaScript引擎看到的代碼至關於:

function foo() {
    var y; // 提高變量y的申明
    var x = 'Hello, ' + y;
    alert(x);
    y = 'Bob';
}

因爲JavaScript的這一怪異的「特性」,咱們在函數內部定義變量時,請嚴格遵照「在函數內部首先申明全部變量」這一規則。最多見的作法是用一個var申明函數內部用到的全部變量:

function foo() {
    var
        x = 1, // x初始化爲1
        y = x + 1, // y初始化爲2
        z, i; // z和i爲undefined
    // 其餘語句:
    for (i=0; i<100; i++) {
        ...
    }
}

全局做用域

不在任何函數內定義的變量就具備全局做用域。實際上,JavaScript默認有一個全局對象window,全局做用域的變量實際上被綁定到window的一個屬性:

'use strict';

var course = 'Learn JavaScript';
alert(course); // 'Learn JavaScript'
alert(window.course); // 'Learn JavaScript'

所以,直接訪問全局變量course和訪問window.course是徹底同樣的。

你可能猜到了,因爲函數定義有兩種方式,以變量方式var foo = function () {}定義的函數實際上也是一個全局變量,所以,頂層函數的定義也被視爲一個全局變量,並綁定到window對象:

'use strict';

function foo() {
    alert('foo');
}

foo(); // 直接調用foo()
window.foo(); // 經過window.foo()調用

進一步大膽地猜想,咱們每次直接調用的alert()函數其實也是window的一個變量:

 
 

JavaScript實際上只有一個全局做用域。任何變量(函數也視爲變量),若是沒有在當前函數做用域中找到,就會繼續往上查找,最後若是在全局做用域中也沒有找到,則報ReferenceError錯誤。

名字空間

全局變量會綁定到window上,不一樣的JavaScript文件若是使用了相同的全局變量,或者定義了相同名字的頂層函數,都會形成命名衝突,而且很難被發現。

減小衝突的一個方法是把本身的全部變量和函數所有綁定到一個全局變量中。例如:

// 惟一的全局變量MYAPP:
var MYAPP = {};

// 其餘變量:
MYAPP.name = 'myapp';
MYAPP.version = 1.0;

// 其餘函數:
MYAPP.foo = function () {
    return 'foo';
};

把本身的代碼所有放入惟一的名字空間MYAPP中,會大大減小全局變量衝突的可能。

許多著名的JavaScript庫都是這麼幹的:jQuery,YUI,underscore等等。

局部做用域

因爲JavaScript的變量做用域其實是函數內部,咱們在for循環等語句塊中是沒法定義具備局部做用域的變量的:

'use strict';

function foo() {
    for (var i=0; i<100; i++) {
        //
    }
    i += 100; // 仍然能夠引用變量i
}

爲了解決塊級做用域,ES6引入了新的關鍵字let,用let替代var能夠申明一個塊級做用域的變量:

'use strict';

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

常量

因爲varlet申明的是變量,若是要申明一個常量,在ES6以前是不行的,咱們一般用所有大寫的變量來表示「這是一個常量,不要修改它的值」:

var PI = 3.14;

ES6標準引入了新的關鍵字const來定義常量,constlet都具備塊級做用域:

'use strict';

const PI = 3.14;
PI = 3; // 某些瀏覽器不報錯,可是無效果!
PI; // 3.14

解構賦值

從ES6開始,JavaScript引入瞭解構賦值,能夠同時對一組變量進行賦值。

什麼是解構賦值?咱們先看看傳統的作法,如何把一個數組的元素分別賦值給幾個變量:

var array = ['hello', 'JavaScript', 'ES6'];
var x = array[0];
var y = array[1];
var z = array[2];

如今,在ES6中,可使用解構賦值,直接對多個變量同時賦值:

'use strict';

// 若是瀏覽器支持解構賦值就不會報錯:
var [x, y, z] = ['hello', 'JavaScript', 'ES6'];

注意,對數組元素進行解構賦值時,多個變量要用[...]括起來。

若是數組自己還有嵌套,也能夠經過下面的形式進行解構賦值,注意嵌套層次和位置要保持一致:

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

解構賦值還能夠忽略某些元素:

let [, , z] = ['hello', 'JavaScript', 'ES6']; // 忽略前兩個元素,只對z賦值第三個元素
z; // 'ES6'

若是須要從一個對象中取出若干屬性,也可使用解構賦值,便於快速獲取對象的指定屬性:

'use strict';

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

對一個對象進行解構賦值時,一樣能夠直接對嵌套的對象屬性進行賦值,只要保證對應的層次是一致的:

var person = {
    name: '小明',
    age: 20,
    gender: 'male',
    passport: 'G-12345678',
    school: 'No.4 middle school',
    address: {
        city: 'Beijing',
        street: 'No.1 Road',
        zipcode: '100001'
    }
};
var {name, address: {city, zip}} = person;
name; // '小明'
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, passort:id} = person;
name; // '小明'
id; // 'G-12345678'
// 注意: passport不是變量,而是爲了讓變量id得到passport屬性:
passport; // Uncaught ReferenceError: passport is not defined

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

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

// 若是person對象沒有single屬性,默認賦值爲true:
var {name, single=true} = person;
name; // '小明'
single; // true

有些時候,若是變量已經被聲明瞭,再次賦值的時候,正確的寫法也會報語法錯誤:

// 聲明變量:
var x, y;
// 解構賦值:
{x, y} = { name: '小明', x: 100, y: 200};
// 語法錯誤: Uncaught SyntaxError: Unexpected token =

這是由於JavaScript引擎把{開頭的語句看成了塊處理,因而=再也不合法。解決方法是用小括號括起來:

({x, y} = { name: '小明', x: 100, y: 200});

使用場景

解構賦值在不少時候能夠大大簡化代碼。例如,交換兩個變量xy的值,能夠這麼寫,再也不須要臨時變量:

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

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

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

若是一個函數接收一個對象做爲參數,那麼,可使用解構直接把對象的屬性綁定到變量中。例如,下面的函數能夠快速建立一個Date對象:

function buildDate({year, month, day, hour=0, minute=0, second=0}) {
    return new Date(year + '-' + month + '-' + day + ' ' + hour + ':' + minute + ':' + second);
}

它的方便之處在於傳入的對象只須要yearmonthday這三個屬性:

buildDate({ year: 2017, month: 1, day: 1 });
// Sun Jan 01 2017 00:00:00 GMT+0800 (CST)

也能夠傳入hourminutesecond屬性:

buildDate({ year: 2017, month: 1, day: 1, hour: 20, minute: 15 });
// Sun Jan 01 2017 20:15:00 GMT+0800 (CST)

使用解構賦值能夠減小代碼量,可是,須要在支持ES6解構賦值特性的現代瀏覽器中才能正常運行。目前支持解構賦值的瀏覽器包括Chrome,Firefox,Edge等。

相關文章
相關標籤/搜索