译者按:
总结了大量JavaScript基本知识点,很有用!
为了保证可读性,本文采用意译而非直译。另外,本文版权归原作者所有,翻译仅用于学习。
根据StackOverflow调查, 自2014年一来,JavaScript是最流行的编程语言。当然,这也在情理之中,毕竟1/3的开发工作都需要一些JavaScript知识。因此,如果你希望在成为一个开发者,你应该学会这门语言。
这篇博客的主要目的是将所有面试中常见的概念总结,方便你快速去了解。
(译者按:鉴于本文内容过长,方便阅读,将分为三篇博客来翻译, 此为第二部分。)
闭包
闭包由一个函数以及该函数定义是所在的环境组成。我们通过例子来形象解释它。
function sayHi(name){ var message = `Hi ${name}!`; function greeting() { console.log(message) } return greeting } var sayHiToJon = sayHi('Jon'); console.log(sayHiToJon) console.log(sayHiToJon())
|
请理解
var sayHiToJon = sayHi('Jon');
这行代码的执行过程,
sayHi
函数执行,首先将
message
的值计算出来;然后定义了
greeting
函数,函数中引用了
message
变量;最后,返回
greeting
函数。
如果按照C/Java语言的思路,
sayHiToJon
就等价于
greeting
函数,那么会报错:message未定义。但是在JavaScript中不一样,这里的
sayHiToJon
函数等于
greeting
函数以及一个环境,该环境中包含了
message
。因此,当我们调用
sayHiToJon
函数,可以成功地将
message
打印出来。因此,这里的闭包就是
greeting
函数和一个包含
message
变量的环境。(备注: 为了便于理解,此段落未按照原文翻译。)
闭包的一个优势在于
数据隔离
。我们同样用一个例子来说明:
function
SpringfieldSchool() { let staff = ['Seymour Skinner', 'Edna Krabappel']; return { getStaff: function() { console.log(staff) }, addStaff: function(name) { staff.push(name) } } }
let elementary = SpringfieldSchool() console.log(elementary) console.log(staff)
elementary.getStaff() elementary.addStaff('Otto Mann') elementary.getStaff()
|
在
elementary
被创建的时候,
SpringfieldSchool
已经返回。也就是说
staff
无法被外部访问。唯一可以访问的方式就是里面的闭包函数
getStaff
和
addStaff
。
我们来看一个面试题:下面的代码有什么问题,如何修复?
const arr = [10, 12, 15, 21]; for (var i = 0; i < arr.length; i++) { setTimeout(function() { console.log(`The value ${arr[i]} is at index: ${i}`); }, (i+1) * 1000); }
|
上面的代码输出的结果全部都一样:”The value undefined is at index: 4”。因为所有在
setTimeout
中定义的匿名函数都引用了同一个外部变量
i
。当匿名函数执行的时候,
i
的值为4。
这个问题可以改用
IIFE
(后面会介绍)方法来解决,通过对每一个匿名函数构建独立的外部作用域来实现。
const arr = [10, 12, 15, 21]; for (var i = 0; i < arr.length; i++) { (function(j) { setTimeout(function() { console.log(`The value ${arr[j]} is at index: ${j}`); }, j * 1000); })(i) }
|
当然,还有一个方法,使用
let
来声明
i
。
const arr = [10, 12, 15, 21]; for (let i = 0; i < arr.length; i++) { setTimeout(function() { console.log(`The value ${arr[i]} is at index: ${i}`); }, (i) * 1000); }
|
立即调用的函数表达式(Immediate Invoked Function Expression)(IIFE)
一个IIFE是一个函数表达式在定义之后立即被调用。常用在你想对一个新声明的变量创建一个隔离的作用域。
它的格式为:
(function(){....})()
。前面的大括号用于告诉编译器这里不仅仅是函数定义,后面的大括号用于执行该函数。
var
result = []; for (var i=0; i < 5; i++) { result.push( function() { return i } ); } console.log( result[1]() ); console.log( result[3]() ); result = []; for (var i=0; i < 5; i++) { (function () { var j = i; result.push( function() { return j } ); })(); } console.log( result[1]() ); console.log( result[3]() );
|
使用IIFE可以:
-
为函数绑定私有数据
-
创建一个新的环境
-
避免污染全局命名空间
环境(Context)
我们往往容易将环境(Context)和作用域(Scope)搞混,我来简单解释一下:
函数调用:call, apply, bind
这三个方法都是为了将this绑定到函数,区别在于调用的方式。
.call()
和
.apply()
几乎相同。哪个传入参数方便,你就选择哪个。
const Snow = {surename: 'Snow'} const char = { surename: 'Stark'
, knows: function(arg, name) { console.log(`You know ${arg}, ${name} ${this.surename}`); } } char.knows('something', 'Bran'); char.knows.call(Snow, 'nothing', 'Jon'); char.knows.apply(Snow, ['nothing', 'Jon']);
|
注意:如果你将数组传入
call
函数,它会认为只有一个参数。
ES6允许使用新的操作符将数组变换为一个序列。
char.knows.call(Snow, ...["nothing", "Jon"]);
|
.bind()
返回一个新的函数,以及相应的环境和参数。如果你想该函数稍后调用,那么推荐使用
bind
。
.bind()
函数的优点在于它可以记录一个执行环境,对于异步调用和事件驱动的编程很有用。
.bind()
传参数的方式和
call
相同。
const Snow = {surename: 'Snow'} const char = { surename: 'Stark', knows: function(arg, name) { console.log(`You know ${arg}, ${name} ${this.surename}`);} } const whoKnowsNothing = char.knows.bind(Snow, 'nothing'); whoKnowsNothing('Jon');
|
this关键字
要理解JavaScript中
this
关键字,特别是它指向谁,有时候相当地复杂。
this
的值通常由函数的执行环境决定。简单的说,执行环境指函数如何被调用的。
this
像是一个占位符(placeholder),它指向当方法被调用时,调用对应的方法的对象。
下面有序地列出了判断
this
指向的规则。如果第一条匹配,那么就不用去检查第二条了。