你有必要知道的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 弹出层组件(升级版)
May 12 Javascript
深入解读JavaScript中的Hoisting机制
Aug 12 Javascript
js实现网页多级级联菜单代码
Aug 20 Javascript
JavaScript探测CSS动画是否已经完成的方法
Aug 30 Javascript
JS 动态加载js文件和css文件 同步/异步的两种简单方式
Sep 23 Javascript
JavaScript实现自动切换图片代码
Oct 11 Javascript
原生JS获取元素集合的子元素宽度实例
Dec 14 Javascript
linux 后台运行node服务指令方法
May 23 Javascript
vue左右侧联动滚动的实现代码
Jun 06 Javascript
Vue中android4.4不兼容问题的解决方法
Sep 04 Javascript
微信小程序实现卡片左右滑动效果的示例代码
May 01 Javascript
JavaScript WeakMap使用详解
Feb 05 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
Codeigniter实现处理用户登录验证后的URL跳转
2014/06/12 PHP
php实现汉字验证码和算式验证码的方法
2015/03/07 PHP
php结合安卓客户端实现查询交互实例
2015/05/05 PHP
php中header设置常见文件类型的content-type
2015/06/23 PHP
PHP获取某个月最大天数(最后一天)的方法
2015/07/29 PHP
Smarty3配置及入门语法
2017/02/22 PHP
Laravel5.5以下版本中如何自定义日志行为详解
2018/08/01 PHP
用Javascript 和 CSS 实现脚注(Footnote)效果
2009/09/09 Javascript
IE6 弹出Iframe层中的文本框“经常”无法获得输入焦点
2009/12/27 Javascript
JS获取后台Cookies值的小例子
2013/03/04 Javascript
JQuery 图片的展开和伸缩实例讲解
2013/04/18 Javascript
js获取客户端网卡的IP地址、MAC地址
2014/03/26 Javascript
js常用数组操作方法简明总结
2014/06/20 Javascript
JavaScript显示表单内元素数量的方法
2015/04/02 Javascript
js编写一个简单的产品放大效果代码
2016/06/27 Javascript
简单实现node.js图片上传
2016/12/18 Javascript
深入理解node.js http模块
2018/01/24 Javascript
详解Angular5/Angular6项目如何添加热更新(HMR)功能
2018/10/10 Javascript
使用koa2创建web项目的方法步骤
2019/03/12 Javascript
JavaScript的级联函数用法简单示例【链式调用】
2019/03/26 Javascript
jQuery属性选择器用法实例分析
2019/06/28 jQuery
VUE 组件转换为微信小程序组件的方法
2019/11/06 Javascript
浅谈Vue 自动化部署打包上线
2020/06/14 Javascript
mustache.js实现首页元件动态渲染的示例代码
2020/12/28 Javascript
python查询sqlite数据表的方法
2015/05/08 Python
Python for Informatics 第11章之正则表达式(二)
2016/04/21 Python
python数据类型_元组、字典常用操作方法(介绍)
2017/05/30 Python
快速创建python 虚拟环境
2020/11/28 Python
OpenCV灰度化之后图片为绿色的解决
2020/12/01 Python
HTML5文档结构标签
2017/04/21 HTML / CSS
汽车维修专业个人求职信范文
2014/01/01 职场文书
二手房购房意向书
2015/05/09 职场文书
Python如何导出导入所有依赖包详解
2021/06/08 Python
opencv检测动态物体的实现
2021/07/21 Python
nginx.conf配置文件结构小结
2022/04/08 Servers
MySQL中EXPLAIN语句及用法
2022/05/20 MySQL