10个JavaScript难点

1. 立即执行函数

立即执行函数,即Immediately Invoked Function Expression (IIFE),正如它的名字,就是创建函数的同时立即执行。它没有绑定任何事件,也无需等待任何异步操作:


  1. (function() { 
  2.  
  3. // 代码 
  4.  
  5. // ... 
  6.  
  7. })();  

function(){…}是一个匿名函数,包围它的一对括号将其转换为一个表达式,紧跟其后的一对括号调用了这个函数。立即执行函数也可以理解为立即调用一个匿名函数。立即执行函数最常见的应用场景就是:将var变量的作用域限制于你们函数内,这样可以避免命名冲突。

2. 闭包

对于闭包(closure),当外部函数返回之后,内部函数依然可以访问外部函数的变量。


  1. function f1() 
  2.     var N = 0; // N是f1函数的局部变量 
  3.      
  4.     function f2() // f2是f1函数的内部函数,是闭包 
  5.     { 
  6.         N += 1; // 内部函数f2中使用了外部函数f1中的变量N 
  7.         console.log(N); 
  8.     } 
  9.  
  10.     return f2; 
  11.  
  12. var result = f1(); 
  13.  
  14. result(); // 输出1 
  15. result(); // 输出2 
  16. result(); // 输出3  

代码中,外部函数f1只执行了一次,变量N设为0,并将内部函数f2赋值给了变量result。由于外部函数f1已经执行完毕,其内部变量N应该在内存中被清除,然而事实并不是这样:我们每次调用result的时候,发现变量N一直在内存中,并且在累加。为什么呢?这就是闭包的神奇之处了!

3. 使用闭包定义私有变量

通常,JavaScript开发者使用下划线作为私有变量的前缀。但是实际上,这些变量依然可以被访问和修改,并非真正的私有变量。这时,使用闭包可以定义真正的私有变量:


  1. function Product() { 
  2.  
  3.     var name; 
  4.  
  5.     this.setName = function(value) { 
  6.         name = value; 
  7.     }; 
  8.  
  9.     this.getName = function() { 
  10.         return name; 
  11.     }; 
  12.  
  13. var p = new Product(); 
  14. p.setName("Fundebug"); 
  15.  
  16. console.log(p.name); // 输出undefined 
  17. console.log(p.getName()); // 输出Fundebug  

代码中,对象p的的name属性为私有属性,使用p.name不能直接访问。

4. prototype

每个JavaScript构造函数都有一个prototype属性,用于设置所有实例对象需要共享的属性和方法。prototype属性不能列举。JavaScript仅支持通过prototype属性进行继承属性和方法。


  1. function Rectangle(x, y) 
  2.     this._length = x; 
  3.     this._breadth = y; 
  4.  
  5. Rectangle.prototype.getDimensions = function() 
  6.     return { 
  7.         length: this._length, 
  8.         breadth: this._breadth 
  9.     }; 
  10. }; 
  11.  
  12. var x = new Rectangle(3, 4); 
  13. var y = new Rectangle(4, 3); 
  14.  
  15. console.log(x.getDimensions()); // { length: 3, breadth: 4 } 
  16. console.log(y.getDimensions()); // { length: 4, breadth: 3 }  

代码中,x和y都是构造函数Rectangle创建的对象实例,它们通过prototype继承了getDimensions方法。

5. 模块化

JavaScript并非模块化编程语言,至少ES6落地之前都不是。然而对于一个复杂的Web应用,模块化编程是一个最基本的要求。这时,可以使用立即执行函数来实现模块化,正如很多JS库比如jQuery以及我们Fundebug都是这样实现的。


  1. var module = (function() { 
  2.     var N = 5; 
  3.  
  4.     function print(x) { 
  5.         console.log("The result is: " + x); 
  6.     } 
  7.  
  8.     function add(a) { 
  9.         var x = a + N; 
  10.         print(x); 
  11.     } 
  12.  
  13.     return { 
  14.         description: "This is description", 
  15.         add: add 
  16.     }; 
  17. })(); 
  18.  
  19.  
  20. console.log(module.description); // 输出"this is description"  
  21.  
  22. module.add(5); // 输出“The result is: 10”  

所谓模块化,就是根据需要控制模块内属性与方法的可访问性,即私有或者公开。在代码中,module为一个独立的模块,N为其私有属性,print为其私有方法,decription为其公有属性,add为其共有方法。

6. 变量提升

JavaScript会将所有变量和函数声明移动到它的作用域的最前面,这就是所谓的变量提升(Hoisting)。也就是说,无论你在什么地方声明变量和函数,解释器都会将它们移动到作用域的最前面。因此我们可以先使用变量和函数,而后声明它们。

但是,仅仅是变量声明被提升了,而变量赋值不会被提升。如果你不明白这一点,有时则会出错:


  1. console.log(y);  // 输出undefined 
  2.  
  3. y = 2; // 初始化y  

上面的代码等价于下面的代码:


  1. var y;  // 声明y 
  2.  
  3. console.log(y);  // 输出undefined 
  4.  
  5. y = 2; // 初始化y  

为了避免BUG,开发者应该在每个作用域开始时声明变量和函数。

7. 柯里化

柯里化,即Currying,可以是函数变得更加灵活。我们可以一次性传入多个参数调用它;也可以只传入一部分参数来调用它,让它返回一个函数去处理剩下的参数。


  1. var add = function(x) { 
  2.     return function(y) { 
  3.         return x + y; 
  4.     }; 
  5. }; 
  6.  
  7. console.log(add(1)(1)); // 输出2 
  8.  
  9. var add1 = add(1); 
  10. console.log(add1(1)); // 输出2 
  11.  
  12. var add10 = add(10); 
  13. console.log(add10(1)); // 输出11  

代码中,我们可以一次性传入2个1作为参数add(1)(1),也可以传入1个参数之后获取add1与add10函数,这样使用起来非常灵活。

8. apply, call与bind方法

JavaScript开发者有必要理解apply、call与bind方法的不同点。它们的共同点是第一个参数都是this,即函数运行时依赖的上下文。

三者之中,call方法是最简单的,它等价于指定this值调用函数:


  1. var user = { 
  2.     name: "Rahul Mhatre", 
  3.     whatIsYourName: function() { 
  4.         console.log(this.name); 
  5.     } 
  6. }; 
  7.  
  8. user.whatIsYourName(); // 输出"Rahul Mhatre", 
  9.  
  10. var user2 = { 
  11.     name: "Neha Sampat" 
  12. }; 
  13.  
  14. user.whatIsYourName.call(user2); // 输出"Neha Sampat"  
  • apply方法与call方法类似。两者唯一的不同点在于,apply方法使用数组指定参数,而call方法每个参数单独需要指定:
  • apply(thisArg, [argsArray]) 

  1. var user = { 
  2.     greet: "Hello!", 
  3.     greetUser: function(userName) { 
  4.         console.log(this.greet + " " + userName); 
  5.     } 
  6. }; 
  7.  
  8. var greet1 = { 
  9.     greet: "Hola" 
  10. }; 
  11.  
  12. user.greetUser.call(greet1, "Rahul"); // 输出"Hola Rahul" 
  13. user.greetUser.apply(greet1, ["Rahul"]); // 输出"Hola Rahul"  

使用bind方法,可以为函数绑定this值,然后作为一个新的函数返回:


  1. var user = { 
  2.      greet: "Hello!", 
  3.      greetUser: function(userName) { 
  4.      console.log(this.greet + " " + userName); 
  5.      } 
  6. }; 
  7.  
  8. var greetHola = user.greetUser.bind({greet: "Hola"}); 
  9. var greetBonjour = user.greetUser.bind({greet: "Bonjour"}); 
  10.  
  11. greetHola("Rahul") // 输出"Hola Rahul" 
  12. greetBonjour("Rahul") // 输出"Bonjour Rahul"  

9. Memoization

Memoization用于优化比较耗时的计算,通过将计算结果缓存到内存中,这样对于同样的输入值,下次只需要中内存中读取结果。


  1. function memoizeFunction(func) 
  2.     var cache = {}; 
  3.     return function() 
  4.     { 
  5.         var key = arguments[0]; 
  6.         if (cache[key]) 
  7.         { 
  8.             return cache[key]; 
  9.         } 
  10.         else 
  11.         { 
  12.             var val = func.apply(this, arguments); 
  13.             cache[key] = val; 
  14.             return val; 
  15.         } 
  16.     }; 
  17.  
  18.  
  19. var fibonacci = memoizeFunction(function(n) 
  20.     return (n === 0 || n === 1) ? n : fibonacci(n - 1) + fibonacci(n - 2); 
  21. }); 
  22.  
  23. console.log(fibonacci(100)); // 输出354224848179262000000 
  24. console.log(fibonacci(100)); // 输出354224848179262000000  

代码中,第2次计算fibonacci(100)则只需要在内存中直接读取结果。

10. 函数重载

所谓函数重载(method
overloading),就是函数名称一样,但是输入输出不一样。或者说,允许某个函数有各种不同输入,根据不同的输入,返回不同的结果。凭直觉,函数重载可以通过if...else或者switch实现,这就不去管它了。jQuery之父John
Resig提出了一个非常巧(bian)妙(tai)的方法,利用了闭包。

从效果上来说,people对象的find方法允许3种不同的输入: 0个参数时,返回所有人名;1个参数时,根据firstName查找人名并返回;2个参数时,根据完整的名称查找人名并返回。

难点在于,people.find只能绑定一个函数,那它为何可以处理3种不同的输入呢?它不可能同时绑定3个函数find0,find1与find2啊!这里的关键在于old属性。

由addMethod函数的调用顺序可知,people.find最终绑定的是find2函数。然而,在绑定find2时,old为find1;同理,绑定find1时,old为find0。3个函数find0,find1与find2就这样通过闭包链接起来了。

根据addMethod的逻辑,当f.length与arguments.length不匹配时,就会去调用old,直到匹配为止。


  1. function addMethod(object, name, f) 
  2.     var old = object[name]; 
  3.     object[name] = function() 
  4.     { 
  5.         // f.length为函数定义时的参数个数 
  6.         // arguments.length为函数调用时的参数个数 
  7.         if (f.length === arguments.length) 
  8.         { 
  9.             return f.apply(this, arguments); 
  10.         } 
  11.         else if (typeof old === "function") 
  12.         { 
  13.             return old.apply(this, arguments); 
  14.         } 
  15.     }; 
  16.  
  17.  
  18. // 不传参数时,返回所有name 
  19. function find0() 
  20.     return this.names; 
  21.  
  22.  
  23. // 传一个参数时,返回firstName匹配的name 
  24. function find1(firstName) 
  25.     var result = []; 
  26.     for (var i = 0; i < this.names.length; i++) 
  27.     { 
  28.         if (this.names[i].indexOf(firstName) === 0) 
  29.         { 
  30.             result.push(this.names[i]); 
  31.         } 
  32.     } 
  33.     return result; 
  34.  
  35.  
  36. // 传两个参数时,返回firstName和lastName都匹配的name 
  37. function find2(firstName, lastName) 
  38. {  
  39.     var result = []; 
  40.     for (var i = 0; i < this.names.length; i++) 
  41.     { 
  42.         if (this.names[i] === (firstName + " " + lastName)) 
  43.         { 
  44.             result.push(this.names[i]); 
  45.         } 
  46.     } 
  47.     return result; 
  48.  
  49.  
  50. var people = { 
  51.     names: ["Dean Edwards", "Alex Russell", "Dean Tom"] 
  52. }; 
  53.  
  54.  
  55. addMethod(people, "find", find0); 
  56. addMethod(people, "find", find1); 
  57. addMethod(people, "find", find2); 
  58.  
  59.  
  60. console.log(people.find()); // 输出["Dean Edwards", "Alex Russell", "Dean Tom"] 
  61. console.log(people.find("Dean")); // 输出["Dean Edwards", "Dean Tom"] 
  62. console.log(people.find("Dean", "Edwards")); // 输出["Dean Edwards"]  


作者:Fundebug

来源:51CTO

时间: 2024-11-02 00:38:34

10个JavaScript难点的相关文章

分享10个 javascript 在线 debugging 工具

调试Javascript可能是web开发中最让人郁闷的事情.所以这里我们绝定来寻找一些好的工具来帮助大家调试.这里是10款我们精选的基于浏览器的JS在线调试工具,希望大家喜欢! Online Debugging Tools 1. Pastebin Pastebin是一个协作式的调试工具,帮助你在IRC,IM或者消息版上对话来分享和修改代码片段 2. JSON Formatter and Validator JSON格式化工具用来帮助打来调试JSON.因为JSON数据格式经常没有换行,可能非常难于

最常用的10个javascript自定义函数

javascript|函数 If there was ever a universal common.js shared among the entire develosphere, you'd fine these ten (plus one bonus) functions. It would be the swiss army knife no developer would go into production without. They have no doubt been teste

非常常用实用的10段 JavaScript 代码

avaScript正变得越来越流行,它已经成为前端开发的第一选择,并且利用基于JavaScript语言的NodeJS,我们也可以开发出高性能的后端服务,甚至我还看到在硬件编程领域也出现了JavaScript的身影.JavaScript正在逐渐进化为一门全能的开发语言. 但用好JavaScript并不容易,你除了需要掌握它的语法并知道如何写出高质量的代码之外,还需要了解如何解决那些几乎在每个项目中都会遇到的需求场景,比如:判断日期,高亮文本,限制字符数等等,有很多第三方库可以解决这些问题,但这些库

10个JavaScript中易犯小错误_javascript技巧

在今天,JavaScript已经成为了网页编辑的核心.尤其是过去的几年,互联网见证了在SPA开发.图形处理.交互等方面大量JS库的出现. 如果初次打交道,很多人会觉得js很简单.确实,对于很多有经验的工程师,或者甚至是初学者而言,实现基本的js功能几乎毫无障碍.但是JS的真实功能却比很多人想象的要更加多样.复杂.JavaScript的许多细节规定会让你的网页出现很多意想不到的bug,搞懂这些bug,对于成为一位有经验的JS开发者很重要. 常见错误一:对于this关键词的不正确引用 我曾经听一位喜

DHTML【10】--Javascript

      大家好,这一节主要介绍Javascript的函数.函数是Javascript的核心中的核心,这么强调一点都不过分,相信没有人反对,如果有人反对,你以后可以不用函数,呵呵,说的有点绝了啊.        下面看一下Javascript定义函数的基本语法格式:       格式一:        function 函数名(m){            return m;        }      格式二:匿名函数       function (m,n){              

谈谈javascript语法里一些难点问题(一)

1)    引子 前不久我建立的技术群里一位MM问了一个这样的问题,她贴出的代码如下所示: var a = 1; function hehe() {          window.alert(a);          var a = 2;          window.alert(a); } hehe(); 执行结果如下所示: 第一个alert:   第二个alert:   这是一个令人诧异的结果,为什么第一个弹出框显示的是undefined,而不是1呢?这种疑惑的原理我描述如下: 一个页面

谈谈javascript语法里一些难点问题(二)

3)    作用域链相关的问题 作用域链是javascript语言里非常红的概念,很多学习和使用javascript语言的程序员都知道作用域链是理解javascript里很重要的一些概念的关键,这些概念包括this指针,闭包等等,它非常红的另一个重要原因就是作用域链理解起来太难,就算有人真的感觉理解了它,但是碰到很多实际问题时候任然会是丈二和尚摸不到头脑,例如上篇引子里讲到的例子,本篇要讲的主题就是作用域链,再无别的内容,希望看完本文的朋友能有所收获. 讲作用域链首先要从作用域讲起,下面是百度百

WEBJX推荐10个带有漂亮UI组件的JavaScript框架

文章简介:十个拥有丰富 UI 组件的 JavaScript 开发框架. 如今,网上有各种各样的JavaScript框架用来简化 Web 应用开发.这些框架都提供了一些核心的特性,例如 DOM 操作,动画,事件处理以及 Ajax 交互,但不是都带有 UI 组件.今天这篇文章向大家推荐的10个JavaScript框架提供了丰富的 Web UI 组件,帮助你构建一致,可靠以及高度交互的漂亮用户界面. ExtJS ExtJS 是最流行的 JavaScript 框架之一,提供了非常丰富的 UI 组件,包括

JavaScript编程的10个实用小技巧_javascript技巧

在这篇文章中,我将列出10个Javascript实用小技巧,主要面向Javascript新手和中级开发者.希望每个读者都能至少从中学到一个有用的技巧. 1.变量转换 看起来很简单,但据我所看到的,使用构造函数,像Array()或者Number()来进行变量转换是常用的做法.始终使用原始数据类型(有时也称为字面量)来转换变量,这种没有任何额外的影响的做法反而效率更高. 复制代码 代码如下: var myVar   = "3.14159",str     = ""+ m