你有必要知道的10个JavaScript难点


Posted in Javascript onJuly 25, 2017

能够读懂这篇博客的JavaScript开发者,运气不会太差…

1. 立即执行函数

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

(function() {
 // 代码
 // ...
})();

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

2. 闭包

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

function f1()
{
 var N = 0; // N是f1函数的局部变量
 function f2() // f2是f1函数的内部函数,是闭包
 {
 N += 1; // 内部函数f2中使用了外部函数f1中的变量N
 console.log(N);
 }
 return f2;
}
var result = f1();
result(); // 输出1
result(); // 输出2
result(); // 输出3

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

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

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

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); // 输出undefined
console.log(p.getName()); // 输出Fundebug

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

4. prototype

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

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()); // { length: 3, breadth: 4 }
console.log(y.getDimensions()); // { length: 4, breadth: 3 }

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

5. 模块化

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

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); // 输出"this is description" 
module.add(5); // 输出“The result is: 10”

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

6. 变量提升

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

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

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

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

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

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

7. 柯里化

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

var add = function(x) {
 return function(y) {
 return x + y;
 };
};
console.log(add(1)(1)); // 输出2
var add1 = add(1);
console.log(add1(1)); // 输出2
var add10 = add(10);
console.log(add10(1)); // 输出11

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

8. apply, call与bind方法

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

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

var user = {
 name: "Rahul Mhatre",
 whatIsYourName: function() {
 console.log(this.name);
 }
};
user.whatIsYourName(); // 输出"Rahul Mhatre",
var user2 = {
 name: "Neha Sampat"
};
user.whatIsYourName.call(user2); // 输出"Neha Sampat"

apply方法与call方法类似。两者唯一的不同点在于,apply方法使用数组指定参数,而call方法每个参数单独需要指定:

apply(thisArg, [argsArray])
call(thisArg, arg1, arg2, …)
var user = {
 greet: "Hello!",
 greetUser: function(userName) {
 console.log(this.greet + " " + userName);
 }
};
var greet1 = {
 greet: "Hola"
};
user.greetUser.call(greet1, "Rahul"); // 输出"Hola Rahul"
user.greetUser.apply(greet1, ["Rahul"]); // 输出"Hola Rahul"

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

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") // 输出"Hola Rahul"
greetBonjour("Rahul") // 输出"Bonjour Rahul"

9. Memoization

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

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)); // 输出354224848179262000000
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,直到匹配为止。

function addMethod(object, name, f)
{
 var old = object[name];

 object[name] = function()
 {
 // f.length为函数定义时的参数个数
 // arguments.length为函数调用时的参数个数


 if (f.length === arguments.length)
 {

  return f.apply(this, arguments);


 }
 else if (typeof old === "function")
 {
  return old.apply(this, arguments);


 }

 };
}
// 不传参数时,返回所有name
function find0()
{

 return this.names;
}
// 传一个参数时,返回firstName匹配的name
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;
}
// 传两个参数时,返回firstName和lastName都匹配的name
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()); // 输出["Dean Edwards", "Alex Russell", "Dean Tom"]
console.log(people.find("Dean")); // 输出["Dean Edwards", "Dean Tom"]
console.log(people.find("Dean", "Edwards")); // 输出["Dean Edwards"]

原文:http://www.infoworld.com/article/3196070/node-js/10-javascript-concepts-nodejs-programmers-must-master.html

译者:Fundebug

Javascript 相关文章推荐
javascript div 弹出可拖动窗口
Feb 26 Javascript
javascript 表单验证常见正则
Sep 28 Javascript
javascript学习笔记(七)利用javascript来创建和存储cookie
Apr 08 Javascript
iframe调用父页面函数示例详解
Jul 17 Javascript
js实现仿百度汽车频道选择汽车图片展示实例
May 06 Javascript
解决angular的post请求后SpringMVC后台接收不到参数值问题的方法
Dec 10 Javascript
JS原型链怎么理解
Jun 27 Javascript
Node.js 数据加密传输浅析
Nov 16 Javascript
微信小程序侧边栏滑动特效(左右滑动)
Jan 23 Javascript
微信小程序:数据存储、传值、取值详解
May 07 Javascript
解决layui checkbox 提交多个值的问题
Sep 02 Javascript
详解vue-router的导航钩子(导航守卫)
Nov 02 Javascript
Vue.js学习笔记之常用模板语法详解
Jul 25 #Javascript
jQuery实现键盘回车搜索功能
Jul 25 #jQuery
简单实现js鼠标跟随效果
Aug 02 #Javascript
BootStrap的双日历时间控件使用
Jul 25 #Javascript
vue事件修饰符和按键修饰符用法总结
Jul 25 #Javascript
Vue.js学习笔记之修饰符详解
Jul 25 #Javascript
ES6正则表达式扩展笔记
Jul 25 #Javascript
You might like
人族 Terran 魔法与科技
2020/03/14 星际争霸
使用PHP模拟HTTP认证
2006/10/09 PHP
php 之 没有mysql支持时的替代方案
2006/10/09 PHP
php制作文本式留言板
2015/03/18 PHP
php简单实现多语言切换的方法
2016/05/09 PHP
用js调用迅雷下载代码的二种方法
2013/04/15 Javascript
用函数模板,写一个简单高效的 JSON 查询器的方法介绍
2013/04/17 Javascript
JavaScript显示当然日期和时间即年月日星期和时间
2013/10/29 Javascript
JavaScript编写推箱子游戏
2015/07/07 Javascript
jquery实现在网页指定区域显示自定义右键菜单效果
2015/08/25 Javascript
超实用的JavaScript代码段 附使用方法
2016/05/22 Javascript
值得学习的bootstrap fileinput文件上传工具
2016/11/08 Javascript
js 单引号替换成双引号,双引号替换成单引号的实现方法
2017/02/16 Javascript
纯JS单页面赛车游戏制作代码分享
2017/03/03 Javascript
jQuery插件FusionWidgets实现的AngularGauge图效果示例【附demo源码】
2017/03/23 jQuery
Vue.js移动端左滑删除组件的实现代码
2017/09/08 Javascript
JavaScript面向对象精要(上部)
2017/09/12 Javascript
mac中利用NVM管理不同node版本的方法详解
2017/11/08 Javascript
layui下拉列表select实现可输入查找的方法
2019/09/28 Javascript
vue flex 布局实现div均分自动换行的示例代码
2020/08/05 Javascript
[01:03:37]Secret vs VGJ.S Supermajor小组赛C组 BO3 第二场 6.3
2018/06/04 DOTA
python使用PyV8执行javascript代码示例分享
2013/12/04 Python
Python自定义函数的创建、调用和函数的参数详解
2014/03/11 Python
Python中字符编码简介、方法及使用建议
2015/01/08 Python
Python写入CSV文件的方法
2015/07/08 Python
python3中property使用方法详解
2019/04/23 Python
Python数据类型之列表和元组的方法实例详解
2019/07/08 Python
wxPython电子表格功能wx.grid实例教程
2019/11/19 Python
python实现Oracle查询分组的方法示例
2020/04/30 Python
浏览器实现移动端高性能css3动画(开启gpu加速)
2013/12/23 HTML / CSS
美国在线印刷公司:PsPrint
2017/10/12 全球购物
医科大学生的自我评价
2013/12/04 职场文书
材料物理专业求职信
2014/09/01 职场文书
户外活动总结
2015/02/04 职场文书
法制教育讲座心得体会
2016/01/14 职场文书
2019送给家人们的中秋节祝福语
2019/08/15 职场文书