11. 匿名函式、作用域及閉包
anonymous 匿名函式
- 沒指定名稱的函式
- 一次性函式
只有執行一次的函數,給他個名稱存參址器有點浪費, 於是把 "參址器" 替換為任何函式運算式。
// e.g.1
window.onload = function(){
//...
}
//e.g.2
setTimeout( function(){
// ...
}, 2000 )
定義在函式中的函式:嵌套的函式
定義在函式中的函式,會影響到外層函式的作用域。
- 若是函式宣告,會和外層有相同作用域。
- 若是運算式,則要看他在哪裡被求值,為作用域
語彙作用域、閉包 closure
- 語彙作用域:從閱讀程式碼即可判斷作用域。
- 以 "函式宣告" 或 "函式運算式" 定義,會影響該函式的"作用域"。
「函式宣告」會最先被處理、定義,作用域是 "當前作用域" 的全域; 「函式運算式」 (必須在使用前宣告) 在執行運算後才被定義, 作用域在 "當前作用域" 中,執行它的區塊中。 所以就語彙作用域而言,重要的是函式在哪裡被定義。
http://jsbin.com/toxadinome/edit?js,console 注意 return 的是「參址器」還是 function 運算出的「結果」。 「參址器」指向的是一個具體化的「程式碼區塊」。
function makeCounter (){
let count = 0;
function counter(){
count ++
return count;
}
return counter; // 回傳一個參址器,包含了操作環境
}
區域作用域的所有變數會被存在一個「操作環境」, 所有函式會接附一個操作環境。
上例子,回傳的是參址器指向的「函式主體」, 該函式,使用到「沒在函式自己作用域中定義」的變數 (自由變數) , 那些「自由變數」和那個「函式」,會存在在一個操作環境, 所以回傳「參址器指向的函式」時,也會連帶回傳了「操作環境」。
閉包就是一個「操作環境」+「函式」。 你回傳了一個閉包。
函式使用不在該函式定義的變數,叫做「自由變數」。 當你取得一個參址器,該參址器指向一個「擁有自由變數的函式」, 且該函式是在他被建立的語境外被執行,你建立的就是閉包。 閉包所含的操作環境不是副本,而是「實際」操作環境。
function lockWith( setPW ){
return function openWith ( guessPW ){
return ( guessPW === setPW );
}
}
let openLock = lockWith('ooopen'); // set pw
// openLock == openWith(guess)
// openLock(guess) == true or false
openLock('open') // guess pw
// 也是閉包
// 說話計時器函式:
// 設計輸入一個字串和毫秒數,之後隔毫秒數就會 alert 字串
function makrTimer ( say , num ){
// 裡面用到 setTimeout,
// 給他一句話和時間,再把兩個參數給 setTimeout
setTimeout( function(){ // 我們有個匿名函式
alert(say) // 使用到自由變數
}, num)
}
makrTimer( 'haha', 3000 )
替換匿名函式
// 1
(function (food){
// ...
})('cake');
// 2
var eat = function (food){
// ...statement
}
// 把 eat 替換了 //...statement
(eat)('cake')
// or 寫成
eat('cake')
函式宣告是 function
後接 "名稱"
fucntion eat(food){ //...
函式運算式是要放在敘述 statement 中,
若不放在 ()
js 會視為宣告而不是運算式
物件建構函式
Dog 的物件建構式
function Dog (name, breed, weight){
this.name = name;
this.breed = breed;
this.weight = weight;
}
var lucky = new Dog("lucky", "mix", 10 )
// 原型上新增屬性 sitting
Dog.prototype.sitting = false;
Dog.prototype.sit = function(){
if (this.sitting) {
// 指到原型屬性,因為找不到屬性就會往原型找
} else {
this.sitting = true
// this 代表實例,所以會建立在實例屬性
}
}
// 測試是否有屬性 'name' 在自己的屬性 (not from prototype)
console.log(lucky.hasOwnProperty('sitting'))
// false,因為在 Dog 原型上,不是 lucky 上,直到執行 lucky.sit()
若是...
var lucky = Dog("lucky", "mix", 10 )
// lucky >> undefine
// this.name >> window.name >> "lucky"
new
會建立新物件,並將 this 指向新物件自己。
沒 new
不會回傳新物件,函式會回傳 undefine
。
Dog 函式裡面的 this
是指向全域物件 "window"(瀏覽器),所以會新增給 window 屬性。
所以 lucky = undefine
。
http://jsbin.com/dubudit/2/edit?js,console
使用 new
關鍵字會建立且回傳該物件,並將物件中的 this 指向新物件自己。
沒 new
不會回傳新物件,函式會回傳 undefine
。
然後函式裡面的 this
是指向全域物件 "window"(瀏覽器),
會新增給 window 屬性。
let obj ={...}
也是,this 會指向 window 但在物件中的 function 會指向物件自己。
圓形
承上正確示例...
// 建立一個 ShowDog 原型,稍後要繼承 Dog 原型物件
var ShowDog (name, breed, weight, handler){
Dog.call (this, name, breed, weight);
// call 把所有參數傳入 Dog,
// 第一個參數是 Dog 主體的佔位符,
// call 把 ShowDog 的 "this" 指定到 Dog 裏,
// 代替 Dog 中的 "this",
// Dog 裡面的 "this" 現在指的皆為 ShowDog
this.handler = handler;
}
// 繼承自 Dog 原型(在建立任何實例之前,設計好原型)
ShowDog.prototype = new Dog();
// add 原型屬性
ShowDog.prototype.league = "wii";
ShowDog.prototype.bait = function(){
//...
};
// 建立展示犬 spot
var spot = new ShowDog('spot', "mix", 10, 'cookie')
// 是哪個實體的實例?
console.log(lucky instanceof Dog) // true
console.log(spot instanceof Dog) // true
// 由哪個建構式生成?
console.log(lucky.constructor) // Dog
console.log(spot.constructor) // Dog
// 沒有主動建立 "constructor" 屬性,
// 所以他是來自原型:自動產生的...
// 主動為原型建立正確的 constructor 屬性
ShowDog.prototype.constructor = ShowDog;
console.log(spot.constructor) // ShowDog
複寫屬性
Don't
- constructor
- hasOwnProperty
- isPrototypeOf 物件是否為另一個物件的原型
- propertyIsEnumerable
CanDo
- toString
- toLocaleString
- valueOf