一段奇葩Javascript代码引发的思考

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

function DemoFunction(){
    this.init = function(){
        var func = (function(va){
        this.va = va;
        return function(){
            va += this.va;
            return va;
        }
        })(function(va1, va2){
            var va3 = va1 + va2; //干扰代码
            return va1;
        }(1,2));
        
        console.log(func(20));
        this.func = func;
        console.log(this.func(100));
    }
}
var a = new DemoFunction();
a.init();

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

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

详细解释此段代码
一、首先看DemoFunction的构造函数

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

 function DemoFunction(){
   this.init = function(){
     //省略代码....
   }
 }

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

二、在init方法中,再次省略代码如下:
 var func = (function(va){
   this.va = va;
   return function(){
     va += this.va;
     return va;
   }
  })(8/*省略代码...*/);

//省略代码....

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

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

 func = function(){
   va += this.va;
   return va;
 }

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

三、在init方法中,注意如下代码:
 var func = (function(va){
   this.va = va;
   return function(){
      va += this.va;
      return va;
   }
  })(function(va1, va2){
       var va3 = va1 + va2;
       return va1;
     }(1,2));
  //省略代码....

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

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

 var func = (function(va){
   this.va = va;
   return function(){
     va += this.va;
     return va;
   }
 })(function(va1, va2){
      var va3 = va1 + va2;
      return va1;
   }(1,2));

  console.log(func(20));

  this.func = func;
  console.log(this.func(100));
 

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

(1)第一个console.log输出的是func(20),这里一定要注意调用者是没有具体指定的,此时默认的就是Global(也就是widnow对象),因此输出为:2
(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了。


与本文有关的文章

express/ multer 上传图片文件 html图片延迟加载 一段奇葩Javascript代码引发的思考 JavaScript两个变量交换值(不使用临时变量) ES6中Math对象的部分扩展 JavaScript中为什么string可以拥有方法? 前端js中经常出现的算法总结 canvas粒子瀑布 canvas纺纱飘带 JavaScript求最长公共子串 JavaScript中的__proto__ JS判断访问设备或型号 js获取url?后的参数 js键盘keyCode对照表 页面鼠标滚动事件 对象克隆或拷贝不是赋值 URL参数含有中文出现乱码 es6常用的的语法大集合 js中的this关键字 js中实现 a*寻路算法 js中用 '==' 还是 '===' web性能优化 reflow(回流)与repaint(重绘) 7年前端大神总结出的js经验 scroll事件常用到的场景,以及判断 js中深度拷贝与浅拷贝 requirejs基础知识 判断参数是什么类型?array?object? 有趣的js基础选择题 js中哪些值能作为if的条件,if使用小技巧 js中键盘按下事件keydown js中的!function到底是什么意思? AMD与CMD的区别到底在哪? Array与Math属性 方法一览 js中的栈与堆 jquery为动态添加生成的元素添加绑定事件 delegate 自己常用的正则(regexp)整理 57秒读完《10 分钟学会 JavaScript 的 Async/Await》 最简单的数组去重 js中数字调用方法 jq或者原生js动态加载js文件方法 个人常用封装的js插件 select默认选中某个option 正则表达式提取cookie 原生ajax写法 js倒计时方法 js 时间戳相关操作方法 html关于页面跳转url带参数 indexOf,charAt,subString的简要区别 JS页面返回带参数或者保持原有位置 js中的钩子机制(hook) vue中的addClass removeClass &#x(unicode编码后的汉字)JS转译方法-nodejs爬虫转译乱码 es6/7 js数组深拷贝和数组合并方法 【html5】原生JS控制video的播放和暂停切换 前端 fetch 前端 PWA? js处理手机号|身份证中间替换成 * 号 js时间戳转换为本地时间 timestamp>localtime es6中 数组位置对换 js 数组对象循环各种方法以及性能对比 js实用黑科技之生产随机数 js加密之AES es6遍历对象方法 js删除字符串的的方法 JS中的Array长度最大可以设置为多少? html img加载失败的话替换成默认图片 图片压缩插件 lrz.bundle.js js手机号中间四位变成*号 javascript 到底要不要加分号 酷炫烟雾效果的前端js插件 waterpipe.js js稀奇古怪问题之指向 现代浏览器和触摸设备上重新排序拖放列表 - Sortable.min.js 一个监听键盘的js库 - hotkeys.js canvas操作图片合成 或文字叠加 并导出base64 字符转换-unicode <=> ASCII js删除某一个指定元素 禁止 百度ueditor过滤script link js正则效验金额最多两位小数 原生js获取元素offsetTop值不准确? 浏览器控制台跳过debugger 打乱数组顺序的两种方法 js 中的 bind介绍 原生js移除元素 document.removeElementById ?? js string进制互转 input 点击选择全部文本 点击全选 js点击复制input的值 JS/JQ获取各种屏幕的高度和宽度 闭包之[[Scopes]]属性 JS操纵html5 audio 播放以及暂停 原生js 给dom添加 onmouseenter ontouchstart 事件 Js charCodeAt fromCharCode parseInt js位操作教程 js byte[] 和string 相互转换 UTF-8 data URI 转 image data 原生JS实现 addClass removeClass hasClass JS实现为动态添加的元素增加事件 js查找页面中alert弹窗位置 JS实现Jquery的addClass,removeClass,changeClass,toggleClass HTML5 Blob与ArrayBuffer、TypeArray和字符串String之间转换 JavaScript如何转换二进制数据显示成图片 Uint8Array转成可用的imgurl es6数组快速删除指定元素 支付宝小程序与微信小程序的不同点对比 JS数组按数字的大小排序 js对象数组按照对象属性排序 js点击按钮播放音乐
回到顶部