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

今天看一个妹子写的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);

    }

细细琢磨了一会,看懂了ifelse if判断的用意。

在这之前先说明下CanvasStar是什么。代码里有这样一句。

function CanvasStar() {}

所以这个方法就是在代码里执行这个canvas的入口,其他所有相关的内容都作为一个对象赋值给了他的原型对象。

再说回那两个判断,因为在es6之前,都用的是commonJSAMD规范进行代码加载,所以含义就在于当前的环境支不支持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)赋值给一个变量。我consolethis,按理说,这里的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赋值的过程中,从左至右依次执行了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,也就是全局

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343

推荐阅读更多精彩内容

  • 第5章 引用类型(返回首页) 本章内容 使用对象 创建并操作数组 理解基本的JavaScript类型 使用基本类型...
    大学一百阅读 3,211评论 0 4
  • 深入理解JavaScript系列文章,包括了原创,翻译,转载,整理等各类型文章,如果对你有用,请推荐支持一把,给大...
    DaveWeiYong阅读 589评论 0 1
  • 今天继续读刘墉 工作学习过日子,不可能每一天都快乐顺心,也不可能每一天都遇见倒霉的事。需要开拓视野,开放思维,不能...
    语宇2017阅读 104评论 0 0
  • 我有一个阿里云9折推荐码:czhks3 ;分享给你,第一次购买云服务器或云数据库可享受原价9折优惠,还可多人使用,...
    Jackzzg阅读 132评论 0 0
  • 当我执起笔的时候,不妨又一滴热泪打湿了稿纸,留下了一纹水圈,久久不散...... 二月二十一日,那是我高考前的那年...
    清晨雨阅读 265评论 0 0