专栏名称: Fundebug
Fundebug为JavaScript、微信小程序及Node.js开发团队提供专业的线上代码bug监控和智能分析服务。
目录
相关文章推荐
前端早读课  ·  【早阅】始终将你的估算值乘以π ·  7 小时前  
前端大全  ·  从 DeepSeek 看25年前端的一个小趋势 ·  昨天  
前端大全  ·  10年了,开发人员仍然不明白 ... ·  昨天  
前端早读课  ·  【第3454期】如何用语音学习编程的 ·  2 天前  
前端大全  ·  前端行情变了,差别真的挺大。。。 ·  3 天前  
51好读  ›  专栏  ›  Fundebug

快速掌握JavaScript面试基础知识(二)

Fundebug  · 公众号  · 前端  · 2018-01-22 09:18

正文

译者按: 总结了大量JavaScript基本知识点,很有用!

  • 原文: The Definitive JavaScript Handbook for your next developer interview

为了保证可读性,本文采用意译而非直译。另外,本文版权归原作者所有,翻译仅用于学习。



根据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(message) }
console.log(sayHiToJon())   // 'Hi Jon!'

请理解 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)        // { getStaff: ƒ, addStaff: ƒ }
console.log(staff)             // ReferenceError: staff is not defined
/* Closure allows access to the staff variable */
elementary.getStaff()          // ["Seymour Skinner", "Edna Krabappel"]
elementary.addStaff('Otto Mann')
elementary.getStaff()          // ["Seymour Skinner", "Edna Krabappel", "Otto Mann"]

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]() ); // 5
console.log( result[3]() ); // 5
result = [];
for (var i=0; i < 5; i++) {
 (function () {
   var j = i; // copy current value of i
   result.push( function() { return j } );
 })();
}
console.log( result[1]() ); // 1
console.log( result[3]() ); // 3

使用IIFE可以:

  • 为函数绑定私有数据

  • 创建一个新的环境

  • 避免污染全局命名空间

环境(Context)

我们往往容易将环境(Context)和作用域(Scope)搞混,我来简单解释一下:

  • 环境(Context) : 由函数如何被调用而决定,往往指 this

  • 作用域(Scope) : 可访问的变量。

函数调用:call, apply, bind

这三个方法都是为了将this绑定到函数,区别在于调用的方式。

  • .call() 会立即执行函数,你需要把参数按顺序传入;

  • .apply() 会立即执行函数,你需要把所有的参数组合为一个数组传入;

.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');              // You know something, Bran Stark
char.knows.call(Snow, 'nothing', 'Jon');      // You know nothing, Jon Snow
char.knows.apply(Snow, ['nothing', 'Jon']);   // You know nothing, Jon Snow

注意:如果你将数组传入 call 函数,它会认为只有一个参数。

ES6允许使用新的操作符将数组变换为一个序列。

char.knows.call(Snow, ...["nothing", "Jon"]);  // You know nothing, Jon Snow

.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');  // You know nothing, Jon Snow

this关键字

要理解JavaScript中 this 关键字,特别是它指向谁,有时候相当地复杂。 this 的值通常由函数的执行环境决定。简单的说,执行环境指函数如何被调用的。 this 像是一个占位符(placeholder),它指向当方法被调用时,调用对应的方法的对象。

下面有序地列出了判断 this 指向的规则。如果第一条匹配,那么就不用去检查第二条了。

  • new 绑定 - 当使用 new 关键字调用函数的时候, this 指向新构建的对象。

    function Person(name, age) {
     this.name = name;
     this.age =age;
     console.log(this);
    }
    const Rachel = new Person('Rachel', 30);   // { age: 30, name: 'Rachel' }
  • 显示绑定(Explicit binding) - 当使用 call 或则 apply 的时候,我们显示的传入一个对象参数,该参数会绑定到 this 。 注意: .bind() 函数不一样。用 bind 定义一个新的函数,但是依然绑定到原来的对象。

    function fn() {
     console.log(this);






请到「今天看啥」查看全文