由插件封装引出的一丢丢思考

今天看一个妹子写的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,也就是全局

不知道我有没有说清楚呀。

相关文章
相关标签/搜索