由插件封裝引出的一丟丟思考

今天看一個妹子寫的canvas的插件,好羞愧啊,比我小還比我厲害得多,氮素,得向厲害的的人學習呀。因此就拜讀了源碼,業務方面的東西我就不說了,我也沒仔細看,主要是被下面這一部分代碼吸引了。javascript

_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);

    }

細細琢磨了一會,看懂了ifelse if判斷的用意。html

在這以前先說明下CanvasStar是什麼。代碼裏有這樣一句。java

function CanvasStar() {}

因此這個方法就是在代碼裏執行這個canvas的入口,其餘全部相關的內容都做爲一個對象賦值給了他的原型對象。es6

再說回那兩個判斷,由於在es6以前,都用的是commonJSAMD規範進行代碼加載,因此含義就在於當前的環境支不支持commonjs或者AMD規範。在HTML文件裏引用的話,這兩個就先跳過吧。主要看這兩句。canvas

//問題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)賦值給一個變量。我consolethis,按理說,這裏的this指向的就應該是全局變量,爲何還要後面的代碼從新指向全局呢?閉包

而後打算從新看一遍代碼的時候發現她在寫這個插件的時候用的是嚴格模式,因此這裏的this只多是underfined。我貼一下MDN對於嚴格模式下this的指向。app

在嚴格模式下經過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賦值的過程當中,從左至右依次執行了func1func2兩個方法,可是在賦值的時候,只返回了最後的那個值,也就是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,也就是全局

不知道我有沒有說清楚呀。

相關文章
相關標籤/搜索