今天看一個妹子寫的canvas的插件,好羞愧啊,比我小還比我厲害得多,氮素,得向厲害的的人學習呀。所以就拜讀了源碼,業務方面的東西我就不說了,我也沒仔細看,主要是被下面這一部分代碼吸引了。
_global = (function() {
return this || (0, eval)('this');
}());
if (typeof module !== "undefined" && module.exports) {
module.exports = CanvasStar;
} else if (typeof define === "function" && define.amd) {
define(function() {
return CanvasStar;
});
} else {
!('CanvasStar' in _global) && (_global.CanvasStar = CanvasStar);
}
細細琢磨了一會,看懂了if
和else if
判斷的用意。
在這之前先說明下CanvasStar
是什么。代碼里有這樣一句。
function CanvasStar() {}
所以這個方法就是在代碼里執行這個canvas的入口,其他所有相關的內容都作為一個對象賦值給了他的原型對象。
再說回那兩個判斷,因為在es6之前,都用的是commonJS
和AMD
規范進行代碼加載,所以含義就在于當前的環境支不支持commonjs
或者AMD
規范。在HTML
文件里引用的話,這兩個就先跳過吧。主要看這兩句。
//問題1
_global = (function() {
return this || (0, eval)('this');
}());
//問題2
else{
!('CanvasStar' in _global) && (_global.CanvasStar = CanvasStar);
}
我google了(0, eval)('this')
,有篇文章是這么說的:
無論如何方式調用
(0, eval)('this')
,返回的都是全局對象
所以問題1其實就是在將全局環境(也就是window)賦值給一個變量。我console
了this
,按理說,這里的this
指向的就應該是全局變量,為什么還要后面的代碼重新指向全局呢?
然后打算重新看一遍代碼的時候發現她在寫這個插件的時候用的是嚴格模式,所以這里的this
只可能是underfined
。我貼一下MDN對于嚴格模式下this
的指向。
在嚴格模式下通過this傳遞給一個函數的值不會被強制轉換為一個對象。對一個普通的函數來說,this總會是一個對象:不管調用時this它本來就是一個對象;還是用布爾值,字符串或者數字調用函數時函數里面被封裝成對象的this;還是使用undefined或者null調用函數式this代表的全局對象(使用call, apply或者bind方法來指定一個確定的this)。這種自動轉化為對象的過程不僅是一種性能上的損耗,同時在瀏覽器中暴露出全局對象也會成為安全隱患,因為全局對象提供了訪問那些所謂安全的JavaScript環境必須限制的功能的途徑。所以對于一個開啟嚴格模式的函數,指定的this不再被封裝為對象,而且如果沒有指定this的話它值是undefined
很長是吧,簡短的說,在嚴格模式下,如果沒有給this
指定值的話,它就是未定義的。所以在賦值的時候就跳過了這個this
,返回了(0, eval)('this')
。
這里說明一下eval
,在我找資料的過程中,都提到它的兩種使用方式間接eval調用和直接eval調用,這兩種的調用方式的結果完全不同,一般我見到的都是直接eval調用
,甚至于由于不提倡使用,所以eval
幾乎很少出現。
等我在看多一點資料以后在寫一個eval
相關的博文吧。但我可以先對這里面的逗號操作符做一點說明。
逗號操作符
這是MDN上的解釋
逗號操作符 對它的每個操作數求值(從左到右),并返回最后一個操作數的值。
我就用幾個代碼說明一下
function func1() {
let a = '我是第一個賦值方法'
console.log('一號喵')
return a
}
function func2() {
let b = '我是第二個賦值方法'
console.log('二號喵')
return b
}
let c = (func1(), func2())
console.log(c)
猜猜這里有幾個console
,分別是什么。
現在揭曉答案
//console.log結果
一號喵
二號喵
我是第二個賦值方法
所以根據定義來看,在對c
賦值的過程中,從左至右依次執行了func1
和func2
兩個方法,但是在賦值的時候,只返回了最后的那個值,也就是func2
里寫的return
。
所以我們在看一下eval
(0, eval)
這里返回的也是eval
,等同于這個
eval('this')
然而還是因為調用方式的不一樣,所以最后的結果不一樣,先按下不表了。
立即執行函數的公與私
那再來看問題2就簡單明了多了,他就是在判斷全局是否存在CanvasStar
這個方法,如果不存在,就在全局創建一個變量并將內部的方法賦值給他。
但這里就涉及一個問題,像是我,單獨寫js文件并引入使用的時候,都是直接調取方法使用,為什么這么麻煩啊,所以這里我也嘗試在HTML文件里直接調用CanvasStar
(前提是把那些代碼注釋了)。
但很可惜,瀏覽器報錯:
Uncaught TypeError: CanvasStar is not a constructor
所以這里我就想說說共有方法和私有方法,代碼如下
//main.js
(function() {
let a = '猜猜我是什么類型'
function sum() {
console.log(a)
}
let log = function() {
console.log(a)
}
})()
然后html文件里調用:
sum(); // Uncaught ReferenceError: sum is not defined
log(); // Uncaught ReferenceError: log is not defined
我對main.js
的文件做一丟丟修改
//main.js
(function() {
let a = '猜猜我是什么類型'
log = function() {
console.log(a)
}
function sum() {
console.log(a)
}
})()
重新運行:
log(); // 猜猜我是什么類型
sum(); // Uncaught ReferenceError: sum is not defined
我第一次在js文件里寫了一個函數聲明和一個函數表達式,但是在外部都無法調用,第二次我把函數表達式賦值的變量聲明去掉之后,就能正常訪問了。
這個問題的關鍵在作用域,當我建立這個立即執行函數是,作用域鏈是這樣的:
全局作用域 |
---|
匿名函數 |
函數作用域 |
---|
變量a |
log函數 |
sun函數 |
而當匿名函數執行完之后,它本身的作用域就被銷毀了,從他的上一級,也就是全局作用域根本訪問不到任何東西,但如果在進行函數賦值時,賦值的變量并沒有經過var
或者let
生明,在這里log
這個變量是被寫在全局作用域里面的,所以外部直接調用完全沒問題。
所以得出的一個結論是:講過let
或者var
生明的變量都是私有的,函數聲明一定是私有的方法。其他都是共有變量或者方法。另外,共有方法能訪問作用域里的私有變量,但是私有變量無法從外部直接獲取。
其實這也就是某種意義上的閉包啦。
另一種封裝方法
要是只講上面的多沒意思啊,正好我最近在看underscore的源碼,我就想著看看人家的封裝方法是啥。
在規范判斷那一塊大同小異,就不說了,但是對于全局變量的賦值走的是一條完全不同的路。
(function() {
let root = this;
............
.............
root._ = _
}.call(this))
這樣外部直接
_.方法名;
就可以使用了。
那在這里,underscore在執行這段匿名函數的時候,使用call
將函數的this
指向了全局變量,這里就是this
,可能這句話比較繞,但事實就是這樣。如果實在理解不了,我舉個例子:
一艘船在海上航行,夜間,如果天空晴朗,指的是一般模式,那水手可以根據天上的星辰判斷方位,如果不幸烏云密布,就是嚴格模式,那就迷路啦,但恰好,轉過一個海灣,發現了一座著名的燈塔,重新給你指引了方向,這就是call重新指向當前作用域的this,也就是全局。
不知道我有沒有說清楚呀。