你有必要知道的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 相关文章推荐
jQuery ajax在GBK编码下表单提交终极解决方案(非二次编码方法)
Oct 20 Javascript
php实例分享之实现显示网站运行时间
May 20 Javascript
jquery数组过滤筛选方法grep()简介
Jun 06 Javascript
解释&amp;&amp;和||在javascript中的另类用法
Jul 28 Javascript
JS实现窗口加载时模拟鼠标移动的方法
Jun 03 Javascript
vuejs实现标签选项卡动态更改css样式的方法
May 31 Javascript
scrapyd schedule.json setting 传入多个值问题
Aug 07 Javascript
微信小程序实现左侧滑栏过程解析
Aug 26 Javascript
在Layui 的表格模板中,实现layer父页面和子页面传值交互的方法
Sep 10 Javascript
vue之debounce属性被移除及处理详解
Nov 13 Javascript
JS实现秒杀倒计时特效
Jan 02 Javascript
vue自定义右键菜单之全局实现
Apr 09 Vue.js
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
php递归删除目录与文件的方法
2015/01/30 PHP
php获取文件类型和文件信息的方法
2015/07/10 PHP
使用PHP+MySql实现微信投票功能实例代码
2017/09/29 PHP
PHP时间戳和日期相互转换操作实例小结
2018/12/18 PHP
javascript 密码强度验证规则、打分、验证(给出前端代码,后端代码可根据强度规则翻译)
2010/05/18 Javascript
artDialog双击会关闭对话框的修改过程分享
2013/08/05 Javascript
jQuery的live()方法对hover事件的处理示例
2014/02/27 Javascript
node.js中的console.log方法使用说明
2014/12/09 Javascript
微信内置浏览器私有接口WeixinJSBridge介绍
2015/05/25 Javascript
javascript处理a标签超链接默认事件的方法
2015/06/29 Javascript
深入解读JavaScript中的Iterator和for-of循环
2015/07/28 Javascript
让图片跳跃起来  javascript图片轮播特效
2016/02/16 Javascript
百度地图给map添加右键菜单(判断是否为marker)
2016/03/04 Javascript
Angularjs整合微信UI(weui)
2016/03/15 Javascript
Javascript同时声明一连串(多个)变量的方法
2017/01/23 Javascript
JS检测数组类型的方法小结
2017/03/14 Javascript
第一次记录Bootstrap table学习笔记(1)
2017/05/18 Javascript
[原创]jQuery实现合并/追加数组并去除重复项的方法
2018/04/11 jQuery
微信小程序在ios下Echarts图表不能滑动的问题解决
2019/07/10 Javascript
浅析Python中的getattr(),setattr(),delattr(),hasattr()
2016/06/14 Python
Python中pandas模块DataFrame创建方法示例
2018/06/20 Python
浅析Python四种数据类型
2018/09/26 Python
详解django的serializer序列化model几种方法
2018/10/16 Python
python: 自动安装缺失库文件的方法
2018/10/22 Python
python实现剪切功能
2019/01/23 Python
代码实例讲解python3的编码问题
2019/07/08 Python
python的移位操作实现详解
2019/08/21 Python
详解python 内存优化
2020/08/17 Python
Python3.7安装PyQt5 运行配置Pycharm的详细教程
2020/10/15 Python
amazeui模态框弹出后立马消失并刷新页面
2020/08/19 HTML / CSS
Bibloo荷兰:女士、男士和儿童的服装、鞋子和配饰
2019/02/25 全球购物
Deichmann英国:德国鞋类零售商
2021/01/30 全球购物
幼儿园师德演讲稿
2014/05/06 职场文书
运动会演讲稿300字
2014/08/25 职场文书
关于九一八事变的演讲稿2014
2014/09/17 职场文书
2015年房产销售工作总结范文
2015/05/22 职场文书