今天与一挚友加同事调试一段奇葩的javascript代码,在分析出结果后,让我萌生了写此篇文章的想法,如有不对之处望指正,也欢迎大家一起讨论。缩减后的js代码如下,你是否能准确说明他的输出值呢?

  1. function DemoFunction(){
  2. this.init = function(){
  3. var func = (function(va){
  4. this.va = va;
  5. return function(){
  6. va += this.va;
  7. return va;
  8. }
  9. })(function(va1, va2){
  10. var va3 = va1 + va2; //干扰代码
  11. return va1;
  12. }(1,2));
  13.  
  14. console.log(func(20));
  15.  
  16. this.func = func;
  17. console.log(this.func(100));
  18. }
  19. }
  20. var a = new DemoFunction();
  21. a.init();

如果要解释这段代码,首先我们得有如下几个概念:

  • 执行上下文:每次当控制器转到ECMAScript可执行代码时,即会进入一个可执行上下文,参考文献
  • this:this的创建是在 “进入执行上下文” 时创建的,在代码执行过程中是不可变的,参考文献
  • 自执行函数:准确来说应该叫:立即调用函数表达式。因为他声明后即执行,参考文献

详细解释此段代码

一、首先看DemoFunction的构造函数

这是代码的重点,第一层代码可以缩减为如下:

  1. function DemoFunction(){
  2. this.init = function(){
  3. //省略代码....
  4. }
  5. }

表示为DemoFunction的实例提供init方法(声明:此处有误导成份,方法应尽可能放在原型链接上,也就是prototype上。),对外公开的接口。

二、在init方法中,再次省略代码如下:

  1. var func = (function(va){
  2. this.va = va;
  3. return function(){
  4. va += this.va;
  5. return va;
  6. }
  7. })(8/*省略代码...*/);
  8.  
  9. //省略代码....

代码虽短,但信息量巨大,但这样省略部分代码后,你是否可以清晰的看出他的层次结构。

  • 首先定义了一个立即执行函数,并把此函数的执行结果赋值给func。
  • 需要注意立即执行函数中this.va=va这行代码,由于立即执行函数没有调用者,所以在进入可执行上下文时,this会被赋值为Global(浏览器中为window对象)。
  • 更需要注意立即执行函数,返回的是一个闭包,在这里一定要注意一个问题:this是在进入可执行上下文时创建的。
  • 也就是说此段代码执行完成后,func的值应该是这样的:
  1. func = function(){
  2. va += this.va;
  3. return va;
  4. }

只不过此时他的父级作用域是立即执行函数而已。

三、在init方法中,注意如下代码:

  1. var func = (function(va){
  2. this.va = va;
  3. return function(){
  4. va += this.va;
  5. return va;
  6. }
  7. })(function(va1, va2){
  8. var va3 = va1 + va2;
  9. return va1;
  10. }(1,2));
  11. //省略代码....

va又为一个立即执行函数,这个立即执行函数接受了两个参数va1,va2,但只返回了va1。以此为据,那么可以确定va的值也就为1。接着就执行this.va=va这句代码,由于当前this为window,所以参数va的值被赋值到了window的一个叫va的属性上。

四、在init方法中,加上输出语句:

  1. var func = (function(va){
  2. this.va = va;
  3. return function(){
  4. va += this.va;
  5. return va;
  6. }
  7. })(function(va1, va2){
  8. var va3 = va1 + va2;
  9. return va1;
  10. }(1,2));
  11.  
  12. console.log(func(20));
  13.  
  14. this.func = func;
  15. console.log(this.func(100));
  16. }

主要分析两个console.log,也是产出结果的时候到了。

  • 第一个console.log输出的是func(20),这里一定要注意调用者是没有具体指定的,此时默认的就是Global(也就是widnow对象),因此输出为:2
  • 第二个console.log输出的是this.func(100),可以看到this.func与func是指向同一个函数的引用,但此时的调用者则指定为this,也就是当前对象的实例,因此输出为:NaN。

    原因:this(当前对象的实例)作为调用者,在func的函数中va += this.va这句代码中的this是指向当前对象的实例,但当前对象的实例上是没有va属性的。如果你调试跟踪会发现va是有值的,他的值为:1,这是为什么呢?这就是作用域链,va虽然在当前作用域没有,但在父级(也就是window的作用域下)是存一个叫va的属性的。

总结

通过此段示例代码的分析,我们可以体会到要深入理解Javascript代码,必须要明白且深度掌握他的:闭包、this、原型链(作用域链)、立即调用函数表达式、函数等概念和机理。此类概念每时每该都充斥任务一个库或者框架的代码中,有了他们做为基石,理解和看懂别人的Js代码就so easy了。

一段奇葩Javascript代码引发的思考的更多相关文章

  1. 由闭包引起的对javascript代码可维护性的思考

    在最近的编程实践中由闭包的使用引起了我对javascript代码可维护性的思考.面向对象的其中一个特性封装性通过封装可以降低类与类之间或模块与模块之间耦合性从而使我们的设计更加高内聚低耦合,在大规模的 ...

  2. 因一段JavaScript代码引发的闲扯

    前两天,一朋友给我发了一段JavaScript代码: function f1(){ var n=999; nAdd=function(){ n+=1 }; function f2(){ alert(n ...

  3. 由一段JS代码引发的思考

    不知道大家在编程的时候有没有遇到过这种情况,就是在循环遍历删除一部分内容的时候,发现只能删除其中一部分,而另一部分却总也删不掉,然后觉得自己的逻辑没有问题啊,于是陷入了深深的抑郁之中…… 昨天在处理一 ...

  4. 一段自用javascript代码

    function jsontoarray(mjson) { var arr = []; ; for(var x in mjson.data){ arr[i] = new Array(); arr[i] ...

  5. 前端福利!10个短小却超实用的JavaScript 代码段

    JavaScript正变得越来越流行,它已经成为前端开发的第一选择,并且利用基于JavaScript语言的NodeJS,我们也可以开发出高 性能的后端服务,甚至我还看到在硬件编程领域也出现了JavaS ...

  6. 以优美方式编写JavaScript代码

    英文原文:CoffeeScript: The beautiful way to write JavaScript 我用 JavaScript 编程很多年了,写了大量的 JavaScript 代码,即便 ...

  7. silverlight 中javascript 代码与托管代码的互调用 以及一些思考

    silverlight 客户端javascript 代码与托管代码的互调用时比较用意义的同时,因为silverlight本身就是一个插件,如果两者之间不能进行相互的调用,对于web 上的一些特殊的功能 ...

  8. JavaScript代码段整理笔记系列(二)

    上篇介绍了15个常用代码段,本篇将把剩余的15个补齐,希望对大家有所帮助!!! 16.检测Shift.Alt.Ctrl键: event.shiftKey; //检测Shift event.altKey ...

  9. JavaScript代码段整理笔记系列(一)

    30段JavaScript代码——上篇 1.如何区分IE及非IE浏览器: if(!+[1,]){ //IE 11 不支持 alert("这是 IE 浏览器"): }else{ al ...

随机推荐

  1. 八、OpenStack—Cinder组件安装

    一.安装和配置控制器节点 1.先决条件 1)创建数据库 # mysql -u root -p 2)创建cinder数据库 MariaDB [(none)]> CREATE DATABASE ci ...

  2. 单调栈&单调队列入门

    单调队列是什么呢?可以直接从问题开始来展开. Poj 2823 给定一个数列,从左至右输出每个长度为m的数列段内的最小数和最大数. 数列长度:\(N <=10^6 ,m<=N\) 解法① ...

  3. 心得体会,搞清楚你为什么学习C++?

    小编作为一名初学者时,从来没问过自己学习C语言.C++等语言是为了什么? 一开始,接触到这个行业可以说是有种魔力引导我,感到了很大的兴趣,很有意思. 我试着读资料,报名学习,找资料,可算是功夫不负有心 ...

  4. PPT vba从Execl 拷贝图表

    在PPT 需要引用Execl的COM组件 Dim wkb As Workbook Sub Change() Set wkb = Workbooks.Open("D:\D2_月报基础数据.xl ...

  5. KMP模板实现

    看了出题知识点才发现自己连KMP都没有好好的理解,甚至一共就打过一次板子=-= 于是照着之前的课件学了一学...发现没怎么弄懂qwq 我太弱啦! 找了一篇自认为全网最好的介绍 觉得写得很棒 字符串匹配 ...

  6. Fio测试工具参数

    以随机读为例:fio -ioengine=libaio -group_reporting -direct=1 -name=testsda -numjobs=1 --time_based --runti ...

  7. SpringBoot几种定时任务的实现方式

    定时任务实现的几种方式: Timer:这是java自带的java.util.Timer类,这个类允许你调度一个java.util.TimerTask任务.使用这种方式可以让你的程序按照某一个频度执行, ...

  8. 用JS来判断版本号比如v21.2.2.2和v21.2.2.2.15

    当判断两个版本号的时候,不能单纯的去点然后相互比较,版本比较可能比较特殊,但是分析起来又挺简单的,既然不能直接去点比较,那就拆分成数组,一组一组对应的去比,为了能正常的进行比较 如果上一个版本长度小于 ...

  9. Web版记账本开发记录(三)开发过程遇到的问题小结2

    问题1,获得当前时间 Date d = new Date(); SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss ...

  10. 你不知道的JS之作用域和闭包 附录

     原文:你不知道的js系列 A 动态作用域 动态作用域 是和 JavaScript中的词法作用域 对立的概念. 动态作用域和 JavaScript 中的另外一个机制 (this)很相似. 词法作用域是 ...