這裡是講到一些關於宣告變數一些很基本的事。
Example:
var foo; // 使用 var 來宣告一個變數叫做 foo
foo; // 一個未賦予值的變數,會得到 undefined
foo = 3; // 把 3 這個數字賦予給 foo
bar = 5; // 這裡不建議這麼做,bar 是一個未宣告的變數
Example:
var abc = 3; // 使用 var 來宣告一個變數叫做 abc,接著初始化 (賦予該變數初始值)
Example:
function f() {
function g() { // 函式 g 內嵌於函式 f 中。
// ...
}
}
Example:
function f() {
console.log('Hello')
}
function g() {
f()
}
g() // 執行函式 g
這裡將介紹四個概念:
Example:
function foo() {
var x;
}
Example:
function foo() {
function goo() {
var x; // x 變數的範疇是在 goo() 函式裡面
console.log(x)
}
function zoo() {
// ....
}
goo() // x 變數的範疇跟在這裡呼叫 goo() 函式沒有關係
}
foo()
Example:
function foo(arg) {
function bar() {
console.log(`arg: ${arg}`)
}
bar()
}
foo('結衣結婚了 ... 哭')
Example:
var x = 'global'
function f() {
var x = 'local'
console.log(x)
}
f()
console.log(x)
Example:
針對上述第四點,這裡在使用此範例再說明一次。
function f() {
var x = 'local'
console.log(x) // 這裡會印出 local
}
f()
console.log(x) // 這裡不會得到 local,會得到 ReferenceError: x is not defined
Example:
public static void main(string[] args) {
{
int foo = 4
}
System.out.printIn(foo) // 會出現 Error
}
Example:
function main() {
{
var foo = 4
}
console.log(foo) // 會得到 4
}
main()
Example:
function f() {
consol.log(bar); // 1. undefined
var bar = 'Hello';
console.loeg(bar); // 2. hello
}
consol.loeg(bar);
會印出 undefined
而不是 bar is not defined
,是因為使用到 var 來宣告變數 bar,宣告變數 bar 會被拉升到第一行。Example:根據上述例題,程式實際上會這樣執行
function f() {
var bar;
console.log(bar); // 這時候還未給值,因此會得到 undefined
bar = 'Hello';
consol.loeg(bar); // 會得到 hello
}
這裡是講到重複宣告的話,會發生的事情。
Example:情境1
var x = 1;
var x;
console.log(x) // 會得到 1
Example:情境2
var x = 1;
var x = undefined;
console.log(x) // 會得到 undefined
Example:情境3
var x = 1;
var x = 2;
console.log(x) // 會得到 2
Example:
function f() {
x = 123; // 不使用 var、let、const 來宣告
}
f()
console.log(x) // 會得到 123
console.log(window.x) // 會得到 123
function f() {
'use strict'
x = 123;
}
f() // 程式運行到這就會出現,x is not defined 的錯誤訊息,在這之下的程式就不執行了
console.log(x)
console.log(window.x)
情境:藉由創造一個新的範疇來限制一個變數的生命週期。
在哪發生:if else
陳述句中,當條件成立時才會執行我們要執行的動作。
Example:
function f() {
if (true) { // 條件成立時,程式執行區塊
var foo = 123;
}
console.log(foo) // 這裡依然可以得到 foo 變數的值 123,可這不是我們想要的
}
f()
Example:如何解決上述 foo 變數,依然可以得到值的問題
function f() {
if (true) { // 條件成立時,程式執行區塊
(function () { // 創造一個新的函式範疇
var foo = 123; // foo 這個變數被鎖在新的函式範疇
}());
}
console.log(foo) // 會得到 ReferenceError: foo is not defined
}
f()
Example:
(function () { // IIFE 開頭
// IIFE ... 內部
}()); // IIFE 結尾
}
右大括號之後的 ()
小括號,代表該函式即刻調用,套一句我們比較熟的說話,就是立即呼叫該函式這樣。function
開頭時,JavaScript 中的剖析器 (parser) 會預期這是一個函式宣告的用法。(
為開頭,來做為該陳述句的開頭,告訴 JavaScript 中的剖析器 (parser),這個使用 function
是一個函式運算式的開頭。Example:在兩個 IIFE 之間,第一個 IIFE 並未加上分號
(function () { // 第一個 IIFE
// ...
}()) // 沒有加上分號
(function () { // 第二個 IIFE
// ...
}());
Example:
可使用前綴運算子來強制要求運算式情境。
Example:
!function () { // IIFE 開頭
// IIFE 內部
}(); // IIFE 結尾
Example:
void function () { // IIFE 開頭
// IIFE 內部
}(); // IIFE 結尾
Example:
var File = function () { // IIFE 開頭
var UNTITLED = 'untitled'
function File(name) {
this.name = name || UNTITLED
}
return File
/*
function File(name) {
this.name = name || UNTITLED
}
*/
}(); // IIFE 結尾
File()
可以使用參數來做為 IIFE 的內部定義變數。
Example:
var x = 23;
(function (twice) {
console.log(twice) // 會得到 46
}(x * 2))
// 更像是如下
var x = 23;
(function () {
var twice = x * 2;
console.log(twice); // 會得到 46
}())
Example:
var setValue = function () {
var prevValue;
return function (value) {
if (value !== prevValue) { // 定義 setValue
console.log('Changed: '+ value); // "Changed: Hello"
prevValue = value
}
};
}();
setValue('Hello')
Example:等於以上程式碼
var setValue = function () {
var prevValue;
return function (value) {
if (value !== prevValue) { // 定義 setValue
console.log('Changed: '+ value); // "Changed: World"
prevValue = value
}
};
};
setValue()('World')
後面幾個章節都會講到,這裡就不在一一說明。
<script>
標記、某某 .js 檔案。Example:
// 這裡就是全域範疇
var globalVariable = 'globalValue';
function f() {
var localVariable = true;
function g() {
// 周圍範疇的所有變數都能夠存取
var anotherLocalVariable = 123;
localVariable = false;
globalVariable = 'anotherLocalValue';
console.log(globalVariable);
}
g()
}
f()
console.log(globalVariable);
// 這裡我們再次回到了全域範疇
全域變數會有兩個缺點。
Example:
<script>
// 全域範疇
var tmp = generateData();
processData(tmp);
persistData(tmp);
</script>
Example:藉由一個 IIFE 來引進一個新的範疇
<script>
// 全域範疇
(function () { // IIFE 開頭
// 區域範疇
var tmp = generateData();
processData(tmp);
persistData(tmp);
}()); // IIFE 結尾
</script>
待後面章節會講到。
要特別講到的是在 ES6 多了 import & export 這兩個用法。
this
指向的就是它。Example:
var foo = 123;
this.foo; // 讀取全域變數,會得到 123
this.bar = 456; // 建立全域變數
bar // 會得到 456
瀏覽器與 Node.js 都有用來參考這個全域物件的全域變數。
Example:
(function (glob) {
// glob 指向全域物件
}(typeof window !== 'undefined' ? window : global));
這節將講到在什麼情境下,會使用到 window 來存取全域變數,這裡書中也提到,能盡量避免的話,盡量不要這樣做。
window 這個前綴 (prefix) 是一種視覺線索,告訴我們程式碼參考到一個全域變數,而非區域變數。
Example:
var foo = 123;
(function () {
console.log(window.foo); // 會得到 123
}());
Example:上述程式碼很容易會被範疇所影響到
(function () {
var foo = 123;
console.log(window.foo); // 因為 window.foo 沒有這個屬性,因此會得到 undefined
}());
呈上述程式碼,要怎麼避免這個狀況。
Example:
var g_foo = 123; // 加上 g 這個前綴詞
(function () {
console.log(g_foo); // 不使用 window 來存取 g_foo,而是用一個變數的角度來看待
}());
Example:
window.isNaN(...) // 不要這樣做,會很混亂
isNaN(...) // 這樣是最好的
這裡要講的是,如果要檢查某個全域變數是否存在,該怎麼做是最恰當的。
Example:
if (window.someVariable) {...} // 1 使用 window 來檢查
if (someVariable) {...} // 2 如果 someVariable 未宣告,這個判斷會有錯誤產生,不建議
if (window.someVariable !== undefined) {...} // 比第 1 種判斷方式更明確
if ('someVariable' in window) {...} // 比第 1 種判斷方式更明確
if (typeof someVariable !== 'undefined') {...} // 判斷是否存在 (並有值)
Example:
window.someVariable = 123 // 直接這樣用,就可在全域變數新增該特性
var someVariable = 123 // 建議作法,還是使用變數宣告的方式
變數會以兩種方式來傳遞。也可以說,它們有兩個維度 (dimensions):
Example:
function fac(n) {
if (n <= 1) {
return 1;
}
return n * fac(n - 1);
}
console.log(fac(3));
if (n <= 1)
判斷式,所以回傳 n 為 1 的值。一個函式在一直被呼叫的情況下,該函式會需要存取它自己 (全新) 的區域變數及外圍範疇的變數。
Example:
function outer(n, action) {
function inner(x) {
if (x >= 1) {
action();
inner(x - 1);
}
}
inner(n)
}
function foo() {
console.log('Hello')
}
console.log(outer(3, foo));
/*
Hello
Hello
Hello
*/
處理這兩個維度的方式如下:
當一個函式被呼叫時,就會建立一個新的環境,由於 JavaScript 是一個單執行緒的程式語言,意思就是一次只能做一件事情,如果安排了很多事情要給他做,他就會讓這些事情去排隊,再一件一件做。
像書中提到的遞迴,又或是一般的函式呼叫也好,這些在當下都會被放入一個堆疊 (stack) 中來管理。這是個「後進先出」的概念,以下面這段程式為例:
Example:
function a() {
b();
}
function b() {
console.log('hi');
}
a();
Example:
function myFunction(myParam) { // 函式宣告
var myVar = 123;
console.log(myFloat)
return myFloat;
}
var myFloat = 1.3;
myFunction('abc')
mvVar
變數。所以,若是在自己層級找不到就會一層一層往外找,直到 global 為止。所謂的閉包 (Closure) 就是「在函數內引用區域變數的函式」,哪我知道這樣的解釋還是非常抽象跟不明遼,所以直接來看例題吧。
Example:
function closure(init) {
var counter = init;
return function () {
return ++counter;
}
}
var myClosure = closure(1)
console.log(myClosure()); // 2
console.log(myClosure()); // 3
console.log(myClosure()); // 4
在這個函式中我們使用了巢狀函式來做為回傳值,關於這點就是屬於閉包的機制了。
在一般的函式概念來講可使用的區域變數 (這裡是變數 counter)在函式處理結束後,就會被回收這樣。