转载:http://www.techug.com/post/10-javascript-concepts-every-programmer-must-master.html 原文:10 JavaScript concepts every Node.js programmer must master
立即执行函数 立即执行函数 ,即 Immediately Invoked Function Expression (IIFE),正如它的名字,就是创建函数的同时立即执行。它没有绑定任何事件,也无需等待任何异步操作:
function(){…} 是一个匿名函数,包围它的一对括号将其转换为一个表达式,紧跟其后的一对括号调用了这个函数。立即执行函数 也可以理解为立即调用一个匿名函数。立即执行函数 最常见的应用场景就是:将 var 变量的作用域限制于你们函数内,这样可以避免命名冲突。
闭包 对于闭包 (closure),当外部函数返回之后,内部函数依然可以访问外部函数的变量。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 function f1 ( ){ var N = 0 ; function f2 ( ) { N += 1 ; console .log (N); } return f2; } var result = f1 ();result (); result (); result ();
代码中,外部函数 f1 只执行了一次,变量 N 设为 0 ,并将内部函数 f2 赋值给了变量 result 。由于外部函数 f1 已经执行完毕,其内部变量 N 应该在内存中被清除,然而事实并不是这样:我们每次调用 result 的时候,发现变量 N 一直在内存中,并且在累加。为什么呢?这就是闭包的神奇之处了!
使用闭包定义私有变量 通常,JavaScript 开发者使用下划线作为私有变量的前缀。但是实际上,这些变量依然可以被访问和修改,并非真正的私有变量。这时,使用闭包可以定义真正的私有变量:
1 2 3 4 5 6 7 8 9 10 11 12 13 function Product ( ){ var name; this .setName = function (value ) { name = value; }; this .getName = function ( ) { return name; }; } var p = new Product ();p.setName ("Fundebug" ); console .log (p.name ); console .log (p.getName ());
代码中,对象 p 的的 name 属性为私有属性,使用 p.name 不能直接访问。
prototype 每个 JavaScript 构造函数都有一个 prototype 属性,用于设置所有实例对象需要共享的属性和方法。prototype 属性不能列举。JavaScript 仅支持通过 prototype 属性进行继承属性和方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 function Rectangle (x, y ){this ._length = x; this ._breadth = y; } Rectangle .prototype .getDimensions = function ( ){ return { length : this ._length , breadth : this ._breadth }; }; var x = new Rectangle (3 , 4 );var y = new Rectangle (4 , 3 );console .log (x.getDimensions ()); console .log (y.getDimensions ());
代码中,x 和 y 都是构造函数 Rectangle 创建的对象实例,它们通过 prototype 继承了 getDimensions 方法。
模块化 JavaScript 并非模块化编程语言,至少 ES6 落地之前都不是。然而对于一个复杂的 Web 应用,模块化编程是一个最基本的要求。这时,可以使用立即执行函数 来实现模块化,正如很多 JS 库比如 jQuery 以及我们 Fundebug 都是这样实现的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 var module =(function ( ) {var N = 5 ; function print (x ) { console .log ("The result is: " + x); } function add (a ) { var x = a + N; print (x); } return { description : "This is description" , add : add }; })(); console .log (module .description ); module .add (5 );
所谓模块化,就是根据需要控制模块内属性与方法的可访问性,即私有或者公开。在代码中,module 为一个独立的模块,N 为其私有属性,print 为其私有方法,decription 为其公有属性,add 为其共有方法。
变量提升 JavaScript 会将所有变量和函数声明移动到它的作用域的最前面,这就是所谓的**变量提升 (Hoisting)**。也就是说,无论你在什么地方声明变量和函数,解释器都会将它们移动到作用域的最前面。因此我们可以先使用变量和函数,而后声明它们。
但是,仅仅是变量声明被提升了,而变量赋值不会被提升。如果你不明白这一点,有时则会出错:
上面的代码等价于下面的代码:
1 2 3 var y; console .log (y); y = 2 ;
为了避免 BUG,开发者应该在每个作用域开始时声明变量和函数。
柯里化 柯里化,即 Currying ,可以是函数变得更加灵活。我们可以一次性传入多个参数调用它;也可以只传入一部分参数来调用它,让它返回一个函数去处理剩下的参数。
1 2 3 4 5 6 7 8 9 10 var add =function (x ) { return function (y ) { return x + y; }; }; console .log (add (1 )(1 )); var add1 = add (1 );console .log (add1 (1 )); var add10 = add (10 );console .log (add10 (1 ));
代码中,我们可以一次性传入 2 个 1 作为参数 **add(1)(1)**,也可以传入 1 个参数之后获取 add1 与 add10 函数,这样使用起来非常灵活。
apply, call 与 bind 方法 JavaScript 开发者有必要理解 apply 、call 与 bind 方法的不同点。它们的共同点是第一个参数都是 this ,即函数运行时依赖的上下文。
三者之中,call 方法是最简单的,它等价于指定 this 值调用函数:
1 2 3 4 5 6 7 8 9 10 11 var user = { name : "Rahul Mhatre" , whatIsYourName : function ( ) { console .log (this .name ); } }; user.whatIsYourName (); var user2 = { name : "Neha Sampat" }; user.whatIsYourName .call (user2);
apply 方法与 call 方法类似。两者唯一的不同点在于,apply 方法使用数组指定参数,而 call 方法每个参数单独需要指定:
apply(thisArg, [argsArray])
call(thisArg, arg1, arg2, …)
1 2 3 4 5 6 7 8 9 10 11 var user ={ greet : "Hello!" , greetUser : function (userName ) { console .log (this .greet + " " + userName); } }; var greet1 = { greet : "Hola" }; user.greetUser .call (greet1, "Rahul" ); user.greetUser .apply (greet1, ["Rahul" ]);
使用 bind 方法,可以为函数绑定 this 值,然后作为一个新的函数返回:
1 2 3 4 5 6 7 8 9 10 var user ={ greet : "Hello!" , greetUser : function (userName ) { console .log (this .greet + " " + userName); } }; var greetHola = user.greetUser .bind ({greet : "Hola" });var greetBonjour = user.greetUser .bind ({greet : "Bonjour" });greetHola ("Rahul" ) greetBonjour ("Rahul" )
Memoization Memoization 用于优化比较耗时的计算,通过将计算结果缓存到内存中,这样对于同样的输入值,下次只需要中内存中读取结果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 function memoizeFunction (func ){ var cache ={}; return function ( ) { var key = arguments [0 ]; if (cache[key]) { return cache[key]; } else { var val = func.apply (this , arguments ); cache[key] = val; return val; } }; } var fibonacci = memoizeFunction (function (n ){ return (n === 0 || n === 1 ) ? n : fibonacci (n - 1 ) + fibonacci (n - 2 ); }); console .log (fibonacci (100 )); console .log (fibonacci (100 ));
代码中,第 2 次计算 fibonacci(100) 则只需要在内存中直接读取结果。
函数重载 (还没看懂) 所谓函数重载 (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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 function addMethod (object, name, f ){ var old = object[name]; object[name] = function ( ) { if (f.length === arguments .length ) { return f.apply (this , arguments ); } else if (typeof old === "function" ) { return old.apply (this , arguments ); } }; } function find0 ( ){ return this .names ; } function find1 (firstName ){ var result = []; for (var i = 0 ; i < this .names .length ; i++) { if (this .names [i].indexOf (firstName) === 0 ) { result.push (this .names [i]); } } return result; } function find2 (firstName, lastName ){ var result = []; for (var i = 0 ; i < this .names .length ; i++) { if (this .names [i] === (firstName + " " + lastName)) { result.push (this .names [i]); } } return result; } var people = { names : ["Dean Edwards" , "Alex Russell" , "Dean Tom" ] }; addMethod (people, "find" , find0);addMethod (people, "find" , find1);addMethod (people, "find" , find2);console .log (people.find ()); console .log (people.find ("Dean" )); console .log (people.find ("Dean" , "Edwards" ));