资讯专栏INFORMATION COLUMN

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

Baaaan / 2406人阅读

摘要:今天看一个妹子写的的插件,好羞愧啊,比我小还比我厉害得多,氮素,得向厉害的的人学习呀。在文件里引用的话,这两个就先跳过吧。在严格模式下通过传递给一个函数的值不会被强制转换为一个对象。

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

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

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/88589.html

相关文章

  • 2017-09-22 前端日报

    摘要:前端日报精选在中的元素种类及性能优化译异步递归回调译定位一个页面阻塞问题的排查过程前端分享之的使用及单点登录中文视频如何用做好一个大型应用云际个实用技巧众成翻译年一定不要错过的五本编程书籍年前端领域有哪些探索和实践实现一个时光网掘金 2017-09-22 前端日报 精选 JavaScript 在 V8 中的元素种类及性能优化【译】异步递归:回调、Promise、Async[译]HTML...

    xiaochao 评论0 收藏0
  • 第八期杭州NodeParty x Rokid技术分享会回顾

    摘要:月号,杭州和联合主办的第八期技术分享会,在公司如期举行。张伟林,宋小菜资深前端开发工程师,年,霹雳迷,已手残的纸牌魔术师,喜欢神奇的东西,技术栈从上向下不断横向纵向贯穿,目前在寻找前后端大一统思想的路上越走越偏。 showImg(https://segmentfault.com/img/bVbkWN4?w=3000&h=1686); 12 月 9 号,杭州 NodeParty 和 Ro...

    gself 评论0 收藏0
  • 一些 Prometheus 引出闲言碎语

    摘要:普罗米修斯是谁在希腊神话中,是泰坦神族的神明之一,名字的意思是先见之明。普罗米修斯与智慧女神雅典娜共同创造了人类,普罗米修斯负责用泥土雕塑出人的形状,雅典娜则为泥人灌注灵魂,并教会了人类很多知识。普罗米修斯是谁?From Wikipedia:在希腊神话中,是泰坦神族的神明之一,名字的意思是先见之明。普罗米修斯与智慧女神雅典娜共同创造了人类,普罗米修斯负责用泥土雕塑出人的形状,雅典娜则为泥人灌...

    Tecode 评论0 收藏0
  • jQuery入门笔记之(七)插件

    摘要:目前插件已超过几千种,由来自世界各地的开发者共同编写验证和完善。而对于开发者而言,直接使用这些插件将快速稳定架构系统,节约项目成本。也就是说,插件也是代码,通过文件引入的方式植入即可。现在我们就完成了一个下拉菜单的插件。 插件(Plugin)也成为 jQuery 扩展(Extension),是一种遵循一定规范的应用程序接口编写出来的程序。目前 jQuery 插件已超过几千种,由来自世界...

    defcon 评论0 收藏0
  • 基于droneCI/CD,对接kubernetes,见证灵活与自,CI/CD对接kubernet

    摘要:所以我们选一个仓库仓库比较多,我这里选用,都行,根据需求自行选择访问端口,然后就没有然后了功能没有那么强大,不过占用资源少,速度快,我们稳定运行了几年了。 kubernetes集群三步安装 CI 概述 用一个可描述的配置定义整个工作流 程序员是很懒的动物,所以想各种办法解决重复劳动的问题,如果你的工作流中还在重复一些事,那么可能就得想想如何优化了 持续集成就是可以帮助我们解决重复的代码...

    iOS122 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<