当前位置: 首页 > news >正文

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

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

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

相关文章:

  • 背水一战 Windows 10 (53) - 控件(集合类): ItemsControl 的布局控件 - ItemsStackPanel, ItemsWrapGrid...
  • 三网卡服务器 配置三线三IP linux策略路由
  • Oracle 插入时间时 报错:ORA-01861: 文字与格式字符串不匹配 的解决办法
  • Entity Framework中的字符串插值引发担忧
  • CentOS 利用 yum 安装卸载软件常用命令
  • jarjar-maven-plugin打包
  • Vim 删除不包含指定字符串的行及统计匹配个数
  • JVM指令助记符
  • FTP与TFTP
  • Android通过Gradle发布开源项目到binary/Jcenter
  • 针对通过 SSH 连接到 Azure Linux VM 时发生的失败、错误或被拒绝问题进行故障排除...
  • bootstrap-table使用总结
  • Tensorflow CNN入门
  • charles抓包并分析问题
  • Veeam任命新的中国区总经理 坚信可用性领域大有可为
  • [数据结构]链表的实现在PHP中
  • 【108天】Java——《Head First Java》笔记(第1-4章)
  • Android Studio:GIT提交项目到远程仓库
  • cookie和session
  • CSS3 聊天气泡框以及 inherit、currentColor 关键字
  • Django 博客开发教程 16 - 统计文章阅读量
  • input实现文字超出省略号功能
  • javascript 总结(常用工具类的封装)
  • JavaScript实现分页效果
  • Just for fun——迅速写完快速排序
  • mysql中InnoDB引擎中页的概念
  • Redis 懒删除(lazy free)简史
  • uni-app项目数字滚动
  • vue2.0项目引入element-ui
  • 大整数乘法-表格法
  • 飞驰在Mesos的涡轮引擎上
  • 通过来模仿稀土掘金个人页面的布局来学习使用CoordinatorLayout
  • 新手搭建网站的主要流程
  • 学习笔记DL002:AI、机器学习、表示学习、深度学习,第一次大衰退
  • 云栖大讲堂Java基础入门(三)- 阿里巴巴Java开发手册介绍
  • 智能合约开发环境搭建及Hello World合约
  • 没有任何编程基础可以直接学习python语言吗?学会后能够做什么? ...
  • ​LeetCode解法汇总2808. 使循环数组所有元素相等的最少秒数
  • ​软考-高级-信息系统项目管理师教程 第四版【第19章-配置与变更管理-思维导图】​
  • (03)光刻——半导体电路的绘制
  • (3)选择元素——(17)练习(Exercises)
  • (C++17) optional的使用
  • (Java实习生)每日10道面试题打卡——JavaWeb篇
  • (zt)基于Facebook和Flash平台的应用架构解析
  • (附源码)小程序儿童艺术培训机构教育管理小程序 毕业设计 201740
  • (力扣)循环队列的实现与详解(C语言)
  • (没学懂,待填坑)【动态规划】数位动态规划
  • (一) storm的集群安装与配置
  • ***检测工具之RKHunter AIDE
  • .bat批处理(六):替换字符串中匹配的子串
  • .cn根服务器被攻击之后
  • .NET Framework与.NET Framework SDK有什么不同?
  • .NET多线程执行函数
  • .NET国产化改造探索(三)、银河麒麟安装.NET 8环境
  • .NET是什么