最近在看John Resig 与 Bear Bibeault的《JavaScript 忍者秘籍》。这本书处处提现了js的魔法(从我这个写强类型语言的人看来)。js能够点石成金,处处体现着它特有的魅力。所以将一些有意思的地方记录了下来。

1.准备知识

1.1 闭包

闭包是一个函数在创建时,允许该自身函数访问并操作该自身函数以外的变量时所创建的作用域。闭包可以让函数访问所有存在于该函数声明时的作用域内的变量和函数。

  1. <script>
  2. var outerValue = "ninja";
  3. var later;
  4. function outerFunction(){
  5. var innerValue = 'samural';
  6. function innerFunction(paramValue){
  7. console.log(outerValue); // ninja
  8. console.log(innerValue); // samural
  9. console.log(paramValue); // wakizshai
  10. console.log(tooLate); // ronin
  11. }
  12. later = innerFunction;
  13. }
  14. outerFunction();
  15. var tooLate = "ronin";
  16. later('wakizshai');
  17. </script>

innerFunction 方法能够访问到该方法调用之前的声明作用域内的变量和函数。在函数声明innerFunction的时候,同时创建了一个闭包。将函数声明这个时刻该作用域中所有的变量(参数)以及函数作用域之外的都放到了闭包中保存。

1.2 函数上下文

函数的上下文就是函数的this,调用函数的方式不同,函数的上下文不同。大致包括以下四种方式

1.2.1 作为函数调用
  1. // 定义一个函数
  2. function ninja(){};
  3. // 对函数进行调用,ninja的this是window
  4. ninja();

通过声明函数后利用()对函数调用,那么这个函数的this就是window对象。也就是说这个函数是全局的。

1.2.2 作为方法调用
  1. var o = {};
  2. // o对象的whatever方法作为一个匿名的函数
  3. o.whatever= function(){};
  4. // 作为方法进行调用,whatever的this就是o对象
  5. o.whatever();

将函数作为对象的方法进行调用,那么这个对象就是这个方法的上下文(this就是指的这个对象)

1.2.3 作为构造器调用
  1. function Ninja(){
  2. this.skulk = function(){
  3. // 通过返回this来验证判断
  4. return this;
  5. }
  6. }
  7. // 通过new关键字,来使ninja()作为构造器来进行调用
  8. var ninja1 = new Ninja();
  9. var ninja2 = new Ninja();
  10. // ninja1.skulk() 返回的是ninja1 说明this 就是ninja1
  11. console.log((ninja1.skulk() === ninja1));
  12. // ninja2.skulk() 返回的是ninja2 说明this 就是ninja2
  13. console.log((ninja2.skulk() === ninja2));

通过上述的代码证明了,如果一个函数作为构造器进行调用,那么这个函数的上下文this 就是指新创建的对象,这里指的是ninja1和ninja2

1.2.4 使用apply()和call()调用

js提供apply()和call()方法,这样可以自由的为函数指定上下文。

通过apply()来调用函数,需要给apply()指定两个参数:一个是函数上下文对象,一个作为函数参数所组成的数组。

通过call()来调用函数,需要传入的参数:函数的上下文对象,函数的参数列表(call()方法的函数参数个数不一定为2

  1. function juggle(){
  2. var result = 0;
  3. // 对参数进行相加
  4. for(var n =0;n<argument.length;n++){
  5. result += arguments[n];
  6. }
  7. // 可以通过result属性来检查juggle的上下文
  8. this.result = result;
  9. }
  10. var ninja1 = {};
  11. var ninja2 = {};
  12. // 利用apply()将juggle的上下文绑定成ninja1,并且用数组的方式指定了参数列表[1,2,3,4]
  13. juggle.apply(ninja1,[1,2,3,4]);
  14. // 利用call()将juggle的上下文绑定成ninja2,并且传入了该方法的参数。
  15. juggle.call(ninja2,5,6,7,8);
  16. // 验证
  17. console.log(ninja1.result);
  18. console.log(ninja2.result);

2.基于参数个数进行重载

  1. function addMethod(object,name,fn){
  2. // 保存原有的函数
  3. var old = object[name];
  4. object[name] = function(){
  5. // 如果该匿名函数的形参个数和实参个数匹配,就调用该函数
  6. if(fn.length == arguments.length){
  7. return fn.apply(this,arguments);
  8. }else{
  9. // 否则就调用原来的函数
  10. return old.apply(this,arguments);
  11. }
  12. }
  13. }
  14. /*****************测试代码***************************/
  15. var ninjas = {
  16. values:["Dean Edwards","Sam Stephenson","Alex Russell"]
  17. };
  18. // 声明无参的函数
  19. addMethod(ninjas,'find',function(){
  20. return this.values;
  21. });
  22. // 声明一个参数的函数
  23. addMethod(ninjas,'find',function(name){
  24. var ret = [];
  25. for(var i = 0;i < this.values.length;i++){
  26. if(this.values[i].indexOf(name) == 0){
  27. ret.push(this.values[i]);
  28. }
  29. }
  30. return ret;
  31. });
  32. // 声明两个参数的函数
  33. addMethod(ninjas,"find",function(first,last)){
  34. var ret = [];
  35. for(var i = 0;i < this.values.length;i++){
  36. if(this.values[i] == (first + " " + last)){
  37. ret.push(this.values[i]);
  38. }
  39. }
  40. return ret;
  41. }
  42. // 检测无参的方法
  43. console.log(ninjas.find().length == 3);
  44. // 检测一个参数的方法
  45. console.log(ninjas.find("Sam").length == 1);
  46. // 检测两个参数的方法
  47. console.log(ninjas.find("Dean","Edwards").length == 1);

所有的函数都有一个length属性,这个属性的值等于该函数声明时所需要传入值的数量。

该方法能够通过优雅的闭包来实现多个参数的函数重载。但是这里没有考虑到参数的类型。

addMethod()每一次的调用都会产生一个新的匿名函数。并且这个新的匿名函数通过闭包包含着一个old对象。

old方法中包含之前旧的方法。这样就像洋葱一样一层一层。通过闭包访问old和fn来实现函数的重载。

3.新功能包装旧功能的重载方式


  1. // 定义一个包装函数,
  2. // 接收三个参数:需要包装的方法的上下文,要包装的方法名称,需要替原有方法进行执行的方法
  3. function wrap(object,method,wrapper){
  4. var fn = object[method];
  5. return object[method] = function(){
  6. // 通过闭包来访问fn
  7. return wrapper.apply(this,[fn.bind(this)].concat(Array.prototype.slice.call(arguments)));
  8. }
  9. }
  10. //////////////////////////////////////测试代码//////////////////////////////////////
  11. var o = {};
  12. // 旧方法
  13. o.oldFn = function(){
  14. console.log("This is old fn");
  15. }
  16. // 利用wrap方法对old方法进行重载。第一个参数就是原函数
  17. wrap(o,"oldFn",function(oldFn){
  18. console.log("This is new fn");
  19. oldFn();
  20. });
  21. // 调用
  22. o.oldFn();

首先,原有的方法会保存在fn中。当新的方法来进行重载时。新函数执行前的包装器函数。会返回一个重新构造过得参数列表。在这个参数列表中的第一个参数就是要重载的原有函数。

javascript闭包的妙用——实现函数的重载的更多相关文章

  1. JavaScript 闭包系列二(匿名函数及函数的闭包)

    一. 匿名函数 1. 函数的定义,可分为三种 1) 函数声明方式 function double(x) {     return 2*x; } 2)Function构造函数,把参数列表和函数体都作为字 ...

  2. javascript 中break、 continue、函数不能重载

    在javascript中,break与continue有着显著的差别. 如果遇到break语句,会终止最内层循环,无论后面还有多少计算. 如果遇到continue,只会终止此次循环,后面的自循环依然执 ...

  3. JavaScript闭包学习笔记

    此文都是大牛们关于闭包的观点,在此只是总结. 闭包应用的两种情况即可——函数作为返回值,函数作为参数传递. 1 深入理解javascript原型和闭包 判断一个变量是不是对象非常简单.值类型的类型判断 ...

  4. JavaScript中通过arguments对象实现对象的重载

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...

  5. Javascript闭包和C#匿名函数对比分析

    C#中引入匿名函数,多少都是受到Javascript的闭包语法和面向函数编程语言的影响.人们发现,在表达式中直接编写函数代码是一种普遍存在的需求,这种语法将比那种必须在某个特定地方定义函数的方式灵活和 ...

  6. JavaScript闭包理解【关键字:普通函数、闭包、解决获取元素标签索引】

    以前总觉得闭包很抽象,很难理解,所以百度一下"闭包"概览,百度的解释是:“闭包是指可以包含自由(未绑定到特定对象)变量的代码块:这些变量不是在这个代码块内或者任何全局上下文中定义的 ...

  7. JavaScript闭包函数的写法

    <script type="text/javascript"> //通过js内置的函数构造器创建函数 var func=new Function('a','b','re ...

  8. Javascript 闭包与高阶函数 ( 二 )

    在上一篇 Javascript 闭包与高阶函数 ( 一 )中介绍了两个闭包的作用. 两位大佬留言指点,下来我会再研究闭包的实现原理和Javascript 函数式编程 . 今天接到头条 HR 的邮件,真 ...

  9. JavaScript闭包理解【关键字:普通函数、变量访问作用域、闭包、解决获取元素标签索引】

        一.闭包(Closure)模糊概述 之前总觉得闭包(Closure)很抽象而且难理解,百度一下"闭包"名词,百度的解释是:“闭包是指可以包含自由(未绑定到特定对象)变量的代 ...

随机推荐

  1. 简易数据加密传输电路(VHDL)(原创)

    LIBRARY IEEE; USE IEEE.STD_LOGIC_1164.ALL; USE IEEE.std_logic_unsigned.ALL; ENTITY KEKE IS --定义实体wsj ...

  2. JBoss7安装、测试、配置和启动以及停止,部署

    转:http://www.hongyanliren.com/2014m01/3013.html 内容概要 JBoss系列三主要目的是演示如何部署应用到JBoss7/WildFly,如下图中描述了部署应 ...

  3. spring service层单元测试

    service层测试较简单,目前大多数测试主要是针对public方法进行的.依据测试方法划分,可以分为两种:基于mock的隔离测试和基于dbunit的普通测试. mock隔离测试 配置pom.xml ...

  4. web工作过程

    了解浏览器与服务器进行通信和访问的过程 打开浏览器-输入URL地址 当我们访问一个网页时,如http://www.baidu.com,这个网址包含四个部分的内容: 1.第一部分:协议类型:这里是htt ...

  5. HttpClien Get&Post

    新公司上班第二周,开始进军.Net Core,这方面的东西比较新,所以已经封装好的东西比较少,比如HttpClien之类的开源类库,找了NuGet好久,没有找到,所以先写个简陋的来用着先. 引用: u ...

  6. Java静态代理与动态代理模式的实现

    前言:    在现实生活中,考虑以下的场景:小王打算要去租房,他相中了一个房子,准备去找房东洽谈相关事宜.但是房东他很忙,平时上班没时间,总找不到时间去找他,他也没办法.后来,房东想了一个办法,他找到 ...

  7. Eclipse之JSON导包

    1.选中要导包的工程-–>2.右击选择创建文件夹--->3.将要导的包复制到该文件夹下--–>4.右击要导入的包-->5.选择Build path->Add to Bui ...

  8. 使用wget下载JDK8

    每次去官网下载JDK有点烦 但是直接使用wget 又得同意协议所以 使用如下的wget就好了(注意是64位的哦) 先去官网看一下地址变化 没有如下 :修改后面的下载地址即可 注意哦~ 2.然后使用下面 ...

  9. node.js入门系列(一)--Node.js简介

    什么是NodeJS JS是脚本语言,脚本语言都需要一个解析器才能运行.对于写在HTML页面里的JS,浏览器充当了解析器的角色.而对于需要独立运行的JS,NodeJS就是一个解析器. 每一种解析器都是一 ...

  10. 用GAN生成二维样本的小例子

    同步自我的知乎专栏:https://zhuanlan.zhihu.com/p/27343585 本文完整代码地址:Generative Adversarial Networks (GANs) with ...